<commit_before>/*:
 * @plugindesc This plugin allows you to implement the character effect like as ghost. <RS_GhostEffect>
 * @author biud436
 * @help
 *
 * Note that this plugin only works in WebGL mode.
 *
 * =======================================================
 * Scripts Call
 * =======================================================
 * In the event command called 'Set Move Route',
 *
 * To activate ghost mode, you call the following script code:
 * this.ghostModeOn();
 *
 * To disable ghost mode, you call the following script code:
 * this.ghostModeOff();
 *
 * =======================================================
 * Plugin Commands
 * =======================================================
 * GhostEffect lifetime 100
 * GhostEffect threshold 0.7
 *
 * =======================================================
 * Version Log
 * =======================================================
 * 2019.01.19 (v1.0.0-alpha) - First Release.
 */

var Imported = Imported || {};
Imported.RS_GhostEffect = true;

var RS = RS || {};
RS.GhostEffect = RS.GhostEffect || {};

(function($) {

    "use strict";

    var parameters = $plugins.filter(function (i) {
        return i.description.contains('<RS_GhostEffect>');
    });

    parameters = (parameters.length > 0) && parameters[0].parameters;

    RS.GhostEffect.Params = RS.GhostEffect.Params || {};

    RS.GhostEffect.Params.lifeTime = 100;
    RS.GhostEffect.Params.threshold = 0.7;

    //============================================================================
    // PIXI.GhostEffect
    //============================================================================

    PIXI.GhostEffect = function() {

        var vertexSrc = [
            'attribute vec2 aVertexPosition;',
            'attribute vec2 aTextureCoord;',

            'uniform mat3 projectionMatrix;',

            'varying vec2 vTextureCoord;',

            'void main(void){',
            '    gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);',
            '    vTextureCoord = aTextureCoord;',
            '}'
        ].join('\n');

        var fragmentSrc = `

        precision mediump float;

        uniform vec2 u_scale;

        varying vec2 vTextureCoord;
        uniform sampler2D uSampler;

        uniform vec2 dimensions;
        uniform vec4 filterArea;
        uniform vec4 filterClamp;

        void main(void){

            vec4 baseColor = texture2D(uSampler, vTextureCoord);

            vec2 vRec = (vTextureCoord * filterArea.xy) / dimensions;
            vRec -= 0.5;
            vRec *= u_scale;
            vRec += 0.5;
            vRec.x += 0.07;

            vec4 recColor = texture2D(uSampler, vRec);

            gl_FragColor = baseColor * recColor;
        }

        `;

        PIXI.Filter.call( this, vertexSrc, fragmentSrc);

        this.uniforms.dimensions = new Float32Array(2);
        this.uniforms.u_scale = [0.5, 0.5];

        this._effectVal = 0;
        this._time = performance.now();

        this.enabled = true;
        this.resolution = 1;

    };

    PIXI.GhostEffect.prototype = Object.create( PIXI.Filter.prototype );
    PIXI.GhostEffect.prototype.constructor = PIXI.GhostEffect;

    PIXI.GhostEffect.prototype.apply = function(filterManager, input, output, clear) {
        this.uniforms.dimensions[0] = input.sourceFrame.width;
        this.uniforms.dimensions[1] = input.sourceFrame.height;

        filterManager.applyFilter(this, input, output, clear);
    };

    PIXI.GhostEffect.prototype.updateEffect = function() {
        if(performance.now() - this._time < RS.GhostEffect.Params.lifeTime) return;
        this._effectVal = Math.random();

        if(this._effectVal > RS.GhostEffect.Params.threshold) {
            this._effectVal = RS.GhostEffect.Params.threshold;
        }
        this.uniforms.u_scale[0] = this._effectVal;
        this.uniforms.u_scale[1] = this._effectVal;

        this._time = performance.now();
    };

    //============================================================================
    // Game_CharacterBase
    //============================================================================

    var alias_Game_CharacterBase_initMembers = Game_CharacterBase.prototype.initMembers;
    Game_CharacterBase.prototype.initMembers = function() {
        alias_Game_CharacterBase_initMembers.call(this);
        this._isGhost = false;
    };

    Game_CharacterBase.prototype.isGhost = function() {
        return this._isGhost;
    };

    Game_CharacterBase.prototype.ghostModeOn = function() {
        this._isGhost = true;
    };

    Game_CharacterBase.prototype.ghostModeOff = function() {
        this._isGhost = false;
    };

    Game_Player.prototype.ghostModeOn = function() {
        this._isGhost = true;
        this._followers.forEach(function(follower) {
            follower.ghostModeOn();
        }, this);
    };

    Game_Player.prototype.ghostModeOff = function() {
        this._isGhost = false;
        this._followers.forEach(function(follower) {
            follower.ghostModeOff();
        }, this);
    };

    //============================================================================
    // Sprite_Character
    //============================================================================

    var alias_Sprite_Character_initialize = Sprite_Character.prototype.initialize;
    Sprite_Character.prototype.initialize = function(character) {
        alias_Sprite_Character_initialize.call(this, character);
        this.createGhostEffect();
    };

    var alias_Sprite_Character_update = Sprite_Character.prototype.update;
    Sprite_Character.prototype.update = function() {
        alias_Sprite_Character_update.call(this);
        this.updateGhostEffect();
    };

    Sprite_Character.prototype.createGhostEffect = function() {

        var isValid = this._GhostEffect;

        if(!isValid) {
            this._GhostEffect = new PIXI.GhostEffect();
            if(!this.filters) {
                this.filters = [];
            }
            this.filters = [this._GhostEffect].concat(this.filters);
        } else {
            if(!this.filters) {
                this.filters = [];
            }
            this.filters = this.filters.filter(function(filter) {
                return filter !== isValid;
            }, this);
        }
    };

    Sprite_Character.prototype.updateGhostEffect = function() {
        if(!$gameSystem) return;
        if(!this._GhostEffect) return;
        if(!this._character) return;
        var isValid = this._character.isGhost();
        this._GhostEffect.enabled = isValid;
        if(isValid) {
            this._GhostEffect.updateEffect();
        }

    };

    var alias_Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command, args) {
        alias_Game_Interpreter_pluginCommand.call(this, command, args);
        if(command === "GhostEffect") {
            switch(args[0]) {
            case 'lifetime':
                RS.GhostEffect.Params.lifeTime = Number(args[1] || 100);
                break;
            case 'threshold':
                RS.GhostEffect.Params.threshold = parseFloat(args[1] || 0.7);
                break;
            }
        }
    };

})(RS.GhostEffect);<commit_msg>2019.01.19 (v1.0.0) - First Release.<commit_after>/*:
 * @plugindesc This plugin allows you to implement the character effect like as ghost. <RS_GhostEffect>
 * @author biud436
 *
 * @param Uniform
 *
 * @param lifeTime
 * @parent Uniform
 * @type number
 * @desc All of the effects has a lifeTime, After that time, the effect pattern will be changed.
 * @default 100
 * @min 1
 *
 * @param threshold
 * @parent Uniform
 * @type number
 * @desc if threshold is higher, the effect can decrease.
 * @default 0.7
 * @decimals 2
 * @min 0.10
 * @max 1.00
 *
 * @param xoffset
 * @parent Uniform
 * @type number
 * @desc When the value approaches 0.0, it would look like the paper burning.
 * @default 0.07
 * @decimals 2
 *
 * @help
 *
 * Note that this plugin only works in WebGL mode.
 *
 * =======================================================
 * Scripts Call
 * =======================================================
 * In the event command called 'Set Move Route',
 *
 * To activate ghost mode, you call the following script code:
 * this.ghostModeOn();
 *
 * To disable ghost mode, you call the following script code:
 * this.ghostModeOff();
 *
 * =======================================================
 * Plugin Commands
 * =======================================================
 * All of the effects has a lifeTime.
 * After that time, the effect pattern will be changed.
 * The time unit is milliseconds,
 * so setting it to 100 will result in 0.1 second.
 *
 *   GhostEffect lifetime 100
 *
 * If the threshold is close to 1, the effect may be reduced.
 *
 *      GhostEffect threshold 0.7
 *
 * When the value approaches 0.0, it would look like the paper burning.
 *
 * GhostEffect xoffset 0.07
 *
 * =======================================================
 * Version Log
 * =======================================================
 * 2019.01.19 (v1.0.0) - First Release.
 */

var Imported = Imported || {};
Imported.RS_GhostEffect = true;

var RS = RS || {};
RS.GhostEffect = RS.GhostEffect || {};

