<commit_before>'use strict';
/**
 * Webbrowser abstraction for communicating with discourse
 * @module browser
 * @license MIT
 */

const config = require('./config');

const request = require('request'),
    async = require('async');

const signature = '\n\n<!-- Posted by a clever robot -->',
    defaults = {
        rejectUnauthorized: false,
        jar: request.jar(),
        headers: {
            'X-Requested-With': 'XMLHttpRequest',
            'User-Agent': 'SockBot 2.0.x Angelic Ariel'
        }
    },
    internals = {
        request: request.defaults(defaults),
        queue: async.queue(queueWorker, 1),
        queueWorker: queueWorker,
        defaults: defaults,
        setTrustLevel: setTrustLevel,
        setPostUrl: setPostUrl,
        cleanPostRaw: cleanPostRaw,
        cleanPost: cleanPost,
        signature: signature
    },
    /**
     * SockBot Virtual Trust Levels
     *
     * @readonly
     * @enum
     */
    trustLevels = {
        /** Bot Owner Trust Level */
        owner: 9,
        /** Forum Admin Trust Level */
        admin: 8,
        /** Forum Moderator Trust Level */
        moderator: 7,
        /** Forum Staff Trust Level */
        staff: 6,
        /** Discourst trust_level 4 Trust Level */
        tl4: 4,
        /** Discourst trust_level 3 Trust Level */
        tl3: 3,
        /** Discourst trust_level 2 Trust Level */
        tl2: 2,
        /** Discourst trust_level 1 Trust Level */
        tl1: 1,
        /** Discourst trust_level 0 Trust Level */
        tl0: 0,
        /** Ignored User Trust Level */
        ignored: 0
    };
exports.trustLevels = trustLevels;

/**
 * Process browser tasks with rate limiting
 *
 * @param {object} task Task configuration
 * @param {string} [task.method=GET] HTTP method to request
 * @param {string} task.url Site relative URL to request
 * @param {object} [task.form] HTTP form to use in HTTP request
 * @param {browser~requestComplete} [task.callback] Callback toprovide request results to
 * @param {Number} [task.delay=0] Seconds to delay callback after request for additional rate limiting
 * @param {Function} callback Queue task complete callback
 */
function queueWorker(task, callback) {
    if (task.url && task.url[0] === '/') {
        task.url = config.config.core.forum + task.url;
    }
    internals.request({
        url: task.url,
        method: task.method || 'GET',
        form: task.form
    }, (e, _, body) => {
        try {
            body = JSON.parse(body);
        } catch (ignore) {} //eslint-disable-line no-empty
        if (task.callback && typeof task.callback === 'function') {
            setTimeout(() => task.callback(e, body), task.delay || 0);
        }
        setTimeout(callback, 5000);
    });
}

/**
 * Post content to an existing content
 *
 * @param {number} topicId Topic to post to
 * @param {number} [replyTo] Post Number in topic that this post is in reply to
 * @param {string} content Post Contents to post
 * @param {postedCallback} callback Completion callback
 */
exports.createPost = function createPost(topicId, replyTo, content, callback) {
    if (callback === undefined) {
        callback = content;
        content = replyTo;
        replyTo = undefined;
    }
    if (typeof callback !== 'function') {
        throw new Error('callback must be supplied');
    }
    internals.queue.push({
        method: 'POST',
        url: '/posts',
        form: {
            raw: content + internals.signature,
            'topic_id': topicId,
            'is_warning': false,
            'reply_to_post_number': replyTo,
            category: '',
            archetype: 'regular',
            'auto_close_time': ''
        },
        callback: (err, body) => {
            if (err) {
                return callback(err);
            }
            callback(null, cleanPost(body));
        }
    });
};

/**
 * Edit an existing post.
 *
 * @param {number} postId Id number of the post to edit
 * @param {string} content New post content
 * @param {string} [editReason] Optional Edit Reason that no one ever uses
 * @param {postedCallback} callback Completion callback
 */
exports.editPost = function editPost(postId, content, editReason, callback) {
    if (callback === undefined) {
        callback = editReason;
        editReason = '';
    }
    if (typeof callback !== 'function') {
        throw new Error('callback must be supplied');
    }
    internals.queue.push({
        method: 'PUT',
        url: '/posts/' + postId,
        form: {
            post: {
                raw: content + internals.signature,
                'edit_reason': editReason
            }
        },
        callback: (err, body) => {
            if (err) {
                return callback(err);
            }
            callback(null, cleanPost(body));
        }
    });
};

/**
 * construct direct post link and direct in reply to link
 *
 * @see {@link ../external/posts/#external.module_posts.Post|Post}
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.module_posts.Post} post Post to generate links for
 * @param {number} post.topic_id Topic Id that the input post belongs to
 * @param {string} post.topic_slug URL slug of the topic
 * @param {number} post.post_number Ordinal of the input post in topic.
 * @param {number} post.reply_to_post_number The post_number the input post is a reply to
 * @returns {external.module_posts.CleanedPost} input post with urls set
 */
function setPostUrl(post) {
    post.url = config.config.core.forum + '/t/' + post.topic_slug + '/' + post.topic_id + '/';
    // not using camelcase for consistency with discourse
    post.reply_to = post.url + (post.reply_to_post_number || ''); //eslint-disable-line camelcase
    post.url += post.post_number;
    return post;
}

/**
 * Normalize discourse trust level to SockBot Virtual Trust Level
 *
 * @see {@link ../external/posts/#external.module_posts.Post|Post}
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.module_posts.Post} post Post to normalize trust levels on
 * @param {string} post.username Username of the post owner
 * @param {Number} post.trust_level Trust level of the post owner
 * @param {boolean} post.moderator Flags whether post owner has moderator powers
 * @param {boolean} post.admin Flags whether post owner has admin powers
 * @param {boolean} post.staff Flags whether post owner has staff powers
 * @returns {external.module_posts.CleanedPost} input post with normalized trust_level
 */
function setTrustLevel(post) {
    // Don't have a choice about using non-camelcase here...
    /*eslint-disable camelcase*/
    if (post.username === config.config.core.owner) {
        post.trust_level = trustLevels.owner;
    } else if (post.admin) {
        post.trust_level = trustLevels.admin;
    } else if (post.moderator) {
        post.trust_level = trustLevels.moderator;
    } else if (post.staff) {
        post.trust_level = trustLevels.staff;
    } else if (config.config.core.ignoreUsers.indexOf(post.username) >= 0) {
        post.trust_level = trustLevels.ignored;
    }
    /*eslint-enable camelcase*/
    return post;
}

/**
 * Clean post raw
 *
 * Provided and commented by flabdablet
 *
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.module_posts.Post} post Post to clean
 * @param {string} post.raw Raw text of the post to clean
 * @returns {external.module_posts.CleanedPost} input post with cleaned raw
 */