(function($) {

    "use strict";

    var parameters = $plugins.filter(function (i) {
        return i.description.contains('<RS_GhostEffect>');
    });

    parameters = (parameters.length > 0) && parameters[0].parameters;

    RS.GhostEffect.Params = RS.GhostEffect.Params || {};

    RS.GhostEffect.Params.lifeTime = parseInt(parameters["lifeTime"] || 100);
    RS.GhostEffect.Params.threshold = parseFloat(parameters["threshold"] || 0.7);
    RS.GhostEffect.Params.xoffset = parseFloat(parameters["xoffset"] || 0.07);

    //============================================================================
    // PIXI.GhostEffect
    //============================================================================

    PIXI.GhostEffect = function() {

        var vertexSrc = [
            'attribute vec2 aVertexPosition;',
            'attribute vec2 aTextureCoord;',

            'uniform mat3 projectionMatrix;',

            'varying vec2 vTextureCoord;',

            'void main(void){',
            '    gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);',
            '    vTextureCoord = aTextureCoord;',
            '}'
        ].join('\n');

        var fragmentSrc = `

        precision mediump float;

        uniform vec2 u_scale;

        varying vec2 vTextureCoord;
        uniform sampler2D uSampler;

        uniform vec2 dimensions;
        uniform float u_xoffset;

        uniform vec4 filterArea;
        uniform vec4 filterClamp;

        void main(void){

            vec4 baseColor = texture2D(uSampler, vTextureCoord);

            vec2 vRec = (vTextureCoord * filterArea.xy) / dimensions;
            vRec -= 0.5;
            vRec *= u_scale;
            vRec += 0.5;
            vRec.x += u_xoffset;

            vec4 recColor = texture2D(uSampler, vRec);

            gl_FragColor = baseColor * recColor;
        }

        `;

        PIXI.Filter.call( this, vertexSrc, fragmentSrc);

        this.uniforms.dimensions = new Float32Array(2);
        this.uniforms.u_scale = [0.5, 0.5];
        this.uniforms.u_xoffset = 0.07;

        this._effectVal = 0;
        this._time = performance.now();

        this.enabled = true;
        this.resolution = 1;

    };

    PIXI.GhostEffect.prototype = Object.create( PIXI.Filter.prototype );
    PIXI.GhostEffect.prototype.constructor = PIXI.GhostEffect;

    PIXI.GhostEffect.prototype.apply = function(filterManager, input, output, clear) {
        this.uniforms.dimensions[0] = input.sourceFrame.width;
        this.uniforms.dimensions[1] = input.sourceFrame.height;

        filterManager.applyFilter(this, input, output, clear);
    };

    PIXI.GhostEffect.prototype.updateEffect = function() {
        if(performance.now() - this._time < RS.GhostEffect.Params.lifeTime) return;
        this._effectVal = Math.random();

        if(this._effectVal > RS.GhostEffect.Params.threshold) {
            this._effectVal = RS.GhostEffect.Params.threshold;
        }
        this.uniforms.u_scale[0] = this._effectVal;
        this.uniforms.u_scale[1] = this._effectVal;
        this.uniforms.u_xoffset = RS.GhostEffect.Params.xoffset;

        this._time = performance.now();
    };

    //============================================================================
    // Game_CharacterBase
    //============================================================================

    var alias_Game_CharacterBase_initMembers = Game_CharacterBase.prototype.initMembers;
    Game_CharacterBase.prototype.initMembers = function() {
        alias_Game_CharacterBase_initMembers.call(this);
        this._isGhost = false;
    };

    Game_CharacterBase.prototype.isGhost = function() {
        return this._isGhost;
    };

    Game_CharacterBase.prototype.ghostModeOn = function() {
        this._isGhost = true;
    };

    Game_CharacterBase.prototype.ghostModeOff = function() {
        this._isGhost = false;
    };

    Game_Player.prototype.ghostModeOn = function() {
        this._isGhost = true;
        this._followers.forEach(function(follower) {
            follower.ghostModeOn();
        }, this);
    };

    Game_Player.prototype.ghostModeOff = function() {
        this._isGhost = false;
        this._followers.forEach(function(follower) {
            follower.ghostModeOff();
        }, this);
    };

    //============================================================================
    // Sprite_Character
    //============================================================================

    var alias_Sprite_Character_initialize = Sprite_Character.prototype.initialize;
    Sprite_Character.prototype.initialize = function(character) {
        alias_Sprite_Character_initialize.call(this, character);
        this.createGhostEffect();
    };

    var alias_Sprite_Character_update = Sprite_Character.prototype.update;
    Sprite_Character.prototype.update = function() {
        alias_Sprite_Character_update.call(this);
        this.updateGhostEffect();
    };

    Sprite_Character.prototype.createGhostEffect = function() {

        var isValid = this._GhostEffect;

        if(!isValid) {
            this._GhostEffect = new PIXI.GhostEffect();
            if(!this.filters) {
                this.filters = [];
            }
            this.filters = [this._GhostEffect].concat(this.filters);
        } else {
            if(!this.filters) {
                this.filters = [];
            }
            this.filters = this.filters.filter(function(filter) {
                return filter !== isValid;
            }, this);
        }
    };

    Sprite_Character.prototype.updateGhostEffect = function() {
        if(!$gameSystem) return;
        if(!this._GhostEffect) return;
        if(!this._character) return;
        var isValid = this._character.isGhost();
        this._GhostEffect.enabled = isValid;
        if(isValid) {
            this._GhostEffect.updateEffect();
        }

    };

    var alias_Game_Interpreter_pluginCommand = Game_Interpreter.prototype.pluginCommand;
    Game_Interpreter.prototype.pluginCommand = function(command, args) {
        alias_Game_Interpreter_pluginCommand.call(this, command, args);
        if(command === "GhostEffect") {
            switch(args[0]) {
            case 'lifetime':
                RS.GhostEffect.Params.lifeTime = Number(args[1] || 100);
                break;
            case 'threshold':
                RS.GhostEffect.Params.threshold = parseFloat(args[1] || 0.7);
                break;
            case 'xoffset':
                RS.GhostEffect.Params.xoffset = parseFloat(args[1] || 0.07);
                break;
            }
        }
    };

})(RS.GhostEffect);<|endoftext|>


<commit_before>var keystone = require('keystone'),
	Types = keystone.Field.Types;

// Create model. Additional options allow menu name to be used what auto-generating URLs
var Disabilities = new keystone.List('Disabilities', {
	autokey: { path: 'key', from: 'disability', unique: true },
	map: { name: 'disability' }
});

// Create fields
Disabilities.add({
	disability: { type: String, label: 'Disability', required: true, index: true, initial: true }
});

// Define default columns in the admin interface and register the model
Disabilities.defaultColumns = 'disability';
Disabilities.register();<commit_msg>Add Reference list model for Disabilities.<commit_after>var keystone = require('keystone'),
	Types = keystone.Field.Types;

// Create model. Additional options allow menu name to be used what auto-generating URLs
var Disability = new keystone.List('Disability', {
	autokey: { path: 'key', from: 'disability', unique: true },
	map: { name: 'disability' }
});

// Create fields
Disability.add({
	disability: { type: String, label: 'Disability', required: true, index: true, initial: true }
});

// Define default columns in the admin interface and register the model
Disability.defaultColumns = 'disability';
Disability.register();<|endoftext|>


<commit_before>////////////////////////////////
<commit_msg>Delete Script.js<commit_after><|endoftext|>


<commit_before>'use strict'

const stdin = require('get-stdin')
const nom = require('nomnom')
const fs = require('graceful-fs')
const yaml = require('js-yaml')
const enhanceHanson = require('../hanson-format').enhanceHanson

function compileArea(data) {
	let obj = yaml.safeLoad(data)
	return enhanceHanson(obj)
}

module.exports.cli = function cli() {
	// let args = nom()
		// .script('compile-area')
		// .option('filename', {
		// 	position: 0,
		// 	help: 'the area to process',
		// })

	// let data = fs.readFileSync(args.filename, {encoding: 'utf-8'})
	stdin().then(data => {
		let student = compileArea(data)
		console.log(JSON.stringify(student, null, 2))
	}).catch(err => console.error(err))
}
<commit_msg>refactor: remove unused imports from bin/compile-area<commit_after>'use strict'

const stdin = require('get-stdin')
const yaml = require('js-yaml')
const enhanceHanson = require('../hanson-format').enhanceHanson

function compileArea(data) {
	let obj = yaml.safeLoad(data)
	return enhanceHanson(obj)
}

module.exports.cli = function cli() {
	// let args = nom()
		// .script('compile-area')
		// .option('filename', {
		// 	position: 0,
		// 	help: 'the area to process',
		// })

	// let data = fs.readFileSync(args.filename, {encoding: 'utf-8'})
	stdin().then(data => {
		let student = compileArea(data)
		console.log(JSON.stringify(student, null, 2))
	}).catch(err => console.error(err))
}
<|endoftext|>


<commit_before>/* @flow weak */
import { RULE_TYPE } from '../utils/styleTypes'

export default function createDOMInterface(renderer, node) {
  return (change) => {
    // only use insertRule in production as browser devtools might have
    // weird behavior if used together with insertRule at runtime
    if (process.env.NODE_ENV === 'production' && change.type === RULE_TYPE && !change.media) {
      node.sheet.insertRule(`${change.selector}{${change.declaration}}`, node.sheet.cssRules.length)
    } else {
      node.textContent = renderer.renderToString()
    }
  }
}
<commit_msg>hot fix insertRule<commit_after>/* @flow weak */
import { RULE_TYPE } from '../utils/styleTypes'

export default function createDOMInterface(renderer, node) {
  return (change) => {
    // only use insertRule in production as browser devtools might have
    // weird behavior if used together with insertRule at runtime
    if (process.env.NODE_ENV === 'production' && change.type === RULE_TYPE && !change.media) {
      try {
        node.sheet.insertRule(`${change.selector}{${change.declaration}}`, node.sheet.cssRules.length)
      } catch (error) {
        // TODO: MAYBE WARN IN DEV MODE
      }
    } else {
      node.textContent = renderer.renderToString()
    }
  }
}
<|endoftext|>


<commit_before>import _ from 'lodash';
import { qsString } from '../util/index';

var taginfo = {},
    endpoint = 'https://taginfo.openstreetmap.org/api/4/',
    tag_sorts = {
        point: 'count_nodes',
        vertex: 'count_nodes',
        area: 'count_ways',
        line: 'count_ways'
    },
    tag_filters = {
        point: 'nodes',
        vertex: 'nodes',
        area: 'ways',
        line: 'ways'
    };

function sets(parameters, n, o) {
    if (parameters.geometry && o[parameters.geometry]) {
        parameters[n] = o[parameters.geometry];
    }
    return parameters;
}

function setFilter(parameters) {
    return sets(parameters, 'filter', tag_filters);
}

function setSort(parameters) {
    return sets(parameters, 'sortname', tag_sorts);
}

function clean(parameters) {
    return _.omit(parameters, 'geometry', 'debounce');
}

function filterKeys(type) {
    var count_type = type ? 'count_' + type : 'count_all';
    return function(d) {
        return parseFloat(d[count_type]) > 2500 || d.in_wiki;
    };
}

function filterMultikeys() {
    return function(d) {
        return (d.key.match(/:/g) || []).length === 1;  // exactly one ':'
    };
}

function filterValues() {
    return function(d) {
        if (d.value.match(/[A-Z*;,]/) !== null) return false;  // exclude some punctuation, uppercase letters
        return parseFloat(d.fraction) > 0.0 || d.in_wiki;
    };
}

function valKey(d) {
    return {
        value: d.key,
        title: d.key
    };
}

function valKeyDescription(d) {
    return {
        value: d.value,
        title: d.description || d.value
    };
}

// sort keys with ':' lower than keys without ':'
function sortKeys(a, b) {
    return (a.key.indexOf(':') === -1 && b.key.indexOf(':') !== -1) ? -1
        : (a.key.indexOf(':') !== -1 && b.key.indexOf(':') === -1) ? 1
        : 0;
}

var debounced = _.debounce(d3.json, 100, true);

function request(url, debounce, callback) {
    var cache = taginfo.cache;

    if (cache[url]) {
        callback(null, cache[url]);
    } else if (debounce) {
        debounced(url, done);
    } else {
        d3.json(url, done);
    }

    function done(err, data) {
        if (!err) cache[url] = data;
        callback(err, data);
    }
}

export function init() {
    taginfo.keys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 10,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterKeys(parameters.filter);
                callback(null, d.data.filter(f).sort(sortKeys).map(valKey));
            });
    };

    taginfo.multikeys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterMultikeys();
                callback(null, d.data.filter(f).map(valKey));
            });
    };

    taginfo.values = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(setFilter(parameters)));
        request(endpoint + 'key/values?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterValues();
                callback(null, d.data.filter(f).map(valKeyDescription));
            });
    };

    taginfo.docs = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));

        var path = 'key/wiki_pages?';
        if (parameters.value) path = 'tag/wiki_pages?';
        else if (parameters.rtype) path = 'relation/wiki_pages?';

        request(endpoint + path + qsString(parameters), debounce, function(err, d) {
            if (err) return callback(err);
            callback(null, d.data);
        });
    };

    taginfo.endpoint = function(_) {
        if (!arguments.length) return endpoint;
        endpoint = _;
        return taginfo;
    };

    taginfo.reset = function() {
        taginfo.cache = {};
        return taginfo;
    };

    if (!taginfo.cache) {
        taginfo.reset();
    }

    return taginfo;
}
<commit_msg>Allow uppercase in network taginfo (#3280)<commit_after>import _ from 'lodash';
import { qsString } from '../util/index';

var taginfo = {},
    endpoint = 'https://taginfo.openstreetmap.org/api/4/',
    tag_sorts = {
        point: 'count_nodes',
        vertex: 'count_nodes',
        area: 'count_ways',
        line: 'count_ways'
    },
    tag_filters = {
        point: 'nodes',
        vertex: 'nodes',
        area: 'ways',
        line: 'ways'
    };

function sets(parameters, n, o) {
    if (parameters.geometry && o[parameters.geometry]) {
        parameters[n] = o[parameters.geometry];
    }
    return parameters;
}

function setFilter(parameters) {
    return sets(parameters, 'filter', tag_filters);
}

function setSort(parameters) {
    return sets(parameters, 'sortname', tag_sorts);
}

function clean(parameters) {
    return _.omit(parameters, 'geometry', 'debounce');
}

function filterKeys(type) {
    var count_type = type ? 'count_' + type : 'count_all';
    return function(d) {
        return parseFloat(d[count_type]) > 2500 || d.in_wiki;
    };
}

function filterMultikeys() {
    return function(d) {
        return (d.key.match(/:/g) || []).length === 1;  // exactly one ':'
    };
}

function filterValues(allowUpperCase) {
    return function(d) {
        if (d.value.match(/[;,]/) !== null) return false;  // exclude some punctuation
        if (!allowUpperCase && d.value.match(/[A-Z*]/) !== null) return false;  // exclude uppercase letters
        return parseFloat(d.fraction) > 0.0 || d.in_wiki;
    };
}

function valKey(d) {
    return {
        value: d.key,
        title: d.key
    };
}

function valKeyDescription(d) {
    return {
        value: d.value,
        title: d.description || d.value
    };
}

// sort keys with ':' lower than keys without ':'
function sortKeys(a, b) {
    return (a.key.indexOf(':') === -1 && b.key.indexOf(':') !== -1) ? -1
        : (a.key.indexOf(':') !== -1 && b.key.indexOf(':') === -1) ? 1
        : 0;
}

var debounced = _.debounce(d3.json, 100, true);

function request(url, debounce, callback) {
    var cache = taginfo.cache;

    if (cache[url]) {
        callback(null, cache[url]);
    } else if (debounce) {
        debounced(url, done);
    } else {
        d3.json(url, done);
    }

    function done(err, data) {
        if (!err) cache[url] = data;
        callback(err, data);
    }
}

export function init() {
    taginfo.keys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 10,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterKeys(parameters.filter);
                callback(null, d.data.filter(f).sort(sortKeys).map(valKey));
            });
    };

    taginfo.multikeys = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));
        request(endpoint + 'keys/all?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterMultikeys();
                callback(null, d.data.filter(f).map(valKey));
            });
    };

    taginfo.values = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(setFilter(parameters)));
        request(endpoint + 'key/values?' +
            qsString(_.extend({
                rp: 25,
                sortname: 'count_all',
                sortorder: 'desc',
                page: 1
            }, parameters)), debounce, function(err, d) {
                if (err) return callback(err);
                var f = filterValues(parameters.key === 'cycle_network' || parameters.key === 'network');
                callback(null, d.data.filter(f).map(valKeyDescription));
            });
    };

    taginfo.docs = function(parameters, callback) {
        var debounce = parameters.debounce;
        parameters = clean(setSort(parameters));

        var path = 'key/wiki_pages?';
        if (parameters.value) path = 'tag/wiki_pages?';
        else if (parameters.rtype) path = 'relation/wiki_pages?';

        request(endpoint + path + qsString(parameters), debounce, function(err, d) {
            if (err) return callback(err);
            callback(null, d.data);
        });
    };

    taginfo.endpoint = function(_) {
        if (!arguments.length) return endpoint;
        endpoint = _;
        return taginfo;
    };

    taginfo.reset = function() {
        taginfo.cache = {};
        return taginfo;
    };

    if (!taginfo.cache) {
        taginfo.reset();
    }

    return taginfo;
}
<|endoftext|>


<commit_before>import { dispatch as d3_dispatch } from 'd3-dispatch';

import {
    select as d3_selectAll
} from 'd3-selection';

import { t } from '../util/locale';
import { svgIcon } from '../svg';
import { uiDisclosure } from './disclosure';
import {
    utilRebind,
    utilHighlightEntity
} from '../util';
import { tooltip } from '../util/tooltip';
import { uiTooltipHtml } from './tooltipHtml';

export function uiEntityIssues(context) {
    var dispatch = d3_dispatch('change');
    var _entityID;

    context.validator().on('reload.entity_issues', issuesDidReload);

    function issuesDidReload() {
        var selection = d3_selectAll('.entity-issues .disclosure-wrap');
        renderContent(selection);
        update();
    }

    function entityIssues(selection) {
        selection.call(uiDisclosure(context, 'entity_issues', true)
            .content(renderContent)
        );
        update();
    }

    function update() {

        var issues = context.validator().getIssuesForEntityWithID(_entityID);

        d3_selectAll('.entity-issues')
            .classed('hide', issues.length === 0);

        d3_selectAll('.hide-toggle-entity_issues span')
            .text(t('issues.list_title', { count: issues.length }));
    }

    function renderContent(selection) {

        var issues = context.validator().getIssuesForEntityWithID(_entityID);

        var items = selection.selectAll('.issue')
            .data(issues, function(d) {
                return d.id();
            });

        // Exit
        items.exit()
            .remove();

        // Enter
        var enter = items.enter()
            .append('div')
            .attr('class', function (d) {
                return 'issue severity-' + d.severity;
            })
            .on('mouseover.highlight', function(d) {
                d.entities.forEach(function(entity) {
                    if (entity.id !== _entityID) {
                        utilHighlightEntity(entity.id, true, context);
                    }
                });
            })
            .on('mouseout.highlight', function(d) {
                d.entities.forEach(function(entity) {
                    if (entity.id !== _entityID) {
                        utilHighlightEntity(entity.id, false, context);
                    }
                });
            });

        var label = enter
            .append('button')
            .classed('label', true)
            .call(tooltip()
                .html(true)
                .title(function(d) {
                    var tip = d.tooltip ? d.tooltip : '';
                    return uiTooltipHtml(tip);
                })
                .placement('top')
            );

        label.each(function(d) {
            var iconSuffix = d.severity === 'warning' ? 'alert' : 'error';
            d3_selectAll(this)
                .append('div')
                .attr('title', t('issues.'+d.severity+'s.icon_tooltip'))
                .style('display', 'inline')
                .call(svgIcon('#iD-icon-' + iconSuffix, 'pre-text'));
        });

        label
            .append('span')
            .append('strong')
            .text(function(d) { return d.message; });

        enter.each(function(d) {

            var issue = d3_selectAll(this);

            var list = issue
                .selectAll('ul.fixes')
                .data([0]);

            if (d.fixes && d.fixes.length > 0) {
                list = list.enter()
                    .append('ul')
                    .attr('class', 'fixes')
                    .merge(list);

                issue.selectAll('.label')
                    .on('click', function() {
                        if (!issue.classed('fixes-open')) {
                            issue.classed('fixes-open', true);
                            var loc = d.loc();
                            if (loc) {
                                context.map().centerZoomEase(loc, Math.max(context.map().zoom(), 18));
                            } else if (d.entities && d.entities.length > 0) {
                                context.map().zoomToEase(d.entities[0]);
                            }
                        } else {
                            issue.classed('fixes-open', false);
                        }
                    });

                var fixItems = list
                    .selectAll('li')
                    .data(d.fixes);

                fixItems.exit()
                    .remove();

                fixItems.enter()
                    .append('li')
                    .append('button')
                    .text(function(d) {
                        return d.title;
                    })
                    .on('click', function(d) {
                        d.onClick();
                    })
                    .on('mouseover.highlight', function(d) {
                        d.entityIds.forEach(function(entityId) {
                            utilHighlightEntity(entityId, true, context);
                        });
                    })
                    .on('mouseout.highlight', function(d) {
                        d.entityIds.forEach(function(entityId) {
                            utilHighlightEntity(entityId, false, context);
                        });
                    });
            }
        });

        // Update
        items = items
            .merge(enter);

        // open the fixes for the first issue if no others are already open
        if (selection.selectAll('.issue.fixes-open').empty()) {
            selection.selectAll('.issue:first-child').classed('fixes-open', true);
        }

    }

    entityIssues.entityID = function(val) {
        if (!arguments.length) return _entityID;
        if (_entityID === val) return entityIssues;
        _entityID = val;
        return entityIssues;
    };

    return utilRebind(entityIssues, dispatch, 'on');
}
<commit_msg>Only zoom to the clicked issue in the entity sidebar if it isn't visible<commit_after>import { dispatch as d3_dispatch } from 'd3-dispatch';

import {
    select as d3_selectAll
} from 'd3-selection';