function cleanPostRaw(post) {
    function hidetags(code) {
        return code.replace(/\[(?!\x10)/g, '[\x10'); //DLE
    }

    // Regexes to match various kinds of code block
    const fencedgreedy = /^````.*\n(?:.*\n)*```(?:\n|$)/gm;
    const fencedlazy = /^```(?:[^`\n].*)?\n(?:.*\n)*?```(?:\n|$)/gm;
    const inline = /(`+)[^]*?\1/g;

    let text = post.raw || '',
        // Normalize newlines
        edited = text.
    replace(/\r\n?/g, '\n').

    // Remove low-ASCII control chars except \t (\x09) and \n (\x0a)
    replace(/[\x00-\x08\x0b-\x1f]/g, '').

    // Disable bbcode tags inside all code blocks
    replace(fencedgreedy, hidetags).
    replace(fencedlazy, hidetags).
    replace(inline, hidetags).

    // Ease recognition of bbcode [quote] and
    // [quote=whatever] start tags
    replace(/\[quote(?:=[^[\]]*)?]/ig, '\x02$&'). //STX

    // Ease recognition of bbcode [/quote] end tags
    replace(/\[\/quote]/ig, '$&\x03'); //ETX

    // Repeatedly strip non-nested quoted blocks until
    // no more remain; this removes nested blocks from
    // the innermost outward. Leave markers in places
    // where blocks were removed.
    do {
        text = edited;
        edited = text.replace(/\x02[^\x02\x03]*\x03/g, '\x1a'); //SUB
    } while (edited !== text);

    // Remove any leftover unbalanced quoted text,
    // treating places where blocks were removed
    // as if they were the missing end tags
    post.cleaned = text.
    replace(/\x02[^\x1a]*\x1a/g, '\x1a').

    // Ensure that quote stripping never coalesces
    // adjacent backticks into new GFM fence markers
    replace(/^(`+)\x1a`/gm, '$1 `').

    // Remove leftover control characters
    replace(/[\x00-\x08\x0b-\x1f]/g, '').

    // Remove GFM-fenced code blocks
    replace(fencedgreedy, '').
    replace(fencedlazy, '');
    return post;
}

/**
 * Clean discourse post for processing
 *
 * @see {@link ../external/posts/#external.module_posts.Post|Post}
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.posts.Post} post Input Post
 * @returns {external.posts.CleanedPost} Cleaned Post
 */
function cleanPost(post) {
    cleanPostRaw(post);
    setTrustLevel(post);
    setPostUrl(post);
    return post;
}

/**
 * Browser Request Callback
 *
 * @param {Exception} [err=null] Error encountered processing request
 * @param {Object} body JSON parsed response body. If invalid JSON will be `undefined`
 */
function requestComplete(err, body) {} //eslint-disable-line handle-callback-err, no-unused-vars

/**
 * Post Request Callback
 *
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {Exception} [err=null] Error encountered processing request
 * @param {external.posts.CleanedPost} post Cleaned post
 */
function postedCallback(err, post) {} //eslint-disable-line handle-callback-err, no-unused-vars

/* istanbul ignore else */
if (typeof GLOBAL.describe === 'function') {
    //test is running
    exports.internals = internals;
    exports.stubs = {
        requestComplete: requestComplete,
        postedCallback: postedCallback
    };
}
<commit_msg> add createPrivateMessage()<commit_after>'use strict';
/**
 * Webbrowser abstraction for communicating with discourse
 * @module browser
 * @license MIT
 */

const config = require('./config');

const request = require('request'),
    async = require('async');

const signature = '\n\n<!-- Posted by a clever robot -->',
    defaults = {
        rejectUnauthorized: false,
        jar: request.jar(),
        headers: {
            'X-Requested-With': 'XMLHttpRequest',
            'User-Agent': 'SockBot 2.0.x Angelic Ariel'
        }
    },
    internals = {
        request: request.defaults(defaults),
        queue: async.queue(queueWorker, 1),
        queueWorker: queueWorker,
        defaults: defaults,
        setTrustLevel: setTrustLevel,
        setPostUrl: setPostUrl,
        cleanPostRaw: cleanPostRaw,
        cleanPost: cleanPost,
        signature: signature
    },
    /**
     * SockBot Virtual Trust Levels
     *
     * @readonly
     * @enum
     */
    trustLevels = {
        /** Bot Owner Trust Level */
        owner: 9,
        /** Forum Admin Trust Level */
        admin: 8,
        /** Forum Moderator Trust Level */
        moderator: 7,
        /** Forum Staff Trust Level */
        staff: 6,
        /** Discourst trust_level 4 Trust Level */
        tl4: 4,
        /** Discourst trust_level 3 Trust Level */
        tl3: 3,
        /** Discourst trust_level 2 Trust Level */
        tl2: 2,
        /** Discourst trust_level 1 Trust Level */
        tl1: 1,
        /** Discourst trust_level 0 Trust Level */
        tl0: 0,
        /** Ignored User Trust Level */
        ignored: 0
    };
exports.trustLevels = trustLevels;

/**
 * Process browser tasks with rate limiting
 *
 * @param {object} task Task configuration
 * @param {string} [task.method=GET] HTTP method to request
 * @param {string} task.url Site relative URL to request
 * @param {object} [task.form] HTTP form to use in HTTP request
 * @param {browser~requestComplete} [task.callback] Callback toprovide request results to
 * @param {Number} [task.delay=0] Seconds to delay callback after request for additional rate limiting
 * @param {Function} callback Queue task complete callback
 */
function queueWorker(task, callback) {
    if (task.url && task.url[0] === '/') {
        task.url = config.config.core.forum + task.url;
    }
    internals.request({
        url: task.url,
        method: task.method || 'GET',
        form: task.form
    }, (e, _, body) => {
        try {
            body = JSON.parse(body);
        } catch (ignore) {} //eslint-disable-line no-empty
        if (task.callback && typeof task.callback === 'function') {
            setTimeout(() => task.callback(e, body), task.delay || 0);
        }
        setTimeout(callback, 5000);
    });
}

/**
 * Post content to an existing content
 *
 * @param {number} topicId Topic to post to
 * @param {number} [replyTo] Post Number in topic that this post is in reply to
 * @param {string} content Post Contents to post
 * @param {postedCallback} callback Completion callback
 */
exports.createPost = function createPost(topicId, replyTo, content, callback) {
    if (callback === undefined) {
        callback = content;
        content = replyTo;
        replyTo = undefined;
    }
    if (typeof callback !== 'function') {
        throw new Error('callback must be supplied');
    }
    internals.queue.push({
        method: 'POST',
        url: '/posts',
        form: {
            raw: content + internals.signature,
            'topic_id': topicId,
            'is_warning': false,
            'reply_to_post_number': replyTo,
            category: '',
            archetype: 'regular',
            'auto_close_time': ''
        },
        callback: (err, body) => {
            if (err) {
                return callback(err);
            }
            callback(null, cleanPost(body));
        }
    });
};

/**
 * Create a new private message.
 *
 * @param {string|string[]} to Username or names to create PM to
 * @param {string} title Title of the Private Message
 * @param {string} content Private Message contents
 * @param {postedCallback} callback Completion callback
 */
exports.createPrivateMessage = function createPrivateMessage(to, title, content, callback) {
    if (typeof callback !== 'function') {
        throw new Error('callback must be supplied');
    }
    internals.queue.push({
        method: 'POST',
        url: '/posts',
        form: {
            raw: content + internals.signature,
            'is_warning': false,
            'archetype': 'private_message',
            'title': title,
            'target_usernames': to
        },
        callback: (err, body) => {
            if (err) {
                return callback(err);
            }
            callback(null, cleanPost(body));
        }
    });
};

/**
 * Edit an existing post.
 *
 * @param {number} postId Id number of the post to edit
 * @param {string} content New post content
 * @param {string} [editReason] Optional Edit Reason that no one ever uses
 * @param {postedCallback} callback Completion callback
 */
exports.editPost = function editPost(postId, content, editReason, callback) {
    if (callback === undefined) {
        callback = editReason;
        editReason = '';
    }
    if (typeof callback !== 'function') {
        throw new Error('callback must be supplied');
    }
    internals.queue.push({
        method: 'PUT',
        url: '/posts/' + postId,
        form: {
            post: {
                raw: content + internals.signature,
                'edit_reason': editReason
            }
        },
        callback: (err, body) => {
            if (err) {
                return callback(err);
            }
            callback(null, cleanPost(body));
        }
    });
};

/**
 * construct direct post link and direct in reply to link
 *
 * @see {@link ../external/posts/#external.module_posts.Post|Post}
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.module_posts.Post} post Post to generate links for
 * @param {number} post.topic_id Topic Id that the input post belongs to
 * @param {string} post.topic_slug URL slug of the topic
 * @param {number} post.post_number Ordinal of the input post in topic.
 * @param {number} post.reply_to_post_number The post_number the input post is a reply to
 * @returns {external.module_posts.CleanedPost} input post with urls set
 */
function setPostUrl(post) {
    post.url = config.config.core.forum + '/t/' + post.topic_slug + '/' + post.topic_id + '/';
    // not using camelcase for consistency with discourse
    post.reply_to = post.url + (post.reply_to_post_number || ''); //eslint-disable-line camelcase
    post.url += post.post_number;
    return post;
}

/**
 * Normalize discourse trust level to SockBot Virtual Trust Level
 *
 * @see {@link ../external/posts/#external.module_posts.Post|Post}
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.module_posts.Post} post Post to normalize trust levels on
 * @param {string} post.username Username of the post owner
 * @param {Number} post.trust_level Trust level of the post owner
 * @param {boolean} post.moderator Flags whether post owner has moderator powers
 * @param {boolean} post.admin Flags whether post owner has admin powers
 * @param {boolean} post.staff Flags whether post owner has staff powers
 * @returns {external.module_posts.CleanedPost} input post with normalized trust_level
 */
function setTrustLevel(post) {
    // Don't have a choice about using non-camelcase here...
    /*eslint-disable camelcase*/
    if (post.username === config.config.core.owner) {
        post.trust_level = trustLevels.owner;
    } else if (post.admin) {
        post.trust_level = trustLevels.admin;
    } else if (post.moderator) {
        post.trust_level = trustLevels.moderator;
    } else if (post.staff) {
        post.trust_level = trustLevels.staff;
    } else if (config.config.core.ignoreUsers.indexOf(post.username) >= 0) {
        post.trust_level = trustLevels.ignored;
    }
    /*eslint-enable camelcase*/
    return post;
}

/**
 * Clean post raw
 *
 * Provided and commented by flabdablet
 *
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.module_posts.Post} post Post to clean
 * @param {string} post.raw Raw text of the post to clean
 * @returns {external.module_posts.CleanedPost} input post with cleaned raw
 */
function cleanPostRaw(post) {
    function hidetags(code) {
        return code.replace(/\[(?!\x10)/g, '[\x10'); //DLE
    }

    // Regexes to match various kinds of code block
    const fencedgreedy = /^````.*\n(?:.*\n)*```(?:\n|$)/gm;
    const fencedlazy = /^```(?:[^`\n].*)?\n(?:.*\n)*?```(?:\n|$)/gm;
    const inline = /(`+)[^]*?\1/g;

    let text = post.raw || '',
        // Normalize newlines
        edited = text.
    replace(/\r\n?/g, '\n').

    // Remove low-ASCII control chars except \t (\x09) and \n (\x0a)
    replace(/[\x00-\x08\x0b-\x1f]/g, '').

    // Disable bbcode tags inside all code blocks
    replace(fencedgreedy, hidetags).
    replace(fencedlazy, hidetags).
    replace(inline, hidetags).

    // Ease recognition of bbcode [quote] and
    // [quote=whatever] start tags
    replace(/\[quote(?:=[^[\]]*)?]/ig, '\x02$&'). //STX

    // Ease recognition of bbcode [/quote] end tags
    replace(/\[\/quote]/ig, '$&\x03'); //ETX

    // Repeatedly strip non-nested quoted blocks until
    // no more remain; this removes nested blocks from
    // the innermost outward. Leave markers in places
    // where blocks were removed.
    do {
        text = edited;
        edited = text.replace(/\x02[^\x02\x03]*\x03/g, '\x1a'); //SUB
    } while (edited !== text);

    // Remove any leftover unbalanced quoted text,
    // treating places where blocks were removed
    // as if they were the missing end tags
    post.cleaned = text.
    replace(/\x02[^\x1a]*\x1a/g, '\x1a').

    // Ensure that quote stripping never coalesces
    // adjacent backticks into new GFM fence markers
    replace(/^(`+)\x1a`/gm, '$1 `').

    // Remove leftover control characters
    replace(/[\x00-\x08\x0b-\x1f]/g, '').

    // Remove GFM-fenced code blocks
    replace(fencedgreedy, '').
    replace(fencedlazy, '');
    return post;
}

/**
 * Clean discourse post for processing
 *
 * @see {@link ../external/posts/#external.module_posts.Post|Post}
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {external.posts.Post} post Input Post
 * @returns {external.posts.CleanedPost} Cleaned Post
 */
function cleanPost(post) {
    cleanPostRaw(post);
    setTrustLevel(post);
    setPostUrl(post);
    return post;
}

/**
 * Browser Request Callback
 *
 * @param {Exception} [err=null] Error encountered processing request
 * @param {Object} body JSON parsed response body. If invalid JSON will be `undefined`
 */
function requestComplete(err, body) {} //eslint-disable-line handle-callback-err, no-unused-vars

/**
 * Post Request Callback
 *
 * @see {@link ../external/posts/#external.module_posts.CleanedPost|CleanedPost}
 *
 * @param {Exception} [err=null] Error encountered processing request
 * @param {external.posts.CleanedPost} post Cleaned post
 */
function postedCallback(err, post) {} //eslint-disable-line handle-callback-err, no-unused-vars

/* istanbul ignore else */
if (typeof GLOBAL.describe === 'function') {
    //test is running
    exports.internals = internals;
    exports.stubs = {
        requestComplete: requestComplete,
        postedCallback: postedCallback
    };
}
<|endoftext|>


<commit_before>exports.almostEqual = require('./almostEqual')
exports.create = require('./create')
exports.distance = require('./distance')
exports.equal = require('./equal')
exports.fromArray = require('./fromArray')
exports.offset = require('./offset')
exports.polarOffset = require('./polarOffset')
exports.toArray = require('./toArray')
exports.transform = require('./transform')
exports.transformMany = require('./transformMany')
exports.validate = require('./validate')
<commit_msg>Add point index docs<commit_after>// A set of operators for 2D point objects `{ x, y }`.
//
exports.almostEqual = require('./almostEqual')
exports.create = require('./create')
exports.distance = require('./distance')
exports.equal = require('./equal')
exports.fromArray = require('./fromArray')
exports.offset = require('./offset')
exports.polarOffset = require('./polarOffset')
exports.toArray = require('./toArray')
exports.transform = require('./transform')
exports.transformMany = require('./transformMany')
exports.validate = require('./validate')
<|endoftext|>


<commit_before>'use strict';
module.exports = {
	stdout: false,
	stderr: false
};
<commit_msg>Detect Google Chrome (#111)<commit_after>/* eslint-env browser */
'use strict';

function getChromeVersion() {
	const matches = /(Chrome|Chromium)\/(?<chromeVersion>\d+)\./.exec(navigator.userAgent);

	if (!matches) {
		return;
	}

	return Number.parseInt(matches.groups.chromeVersion, 10);
}

const colorSupport = getChromeVersion() >= 69 ? {
	level: 1,
	hasBasic: true,
	has256: false,
	has16m: false
} : false;

module.exports = {
	stdout: colorSupport,
	stderr: colorSupport
};
<|endoftext|>


<commit_before>(function() { define(['zepto', 'marked', 'db', 'list', 'status', 'uploader'], function($, marked, dbEngine, list, status, uploader) {

  var root = window.location.protocol + "//" + window.location.host;

  Date.prototype.format = function(format) {
    var o = {
      "M+" : this.getMonth()+1, //month
      "d+" : this.getDate(),    //day
      "h+" : this.getHours(),   //hour
      "m+" : this.getMinutes(), //minute
      "s+" : this.getSeconds(), //second
      "q+" : Math.floor((this.getMonth()+3)/3),  //quarter
      "S" : this.getMilliseconds() //millisecond
    }

    if(/(y+)/.test(format)) format=format.replace(RegExp.$1,
      (this.getFullYear()+"").substr(4 - RegExp.$1.length));
    for(var k in o)if(new RegExp("("+ k +")").test(format))
      format = format.replace(RegExp.$1,
        RegExp.$1.length==1 ? o[k] :
          ("00"+ o[k]).substr((""+ o[k]).length));
    return format;
  };

  var scrollTop = function() {
    var hTop = $('html').scrollTop();
    return hTop === 0 ? $('body').scrollTop() : hTop;
  };

  var range = function(el) {
    return $.parseJSON(el.attr('data-range'));
  };

  var heading = /^\s*(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/;
  var lheading = /^([^\n]+)\n *(=|-){3,} *\n*/;
  var getTitle = function(md) {
    var cap;
    if (cap = heading.exec(md)) {
      return cap[2];
    }
    if (cap = lheading.exec(md)) {
      return cap[1];
    }
    var title = md.substr(0, 64);
    if ($.trim(title) === '') {
      return new Date().format("yyyy-MM-dd h:mm:ss");
    }
    return title;
  };

  var getCMMode = (function () {
    var aliases = {
      html: "htmlmixed",
      js: "javascript",
      json: "application/json",
      c: "text/x-csrc",
      "c++": "text/x-c++src",
      java: "text/x-java",
      csharp: "text/x-csharp",
      "c#": "text/x-csharp",
      scala: "text/x-scala"
    };

    var i, modes = {}, mimes = {}, mime;

    var list = [];
    for (var m in CodeMirror.modes)
      if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);
    for (i = 0; i < list.length; i++) {
      modes[list[i]] = list[i];
    }
    var mimesList = [];
    for (var m in CodeMirror.mimeModes)
      if (CodeMirror.mimeModes.propertyIsEnumerable(m))
        mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});
    for (i = 0; i < mimesList.length; i++) {
      mime = mimesList[i].mime;
      mimes[mime] = mimesList[i].mime;
    }

    for (var a in aliases) {
      if (aliases[a] in modes || aliases[a] in mimes)
        modes[a] = aliases[a];
    }

    return function (lang) {
      return CodeMirror.getMode({}, modes[lang]);
    };
  }());

  var setupWorker = function(met) {
    var worker = new Worker(root +"/javascripts/apps/worker.js");
    worker.addEventListener('message', function(e) {
      switch (e.data.type) {
        case 'result': {
          var result = $('
').html(e.data.text).children().length;
          var current = $(met.mbsa).length;
          if (result !== current) {
            met.needFullRender = true;
          }
          break;
        }
        case 'request': {
          worker.postMessage({cmd: 'parse', text: met.editor.getValue()});
          break;
        }
      }
    }, false);
    worker.postMessage({cmd: 'parse', text: met.editor.getValue()});
  };

  var MeT = function(inputArea, previewArea, inputWrapper, previewWrapper, listArea, statusArea) {
    var self = this;
    this.inputArea = inputArea;
    this.previewArea = previewArea;
    this.inputWrapper = inputWrapper;
    this.previewWrapper = previewWrapper;
    this.listArea = listArea;
    this.statusArea = statusArea;
    this.mbsa = previewArea + ' > .marked-block';
    this.area = $(inputArea)[0];
    this.preview = $(previewArea);
    this.editor = CodeMirror.fromTextArea(self.area, {
      mode: 'gfm',
      theme: 'default',
      tabSize: 2,
      autofocus: true,
      lineNumbers: false,
      lineWrapping: true,
      matchBrackets: true,
      showTrailingSpace: true,
      dragDrop: false,
      extraKeys: {
        Tab: function(cm) {
          if (cm.getSelection().length === 0) {
            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
            cm.replaceSelection(spaces, "end", "+input");
          } else {
            CodeMirror.commands.indentMore(cm);
          }
        }
      }
    });
    this.needFullRender = false;
    this.editorMarker = null;

    var newTop = function(h1, t1, h2, t2, m) {
      // moving h1 t1 to match h2 t2
      var top = (h2 + t2) - (h1 + t1) + t1;
      return (top >= m ? m : top);
    };

    var topPadding = parseInt($(self.previewWrapper).css('padding-top'));

    var syncTwo = function(sy1, sy2) {
      var sTop = scrollTop();
      sTop = sTop - topPadding > 0 ? sTop - topPadding : sTop;
      var top = newTop(sy1.h, sy1.t, sy2.h, sy2.t, sTop);
      $(sy1.sel).animate({top: top}, 300);
    };

    var lastTrackedRange = [0,0];
    var lastTrackedCursor = -1;

    self.editor.on('cursorActivity', function(cm) {
      var pos = cm.indexFromPos(cm.getCursor());
      if (pos === lastTrackedCursor) {
        return false;
      }
      lastTrackedCursor = pos;
      $(self.mbsa).each(function (i, v) {
        var b = $(v), r = range(b);
        if (r[0] <= pos && pos <= r[1]) {
          if (lastTrackedRange[0] === r[0] && lastTrackedRange[1] === r[1]) {
            return false;
          }
          clearMarker();
          b.addClass('block-current');
          var h1 = b.position().top + b.height() * 0.5;
          var t1 = $(self.previewWrapper).position().top;
          var h2 = (self.editor.charCoords(self.editor.posFromIndex(r[0]), 'local').top + self.editor.charCoords(self.editor.posFromIndex(r[1]), 'local').bottom) * 0.5;
          var t2 = $(self.inputWrapper).position().top;
          syncTwo({h: h1, t: t1, sel: self.previewWrapper}, {h: h2, t: t2, sel: self.inputWrapper});
          lastTrackedRange = r;
          return false;
        }
      });
    });

    self.preview.on('click', 'a', function(evt) {
      window.open($(this).attr('href'), '_blank');
      evt.stopPropagation(); // Zepto doesn't work
    });

    self.preview.on('click', self.mbsa, function(evt) {
      evt.preventDefault();
      var r = range($(this));
      if (lastTrackedRange[0] === r[0] && lastTrackedRange[1] === r[1]) {
        return;
      }
      clearMarker();
      $(this).addClass('block-current');
      var posTop = self.editor.posFromIndex(r[0]);
      var posBottom = self.editor.posFromIndex(r[1]);
      var h1 = (self.editor.charCoords(posTop, 'local').top + self.editor.charCoords(posBottom, 'local').bottom) * 0.5;
      var t1 = $(self.inputWrapper).position().top;
      var h2 = $(this).position().top + $(this).height() * 0.5;
      var t2 = $(self.previewWrapper).position().top;
      syncTwo({h: h1, t: t1, sel: self.inputWrapper}, {h: h2, t: t2, sel: self.previewWrapper});
      lastTrackedRange = r;
      self.editorMarker = self.editor.markText(posTop, posBottom, {className: 'block-current', clearOnEnter: true});
    });

    $(document).on('scroll', function(evt) {
      evt.preventDefault();
      var sTop = scrollTop();
      var posV = $(self.previewWrapper).position().top;
      var posE = $(self.inputWrapper).position().top;
      sTop = sTop - topPadding > 0 ? sTop - topPadding : sTop;
      if ((sTop == 0 && posV < 0) || posV >= sTop) {
        $(self.previewWrapper).animate({top: sTop}, 'fast');
      }
      if ((sTop == 0 && posE < 0) || posE >= sTop) {
        $(self.inputWrapper).animate({top: sTop}, 'fast');
      }
    });

    self.preview.on('mouseenter', self.mbsa, function() {
      $(this).addClass('block-highlight');
    });
    self.preview.on('mouseleave', self.mbsa, function() {
      $(this).removeClass('block-highlight');
    });

    var clearMarker = function() {
      if (self.editorMarker !== null) self.editorMarker.clear();
      $(self.mbsa).removeClass('block-current');
    };

  };

  MeT.prototype.getEditor = function() {
    return this.editor;
  };

  MeT.prototype.loadLastPost = function(self) {
    self.currentPost = {};
    self.db.getPosts(function(posts) {
      var post = posts.slice(-1)[0];
      if (typeof post !== 'undefined') {
        self.currentPost = post;
        self.editor.setValue(self.currentPost.content);
        self.editor.clearHistory();
      } else {
        $.get(root + '/docs/index.md', function(md){
          self.editor.setValue(md);
          self.editor.clearHistory();
        });
      }
    });
  };

  MeT.prototype.newPost = function(callback) {
    this.currentPost = {};
    this.editor.setValue('');
    this.editor.clearHistory();
    callback();
  };

  MeT.prototype.loadPost = function(key, callback) {
    var self = this;
    if (key !== self.currentPost.created_at) {
      self.db.getPost({created_at: key}, function(p) {
        self.currentPost = p;
        self.editor.setValue(self.currentPost.content);
        self.editor.clearHistory();
        callback();
      });
    }
  };

  MeT.prototype.deletePost = function(key, callback) {
    var self = this;
    self.db.deletePost({created_at: key}, function() {
      self.loadLastPost(self);
      callback();
    });
  };

  MeT.prototype.saveChanges = function() {
    var self = this;
    if (self.editor.getValue() !== self.currentPost.content) {
      $.extend(self.currentPost, {
        title:  getTitle(self.editor.getValue()),
        content: self.editor.getValue()
      });
      self.db.putPost(self.currentPost, function(key) {
        self.currentPost.created_at = key;
      });
    }
  };

  MeT.prototype.met = function() {
    var self = this;
    dbEngine.init(function(db) {
      self.db = db;
      self._met();
      list(self.listArea, self);
      status(self.statusArea, self);
      uploader(self.inputWrapper, self);
    });
    return self;
  };

  MeT.prototype._met = function() {
    var self = this;
    var mbsa = self.mbsa;
    var preview = self.preview;
    var editor = self.editor;

    setupWorker(self);
    marked.setOptions({
      gfm: true,
      tables: true,
      breaks: false,
      blocks: true,
      pedantic: false,
      sanitize: true,
      smartLists: true,
      smartypants: true,
      langPrefix: 'lang-',
      highlight: function(code, lang) {
        var div = $('
');
        CodeMirror.runMode(code, getCMMode(lang), div[0]);
        return '' + div.html() + '';
      }
    });

    self.loadLastPost(self);
    editor.on('change', function(cm, change) {
      self.saveChanges();
      preview = $(self.previewArea);
      if (preview.length === 0) {
        return;
      }

      var blocks = $(mbsa);
      var from = -1, to = -1, relative = 0, baseBlock = null;

      if (self.needFullRender || blocks.length === 0) {
        preview.html(marked(cm.getValue()));
        MathJax.Hub.Queue(["Typeset",MathJax.Hub, preview[0]]);
        self.needFullRender = false;
        return;
      }

      var base = range($(blocks[0]))[0];
      if (base > 0) {
        baseBlock = $('

');
        baseBlock.hide().insertBefore($(blocks[0]));
        blocks = $(mbsa);
      }

      do {
        var removed = change.removed.join("\n");
        var text = change.text.join("\n");
        var f = cm.indexFromPos(change.from);
        var t = f + removed.length;
        if (from < 0 || from > f) from = f;
        if (to < t) to = t;
        relative += (text.length - removed.length);
      } while (change = change.next);

      var sb = -1, eb = -1;
      for (var i = 0; i < blocks.length; i++) {
        var block = $(blocks[i]);
        if (range(block)[0] <= from && from <= range(block)[1]) {
          sb = i;
        }

        if (range(block)[0] <= to && to <= range(block)[1]) {
          eb = i;
        }

        if (i === blocks.length - 1) {
          if (eb < 0) eb = i;
          if (sb < 0) sb = i;
        }

        if (sb < 0 || eb < 0) {
          continue;
        }

        if (sb - 1 >= 0) sb = sb - 1;
        if (eb + 1 < blocks.length) eb = eb + 1;
        var startBlock = $(blocks[sb]), endBlock = $(blocks[eb]);
        var md = cm.getRange(cm.posFromIndex(range(startBlock)[0]), cm.posFromIndex(range(endBlock)[1] + relative + 1));

        marked(md, {}, function(err, htmlOut) {
          if (err !== null) {
            console.log("Markdown parse error: " + err);
            return;
          }
          for (i = sb + 1; i <= eb; i++) {
            blocks[i].remove();
          }
          startBlock.replaceWith(htmlOut);
          MathJax.Hub.Queue(["Typeset",MathJax.Hub, preview[0]]); // TODO should make the changed maths reproduce only?

          blocks = $(mbsa);
          if (blocks.length !== 0) {
            $.each(blocks, function (i, v) {
              var block = $(v);
              if (i > 0) {
                var prev = $(blocks[i-1]);
                if (range(block)[0] !== range(prev)[1] + 1) {
                  var begin = range(prev)[1] + 1;
                  var end = begin - range(block)[0] + range(block)[1];
                  block.attr('data-range', '[' + [begin, end] + ']');
                }
              }
            });
          }

          if (baseBlock !== null) {
            baseBlock.remove();
          }

        });

        break;
      } // for blocks

    }); // editor.on('change')
  };

  return function(input, preview, inputWrapper, previewWrapper, listArea, statusArea) {
    return new MeT(input, preview, inputWrapper, previewWrapper, listArea, statusArea).met();
  };

}); }());
<commit_msg>Sync with blocks top, instead of middle<commit_after>(function() { define(['zepto', 'marked', 'db', 'list', 'status', 'uploader'], function($, marked, dbEngine, list, status, uploader) {

  var root = window.location.protocol + "//" + window.location.host;

  Date.prototype.format = function(format) {
    var o = {
      "M+" : this.getMonth()+1, //month
      "d+" : this.getDate(),    //day
      "h+" : this.getHours(),   //hour
      "m+" : this.getMinutes(), //minute
      "s+" : this.getSeconds(), //second
      "q+" : Math.floor((this.getMonth()+3)/3),  //quarter
      "S" : this.getMilliseconds() //millisecond
    }

    if(/(y+)/.test(format)) format=format.replace(RegExp.$1,
      (this.getFullYear()+"").substr(4 - RegExp.$1.length));
    for(var k in o)if(new RegExp("("+ k +")").test(format))
      format = format.replace(RegExp.$1,
        RegExp.$1.length==1 ? o[k] :
          ("00"+ o[k]).substr((""+ o[k]).length));
    return format;
  };

  var scrollTop = function() {
    var hTop = $('html').scrollTop();
    return hTop === 0 ? $('body').scrollTop() : hTop;
  };

  var range = function(el) {
    return $.parseJSON(el.attr('data-range'));
  };

  var heading = /^\s*(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/;
  var lheading = /^([^\n]+)\n *(=|-){3,} *\n*/;
  var getTitle = function(md) {
    var cap;
    if (cap = heading.exec(md)) {
      return cap[2];
    }
    if (cap = lheading.exec(md)) {
      return cap[1];
    }
    var title = md.substr(0, 64);
    if ($.trim(title) === '') {
      return new Date().format("yyyy-MM-dd h:mm:ss");
    }
    return title;
  };

  var getCMMode = (function () {
    var aliases = {
      html: "htmlmixed",
      js: "javascript",
      json: "application/json",
      c: "text/x-csrc",
      "c++": "text/x-c++src",
      java: "text/x-java",
      csharp: "text/x-csharp",
      "c#": "text/x-csharp",
      scala: "text/x-scala"
    };

    var i, modes = {}, mimes = {}, mime;

    var list = [];
    for (var m in CodeMirror.modes)
      if (CodeMirror.modes.propertyIsEnumerable(m)) list.push(m);
    for (i = 0; i < list.length; i++) {
      modes[list[i]] = list[i];
    }
    var mimesList = [];
    for (var m in CodeMirror.mimeModes)
      if (CodeMirror.mimeModes.propertyIsEnumerable(m))
        mimesList.push({mime: m, mode: CodeMirror.mimeModes[m]});
    for (i = 0; i < mimesList.length; i++) {
      mime = mimesList[i].mime;
      mimes[mime] = mimesList[i].mime;
    }

    for (var a in aliases) {
      if (aliases[a] in modes || aliases[a] in mimes)
        modes[a] = aliases[a];
    }

    return function (lang) {
      return CodeMirror.getMode({}, modes[lang]);
    };
  }());

  var setupWorker = function(met) {
    var worker = new Worker(root +"/javascripts/apps/worker.js");
    worker.addEventListener('message', function(e) {
      switch (e.data.type) {
        case 'result': {
          var result = $('
').html(e.data.text).children().length;
          var current = $(met.mbsa).length;
          if (result !== current) {
            met.needFullRender = true;
          }
          break;
        }
        case 'request': {
          worker.postMessage({cmd: 'parse', text: met.editor.getValue()});
          break;
        }
      }
    }, false);
    worker.postMessage({cmd: 'parse', text: met.editor.getValue()});
  };

  var MeT = function(inputArea, previewArea, inputWrapper, previewWrapper, listArea, statusArea) {
    var self = this;
    this.inputArea = inputArea;
    this.previewArea = previewArea;
    this.inputWrapper = inputWrapper;
    this.previewWrapper = previewWrapper;
    this.listArea = listArea;
    this.statusArea = statusArea;
    this.mbsa = previewArea + ' > .marked-block';
    this.area = $(inputArea)[0];
    this.preview = $(previewArea);
    this.editor = CodeMirror.fromTextArea(self.area, {
      mode: 'gfm',
      theme: 'default',
      tabSize: 2,
      autofocus: true,
      lineNumbers: false,
      lineWrapping: true,
      matchBrackets: true,
      showTrailingSpace: true,
      dragDrop: false,
      extraKeys: {
        Tab: function(cm) {
          if (cm.getSelection().length === 0) {
            var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
            cm.replaceSelection(spaces, "end", "+input");
          } else {
            CodeMirror.commands.indentMore(cm);
          }
        }
      }
    });
    this.needFullRender = false;
    this.editorMarker = null;

    var newTop = function(h1, t1, h2, t2, m) {
      // moving h1 t1 to match h2 t2
      var top = (h2 + t2) - (h1 + t1) + t1;
      return (top >= m ? m : top);
    };

    var topPadding = parseInt($(self.previewWrapper).css('padding-top'));

    var syncTwo = function(sy1, sy2) {
      var sTop = scrollTop();
      sTop = sTop - topPadding > 0 ? sTop - topPadding : sTop;
      var top = newTop(sy1.h, sy1.t, sy2.h, sy2.t, sTop);
      $(sy1.sel).animate({top: top}, 300);
    };

    var lastTrackedRange = [0,0];
    var lastTrackedCursor = -1;

    self.editor.on('cursorActivity', function(cm) {
      var pos = cm.indexFromPos(cm.getCursor());
      if (pos === lastTrackedCursor) {
        return false;
      }
      lastTrackedCursor = pos;
      $(self.mbsa).each(function (i, v) {
        var b = $(v), r = range(b);
        if (r[0] <= pos && pos <= r[1]) {
          if (lastTrackedRange[0] === r[0] && lastTrackedRange[1] === r[1]) {
            return false;
          }
          clearMarker();
          b.addClass('block-current');
          var h1 = b.position().top;
          var t1 = $(self.previewWrapper).position().top;
          var h2 = self.editor.charCoords(self.editor.posFromIndex(r[0]), 'local').top;
          var t2 = $(self.inputWrapper).position().top;
          syncTwo({h: h1, t: t1, sel: self.previewWrapper}, {h: h2, t: t2, sel: self.inputWrapper});
          lastTrackedRange = r;
          return false;
        }
      });
    });

    self.preview.on('click', 'a', function(evt) {
      window.open($(this).attr('href'), '_blank');
      evt.stopPropagation(); // Zepto doesn't work
    });

    self.preview.on('click', self.mbsa, function(evt) {
      evt.preventDefault();
      var r = range($(this));
      if (lastTrackedRange[0] === r[0] && lastTrackedRange[1] === r[1]) {
        return;
      }
      clearMarker();
      $(this).addClass('block-current');
      var posTop = self.editor.posFromIndex(r[0]);
      var posBottom = self.editor.posFromIndex(r[1]);
      var h1 = self.editor.charCoords(posTop, 'local').top;
      var t1 = $(self.inputWrapper).position().top;
      var h2 = $(this).position().top;
      var t2 = $(self.previewWrapper).position().top;
      syncTwo({h: h1, t: t1, sel: self.inputWrapper}, {h: h2, t: t2, sel: self.previewWrapper});
      lastTrackedRange = r;
      self.editorMarker = self.editor.markText(posTop, posBottom, {className: 'block-current', clearOnEnter: true});
    });

    $(document).on('scroll', function(evt) {
      evt.preventDefault();
      var sTop = scrollTop();
      var posV = $(self.previewWrapper).position().top;
      var posE = $(self.inputWrapper).position().top;
      sTop = sTop - topPadding > 0 ? sTop - topPadding : sTop;
      if ((sTop === 0 && posV < 0) || posV >= sTop) {
        $(self.previewWrapper).animate({top: sTop}, 'fast');
      }
      if ((sTop === 0 && posE < 0) || posE >= sTop) {
        $(self.inputWrapper).animate({top: sTop}, 'fast');
      }
    });

    self.preview.on('mouseenter', self.mbsa, function() {
      $(this).addClass('block-highlight');
    });
    self.preview.on('mouseleave', self.mbsa, function() {
      $(this).removeClass('block-highlight');
    });

    var clearMarker = function() {
      if (self.editorMarker !== null) self.editorMarker.clear();
      $(self.mbsa).removeClass('block-current');
    };

  };

  MeT.prototype.getEditor = function() {
    return this.editor;
  };

  MeT.prototype.loadLastPost = function(self) {
    self.currentPost = {};
    self.db.getPosts(function(posts) {
      var post = posts.slice(-1)[0];
      if (typeof post !== 'undefined') {
        self.currentPost = post;
        self.editor.setValue(self.currentPost.content);
        self.editor.clearHistory();
      } else {
        $.get(root + '/docs/index.md', function(md){
          self.editor.setValue(md);
          self.editor.clearHistory();
        });
      }
    });
  };

  MeT.prototype.newPost = function(callback) {
    this.currentPost = {};
    this.editor.setValue('');
    this.editor.clearHistory();
    callback();
  };

  MeT.prototype.loadPost = function(key, callback) {
    var self = this;
    if (key !== self.currentPost.created_at) {
      self.db.getPost({created_at: key}, function(p) {
        self.currentPost = p;
        self.editor.setValue(self.currentPost.content);
        self.editor.clearHistory();
        callback();
      });
    }
  };

  MeT.prototype.deletePost = function(key, callback) {
    var self = this;
    self.db.deletePost({created_at: key}, function() {
      self.loadLastPost(self);
      callback();
    });
  };

  MeT.prototype.saveChanges = function() {
    var self = this;
    if (self.editor.getValue() !== self.currentPost.content) {
      $.extend(self.currentPost, {
        title:  getTitle(self.editor.getValue()),
        content: self.editor.getValue()
      });
      self.db.putPost(self.currentPost, function(key) {
        self.currentPost.created_at = key;
      });
    }
  };

  MeT.prototype.met = function() {
    var self = this;
    dbEngine.init(function(db) {
      self.db = db;
      self._met();
      list(self.listArea, self);
      status(self.statusArea, self);
      uploader(self.inputWrapper, self);
    });
    return self;
  };

  MeT.prototype._met = function() {
    var self = this;
    var mbsa = self.mbsa;
    var preview = self.preview;
    var editor = self.editor;

    setupWorker(self);
    marked.setOptions({
      gfm: true,
      tables: true,
      breaks: false,
      blocks: true,
      pedantic: false,
      sanitize: true,
      smartLists: true,
      smartypants: true,
      langPrefix: 'lang-',
      highlight: function(code, lang) {
        var div = $('
');
        CodeMirror.runMode(code, getCMMode(lang), div[0]);
        return '' + div.html() + '';
      }
    });

    self.loadLastPost(self);
    editor.on('change', function(cm, change) {
      self.saveChanges();
      preview = $(self.previewArea);
      if (preview.length === 0) {
        return;
      }

      var blocks = $(mbsa);
      var from = -1, to = -1, relative = 0, baseBlock = null;

      if (self.needFullRender || blocks.length === 0) {
        preview.html(marked(cm.getValue()));
        MathJax.Hub.Queue(["Typeset",MathJax.Hub, preview[0]]);
        self.needFullRender = false;
        return;
      }

      var base = range($(blocks[0]))[0];
      if (base > 0) {
        baseBlock = $('

');
        baseBlock.hide().insertBefore($(blocks[0]));
        blocks = $(mbsa);
      }

      do {
        var removed = change.removed.join("\n");
        var text = change.text.join("\n");
        var f = cm.indexFromPos(change.from);
        var t = f + removed.length;
        if (from < 0 || from > f) from = f;
        if (to < t) to = t;
        relative += (text.length - removed.length);
      } while (change = change.next);

      var sb = -1, eb = -1;
      for (var i = 0; i < blocks.length; i++) {
        var block = $(blocks[i]);
        if (range(block)[0] <= from && from <= range(block)[1]) {
          sb = i;
        }

        if (range(block)[0] <= to && to <= range(block)[1]) {
          eb = i;
        }

        if (i === blocks.length - 1) {
          if (eb < 0) eb = i;
          if (sb < 0) sb = i;
        }

        if (sb < 0 || eb < 0) {
          continue;
        }

        if (sb - 1 >= 0) sb = sb - 1;
        if (eb + 1 < blocks.length) eb = eb + 1;
        var startBlock = $(blocks[sb]), endBlock = $(blocks[eb]);
        var md = cm.getRange(cm.posFromIndex(range(startBlock)[0]), cm.posFromIndex(range(endBlock)[1] + relative + 1));

        marked(md, {}, function(err, htmlOut) {
          if (err !== null) {
            console.log("Markdown parse error: " + err);
            return;
          }
          for (i = sb + 1; i <= eb; i++) {
            blocks[i].remove();
          }
          startBlock.replaceWith(htmlOut);
          MathJax.Hub.Queue(["Typeset",MathJax.Hub, preview[0]]); // TODO should make the changed maths reproduce only?

          blocks = $(mbsa);
          if (blocks.length !== 0) {
            $.each(blocks, function (i, v) {
              var block = $(v);
              if (i > 0) {
                var prev = $(blocks[i-1]);
                if (range(block)[0] !== range(prev)[1] + 1) {
                  var begin = range(prev)[1] + 1;
                  var end = begin - range(block)[0] + range(block)[1];
                  block.attr('data-range', '[' + [begin, end] + ']');
                }
              }
            });
          }

          if (baseBlock !== null) {
            baseBlock.remove();
          }

        });

        break;
      } // for blocks

    }); // editor.on('change')
  };

  return function(input, preview, inputWrapper, previewWrapper, listArea, statusArea) {
    return new MeT(input, preview, inputWrapper, previewWrapper, listArea, statusArea).met();
  };

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


<commit_before>/* eslint-disable camelcase */
const { get } = require('lodash')

const { format } = require('../../../client/utils/date-utils')
const urls = require('../../../lib/urls')

const formatContacts = (contacts) =>
  contacts.length > 1
    ? 'Multiple contacts'
    : contacts.map((contact) => getContactName(contact))

const getContactName = (contact) =>
  get(contact, 'name') ? contact.name : 'Unknown contact'

const formatParticipantName = (participant) =>
  get(participant, 'adviser') && get(participant, 'team')
    ? `${participant.adviser.name}, ${participant.team.name}`
    : get(participant, 'adviser')
    ? participant.adviser.name
    : 'Unknown adviser'

const formatParticipants = (dit_participants) =>
  dit_participants.length > 1
    ? 'Multiple advisers'
    : dit_participants.map((participant) => formatParticipantName(participant))

const getbadgeLabel = (type, hasFeedback = false) => {
  const badges = {
    interaction: 'interaction',
    service_delivery: 'Service delivery',
  }
  return [
    {
      text: badges[type],
    },
    hasFeedback && {
      text: 'Business intelligence',
    },
  ].filter(Boolean)
}

export const transformInteractionToListItem = ({
  date,
  subject,
  dit_participants,
  service,
  company,
  id,
  contacts,
  kind,
  was_policy_feedback_provided,
} = {}) => ({
  id,
  metadata: [
    { label: 'Date', value: format(date, 'dd MMMM yyyy') },
    {
      label: 'Contact(s)',
      value: contacts && formatContacts(contacts),
    },
    { label: 'Company', value: company?.name },
    {
      label: 'Adviser(s)',
      value: dit_participants && formatParticipants(dit_participants),
    },
    {
      label: 'Service',
      value: service?.name,
    },
  ].filter(({ value }) => Boolean(value)),
  headingUrl: urls.interactions.detail(id),
  badges: getbadgeLabel(kind, was_policy_feedback_provided),
  headingText: subject,
})

export const transformResponseToCollection = ({ count, results = [] }) => ({
  count,
  results: results.map(transformInteractionToListItem),
})
<commit_msg>Uppercase badge<commit_after>/* eslint-disable camelcase */
const { get } = require('lodash')

const { format } = require('../../../client/utils/date-utils')
const urls = require('../../../lib/urls')

const formatContacts = (contacts) =>
  contacts.length > 1
    ? 'Multiple contacts'
    : contacts.map((contact) => getContactName(contact))

const getContactName = (contact) =>
  get(contact, 'name') ? contact.name : 'Unknown contact'

const formatParticipantName = (participant) =>
  get(participant, 'adviser') && get(participant, 'team')
    ? `${participant.adviser.name}, ${participant.team.name}`
    : get(participant, 'adviser')
    ? participant.adviser.name
    : 'Unknown adviser'

const formatParticipants = (dit_participants) =>
  dit_participants.length > 1
    ? 'Multiple advisers'
    : dit_participants.map((participant) => formatParticipantName(participant))

const getbadgeLabel = (type, hasFeedback = false) => {
  const badges = {
    interaction: 'Interaction',
    service_delivery: 'Service delivery',
  }
  return [
    {
      text: badges[type],
    },
    hasFeedback && {
      text: 'Business intelligence',
    },
  ].filter(Boolean)
}

export const transformInteractionToListItem = ({
  date,
  subject,
  dit_participants,
  service,
  company,
  id,
  contacts,
  kind,
  was_policy_feedback_provided,
} = {}) => ({
  id,
  metadata: [
    { label: 'Date', value: format(date, 'dd MMMM yyyy') },
    {
      label: 'Contact(s)',
      value: contacts && formatContacts(contacts),
    },
    { label: 'Company', value: company?.name },
    {
      label: 'Adviser(s)',
      value: dit_participants && formatParticipants(dit_participants),
    },
    {
      label: 'Service',
      value: service?.name,
    },
  ].filter(({ value }) => Boolean(value)),
  headingUrl: urls.interactions.detail(id),
  badges: getbadgeLabel(kind, was_policy_feedback_provided),
  headingText: subject,
})

export const transformResponseToCollection = ({ count, results = [] }) => ({
  count,
  results: results.map(transformInteractionToListItem),
})
<|endoftext|>


<commit_before>'use strict';
const isIp = require('is-ip');

const defaults = {
	timeout: 5000
};

const urls = {
	v4: [
		'https://ipv4.icanhazip.com/',
		'https://api.ipify.org/'
	],
	v6: [
		'https://ipv6.icanhazip.com/',
		'https://api6.ipify.org/'
	]
};

let xhr;

const sendXhr = async (url, options, version) => {
	return new Promise((resolve, reject) => {
		xhr = new XMLHttpRequest();
		xhr.addEventListener('error', reject, {once: true});
		xhr.addEventListener('timeout', reject, {once: true});

		xhr.addEventListener('load', () => {
			const ip = xhr.responseText.trim();

			if (!ip || !isIp[version](ip)) {
				reject();
				return;
			}

			resolve(ip);
		}, {once: true});

		xhr.open('GET', url);
		xhr.timeout = options.timeout;
		xhr.send();
	});
};

const queryHttps = async (version, options) => {
	let ip;
	const urls_ = [].concat.apply(urls[version], options.fallbackUrls || []);
	for (const url of urls_) {
		try {
			// eslint-disable-next-line no-await-in-loop
			ip = await sendXhr(url, options, version);
			return ip;
		} catch (_) {}
	}

	throw new Error('Couldn\'t find your IP');
};

queryHttps.cancel = () => {
	xhr.abort();
};

module.exports.v4 = options => queryHttps('v4', {...defaults, ...options});

module.exports.v6 = options => queryHttps('v6', {...defaults, ...options});
<commit_msg>Fix problem with calling the browser version concurrently (#53)<commit_after>'use strict';
const isIp = require('is-ip');

class CancelError extends Error {
	constructor() {
		super('Request was cancelled');
		this.name = 'CancelError';
	}

	get isCanceled() {
		return true;
	}
}

const defaults = {
	timeout: 5000
};

const urls = {
	v4: [
		'https://ipv4.icanhazip.com/',
		'https://api.ipify.org/'
	],
	v6: [
		'https://ipv6.icanhazip.com/',
		'https://api6.ipify.org/'
	]
};

const sendXhr = (url, options, version) => {
	const xhr = new XMLHttpRequest();

	let _reject;
	const promise = new Promise((resolve, reject) => {
		_reject = reject;
		xhr.addEventListener('error', reject, {once: true});
		xhr.addEventListener('timeout', reject, {once: true});

		xhr.addEventListener('load', () => {
			const ip = xhr.responseText.trim();

			if (!ip || !isIp[version](ip)) {
				reject();
				return;
			}

			resolve(ip);
		}, {once: true});

		xhr.open('GET', url);
		xhr.timeout = options.timeout;
		xhr.send();
	});

	promise.cancel = () => {
		xhr.abort();
		_reject(new CancelError());
	};

	return promise;
};

const queryHttps = (version, options) => {
	let request;
	const promise = (async function () {
		const urls_ = [].concat.apply(urls[version], options.fallbackUrls || []);
		for (const url of urls_) {
			try {
				request = sendXhr(url, options, version);
				// eslint-disable-next-line no-await-in-loop
				const ip = await request;
				return ip;
			} catch (error) {
				if (error instanceof CancelError) {
					throw error;
				}
			}
		}

		throw new Error('Couldn\'t find your IP');
	})();

	promise.cancel = () => {
		request.cancel();
	};

	return promise;
};

module.exports.v4 = options => queryHttps('v4', {...defaults, ...options});

module.exports.v6 = options => queryHttps('v6', {...defaults, ...options});
<|endoftext|>


<commit_before>$(document).ready(function() {

var docroot = '/moz/backpack';

$.timeago.settings.allowFuture = true;
  //the click function for lists of badge thumbnails
  $( ".badge-thumbs" ).delegate( "a", "click", function() {
  	var target = $( this );
  	var hashOrAction = $( this ).attr('class').split(' ')[1];

  	//Display badge content and BadgeUI for clicked badge
   	if (target.hasClass('badgethumb')) {
      //for square thumbnal badges
      if(target.parents('ul').hasClass('square')){
  	    if (target.hasClass('chosen')) {
          $(target).find('.detail').animate({
            top: "160px"
          }, 400, "swing", function(){
            target.removeClass('chosen').parents('li').find('.badgeui').fadeOut('fast', function() {
              $(this).remove();
            });
          });
  	    } else {
          $(target).find('.detail').animate({
            top: "0px"
          }, 400, "swing", function(){
            target.addClass('chosen').parents('li').append(makeBadgeUI(hashOrAction)).find('.badgeui').fadeIn('fast');
          });
  	    }
      //for vertical thumbnail badges (in lists)
      } else if (target.parents('ul').hasClass('vertical')) {
        //SOMEB BUG HERE - WHEN YOU TOGGLE VISIBLE A BUNCH OF VERTICALS THEN TOGGLE INVISI ALL DISAPPEAR
        if (target.hasClass('chosen')) {
          target.removeClass('chosen').parents('li').find('.badgeui').fadeOut('fast', function() {
              $(this).remove();
            });
        } else {
          target.addClass('chosen').parent().append(makeBadgeUI(hashOrAction)).find('.badgeui').fadeIn('fast');
        }

      }
    return false;
  //Perform action based on clicked Badge UI item
	} else if (target.hasClass('badge_action')) {
		badgeAction(target);
		if(!target.hasClass('bcol')) return false;
  } else if (target.hasClass('toggle')) {
    //console.log(hashOrAction);
    $('#'+hashOrAction).fadeToggle();
    return false;
	} else {
		console.log('some other link');
	}
  });

  //a function to generate the dropdown BadgeUI from the clicked badge hash
  function makeBadgeUI(hash) {
  	console.log('making a badge ui for : ' + hash);
  	var output = '' +
  	'
' +
  	'
' +
  	'
Delete
' +
  	'
Collections' +
  	'
' +
              getCollectionsByBadge(hash,'li') +
    '

' +
    '
Add to new or existing collection' +
    '
' +
    '
<input type="text" placeholder="Enter a title..." name="newcollection">
' +
                    getCollections(hash,'li') +
    '
' +
    '
' +
    '
' +
    '
' +
    '
Detail
' +
  	'
' +
  	'
';

  	return output;

  }

  //a function to process BadgeUI clicks (details,delete,etc.)
  function badgeAction(element) {

	var action = element.attr('class').split(' ')[1];
	var hash = element.attr('class').split(' ')[2];

  console.log('action is : ' + action);
  console.log('target is : ' + hash);

	if (action == 'bdel') {
    makeAlert('Are you sure you want to delete ' + $('.' + hash + ' .title').html() + '?','alert');
    } else if (action == 'bdet') {
      makeModal(element);
    } else if (action == 'bcol') {
      console.log(element);
    } else if (action == 'brfc') {
      var collection = element.attr('class').split(' ')[3];
      makeAlert('Are you sure you want to delete ' + $('.' + hash + ' .title').html() + ' from ' + $('.' + collection + ' .title').html() + '?','alert');
    } else if (action == 'batc') {
      var collection = element.attr('class').split(' ')[3];
      makeAlert('Are you sure you want to add ' + $('.' + hash + ' .title').html() + ' to ' + $('.' + collection + ' .title').html() + '?','warning');
    } else {
      console.log('no idea...')
    }
  }

  //a function to create an alert box element and add to the DOM
  function makeAlert(text,status) {
    if($('.alert-box').length != 0) {
      $('.alert-box').remove();
    }
  	var alert = '
' + text + '×
';
  	$(alert).prependTo($('body')).fadeIn('fast');
  }

  //a function to get badge details and display them in a modal
  //display modal to the left,right,or over the list itself depending on circumstances
  function makeModal(element) {

    elemPosition = element.parent().offset().left;
    bodyWidth = $('body').offset().width;

   // console.log('element position is : ' + elemPosition);
   // console.log('body width is : ' + bodyWidth);

    var parentUL = element.parents('.grid');
    var firstli = parentUL.find('li:first-child').find('a').offset();
    var xpos = firstli.left;
    var ypos = firstli.top;
    var firstli_w = firstli.width;
    var firstli_h = firstli.height;
    var numRows = calculateLIsInRow(parentUL.children('li'));
    var height = firstli_h;
    var width = firstli_w;

    if(numRows == 4) {
      width = ((firstli_w * 2) + 20);
      height = ((firstli_h * 2) + 20);
      //display on the right if element is on the left
      if(elemPosition < (bodyWidth / 2)) xpos = (xpos + width + 20);
    } else if(numRows == 3){
      width = ((firstli_w * 1.5) + 20);
      height = ((firstli_h * 4) + 30);
       //display on the right if element is on the left
      if(elemPosition < (bodyWidth / 2)) xpos = (xpos + firstli_w + 20);
    } else {
      console.log("no idea how to display modal");
    }

    var details = retrieveBadge(element.attr('class').split(' ')[2]);
    var close = $('×').click(function(){$('#badge_modal').remove();return false});
    var inner = $('

');
    var outer = $('

');

    outer.append(inner.append(details,close));

    if($('#badge_modal').length != 0) {
      $('#badge_modal').remove();
    }
    outer.appendTo('body').fadeIn('fast');
  }

  //a function to return the number of list items in a row (good for responsive lists)
  function calculateLIsInRow(element) {
    var lisInRow = 0;
    element.each(function() {
        if($(this).prev().length > 0) {
            if($(this).position().top != $(this).prev().position().top) return false;
            lisInRow++;
        }
        else {
            lisInRow++;
        }
    });
    console.log('number of lis in row : ' + lisInRow);
    return lisInRow;
  }

//a function to retrieve full badge details - ideally via Ajax - then format for display
function retrieveBadge(hash) {
  //here is a dummy object
  hash = 'badgehash-a';
  badge=new Object();
  badge.hash=hash;
  badge.name="Some Badge";
  badge.org="Some Organization";
  badge.url_org="some-org.org";
  badge.issue=1346760732;
  badge.expire=1389484800;
  badge.url_criteria="some-org.org/criteria";
  badge.url_evidence="some-org.org/evidence";
  badge.desc="
Here is the user's description of the badge.";
  badge.collections='{ "collName":"Collection A" , "collHash":"123" },{ "collName":"Collection B" , "collHash":"456" }';

  var output = '
' +
  '
' + badge.name + '
' +
  '' +
  '
Issued by :
' + badge.org + '
' + badge.url_org + '
' +
  '
Issued ' + $.timeago(dateFromUnix(badge.issue)) + '.
' +
  '
Expires ' + $.timeago(dateFromUnix(badge.expire)) + '.
' +
  '
Description :
' + badge.desc + '
' +
  '
Criteria : ' + badge.url_criteria + '
' +
  '
Evidence : ' + badge.url_evidence + '
' +
  '
';
  return output;
}

function dateFromUnix(timestamp) {
  var date = new Date(timestamp * 1000);
  return date;
}

//a function to retrieve all a users collections containting a specified badge
function getCollectionsByBadge(hash,style) {
  //fetch collection hash and names
  collection_hash1="collectionhash-a";
  collection_hash2="collectionhash-b";
  collection_hash3="collectionhash-c";

  var output = '' +
  '
xCollection A
' +
  '
xCollection B
' +
  '
xCollection C
';

  return output;
}
//a function to retrieve all a users collections
function getCollections(hash,style) {
  //fetch collection hash and names
  collection_hash1="collectionhash-d";
  collection_hash2="collectionhash-e";
  collection_hash3="collectionhash-f";

  var output = '' +
  '
Add to Collection D</title>
' +
  '
Add to Collection E</title>
' +
  '
Add to Collection F</title>
';

  return output;
}

});<commit_msg>js relative org<commit_after>$(document).ready(function() {

var docroot = '/moz/backpack';

$.timeago.settings.allowFuture = true;
  //the click function for lists of badge thumbnails
  $( ".badge-thumbs" ).delegate( "a", "click", function() {
  	var target = $( this );
  	var hashOrAction = $( this ).attr('class').split(' ')[1];

  	//Display badge content and BadgeUI for clicked badge
   	if (target.hasClass('badgethumb')) {
      //for square thumbnal badges
      if(target.parents('ul').hasClass('square')){
  	    if (target.hasClass('chosen')) {
          $(target).find('.detail').animate({
            top: "160px"
          }, 400, "swing", function(){
            target.removeClass('chosen').parents('li').find('.badgeui').fadeOut('fast', function() {
              $(this).remove();
            });
          });
  	    } else {
          $(target).find('.detail').animate({
            top: "0px"
          }, 400, "swing", function(){
            target.addClass('chosen').parents('li').append(makeBadgeUI(hashOrAction)).find('.badgeui').fadeIn('fast');
          });
  	    }
      //for vertical thumbnail badges (in lists)
      } else if (target.parents('ul').hasClass('vertical')) {
        //SOMEB BUG HERE - WHEN YOU TOGGLE VISIBLE A BUNCH OF VERTICALS THEN TOGGLE INVISI ALL DISAPPEAR
        if (target.hasClass('chosen')) {
          target.removeClass('chosen').parents('li').find('.badgeui').fadeOut('fast', function() {
              $(this).remove();
            });
        } else {
          target.addClass('chosen').parent().append(makeBadgeUI(hashOrAction)).find('.badgeui').fadeIn('fast');
        }

      }
    return false;
  //Perform action based on clicked Badge UI item
	} else if (target.hasClass('badge_action')) {
		badgeAction(target);
		if(!target.hasClass('bcol')) return false;
  } else if (target.hasClass('toggle')) {
    //console.log(hashOrAction);
    $('#'+hashOrAction).fadeToggle();
    return false;
	} else {
		console.log('some other link');
	}
  });

  //a function to generate the dropdown BadgeUI from the clicked badge hash
  function makeBadgeUI(hash) {
  	console.log('making a badge ui for : ' + hash);
  	var output = '' +
  	'
' +
  	'
' +
  	'
Delete
' +
  	'
Collections' +
  	'
' +
              getCollectionsByBadge(hash,'li') +
    '

' +
    '
Add to new or existing collection' +
    '
' +
    '
<input type="text" placeholder="Enter a title..." name="newcollection">
' +
                    getCollections(hash,'li') +
    '
' +
    '
' +
    '
' +
    '
' +
    '
Detail
' +
  	'
' +
  	'
';

  	return output;

  }

  //a function to process BadgeUI clicks (details,delete,etc.)
  function badgeAction(element) {

	var action = element.attr('class').split(' ')[1];
	var hash = element.attr('class').split(' ')[2];

  console.log('action is : ' + action);
  console.log('target is : ' + hash);

	if (action == 'bdel') {
    makeAlert('Are you sure you want to delete ' + $('.' + hash + ' .title').html() + '?','alert');
    } else if (action == 'bdet') {
      makeModal(element);
    } else if (action == 'bcol') {
      console.log(element);
    } else if (action == 'brfc') {
      var collection = element.attr('class').split(' ')[3];
      makeAlert('Are you sure you want to delete ' + $('.' + hash + ' .title').html() + ' from ' + $('.' + collection + ' .title').html() + '?','alert');
    } else if (action == 'batc') {
      var collection = element.attr('class').split(' ')[3];
      makeAlert('Are you sure you want to add ' + $('.' + hash + ' .title').html() + ' to ' + $('.' + collection + ' .title').html() + '?','warning');
    } else {
      console.log('no idea...')
    }
  }

  //a function to create an alert box element and add to the DOM
  function makeAlert(text,status) {
    if($('.alert-box').length != 0) {
      $('.alert-box').remove();
    }
  	var alert = '
' + text + '×
';
  	$(alert).prependTo($('body')).fadeIn('fast');
  }

  //a function to get badge details and display them in a modal
  //display modal to the left,right,or over the list itself depending on circumstances
  function makeModal(element) {

    elemPosition = element.parent().offset().left;
    bodyWidth = $('body').offset().width;

   // console.log('element position is : ' + elemPosition);
   // console.log('body width is : ' + bodyWidth);

    var parentUL = element.parents('.grid');
    var firstli = parentUL.find('li:first-child').find('a').offset();
    var xpos = firstli.left;
    var ypos = firstli.top;
    var firstli_w = firstli.width;
    var firstli_h = firstli.height;
    var numRows = calculateLIsInRow(parentUL.children('li'));
    var height = firstli_h;
    var width = firstli_w;

    if(numRows == 4) {
      width = ((firstli_w * 2) + 20);
      height = ((firstli_h * 2) + 20);
      //display on the right if element is on the left
      if(elemPosition < (bodyWidth / 2)) xpos = (xpos + width + 20);
    } else if(numRows == 3){
      width = ((firstli_w * 1.5) + 20);
      height = ((firstli_h * 4) + 30);
       //display on the right if element is on the left
      if(elemPosition < (bodyWidth / 2)) xpos = (xpos + firstli_w + 20);
    } else {
      console.log("no idea how to display modal");
    }

    var details = retrieveBadge(element.attr('class').split(' ')[2]);
    var close = $('×').click(function(){$('#badge_modal').remove();return false});
    var inner = $('

');
    var outer = $('

');

    outer.append(inner.append(details,close));

    if($('#badge_modal').length != 0) {
      $('#badge_modal').remove();
    }
    outer.appendTo('body').fadeIn('fast');
  }

  //a function to return the number of list items in a row (good for responsive lists)
  function calculateLIsInRow(element) {
    var lisInRow = 0;
    element.each(function() {
        if($(this).prev().length > 0) {
            if($(this).position().top != $(this).prev().position().top) return false;
            lisInRow++;
        }
        else {
            lisInRow++;
        }
    });
    console.log('number of lis in row : ' + lisInRow);
    return lisInRow;
  }

//a function to retrieve full badge details - ideally via Ajax - then format for display
function retrieveBadge(hash) {
  //here is a dummy object
  hash = 'badgehash-a';
  badge=new Object();
  badge.hash=hash;
  badge.name="Some Badge";
  badge.org="Some Organization";
  badge.url_org="some-org.org";
  badge.issue=1346760732;
  badge.expire=1389484800;
  badge.url_criteria="some-org.org/criteria";
  badge.url_evidence="some-org.org/evidence";
  badge.desc="
Here is the user's description of the badge.";
  badge.collections='{ "collName":"Collection A" , "collHash":"123" },{ "collName":"Collection B" , "collHash":"456" }';

  var output = '
' +
  '
' + badge.name + '
' +
  '' +
  '
Issued by :
' + badge.org + '
' + badge.url_org + '
' +
  '
Issued ' + $.timeago(dateFromUnix(badge.issue)) + '.
' +
  '
Expires ' + $.timeago(dateFromUnix(badge.expire)) + '.
' +
  '
Description :
' + badge.desc + '
' +
  '
Criteria : ' + badge.url_criteria + '
' +
  '
Evidence : ' + badge.url_evidence + '
' +
  '
';
  return output;
}

function dateFromUnix(timestamp) {
  var date = new Date(timestamp * 1000);
  return date;
}

//a function to retrieve all a users collections containting a specified badge
function getCollectionsByBadge(hash,style) {
  //fetch collection hash and names
  collection_hash1="collectionhash-a";
  collection_hash2="collectionhash-b";
  collection_hash3="collectionhash-c";

  var output = '' +
  '
xCollection A
' +
  '
xCollection B
' +
  '
xCollection C
';

  return output;
}
//a function to retrieve all a users collections
function getCollections(hash,style) {
  //fetch collection hash and names
  collection_hash1="collectionhash-d";
  collection_hash2="collectionhash-e";
  collection_hash3="collectionhash-f";

  var output = '' +
  '
Add to Collection D</title>
' +
  '
Add to Collection E</title>
' +
  '
Add to Collection F</title>
';

  return output;
}

});<|endoftext|>


<commit_before>import extend from 'lodash/extend'
import he from 'he'
var Raven = require('raven-js')

import location from './utilities/location'

/**
 * Stencila browser application entry point
 */
export default function browser (App) {
  window.onload = function () {
    let props = {}
    // Is this a local host?
    const hostname = window.location.hostname
    props.local = hostname === 'localhost' || hostname === '127.0.0.1'
    // Get `address` and `copy` from the path
    var path = window.location.pathname
    var matches = path.match(/([^@]+)(@(\w+))?/)
    props.address = matches[1]
    props.copy = matches[3]
    // Update with URL query parameters
    var params = location.params()
    extend(props, params)
    // Check if `?static=1`
    if (params.static !== '1') {
      if (!App) return

      // Get component as HTML content or a JSON snapshot data
      // for rerendering by the `App` and then hide content element (if any)
      var content = document.getElementById('content')
      if (content) {
        props.format = 'html'
        props.data = content.innerHTML
        content.style.display = 'none'
      } else {
        var dataElem = document.getElementById('data')
        if (dataElem) {
          props.format = 'json'
          props.data = JSON.parse(he.decode(dataElem.textContent || dataElem.innerHTML))
        } else {
          throw Error('Neither #content or #data is available to initialize the component')
        }
      }

      if (props.local) {
        window.app = App.mount(props, document.body)
      } else {
        Raven
          .config('https://6329017160394100b21be92165555d72@app.getsentry.com/37250')
          .install()
        try {
          window.app = App.mount(props, document.body)
        } catch (e) {
          Raven.captureException(e)
        }
      }
    }
  }
}
<commit_msg>Change data exchange protocol on page<commit_after>import extend from 'lodash/extend'
import he from 'he'
var Raven = require('raven-js')

import location from './utilities/location'

/**
 * Stencila browser application entry point
 */
export default function browser (App) {
  window.onload = function () {
    let props = {}
    // Is this a local host?
    const hostname = window.location.hostname
    props.local = hostname === 'localhost' || hostname === '127.0.0.1'
    // Get `address` and `copy` from the path
    var path = window.location.pathname
    var matches = path.match(/([^@]+)(@(\w+))?/)
    props.address = matches[1]
    props.copy = matches[3]
    // Update with URL query parameters
    var params = location.params()
    extend(props, params)
    // Check if `?static=1`
    if (params.static !== '1') {
      if (!App) return

      // Get component data from page for rerendering by the `App` and then hide it
      var data = document.getElementById('data')
      if (data) {
        props.format = data.getAttribute('data-format')
        if (props.format === 'html') {
          props.data = data.innerHTML
        } else {
          props.data = JSON.parse(he.decode(data.textContent || data.innerHTML))
        }
        data.style.display = 'none'
      } else {
        throw Error('#data is not available to initialize the component')
      }

      if (props.local) {
        window.app = App.mount(props, document.body)
      } else {
        Raven
          .config('https://6329017160394100b21be92165555d72@app.getsentry.com/37250')
          .install()
        try {
          window.app = App.mount(props, document.body)
        } catch (e) {
          Raven.captureException(e)
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>/*
KEYSHANC: KEYboard SHA-based eNCrypter
Javascript Implementation

Copyright (c) 2012 Andrew C. Reed

License: The MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

//NOTE: This script requires sha512-min.js and sha256-min.js
//Both can be obtained from http://pajhome.org.uk/crypt/md5/jshash-2.2.zip

//ALSO: The dynamicTweet/generateTweet portions require jQuery
//Available from http://jquery.com

//create keys[95]
var keys = new Array(95);

//initialize keys[95]
for (x = 0; x < 95; x++) {
    keys[x] = (x + 32);
}

//jQuery code for the Dynamic Tweet button which sends the ciphertext
//    Place the following DIV in your html where you want the Dynamic Tweet
//    button to appear
//

$('#dynamicTweet').html('');

function keyshanc(password) {
    var i = new String(hex_sha512(password));
    var j = new String(hex_sha256(password));

    //this loop resets keys[95] prior to shuffling
    for (x = 0; x < 95; x++) {
        keys[x] = (x + 32);
    }

    //exit function if no password provided
    //this effectively turns Keyshanc OFF
    if (password.length == 0) {
        return;
    }

    var shuffleCode = new Array(95);
    var hexString = new String("");
    var numByte;

    //build the first 64 positions in shuffleCode[] with the entire SHA512 hash
    for (x = 0; x < 64; x++) {
        hexString = hexString.concat(i.charAt(x*2));
        hexString = hexString.concat(i.charAt((x*2)+1));
        numByte = parseInt(hexString, 16);
        shuffleCode[x] = numByte%95;
        hexString = "";
    }

    //build the last 31 positions in shuffleCode[] with the first 31 bytes of the SHA256 hash
    for (x = 0; x < 31; x++) {
        hexString = hexString.concat(j.charAt(x*2));
        hexString = hexString.concat(j.charAt((x*2)+1));
        numByte = parseInt(hexString, 16);
        shuffleCode[x+64] = numByte%95;
        hexString = "";
    }

    //shuffle keys[] using shuffleCode[] for swap positions
    for (x = 0; x < 95; x++) {
        var temp = keys[x];
        keys[x] = keys[shuffleCode[x]];
        keys[shuffleCode[x]] = temp;
    }
}

function decryptKeyshanc() {
    s1 = new String(document.getElementById('inText').value);

    var s2 = new String("");

    for (x = 0; x < s1.length; x++) {
        if (s1.charCodeAt(x) >= 32 && s1.charCodeAt(x) <= 126)
        {
            for (y = 0; y < 95; y++) {
                if (s1.charAt(x) == String.fromCharCode(keys[y]))
                {
                    s2 = s2.concat(String.fromCharCode(y+32));
                    break;
                }
            }
        }
        else
        {
            s2 = s2.concat(s1.charAt(x));
        }
    }

    document.getElementById('outText').value = s2;
}

function encryptKeyshanc() {
    s1 = new String(document.getElementById('inText').value);

    var s2 = new String("");

    for (x = 0; x < s1.length; x++) {
        if (s1.charCodeAt(x) >= 32 && s1.charCodeAt(x) <= 126)
        {
            s2 = s2.concat(String.fromCharCode(keys[((s1.charCodeAt(x))-32)]));
        }
        else
        {
            s2 = s2.concat(s1.charAt(x));
        }
    }

    document.getElementById('outText').value = s2;
}

function generateTweet(tweetText) {
    $('#dynamicTweet').html("\
\

");
}
<commit_msg>Trying new Tweet button code.<commit_after>/*
KEYSHANC: KEYboard SHA-based eNCrypter
Javascript Implementation

Copyright (c) 2012 Andrew C. Reed

License: The MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

//NOTE: This script requires sha512-min.js and sha256-min.js
//Both can be obtained from http://pajhome.org.uk/crypt/md5/jshash-2.2.zip

//ALSO: The dynamicTweet/generateTweet portions require jQuery
//Available from http://jquery.com

//create keys[95]
var keys = new Array(95);

//initialize keys[95]
for (x = 0; x < 95; x++) {
    keys[x] = (x + 32);
}

//jQuery code for the Dynamic Tweet button which sends the ciphertext
//    Place the following DIV in your html where you want the Dynamic Tweet
//    button to appear
//

$('#dynamicTweet').html('');

function keyshanc(password) {
    var i = new String(hex_sha512(password));
    var j = new String(hex_sha256(password));

    //this loop resets keys[95] prior to shuffling
    for (x = 0; x < 95; x++) {
        keys[x] = (x + 32);
    }

    //exit function if no password provided
    //this effectively turns Keyshanc OFF
    if (password.length == 0) {
        return;
    }

    var shuffleCode = new Array(95);
    var hexString = new String("");
    var numByte;

    //build the first 64 positions in shuffleCode[] with the entire SHA512 hash
    for (x = 0; x < 64; x++) {
        hexString = hexString.concat(i.charAt(x*2));
        hexString = hexString.concat(i.charAt((x*2)+1));
        numByte = parseInt(hexString, 16);
        shuffleCode[x] = numByte%95;
        hexString = "";
    }

    //build the last 31 positions in shuffleCode[] with the first 31 bytes of the SHA256 hash
    for (x = 0; x < 31; x++) {
        hexString = hexString.concat(j.charAt(x*2));
        hexString = hexString.concat(j.charAt((x*2)+1));
        numByte = parseInt(hexString, 16);
        shuffleCode[x+64] = numByte%95;
        hexString = "";
    }

    //shuffle keys[] using shuffleCode[] for swap positions
    for (x = 0; x < 95; x++) {
        var temp = keys[x];
        keys[x] = keys[shuffleCode[x]];
        keys[shuffleCode[x]] = temp;
    }
}

function decryptKeyshanc() {
    s1 = new String(document.getElementById('inText').value);

    var s2 = new String("");

    for (x = 0; x < s1.length; x++) {
        if (s1.charCodeAt(x) >= 32 && s1.charCodeAt(x) <= 126)
        {
            for (y = 0; y < 95; y++) {
                if (s1.charAt(x) == String.fromCharCode(keys[y]))
                {
                    s2 = s2.concat(String.fromCharCode(y+32));
                    break;
                }
            }
        }
        else
        {
            s2 = s2.concat(s1.charAt(x));
        }
    }

    document.getElementById('outText').value = s2;
}

function encryptKeyshanc() {
    s1 = new String(document.getElementById('inText').value);

    var s2 = new String("");

    for (x = 0; x < s1.length; x++) {
        if (s1.charCodeAt(x) >= 32 && s1.charCodeAt(x) <= 126)
        {
            s2 = s2.concat(String.fromCharCode(keys[((s1.charCodeAt(x))-32)]));
        }
        else
        {
            s2 = s2.concat(s1.charAt(x));
        }
    }

    document.getElementById('outText').value = s2;
}

function generateTweet(tweetText) {
    $('#dynamicTweet').html("\
\

");
}
<|endoftext|>


<commit_before>"use strict";

var canihas = require('../lib/canihas');

/**
 * Stylus pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.styl = function styl (content, index, total, next) {
  var file = this;

  canihas.stylus(function canihasStylus (err, stylus) {
    if (err) return next(err);

    canihas.nib(function canihasNib (err, nib) {
      if (err) return next(err);

      stylus(content)
        .set('filename', file.meta.location)
        .use(nib())
        .import('nib')
        .render(next);
    });
  });
};

// what extension should we have once compiled
exports.styl.extension = 'css';

/**
 * Less pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.less = function less (content, index, total, next) {
  canihas.less(function canihasLess (err, less) {
    if (err) return next(err);

    less.render(content, next);
  });
};

// what extension should we have once compiled
exports.less.extension = 'css';

/**
 * Sass pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.sass = function sass (content, index, total, next) {
  canihas.sass(function canihasSass (err, sass) {
    if (err) return next(err);

    try {
      return next(null, sass.render(content));
    } catch (e) {
      next(e);
    }
  });
};

// what extension should we have once compiled
exports.sass.extension = 'css';

/**
 * Coffeescript pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.coffee = function coffee (content, index, total, next) {
  canihas['coffee-script'](function canihasCoffee (err, coffeescript) {
    if (err) return next(err);

    try {
      return next(null, coffeescript.compile(content));
    } catch (e) {
      next(e);
    }
  });
};

// what extension should we have once compiled
exports.coffee.extension = 'js';

/**
 * Jade template pre-processor. It inline's your Jade templates and makes it
 * client-side compatible.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.jade = function jade (content, index, total, next) {
  /**
   * Lazy load these core modules
   */

  var path = require('path')
    , fs = require('fs');

  /**
   * Setting for the Jade compiler, to ensure that it's compiled with
   * a client-side compatible coding.
   */

  var file = this
    , template = 'jade.{filename} = {content};'
    , filename = file.meta.filename
    , options = {
          client: true
        , compileDebug: false
    };

  // we are only interested in the filename, not the extension so we need to
  // parse down the filename a bit more in order to make it useful
  filename = filename.replace(path.extname(filename), '').underscore();

  canihas.jade(function lazyload (err, jade) {
    if (err) return next(err);

    var compiled;

    try { compiled = jade.compile(content, options).toString(); }
    catch (e) { return next(e); }

    compiled = template
      .replace('{content}', compiled.replace('function anonymous', 'function ' + filename))
      .replace('{filename}', filename);

    if (index > 0) return next(null, compiled);

    // for the first file we need to include the jade runtime so the templates
    // can actually be executed client side
    var client = path.join(__dirname, '..', 'defaults', 'jade.js');
    compiled = fs.readFileSync(client, 'UTF-8') + compiled;

    next(null, compiled);
  });
};

// what extension should we have once compiled
exports.jade.extension = 'js';
<commit_msg>More error detection<commit_after>"use strict";

var canihas = require('../lib/canihas');

/**
 * Stylus pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.styl = function styl (content, index, total, next) {
  var file = this;

  canihas.stylus(function canihasStylus (err, stylus) {
    if (err) return next(err);

    canihas.nib(function canihasNib (err, nib) {
      if (err) return next(err);

      stylus(content)
        .set('filename', file.meta.location)
        .use(nib())
        .import('nib')
        .render(next);
    });
  });
};

// what extension should we have once compiled
exports.styl.extension = 'css';

/**
 * Less pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.less = function less (content, index, total, next) {
  canihas.less(function canihasLess (err, less) {
    if (err) return next(err);

    var parser;
    try { parser = less.render(content); }
    catch (e) { return next(e); }

    parser.on('error', next);
    parser.on('success', function (css) {
      next(null, css);
    });
  });
};

// what extension should we have once compiled
exports.less.extension = 'css';

/**
 * Sass pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.sass = function sass (content, index, total, next) {
  canihas.sass(function canihasSass (err, sass) {
    if (err) return next(err);

    try {
      return next(null, sass.render(content));
    } catch (e) {
      next(e);
    }
  });
};

// what extension should we have once compiled
exports.sass.extension = 'css';

/**
 * Coffeescript pre-processor.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.coffee = function coffee (content, index, total, next) {
  canihas['coffee-script'](function canihasCoffee (err, coffeescript) {
    if (err) return next(err);

    try {
      return next(null, coffeescript.compile(content));
    } catch (e) {
      next(e);
    }
  });
};

// what extension should we have once compiled
exports.coffee.extension = 'js';

/**
 * Jade template pre-processor. It inline's your Jade templates and makes it
 * client-side compatible.
 *
 * @param {String} content
 * @param {Number} index
 * @param {Number} total
 * @param {Function} next
 * @api private
 */

exports.jade = function jade (content, index, total, next) {
  /**
   * Lazy load these core modules
   */

  var path = require('path')
    , fs = require('fs');

  /**
   * Setting for the Jade compiler, to ensure that it's compiled with
   * a client-side compatible coding.
   */

  var file = this
    , template = 'jade.{filename} = {content};'
    , filename = file.meta.filename
    , options = {
          client: true
        , compileDebug: false
    };

  // we are only interested in the filename, not the extension so we need to
  // parse down the filename a bit more in order to make it useful
  filename = filename.replace(path.extname(filename), '').underscore();

  canihas.jade(function lazyload (err, jade) {
    if (err) return next(err);

    var compiled;

    try { compiled = jade.compile(content, options).toString(); }
    catch (e) { return next(e); }

    compiled = template
      .replace('{content}', compiled.replace('function anonymous', 'function ' + filename))
      .replace('{filename}', filename);

    if (index > 0) return next(null, compiled);

    // for the first file we need to include the jade runtime so the templates
    // can actually be executed client side
    var client = path.join(__dirname, '..', 'defaults', 'jade.js');
    compiled = fs.readFileSync(client, 'UTF-8') + compiled;

    next(null, compiled);
  });
};

// what extension should we have once compiled
exports.jade.extension = 'js';
<|endoftext|>


<commit_before>jQuery(document).ready(function () {
  jQuery('td').each(function(index, value) {
    var descText = jQuery('li.' + value.className).text();
    if(descText.length) {
      jQuery(value).attr('aria-label', descText);
    }
  });
  var base = 'http://www.w3.org/TR/wai-aria';
  var $modal = jQuery('#modal');
  var lastfocus;
  var links;

  jQuery('td a').on('click', function (e) {
    console.log(e.target);
    lastfocus = document.activeElement;
    var $this = jQuery(this);
    var text = $this.text();
    var $popup = jQuery('#popup');

    text = text.replace(/-/gi, '');
    text = text.replace(/aria/, 'aria-');
    $popup
      .load( window.waiBase + ' #' + text, undefined,
        function() {
          $popup.find('a').each(function(index, item) {
            var newHref = item.href.replace(window.location.origin, base);
            item.setAttribute('href', newHref);
            item.setAttribute('target', '_blank');
          });
          $modal.show();
          jQuery('#close').focus();
          links = $modal.find('button, a');
        });
  });
  jQuery('#close').on('click', function () {
    $modal.hide();
    lastfocus.focus();
  });
  $modal.on('keydown', function (e) {
    var cancel = false;
    if (e.ctrlKey || e.metaKey || e.altKey) {
      return;
    }
    switch(e.which) {
      case 27: // ESC
        $modal.hide();
        lastfocus.focus();
        cancel = true;
        break;
      case 9: // TAB
        if (e.shiftKey) {
          if (e.target === links[0]) {
            links[links.length - 1].focus();
            cancel = true;
          }
        } else {
          if (e.target === links[links.length - 1]) {
            links[0].focus();
            cancel = true;
          }
        }
        break;
    }
    if (cancel) {
      e.preventDefault();
    }
  });
  jQuery('[name="colpat"]').on('change', function (e) {
    var value = jQuery(this).val();
    switch(value) {
      case 'colors':
        jQuery(document.body).removeClass('patterns');
        break;
      case 'patterns':
        jQuery(document.body).addClass('patterns');
        break;
    }
  });
});
<commit_msg>use aria-labelledby technique on the 1.0 tables<commit_after>jQuery(document).ready(function () {
  jQuery('#legend li').each(function(index, value) {
    var $this = jQuery(value);
    $this.attr('id', $this.attr('class'));
  });
  jQuery('td').each(function(index, value) {
    if (value.className.length) {
      jQuery(value).attr('aria-labelledby', value.className);
    }
  });
  var base = 'http://www.w3.org/TR/wai-aria';
  var $modal = jQuery('#modal');
  var lastfocus;
  var links;

  jQuery('td a').on('click', function (e) {
    console.log(e.target);
    lastfocus = document.activeElement;
    var $this = jQuery(this);
    var text = $this.text();
    var $popup = jQuery('#popup');

    text = text.replace(/-/gi, '');
    text = text.replace(/aria/, 'aria-');
    $popup
      .load( window.waiBase + ' #' + text, undefined,
        function() {
          $popup.find('a').each(function(index, item) {
            var newHref = item.href.replace(window.location.origin, base);
            item.setAttribute('href', newHref);
            item.setAttribute('target', '_blank');
          });
          $modal.show();
          jQuery('#close').focus();
          links = $modal.find('button, a');
        });
  });
  jQuery('#close').on('click', function () {
    $modal.hide();
    lastfocus.focus();
  });
  $modal.on('keydown', function (e) {
    var cancel = false;
    if (e.ctrlKey || e.metaKey || e.altKey) {
      return;
    }
    switch(e.which) {
      case 27: // ESC
        $modal.hide();
        lastfocus.focus();
        cancel = true;
        break;
      case 9: // TAB
        if (e.shiftKey) {
          if (e.target === links[0]) {
            links[links.length - 1].focus();
            cancel = true;
          }
        } else {
          if (e.target === links[links.length - 1]) {
            links[0].focus();
            cancel = true;
          }
        }
        break;
    }
    if (cancel) {
      e.preventDefault();
    }
  });
  jQuery('[name="colpat"]').on('change', function (e) {
    var value = jQuery(this).val();
    switch(value) {
      case 'colors':
        jQuery(document.body).removeClass('patterns');
        break;
      case 'patterns':
        jQuery(document.body).addClass('patterns');
        break;
    }
  });
});
<|endoftext|>