import { t } from '../util/locale';
import { svgIcon } from '../svg';
import { uiDisclosure } from './disclosure';
import {
    utilRebind,
    utilHighlightEntity
} from '../util';
import { tooltip } from '../util/tooltip';
import { uiTooltipHtml } from './tooltipHtml';

export function uiEntityIssues(context) {
    var dispatch = d3_dispatch('change');
    var _entityID;

    context.validator().on('reload.entity_issues', issuesDidReload);

    function issuesDidReload() {
        var selection = d3_selectAll('.entity-issues .disclosure-wrap');
        renderContent(selection);
        update();
    }

    function entityIssues(selection) {
        selection.call(uiDisclosure(context, 'entity_issues', true)
            .content(renderContent)
        );
        update();
    }

    function update() {

        var issues = context.validator().getIssuesForEntityWithID(_entityID);

        d3_selectAll('.entity-issues')
            .classed('hide', issues.length === 0);

        d3_selectAll('.hide-toggle-entity_issues span')
            .text(t('issues.list_title', { count: issues.length }));
    }

    function renderContent(selection) {

        var issues = context.validator().getIssuesForEntityWithID(_entityID);

        var items = selection.selectAll('.issue')
            .data(issues, function(d) {
                return d.id();
            });

        // Exit
        items.exit()
            .remove();

        // Enter
        var enter = items.enter()
            .append('div')
            .attr('class', function (d) {
                return 'issue severity-' + d.severity;
            })
            .on('mouseover.highlight', function(d) {
                d.entities.forEach(function(entity) {
                    if (entity.id !== _entityID) {
                        utilHighlightEntity(entity.id, true, context);
                    }
                });
            })
            .on('mouseout.highlight', function(d) {
                d.entities.forEach(function(entity) {
                    if (entity.id !== _entityID) {
                        utilHighlightEntity(entity.id, false, context);
                    }
                });
            });

        var label = enter
            .append('button')
            .classed('label', true)
            .call(tooltip()
                .html(true)
                .title(function(d) {
                    var tip = d.tooltip ? d.tooltip : '';
                    return uiTooltipHtml(tip);
                })
                .placement('top')
            );

        label.each(function(d) {
            var iconSuffix = d.severity === 'warning' ? 'alert' : 'error';
            d3_selectAll(this)
                .append('div')
                .attr('title', t('issues.'+d.severity+'s.icon_tooltip'))
                .style('display', 'inline')
                .call(svgIcon('#iD-icon-' + iconSuffix, 'pre-text'));
        });

        label
            .append('span')
            .append('strong')
            .text(function(d) { return d.message; });

        enter.each(function(d) {

            var issue = d3_selectAll(this);

            var list = issue
                .selectAll('ul.fixes')
                .data([0]);

            if (d.fixes && d.fixes.length > 0) {
                list = list.enter()
                    .append('ul')
                    .attr('class', 'fixes')
                    .merge(list);

                issue.selectAll('.label')
                    .on('click', function() {
                        if (!issue.classed('fixes-open')) {
                            issue.classed('fixes-open', true);
                            var loc = d.loc();
                            if (loc) {
                                context.map().centerZoomEase(loc, Math.max(context.map().zoom(), 18));
                            } else if (d.entities && d.entities.length > 0 &&
                                !d.entities[0].intersects(context.map().extent(), context.graph())) {
                                context.map().zoomToEase(d.entities[0]);
                            }
                        } else {
                            issue.classed('fixes-open', false);
                        }
                    });

                var fixItems = list
                    .selectAll('li')
                    .data(d.fixes);

                fixItems.exit()
                    .remove();

                fixItems.enter()
                    .append('li')
                    .append('button')
                    .text(function(d) {
                        return d.title;
                    })
                    .on('click', function(d) {
                        d.onClick();
                    })
                    .on('mouseover.highlight', function(d) {
                        d.entityIds.forEach(function(entityId) {
                            utilHighlightEntity(entityId, true, context);
                        });
                    })
                    .on('mouseout.highlight', function(d) {
                        d.entityIds.forEach(function(entityId) {
                            utilHighlightEntity(entityId, false, context);
                        });
                    });
            }
        });

        // Update
        items = items
            .merge(enter);

        // open the fixes for the first issue if no others are already open
        if (selection.selectAll('.issue.fixes-open').empty()) {
            selection.selectAll('.issue:first-child').classed('fixes-open', true);
        }

    }

    entityIssues.entityID = function(val) {
        if (!arguments.length) return _entityID;
        if (_entityID === val) return entityIssues;
        _entityID = val;
        return entityIssues;
    };

    return utilRebind(entityIssues, dispatch, 'on');
}
<|endoftext|>


<commit_before>(function ( $ ) {
$.fn.ComboMe = function() {

    var placeholder;

    this.button({    // give the button the down arrow.
        text: true,
        icons: {secondary: "ui-icon-triangle-1-s"}
    })
    .click(function() {
        var bt = $(this);                        // get the button
        var menu = $( this ).parent().next();    // get the menu (the UL)
        menu.width( bt.width()-3 );              // set the menu width to match the button width

        placeholder = bt.find("span").text();

        // styling
        menu.css("position", "absolute");

        // for every anchor in the menu bind click to update the button text with the anchor text.
        menu.find("li").each(function( index ) {
            var link = $( this ).find("a");
            link.bind( "click", function() {
                var val = $(this).text();
                bt.find("span").text( val );
                bt.trigger("changed", val);
            });
        });

        // position the menu
        menu.show().position({
            my: "left top",
            at: "left bottom",
            of: this
        });
        // handle hiding the menu
        $( document ).one( "click", function() {
           menu.hide();
        });
        return false;
    })
    .parent()
    .buttonset()
    .next()
    .hide()
    .menu();
};
}( jQuery ));
<commit_msg>Update ComboMe.js<commit_after>(function ( $ ) {
var startingValue = '';

$.widget("nmk.cbMe", {
    _create: function () {
        this._createButton();

        // capture the starting value (eg the placeholder).
        startingValue = this.element.find("span").html();
    },
    // Create a public method.
    reset: function () {
        // reset the value to the original starting value.
        this.element.find("span").html(startingValue);
    },
    // Create a private method.
    _createButton: function () {
        this.element.button({ // give the button the down arrow.
            text: true,
            icons: {
                secondary: "ui-icon-triangle-1-s"
            }
        })
        .click(function () {
            var bt = $(this); // get the button
            var menu = $(this).parent().next(); // get the menu (the UL)
            menu.width(bt.width() - 3); // set the menu width to match the button width

            // styling
            menu.css("position", "absolute");

            // for every anchor in the menu bind click to update the button text with the anchor text.
            menu.find("li").each(function (index) {
                var link = $(this).find("a");
                link.bind("click", function () {
                    var val = $(this).text();
                    bt.find("span").text(val);
                    bt.trigger("changed", val);
                });
            });

            // position the menu
            menu.show().position({
                my: "left top",
                at: "left bottom",
                of: this
            });
            // handle hiding the menu
            $(document).one("click", function () {
                menu.hide();
            });
            return false;
        })
            .parent()
            .buttonset()
            .next()
            .hide()
            .menu();
    }
});
}( jQuery ));
<|endoftext|>


<commit_before>cmapi.channel["map.feature.plot.url"] = {
    schema: {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "title": "map.feature.plot",
        "description": "Have the map plot feature data from a Uniform Resource Locator (URL).",
        "type": "object",
        "properties": {
            "overlayId": {
                "description": "The ID of the overlay this feature should be loaded into. If an overlay with this ID already exists, new feature is merged into existing overlay; otherwise, a new overlay will be created. If no overlayId is included, default overlay with ID equal to sending widget's ID is used. If overlay exists, it will retain its status (whether visible or hidden). If overlay is created, it will be made visible.",
                "required": false,
                "type": "string"
            },
            "featureId": {
                "description": "Unique identifier for the given feature data. Note that feature ids must be unique within a given overlay. Reusing a feature id will be considered a reload with the original feature data being removed and replaced by the new feature data.",
                "required": true,
                "type": "string"
            },
            "name": {
                "description": "Name for the given feature data. Note that feature names do not have to be unique and are intended for display purposes only.",
                "required": false,
                "type": "string"
            },
            "format": {
                "description": "Data format of the given feature. If no format is specified, the format defaults to “kml.” A list of formats supported by a particular map implementation can be obtained by querying the map using the map.status channel (see map.status). Note that for this version of the Common Map Widget API, all map implementations must support KML and WMS (GetMap only).",
                "required": false,
                "type": "string"
            },
            "url": {
                "description": "URL from which to retrieve the feature data to load onto the map",
                "required": true,
                "type": "string"
            },
            "params": {
                "description": "A JSON object containing a list of parameters to be passed to the server along with the URL when loading WMS data. Params object is ignored unless “format” is set to “wms”.  Note that request, exceptions, SRS/CRS, width, height, and bbox params should not be passed in as they are determined by the map as needed and will be ignored if passed. Params as passed will be concatenated to the URL and are expected to follow the WMS specification.   All parameters passed in must not be URL encoded (the map widget implementation will URL encode all passed in params).",
                "type": "object",
                "required": [],
            },
            "zoom": {
                "description": "true if map should zoom to newly loaded feature data, false if not. Default is false.  Ignored when loading WMS data.",
                "required": false,
                "type": "boolean"
            }
        }
    },
    notes:[]
}<commit_msg>added required array to schema<commit_after>cmapi.channel["map.feature.plot.url"] = {
    schema: {
        "$schema": "http://json-schema.org/draft-04/schema#",
        "title": "map.feature.plot",
        "description": "Have the map plot feature data from a Uniform Resource Locator (URL).",
        "type": "object",
        "properties": {
            "overlayId": {
                "description": "The ID of the overlay this feature should be loaded into. If an overlay with this ID already exists, new feature is merged into existing overlay; otherwise, a new overlay will be created. If no overlayId is included, default overlay with ID equal to sending widget's ID is used. If overlay exists, it will retain its status (whether visible or hidden). If overlay is created, it will be made visible.",
                "required": false,
                "type": "string"
            },
            "featureId": {
                "description": "Unique identifier for the given feature data. Note that feature ids must be unique within a given overlay. Reusing a feature id will be considered a reload with the original feature data being removed and replaced by the new feature data.",
                "required": true,
                "type": "string"
            },
            "name": {
                "description": "Name for the given feature data. Note that feature names do not have to be unique and are intended for display purposes only.",
                "required": false,
                "type": "string"
            },
            "format": {
                "description": "Data format of the given feature. If no format is specified, the format defaults to “kml.” A list of formats supported by a particular map implementation can be obtained by querying the map using the map.status channel (see map.status). Note that for this version of the Common Map Widget API, all map implementations must support KML and WMS (GetMap only).",
                "required": false,
                "type": "string"
            },
            "url": {
                "description": "URL from which to retrieve the feature data to load onto the map",
                "required": true,
                "type": "string"
            },
            "params": {
                "description": "A JSON object containing a list of parameters to be passed to the server along with the URL when loading WMS data. Params object is ignored unless “format” is set to “wms”.  Note that request, exceptions, SRS/CRS, width, height, and bbox params should not be passed in as they are determined by the map as needed and will be ignored if passed. Params as passed will be concatenated to the URL and are expected to follow the WMS specification.   All parameters passed in must not be URL encoded (the map widget implementation will URL encode all passed in params).",
                "type": "object",
                "required": [],
            },
            "zoom": {
                "description": "true if map should zoom to newly loaded feature data, false if not. Default is false.  Ignored when loading WMS data.",
                "required": false,
                "type": "boolean"
            }
        },
        "required": ["featureId", "url"]
    },
    notes:[]
}<|endoftext|>


<commit_before>/** @jsx m */
"use strict";

require("./CommentList.scss");

var m = require("mithril");
var _ = require("underscore");

var CommentForm = require("./CommentForm.js");

var Comment = {};

Comment.view = function(ctrl) {
  var comment = ctrl.comment;
  if (!comment.get("comments").isEmpty()) {
    var replies = new CommentList.view(ctrl.listController, {comments: comment.get("comments")});
  }
  if (ctrl.user && ctrl.onDelete && (comment.get("owner_id") === ctrl.listController.user.get("id") || ctrl.listController.user.get("admin"))) {
    var deleteButton = <button type="button" className="btn" onclick={ctrl.onDelete}>Delete</button>;
  }
  if (ctrl.user && ctrl.listController.reply) {
    var replyActive = ctrl.listController.replying() === comment;
    var replyButton = <button type="button" className={"btn " + (replyActive ? "active" : "")} onclick={ctrl.listController.toggleReply(comment)}> Reply</button>;
    if (replyActive) {
      var replyForm = new CommentForm.view(ctrl.listController.controllers.commentForm);
    }
  }
  if (comment.get("anonymous") && comment.get("owner_id") === ctrl.listController.user.get("id")) {
    var removeAnonymousButton = <button type="button" className="btn" onclick={removeAnonymous(comment)}>Make non-anonymous</button>;
  }

  var heading = _.compact([comment.get("authorName"), comment.get("timeAgo")]).join(": ");

  return (

{heading}

{comment.get("comment")}

          {replyButton}
          {deleteButton}
          {removeAnonymousButton}

        {replyForm}
        {replies}

  );
};

var CommentList = {};

CommentList.controller = function(options) {
  this.user = options.user;
  this.reply = options.reply;
  this.comments = options.comments;
  this.replying = m.prop(false);

  this.controllers = {};

  this.interval = setInterval(m.redraw, 60000); // redraw every minute for time-stamps
  var _this = this;
  this.onunload = function() {
    clearInterval(_this.interval);
  };

  this.toggleReply = function(comment) {
    return function(e) {
      if (_this.replying() === false) {
        _this.replying(comment);
        _this.controllers.commentForm = new CommentForm.controller({
          user: _this.user,
          comments: comment.get("comments"),
          onAdd: function() { _this.replying(false); }
        });
      } else {
        _this.replying(false);
        _this.controllers.commentForm = null;
      }
    };
  };
};

CommentList.view = function(ctrl, options) {
  options = options || {};
  var comments = (options.comments || ctrl.comments);
  var commentViews;
  if (ctrl.where) {
    commentViews = _.map(comments.where(ctrl.where), function(comment) {
      return new Comment.view({
        comment: comment,
        listController: ctrl,
        onDelete: onDelete((options.comments || ctrl.comments), comment)
      });
    });
  } else {
    commentViews = comments.map(function(comment) {
      return new Comment.view({
        comment: comment,
        listController: ctrl,
        onDelete: onDelete((options.comments || ctrl.comments), comment)
      });
    });
  }

  return
{commentViews}
;
};

module.exports = CommentList;

// helpers

function onDelete(collection, comment) {
  return function (e) {
    var confirmMessage = "This will delete your comment";
    if (comment.get("owner_id") !== App.user.get("id")) {
      confirmMessage = "This will delete the user's comment";
    }
    if (confirm(confirmMessage)) {
      collection.remove(comment, {sync: true});
    }
  };
}

function removeAnonymous(comment) {
  return function() {
    comment.removeAnonymous();
  }
}
<commit_msg>fix anon comments bug<commit_after>/** @jsx m */
"use strict";

require("./CommentList.scss");

var m = require("mithril");
var _ = require("underscore");

var CommentForm = require("./CommentForm.js");

var Comment = {};

Comment.view = function(ctrl) {
  var comment = ctrl.comment;
  if (!comment.get("comments").isEmpty()) {
    var replies = new CommentList.view(ctrl.listController, {comments: comment.get("comments")});
  }
  if (ctrl.listController.user && ctrl.onDelete && (comment.get("owner_id") === ctrl.listController.user.get("id") || ctrl.listController.user.get("admin"))) {
    var deleteButton = <button type="button" className="btn" onclick={ctrl.onDelete}>Delete</button>;
  }
  if (ctrl.listController.user && ctrl.listController.reply) {
    var replyActive = ctrl.listController.replying() === comment;
    var replyButton = <button type="button" className={"btn " + (replyActive ? "active" : "")} onclick={ctrl.listController.toggleReply(comment)}> Reply</button>;
    if (replyActive) {
      var replyForm = new CommentForm.view(ctrl.listController.controllers.commentForm);
    }
  }
  if (comment.get("anonymous") && ctrl.listController.user && comment.get("owner_id") === ctrl.listController.user.get("id")) {
    var removeAnonymousButton = <button type="button" className="btn" onclick={removeAnonymous(comment)}>Make non-anonymous</button>;
  }

  var heading = _.compact([comment.get("authorName"), comment.get("timeAgo")]).join(": ");

  return (

{heading}

{comment.get("comment")}

          {replyButton}
          {deleteButton}
          {removeAnonymousButton}

        {replyForm}
        {replies}

  );
};

var CommentList = {};

CommentList.controller = function(options) {
  this.user = options.user;
  this.reply = options.reply;
  this.comments = options.comments;
  this.replying = m.prop(false);

  this.controllers = {};

  this.interval = setInterval(m.redraw, 60000); // redraw every minute for time-stamps
  var _this = this;
  this.onunload = function() {
    clearInterval(_this.interval);
  };

  this.toggleReply = function(comment) {
    return function(e) {
      if (_this.replying() === false) {
        _this.replying(comment);
        _this.controllers.commentForm = new CommentForm.controller({
          user: _this.user,
          comments: comment.get("comments"),
          onAdd: function() { _this.replying(false); }
        });
      } else {
        _this.replying(false);
        _this.controllers.commentForm = null;
      }
    };
  };
};

CommentList.view = function(ctrl, options) {
  options = options || {};
  var comments = (options.comments || ctrl.comments);
  var commentViews;
  if (ctrl.where) {
    commentViews = _.map(comments.where(ctrl.where), function(comment) {
      return new Comment.view({
        comment: comment,
        listController: ctrl,
        onDelete: onDelete((options.comments || ctrl.comments), comment)
      });
    });
  } else {
    commentViews = comments.map(function(comment) {
      return new Comment.view({
        comment: comment,
        listController: ctrl,
        onDelete: onDelete((options.comments || ctrl.comments), comment)
      });
    });
  }

  return
{commentViews}
;
};

module.exports = CommentList;

// helpers

function onDelete(collection, comment) {
  return function (e) {
    var confirmMessage = "This will delete your comment";
    if (comment.get("owner_id") !== App.user.get("id")) {
      confirmMessage = "This will delete the user's comment";
    }
    if (confirm(confirmMessage)) {
      collection.remove(comment, {sync: true});
    }
  };
}

function removeAnonymous(comment) {
  return function() {
    comment.removeAnonymous();
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

import { Links } from './Links';

describe('Links Component - List Items', function () {
  let git;
  let stack;
  let user;
  let wrapper;
  let spy;

  function mockGit() {
    return [
      {
        vendor_id: 172123914,
        title: `and i'm sittin' here on capitol hill`,
        modified: '2017-06-22T18:54:57Z',
        status: 'closed',
        comments: 2,
      }
    ]
  }

  function mockStack() {
    return [
      {
        vendor_id: 1,
        title: 'i\'m just a link',
        modified: 'Sun Feb 22 2015',
        views: 47,
      }
    ]
  }

  function mockUser() {
    return {
      id: 1,
    }
  }

  beforeEach('render the component', () => {
    git = mockGit()
    stack = mockStack()
    user = mockUser()
    spy = sinon.spy();
    wrapper = shallow(<Links git={git} stack={stack} dispatchUpvote={spy} user={user}/>);
  });

  describe('Contents', () => {
    it('renders the title of each git and stack search result', () => {
      expect(wrapper.text()).to.contain(`i\'m just a link`);
      expect(wrapper.text()).to.contain(`and i\'m sittin\' here on capitol hill`);
    });

    it('renders the last activity date of each git and stack search result', () => {
      expect(wrapper.text()).to.contain('2017-06-22T18:54:57Z');
      expect(wrapper.text()).to.contain('Sun Feb 22 2015');
    });

    it('renders the status of each git search result', () => {
      expect(wrapper.text()).to.contain('closed');
    });

    it('renders the number of comments for each git search result', () => {
      expect(wrapper.text()).to.contain('2');
    });

    it('renders the number of views for each stack search result', () => {
      expect(wrapper.text()).to.contain('47');
    });
  })

  describe('Voting Buttons - User Who Is Logged in', () => {
    it('calls dispatchUpvote when a logged-in user clicks to upvote git result', () => {
      wrapper = shallow(<Links git={git} user={user} dispatchUpvote={spy} />)
      wrapper.find('.upvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })

    it('calls dispatchDownvote when a logged-in user clicks to downvote git result', () => {
      wrapper = shallow(<Links git={git} user={user} dispatchDownvote={spy} />);
      wrapper.find('.downvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })

    it('calls dispatchUpvote when a logged-in user clicks to upvote stack overflow result', () => {
      wrapper = shallow(<Links stack={stack} user={user} dispatchUpvote={spy} />);
      wrapper.find('.upvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })

    it('calls dispatchDownvote when a logged-in user clicks to downvote stack overflow result', () => {
      wrapper = shallow(<Links stack={stack} user={user} dispatchDownvote={spy} />);
      wrapper.find('.downvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })
  })
});
<commit_msg>Test(Links): add tests for user who is not logged-in<commit_after>import React from 'react';
import { mount, shallow } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

import { Links } from './Links';

describe('Links Component - List Items', function () {
  let git;
  let stack;
  let user;
  let wrapper;
  let spy;

  function mockGit() {
    return [
      {
        vendor_id: 172123914,
        title: `and i'm sittin' here on capitol hill`,
        modified: '2017-06-22T18:54:57Z',
        status: 'closed',
        comments: 2,
      }
    ]
  }

  function mockStack() {
    return [
      {
        vendor_id: 1,
        title: 'i\'m just a link',
        modified: 'Sun Feb 22 2015',
        views: 47,
      }
    ]
  }

  function mockUser() {
    return {
      id: 1,
    }
  }

  beforeEach('render the component', () => {
    git = mockGit()
    stack = mockStack()
    user = mockUser()
    spy = sinon.spy();
    wrapper = shallow(<Links git={git} stack={stack} dispatchUpvote={spy} user={user}/>);
  });

  describe('Contents', () => {
    it('renders the title of each git and stack search result', () => {
      expect(wrapper.text()).to.contain(`i\'m just a link`);
      expect(wrapper.text()).to.contain(`and i\'m sittin\' here on capitol hill`);
    });

    it('renders the last activity date of each git and stack search result', () => {
      expect(wrapper.text()).to.contain('2017-06-22T18:54:57Z');
      expect(wrapper.text()).to.contain('Sun Feb 22 2015');
    });

    it('renders the status of each git search result', () => {
      expect(wrapper.text()).to.contain('closed');
    });

    it('renders the number of comments for each git search result', () => {
      expect(wrapper.text()).to.contain('2');
    });

    it('renders the number of views for each stack search result', () => {
      expect(wrapper.text()).to.contain('47');
    });
  })

  describe('Voting Buttons - User Who Is Logged in', () => {
    it('calls dispatchUpvote when a logged-in user clicks to upvote git result', () => {
      wrapper = shallow(<Links git={git} user={user} dispatchUpvote={spy} />)
      wrapper.find('.upvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })

    it('calls dispatchDownvote when a logged-in user clicks to downvote git result', () => {
      wrapper = shallow(<Links git={git} user={user} dispatchDownvote={spy} />);
      wrapper.find('.downvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })

    it('calls dispatchUpvote when a logged-in user clicks to upvote stack overflow result', () => {
      wrapper = shallow(<Links stack={stack} user={user} dispatchUpvote={spy} />);
      wrapper.find('.upvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })

    it('calls dispatchDownvote when a logged-in user clicks to downvote stack overflow result', () => {
      wrapper = shallow(<Links stack={stack} user={user} dispatchDownvote={spy} />);
      wrapper.find('.downvote').simulate('click');
      expect(spy).to.have.property('callCount', 1);
    })
  })

  describe('Voting Buttons - User Who Is NOT Logged in', () => {
    beforeEach('user is not logged in, or user has not registered', () => {
      user = {}
    });

    it('upvote button should not render when a user is not logged-in', () => {
      wrapper = shallow(<Links git={git} stack={stack} user={user} />)
      expect(wrapper.text()).not.to.contain('Upvote');
    })

    it('downvote button should not render when a user is not logged-in', () => {
      wrapper = shallow(<Links git={git} stack={stack} user={user} />)
      expect(wrapper.text()).not.to.contain('Downvote');
    })
  })
});
<|endoftext|>


<commit_before>const specialHandles = [
  {userId: 'board_members', username: 'board_members'},
  {userId: 'card_members', username: 'card_members'}
];
const specialHandleNames = specialHandles.map(m => m.username);

BlazeComponent.extendComponent({
  onRendered() {
    const textareaSelector = 'textarea';
    const mentions = [
      // User mentions
      {
        match: /\B@([\w.-]*)$/,
        search(term, callback) {
          const currentBoard = Boards.findOne(Session.get('currentBoard'));
          callback(
            _.union(
            currentBoard
              .activeMembers()
              .map(member => {
                const user = Users.findOne(member.userId);
                const username = user.username;
                const fullName = user.profile && user.profile !== undefined && user.profile.fullname ? user.profile.fullname : "";
                return username.includes(term) || fullName.includes(term) ? user : null;
              })
              .filter(Boolean), [...specialHandles])
          );
        },
        template(user) {
          if (user.profile && user.profile.fullname) {
            return (user.profile.fullname + " (" + user.username + ")");
          }
          return user.username;
        },
        replace(user) {
          if (user.profile && user.profile.fullname) {
            return `@${user.username} (${user.profile.fullname}) `;
          }
          return `@${user.username} `;
        },
        index: 1,
      },
    ];
    const enableTextarea = function() {
      const $textarea = this.$(textareaSelector);
      autosize($textarea);
      $textarea.escapeableTextComplete(mentions);
    };
    if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
      const isSmall = Utils.isMiniScreen();
      const toolbar = isSmall
        ? [
            ['view', ['fullscreen']],
            ['table', ['table']],
            ['font', ['bold', 'underline']],
            //['fontsize', ['fontsize']],
            ['color', ['color']],
          ]
        : [
            ['style', ['style']],
            ['font', ['bold', 'underline', 'clear']],
            ['fontsize', ['fontsize']],
            ['fontname', ['fontname']],
            ['color', ['color']],
            ['para', ['ul', 'ol', 'paragraph']],
            ['table', ['table']],
            //['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
            ['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
            ['view', ['fullscreen', 'codeview', 'help']],
          ];
      const cleanPastedHTML = function(input) {
        const badTags = [
          'style',
          'script',
          'applet',
          'embed',
          'noframes',
          'noscript',
          'meta',
          'link',
          'button',
          'form',
        ].join('|');
        const badPatterns = new RegExp(
          `(?:${[
            `<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
            `<(${badTags})[^>]*?\\/>`,
          ].join('|')})`,
          'gi',
        );
        let output = input;
        // remove bad Tags
        output = output.replace(badPatterns, '');
        // remove attributes ' style="..."'
        const badAttributes = new RegExp(
          `(?:${[
            'on\\S+=([\'"]?).*?\\1',
            'href=([\'"]?)javascript:.*?\\2',
            'style=([\'"]?).*?\\3',
            'target=\\S+',
          ].join('|')})`,
          'gi',
        );
        output = output.replace(badAttributes, '');
        output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
        return output;
      };
      const editor = '.editor';
      const selectors = [
        `.js-new-description-form ${editor}`,
        `.js-new-comment-form ${editor}`,
        `.js-edit-comment ${editor}`,
      ].join(','); // only new comment and edit comment
      const inputs = $(selectors);
      if (inputs.length === 0) {
        // only enable richereditor to new comment or edit comment no others
        enableTextarea();
      } else {
        const placeholder = inputs.attr('placeholder') || '';
        const mSummernotes = [];
        const getSummernote = function(input) {
          const idx = inputs.index(input);
          if (idx > -1) {
            return mSummernotes[idx];
          }
          return undefined;
        };
        inputs.each(function(idx, input) {
          mSummernotes[idx] = $(input).summernote({
            placeholder,
            callbacks: {
              onInit(object) {
                const originalInput = this;
                $(originalInput).on('submitted', function() {
                  // when comment is submitted, the original textarea will be set to '', so shall we
                  if (!this.value) {
                    const sn = getSummernote(this);
                    sn && sn.summernote('code', '');
                  }
                });
                const jEditor = object && object.editable;
                const toolbar = object && object.toolbar;
                if (jEditor !== undefined) {
                  jEditor.escapeableTextComplete(mentions);
                }
                if (toolbar !== undefined) {
                  const fBtn = toolbar.find('.btn-fullscreen');
                  fBtn.on('click', function() {
                    const $this = $(this),
                      isActive = $this.hasClass('active');
                    $('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
                  });
                }
              },
              onImageUpload(files) {
                const $summernote = getSummernote(this);
                if (files && files.length > 0) {
                  const image = files[0];
                  const currentCard = Utils.getCurrentCard();
                  const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
                  const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
                  const insertImage = src => {
                    const img = document.createElement('img');
                    img.src = src;
                    img.setAttribute('width', '100%');
                    $summernote.summernote('insertNode', img);
                  };
                  const processUpload = function(file) {
                    const uploader = Attachments.insert(
                      {
                        file,
                        streams: 'dynamic',
                        chunkSize: 'dynamic',
                      },
                      false,
                    );
                    uploader.on('uploaded', (error, fileObj) => {
                      if (!error) {
                        if (fileObj.isImage) {
                          insertImage(
                            `${location.protocol}//${location.host}${fileObj.path}`,
                          );
                        }
                        Utils.addCommonMetaToAttachment(currentCard, fileObj);
                      }
                    });
                    uploader.start();
                  };
                  if (MAX_IMAGE_PIXEL) {
                    const reader = new FileReader();
                    reader.onload = function(e) {
                      const dataurl = e && e.target && e.target.result;
                      if (dataurl !== undefined) {
                        // need to shrink image
                        Utils.shrinkImage({
                          dataurl,
                          maxSize: MAX_IMAGE_PIXEL,
                          ratio: COMPRESS_RATIO,
                          toBlob: true,
                          callback(blob) {
                            if (blob !== false) {
                              blob.name = image.name;
                              processUpload(blob);
                            }
                          },
                        });
                      }
                    };
                    reader.readAsDataURL(image);
                  } else {
                    processUpload(image);
                  }
                }
              },
              onPaste(e) {
                var clipboardData = e.clipboardData;
                var pastedData = clipboardData.getData('Text');

                //if pasted data is an image, exit
                if (!pastedData.length) {
                  e.preventDefault();
                  return;
                }

                // clear up unwanted tag info when user pasted in text
                const thisNote = this;
                const updatePastedText = function(object) {
                  const someNote = getSummernote(object);
                  // Fix Pasting text into a card is adding a line before and after
                  // (and multiplies by pasting more) by changing paste "p" to "br".
                  // Fixes https://github.com/wekan/wekan/2890 .
                  // == Fix Start ==
                  someNote.execCommand('defaultParagraphSeparator', false, 'br');
                  // == Fix End ==
                  const original = someNote.summernote('code');
                  const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
                  someNote.summernote('code', ''); //clear original
                  someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
                };
                setTimeout(function() {
                  //this kinda sucks, but if you don't do a setTimeout,
                  //the function is called before the text is really pasted.
                  updatePastedText(thisNote);
                }, 10);
              },
            },
            dialogsInBody: true,
            spellCheck: true,
            disableGrammar: false,
            disableDragAndDrop: false,
            toolbar,
            popover: {
              image: [
                ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
                ['float', ['floatLeft', 'floatRight', 'floatNone']],
                ['remove', ['removeMedia']],
              ],
              link: [['link', ['linkDialogShow', 'unlink']]],
              table: [
                ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
                ['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
              ],
              air: [
                ['color', ['color']],
                ['font', ['bold', 'underline', 'clear']],
              ],
            },
            height: 200,
          });
        });
      }
    } else {
      enableTextarea();
    }
  },
  events() {
    return [
      {
        'click a.fa.fa-copy'(event) {
          const $editor = this.$('textarea.editor');
          const promise = Utils.copyTextToClipboard($editor[0].value);

          const $tooltip = this.$('.copied-tooltip');
          Utils.showCopied(promise, $tooltip);
        },
      }
    ]
  }
}).register('editor');

import DOMPurify from 'dompurify';

// Additional  safeAttrValue function to allow for other specific protocols
// See https://github.com/leizongmin/js-xss/issues/52#issuecomment-241354114

/*
function mySafeAttrValue(tag, name, value, cssFilter) {
  // only when the tag is 'a' and attribute is 'href'
  // then use your custom function
  if (tag === 'a' && name === 'href') {
    // only filter the value if starts with 'cbthunderlink:' or 'aodroplink'
    if (
      /^thunderlink:/gi.test(value) ||
      /^cbthunderlink:/gi.test(value) ||
      /^aodroplink:/gi.test(value) ||
      /^onenote:/gi.test(value) ||
      /^file:/gi.test(value) ||
      /^abasurl:/gi.test(value) ||
      /^conisio:/gi.test(value) ||
      /^mailspring:/gi.test(value)
    ) {
      return value;
    } else {
      // use the default safeAttrValue function to process all non cbthunderlinks
      return sanitizeXss.safeAttrValue(tag, name, value, cssFilter);
    }
  } else {
    // use the default safeAttrValue function to process it
    return sanitizeXss.safeAttrValue(tag, name, value, cssFilter);
  }
}
*/

// XXX I believe we should compute a HTML rendered field on the server that
// would handle markdown and user mentions. We can simply have two
// fields, one source, and one compiled version (in HTML) and send only the
// compiled version to most users -- who don't need to edit.
// In the meantime, all the transformation are done on the client using the
// Blaze API.
const at = HTML.CharRef({ html: '@', str: '@' });
Blaze.Template.registerHelper(
  'mentions',
  new Template('mentions', function() {
    const view = this;
    let content = Blaze.toHTML(view.templateContentBlock);
    const currentBoard = Boards.findOne(Session.get('currentBoard'));
    if (!currentBoard)
      return HTML.Raw(
        DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }),
      );
    const knowedUsers = _.union(currentBoard.members.map(member => {
      const u = Users.findOne(member.userId);
      if (u) {
        member.username = u.username;
      }
      return member;
    }), [...specialHandles]);
    const mentionRegex = /\B@([\w.-]*)/gi;

    let currentMention;
    while ((currentMention = mentionRegex.exec(content)) !== null) {
      const [fullMention, quoteduser, simple] = currentMention;
      const username = quoteduser || simple;
      const knowedUser = _.findWhere(knowedUsers, { username });
      if (!knowedUser) {
        continue;
      }

      const linkValue = [' ', at, knowedUser.username];
      let linkClass = 'atMention js-open-member';
      if (knowedUser.userId === Meteor.userId()) {
        linkClass += ' me';
      }
      // This @user mention link generation did open same Wekan
      // window in new tab, so now A is changed to U so it's
      // underlined and there is no link popup. This way also
      // text can be selected more easily.
      //const link = HTML.A(
      const link = HTML.U(
        {
          class: linkClass,
          // XXX Hack. Since we stringify this render function result below with
          // `Blaze.toHTML` we can't rely on blaze data contexts to pass the
          // `userId` to the popup as usual, and we need to store it in the DOM
          // using a data attribute.
          'data-userId': knowedUser.userId,
        },
        linkValue,
      );

      content = content.replace(fullMention, Blaze.toHTML(link));
    }

    return HTML.Raw(
      DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }),
    );
  }),
);

Template.viewer.events({
  // Viewer sometimes have click-able wrapper around them (for instance to edit
  // the corresponding text). Clicking a link shouldn't fire these actions, stop
  // we stop these event at the viewer component level.
  'click a'(event, templateInstance) {
    const prevent = true;
    const userId = event.currentTarget.dataset.userid;
    if (userId) {
      Popup.open('member').call({ userId }, event, templateInstance);
    } else {
      const href = event.currentTarget.href;
      if (href) {
        window.open(href, '_blank');
      }
    }
    if (prevent) {
      event.stopPropagation();

      // XXX We hijack the build-in browser action because we currently don't have
      // `_blank` attributes in viewer links, and the transformer function is
      // handled by a third party package that we can't configure easily. Fix that
      // by using directly `_blank` attribute in the rendered HTML.
      event.preventDefault();
    }
  },
});
<commit_msg>Ref: Attachment upload handlers<commit_after>const specialHandles = [
  {userId: 'board_members', username: 'board_members'},
  {userId: 'card_members', username: 'card_members'}
];
const specialHandleNames = specialHandles.map(m => m.username);

BlazeComponent.extendComponent({
  onRendered() {
    const textareaSelector = 'textarea';
    const mentions = [
      // User mentions
      {
        match: /\B@([\w.-]*)$/,
        search(term, callback) {
          const currentBoard = Boards.findOne(Session.get('currentBoard'));
          callback(
            _.union(
            currentBoard
              .activeMembers()
              .map(member => {
                const user = Users.findOne(member.userId);
                const username = user.username;
                const fullName = user.profile && user.profile !== undefined && user.profile.fullname ? user.profile.fullname : "";
                return username.includes(term) || fullName.includes(term) ? user : null;
              })
              .filter(Boolean), [...specialHandles])
          );
        },
        template(user) {
          if (user.profile && user.profile.fullname) {
            return (user.profile.fullname + " (" + user.username + ")");
          }
          return user.username;
        },
        replace(user) {
          if (user.profile && user.profile.fullname) {
            return `@${user.username} (${user.profile.fullname}) `;
          }
          return `@${user.username} `;
        },
        index: 1,
      },
    ];
    const enableTextarea = function() {
      const $textarea = this.$(textareaSelector);
      autosize($textarea);
      $textarea.escapeableTextComplete(mentions);
    };
    if (Meteor.settings.public.RICHER_CARD_COMMENT_EDITOR !== false) {
      const isSmall = Utils.isMiniScreen();
      const toolbar = isSmall
        ? [
            ['view', ['fullscreen']],
            ['table', ['table']],
            ['font', ['bold', 'underline']],
            //['fontsize', ['fontsize']],
            ['color', ['color']],
          ]
        : [
            ['style', ['style']],
            ['font', ['bold', 'underline', 'clear']],
            ['fontsize', ['fontsize']],
            ['fontname', ['fontname']],
            ['color', ['color']],
            ['para', ['ul', 'ol', 'paragraph']],
            ['table', ['table']],
            //['insert', ['link', 'picture', 'video']], // iframe tag will be sanitized TODO if iframe[class=note-video-clip] can be added into safe list, insert video can be enabled
            ['insert', ['link']], //, 'picture']], // modal popup has issue somehow :(
            ['view', ['fullscreen', 'codeview', 'help']],
          ];
      const cleanPastedHTML = function(input) {
        const badTags = [
          'style',
          'script',
          'applet',
          'embed',
          'noframes',
          'noscript',
          'meta',
          'link',
          'button',
          'form',
        ].join('|');
        const badPatterns = new RegExp(
          `(?:${[
            `<(${badTags})s*[^>][\\s\\S]*?<\\/\\1>`,
            `<(${badTags})[^>]*?\\/>`,
          ].join('|')})`,
          'gi',
        );
        let output = input;
        // remove bad Tags
        output = output.replace(badPatterns, '');
        // remove attributes ' style="..."'
        const badAttributes = new RegExp(
          `(?:${[
            'on\\S+=([\'"]?).*?\\1',
            'href=([\'"]?)javascript:.*?\\2',
            'style=([\'"]?).*?\\3',
            'target=\\S+',
          ].join('|')})`,
          'gi',
        );
        output = output.replace(badAttributes, '');
        output = output.replace(/(<a )/gi, '$1target=_ '); // always to new target
        return output;
      };
      const editor = '.editor';
      const selectors = [
        `.js-new-description-form ${editor}`,
        `.js-new-comment-form ${editor}`,
        `.js-edit-comment ${editor}`,
      ].join(','); // only new comment and edit comment
      const inputs = $(selectors);
      if (inputs.length === 0) {
        // only enable richereditor to new comment or edit comment no others
        enableTextarea();
      } else {
        const placeholder = inputs.attr('placeholder') || '';
        const mSummernotes = [];
        const getSummernote = function(input) {
          const idx = inputs.index(input);
          if (idx > -1) {
            return mSummernotes[idx];
          }
          return undefined;
        };
        inputs.each(function(idx, input) {
          mSummernotes[idx] = $(input).summernote({
            placeholder,
            callbacks: {
              onInit(object) {
                const originalInput = this;
                $(originalInput).on('submitted', function() {
                  // when comment is submitted, the original textarea will be set to '', so shall we
                  if (!this.value) {
                    const sn = getSummernote(this);
                    sn && sn.summernote('code', '');
                  }
                });
                const jEditor = object && object.editable;
                const toolbar = object && object.toolbar;
                if (jEditor !== undefined) {
                  jEditor.escapeableTextComplete(mentions);
                }
                if (toolbar !== undefined) {
                  const fBtn = toolbar.find('.btn-fullscreen');
                  fBtn.on('click', function() {
                    const $this = $(this),
                      isActive = $this.hasClass('active');
                    $('.minicards,#header-quick-access').toggle(!isActive); // mini card is still showing when editor is in fullscreen mode, we hide here manually
                  });
                }
              },
              onImageUpload(files) {
                const $summernote = getSummernote(this);
                if (files && files.length > 0) {
                  const image = files[0];
                  const currentCard = Utils.getCurrentCard();
                  const MAX_IMAGE_PIXEL = Utils.MAX_IMAGE_PIXEL;
                  const COMPRESS_RATIO = Utils.IMAGE_COMPRESS_RATIO;
                  const insertImage = src => {
                    const img = document.createElement('img');
                    img.src = src;
                    img.setAttribute('width', '100%');
                    $summernote.summernote('insertNode', img);
                  };
                  const processUpload = function(file) {
                    const uploader = Attachments.insert(
                      {
                        file,
                        chunkSize: 'dynamic',
                      },
                      false,
                    );
                    uploader.on('uploaded', (error, fileObj) => {
                      if (!error) {
                        if (fileObj.isImage) {
                          insertImage(
                            `${location.protocol}//${location.host}${fileObj.path}`,
                          );
                        }
                        Utils.addCommonMetaToAttachment(currentCard, fileObj);
                      }
                    });
                    uploader.start();
                  };
                  if (MAX_IMAGE_PIXEL) {
                    const reader = new FileReader();
                    reader.onload = function(e) {
                      const dataurl = e && e.target && e.target.result;
                      if (dataurl !== undefined) {
                        // need to shrink image
                        Utils.shrinkImage({
                          dataurl,
                          maxSize: MAX_IMAGE_PIXEL,
                          ratio: COMPRESS_RATIO,
                          toBlob: true,
                          callback(blob) {
                            if (blob !== false) {
                              blob.name = image.name;
                              processUpload(blob);
                            }
                          },
                        });
                      }
                    };
                    reader.readAsDataURL(image);
                  } else {
                    processUpload(image);
                  }
                }
              },
              onPaste(e) {
                var clipboardData = e.clipboardData;
                var pastedData = clipboardData.getData('Text');

                //if pasted data is an image, exit
                if (!pastedData.length) {
                  e.preventDefault();
                  return;
                }

                // clear up unwanted tag info when user pasted in text
                const thisNote = this;
                const updatePastedText = function(object) {
                  const someNote = getSummernote(object);
                  // Fix Pasting text into a card is adding a line before and after
                  // (and multiplies by pasting more) by changing paste "p" to "br".
                  // Fixes https://github.com/wekan/wekan/2890 .
                  // == Fix Start ==
                  someNote.execCommand('defaultParagraphSeparator', false, 'br');
                  // == Fix End ==
                  const original = someNote.summernote('code');
                  const cleaned = cleanPastedHTML(original); //this is where to call whatever clean function you want. I have mine in a different file, called CleanPastedHTML.
                  someNote.summernote('code', ''); //clear original
                  someNote.summernote('pasteHTML', cleaned); //this sets the displayed content editor to the cleaned pasted code.
                };
                setTimeout(function() {
                  //this kinda sucks, but if you don't do a setTimeout,
                  //the function is called before the text is really pasted.
                  updatePastedText(thisNote);
                }, 10);
              },
            },
            dialogsInBody: true,
            spellCheck: true,
            disableGrammar: false,
            disableDragAndDrop: false,
            toolbar,
            popover: {
              image: [
                ['imagesize', ['imageSize100', 'imageSize50', 'imageSize25']],
                ['float', ['floatLeft', 'floatRight', 'floatNone']],
                ['remove', ['removeMedia']],
              ],
              link: [['link', ['linkDialogShow', 'unlink']]],
              table: [
                ['add', ['addRowDown', 'addRowUp', 'addColLeft', 'addColRight']],
                ['delete', ['deleteRow', 'deleteCol', 'deleteTable']],
              ],
              air: [
                ['color', ['color']],
                ['font', ['bold', 'underline', 'clear']],
              ],
            },
            height: 200,
          });
        });
      }
    } else {
      enableTextarea();
    }
  },
  events() {
    return [
      {
        'click a.fa.fa-copy'(event) {
          const $editor = this.$('textarea.editor');
          const promise = Utils.copyTextToClipboard($editor[0].value);

          const $tooltip = this.$('.copied-tooltip');
          Utils.showCopied(promise, $tooltip);
        },
      }
    ]
  }
}).register('editor');

import DOMPurify from 'dompurify';

// Additional  safeAttrValue function to allow for other specific protocols
// See https://github.com/leizongmin/js-xss/issues/52#issuecomment-241354114

/*
function mySafeAttrValue(tag, name, value, cssFilter) {
  // only when the tag is 'a' and attribute is 'href'
  // then use your custom function
  if (tag === 'a' && name === 'href') {
    // only filter the value if starts with 'cbthunderlink:' or 'aodroplink'
    if (
      /^thunderlink:/gi.test(value) ||
      /^cbthunderlink:/gi.test(value) ||
      /^aodroplink:/gi.test(value) ||
      /^onenote:/gi.test(value) ||
      /^file:/gi.test(value) ||
      /^abasurl:/gi.test(value) ||
      /^conisio:/gi.test(value) ||
      /^mailspring:/gi.test(value)
    ) {
      return value;
    } else {
      // use the default safeAttrValue function to process all non cbthunderlinks
      return sanitizeXss.safeAttrValue(tag, name, value, cssFilter);
    }
  } else {
    // use the default safeAttrValue function to process it
    return sanitizeXss.safeAttrValue(tag, name, value, cssFilter);
  }
}
*/

// XXX I believe we should compute a HTML rendered field on the server that
// would handle markdown and user mentions. We can simply have two
// fields, one source, and one compiled version (in HTML) and send only the
// compiled version to most users -- who don't need to edit.
// In the meantime, all the transformation are done on the client using the
// Blaze API.
const at = HTML.CharRef({ html: '@', str: '@' });
Blaze.Template.registerHelper(
  'mentions',
  new Template('mentions', function() {
    const view = this;
    let content = Blaze.toHTML(view.templateContentBlock);
    const currentBoard = Boards.findOne(Session.get('currentBoard'));
    if (!currentBoard)
      return HTML.Raw(
        DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }),
      );
    const knowedUsers = _.union(currentBoard.members.map(member => {
      const u = Users.findOne(member.userId);
      if (u) {
        member.username = u.username;
      }
      return member;
    }), [...specialHandles]);
    const mentionRegex = /\B@([\w.-]*)/gi;

    let currentMention;
    while ((currentMention = mentionRegex.exec(content)) !== null) {
      const [fullMention, quoteduser, simple] = currentMention;
      const username = quoteduser || simple;
      const knowedUser = _.findWhere(knowedUsers, { username });
      if (!knowedUser) {
        continue;
      }

      const linkValue = [' ', at, knowedUser.username];
      let linkClass = 'atMention js-open-member';
      if (knowedUser.userId === Meteor.userId()) {
        linkClass += ' me';
      }
      // This @user mention link generation did open same Wekan
      // window in new tab, so now A is changed to U so it's
      // underlined and there is no link popup. This way also
      // text can be selected more easily.
      //const link = HTML.A(
      const link = HTML.U(
        {
          class: linkClass,
          // XXX Hack. Since we stringify this render function result below with
          // `Blaze.toHTML` we can't rely on blaze data contexts to pass the
          // `userId` to the popup as usual, and we need to store it in the DOM
          // using a data attribute.
          'data-userId': knowedUser.userId,
        },
        linkValue,
      );

      content = content.replace(fullMention, Blaze.toHTML(link));
    }

    return HTML.Raw(
      DOMPurify.sanitize(content, { ALLOW_UNKNOWN_PROTOCOLS: true }),
    );
  }),
);

Template.viewer.events({
  // Viewer sometimes have click-able wrapper around them (for instance to edit
  // the corresponding text). Clicking a link shouldn't fire these actions, stop
  // we stop these event at the viewer component level.
  'click a'(event, templateInstance) {
    const prevent = true;
    const userId = event.currentTarget.dataset.userid;
    if (userId) {
      Popup.open('member').call({ userId }, event, templateInstance);
    } else {
      const href = event.currentTarget.href;
      if (href) {
        window.open(href, '_blank');
      }
    }
    if (prevent) {
      event.stopPropagation();

      // XXX We hijack the build-in browser action because we currently don't have
      // `_blank` attributes in viewer links, and the transformer function is
      // handled by a third party package that we can't configure easily. Fix that
      // by using directly `_blank` attribute in the rendered HTML.
      event.preventDefault();
    }
  },
});
<|endoftext|>


<commit_before>'use strict';

function _DebugHelper() {
}

_DebugHelper.prototype.init = function() {
  debugGui.add(this, 'printScene');
};

_DebugHelper.prototype.printScene = function() {
  function _printThis(obj) {
    var out = '';
    out += 'OBJ';
    out += '[' + obj.name + ']';
    out += ' @ ' + obj.position.x + ',' + obj.position.y + ',' + obj.position.z;

    console.groupCollapsed(out);
    for (var j = 0; j < obj.children.length; ++j) {
      _printThis(obj.children[j]);
    }
    console.groupEnd();
  }
  _printThis(scene);
};

var DebugHelper = new _DebugHelper();
<commit_msg>Added a global debug helper to print camera location.<commit_after>'use strict';

function _DebugHelper() {
}

_DebugHelper.prototype.init = function() {
  debugGui.add(this, 'logCameraLocation');
  debugGui.add(this, 'logScene');
};

_DebugHelper.prototype.logCameraLocation = function() {
  var worldPos = camera.localToWorld(new THREE.Vector3(0, 0, 0));
  console.log('Camera Location:', worldPos.x, worldPos.y, worldPos.z);
};

_DebugHelper.prototype.logScene = function() {
  function _printThis(obj) {
    var out = '';
    out += 'OBJ';
    out += '[' + obj.name + ']';
    out += ' @ ' + obj.position.x + ',' + obj.position.y + ',' + obj.position.z;

    console.groupCollapsed(out);
    for (var j = 0; j < obj.children.length; ++j) {
      _printThis(obj.children[j]);
    }
    console.groupEnd();
  }
  _printThis(scene);
};

var DebugHelper = new _DebugHelper();
<|endoftext|>