<commit_before>window.Map = function (divId) {
    this.divId = divId;
};

_.extend(window.Map.prototype, {
    map: null,
    dataLayer: null,
    currentRouteString: null,
    routeFeature: null,

    getMap: function () {
        return this.map;
    },

    /**
     * Initialize and draw map
     */
    draw: function () {
        OpenLayers.ImgPath = "img/openlayers/";
        this.map = new OpenLayers.Map("map", {
            projection: new OpenLayers.Projection("EPSG:4326")
        });

        var mapLayer = new OpenLayers.Layer.OSM("OSM Tiles", "http://gerbera.informatik.uni-stuttgart.de/osm/tiles/${z}/${x}/${y}.png", {
            numZoomLevels: 19
        });

        /* add maplayer and set center of the map */
        this.map.addLayer(mapLayer);

        /* create and add a layer for markers */
        this.dataLayer = new OpenLayers.Layer.Vector("Markers");
        this.map.addLayer(this.dataLayer);
        this.map.setCenter(new OpenLayers.LonLat(1169980, 6640717), 6);

        var that = this;
        var selectFeature = new OpenLayers.Control.SelectFeature(this.dataLayer, {
            hover: true,
            callbacks: {
                over: function (feature) {
                    feature.style.graphicOpacity = 1;
                    this.layer.drawFeature(feature);
                },
                out: function (feature) {
                    feature.style.graphicOpacity = 0.7;
                    this.layer.drawFeature(feature);
                }
            }
        });
        this.map.addControl(selectFeature);
        selectFeature.activate();

        var controlFeature = new OpenLayers.Control.DragFeature(this.dataLayer, {
            //geometryTypes: ['OpenLayers.Feature.Vector'],
            onStart: function(feature) {
                window.mapModel.setDataViewMarker(feature.data.mark);
            },
            onComplete: function(feature, pix) {
                var lonlat = this.map.getLonLatFromPixel(pix);

                feature.data.mark.set({
                    lonlat: lonlat
                });
            }
        });
        this.map.addControl(controlFeature);
        controlFeature.activate();
    },

    /**
     * Resets all Routes drawed in the vectorLayer
     */
    resetRoute: function () {
        if (!_.isNull(this.routeFeature))
            this.dataLayer.removeFeatures([this.routeFeature]);
        this.currentRouteString = this.routeFeature = null;
    },

    /**
     * Reset all Markers drawed in dataLayer
     */
    resetMarkers: function () {
        // - remove all features (route and markers)
        // - then draw route back
        this.dataLayer.removeAllFeatures();
        if (!_.isNull(this.routeFeature))
            this.dataLayer.addFeatures(this.routeFeature);
    },

    /**
     * Forces Map to update itself.
     * Needed, when the div of this map getting resized
     */
    refresh: function () {
        this.map.updateSize();
    },

    /**
     * Draw markers given in markList
     */
    drawMarkers: function () {
        var markList = window.markList;
        for (var i = 0; i < markList.length; i++) {
            var mark = markList.at(i);
            var size = new OpenLayers.Size(21, 25);

            // Icons generated with: http://mapicons.nicolasmollet.com/numbers-letters/?style=default&custom_color=e89733
            var iconPath = 'img/mark.png';
            if (i === 0) {
                iconPath = 'img/startmark.png';
            } else if (i == markList.length - 1) {
                iconPath = 'img/targetmark.png';
            }

            var feature = new OpenLayers.Feature.Vector(
                new OpenLayers.Geometry.Point(mark.get('lonlat').lon, mark.get('lonlat').lat),
                {mark: mark},
                {externalGraphic: iconPath, graphicHeight: size.h, graphicWidth: size.w, graphicXOffset: -(size.w/2), graphicYOffset: -size.h, graphicOpacity: 0.7 }
            );
            this.dataLayer.addFeatures(feature);
        }
    },

    /**
     * Draws the route defined by list of vertices
     * PARAM: List of Vertices
     */
    drawRoute: function (vertexString) {
        // exit, when there is nothing to parse
        if (_.isNull(vertexString) || _.isUndefined(vertexString) || vertexString.length === 0)
            return;

        // parse string of vertices
        var pointList = [];

        if (_.isString(vertexString))
            vertexString = JSON.parse(vertexString);

        var proj = new OpenLayers.Projection("EPSG:4326");
        for (var i = 0; i < vertexString.way.length; i++) {
            // transform points
            var p = vertexString.way[i];
            var point = new OpenLayers.Geometry.Point(p.ln / 1e7, p.lt / 1e7);
            point.transform(proj, this.map.getProjectionObject());

            pointList.push(point);
        }

        //this.drawMarkers();
        // draw route on a layer and add it to map
        var geometry = new OpenLayers.Geometry.LineString(pointList);
        this.routeFeature = new OpenLayers.Feature.Vector(geometry, null, {
            strokeColor: "#0000ff",
            strokeOpacity: 1.7,
            strokeWidth: 2.5
        });
        this.dataLayer.addFeatures(this.routeFeature);
        this.currentRouteString = vertexString;
    },

    setCenter: function(lonlat, bb) {
        this.map.setCenter(this.transformFrom1984(new OpenLayers.LonLat(lonlat.lon, lonlat.lat)));

        if (arguments.length >= 2) {
            var bounds = new OpenLayers.Bounds(bb[2], bb[0], bb[3], bb[1]).transform(new OpenLayers.Projection("EPSG:4326"), this.map.getProjectionObject());
            this.map.zoomToExtent(bounds);
        }
    },

    /**
     * Zoom into map, so that the whole route is visible
     */
    zoomToRoute: function() {
        if (!_.isNull(this.dataLayer.getDataExtent())) {
            this.map.zoomToExtent(this.dataLayer.getDataExtent());
        }
    },

    getLonLatFromPos: function (posX, posY) {
        var pixel = new OpenLayers.Pixel(posX, posY);
        var lonlat = this.map.getLonLatFromPixel(pixel);

        // convert to "correct" projection
        var proj = new OpenLayers.Projection("EPSG:4326");
        lonlat.transform(this.map.getProjectionObject(), proj);
        return lonlat;
    },

    transformTo1984: function (lonlat) {
        var proj = new OpenLayers.Projection("EPSG:4326");
        var lonlatClone = lonlat.clone();
        lonlatClone.transform(this.map.getProjectionObject(), proj);

        return lonlatClone;
    },

    transformFrom1984: function (lonlat) {
        var proj = new OpenLayers.Projection("EPSG:4326");
        var lonlatClone = lonlat.clone();
        lonlatClone.transform(proj, this.map.getProjectionObject());
        return lonlatClone;
    }
});
<commit_msg>Use nns after drag and drop<commit_after>window.Map = function (divId) {
    this.divId = divId;
};

_.extend(window.Map.prototype, {
    map: null,
    dataLayer: null,
    currentRouteString: null,
    routeFeature: null,

    getMap: function () {
        return this.map;
    },

    /**
     * Initialize and draw map
     */
    draw: function () {
        OpenLayers.ImgPath = "img/openlayers/";
        this.map = new OpenLayers.Map("map", {
            projection: new OpenLayers.Projection("EPSG:4326")
        });

        var mapLayer = new OpenLayers.Layer.OSM("OSM Tiles", "http://gerbera.informatik.uni-stuttgart.de/osm/tiles/${z}/${x}/${y}.png", {
            numZoomLevels: 19
        });

        /* add maplayer and set center of the map */
        this.map.addLayer(mapLayer);

        /* create and add a layer for markers */
        this.dataLayer = new OpenLayers.Layer.Vector("Markers");
        this.map.addLayer(this.dataLayer);
        this.map.setCenter(new OpenLayers.LonLat(1169980, 6640717), 6);

        var that = this;
        var selectFeature = new OpenLayers.Control.SelectFeature(this.dataLayer, {
            hover: true,
            callbacks: {
                over: function (feature) {
                    feature.style.graphicOpacity = 1;
                    this.layer.drawFeature(feature);
                },
                out: function (feature) {
                    feature.style.graphicOpacity = 0.7;
                    this.layer.drawFeature(feature);
                }
            }
        });
        this.map.addControl(selectFeature);
        selectFeature.activate();

        var controlFeature = new OpenLayers.Control.DragFeature(this.dataLayer, {
            //geometryTypes: ['OpenLayers.Feature.Vector'],
            onStart: function(feature) {
                window.mapModel.setDataViewMarker(feature.data.mark);
            },
            onComplete: function(feature, pix) {
                var lonlat = this.map.getLonLatFromPixel(pix);

                feature.data.mark.set({
                    lonlat: lonlat
                });
                feature.data.mark.findNearestNeighbour();
            }
        });
        this.map.addControl(controlFeature);
        controlFeature.activate();
    },

    /**
     * Resets all Routes drawed in the vectorLayer
     */
    resetRoute: function () {
        if (!_.isNull(this.routeFeature))
            this.dataLayer.removeFeatures([this.routeFeature]);
        this.currentRouteString = this.routeFeature = null;
    },

    /**
     * Reset all Markers drawed in dataLayer
     */
    resetMarkers: function () {
        // - remove all features (route and markers)
        // - then draw route back
        this.dataLayer.removeAllFeatures();
        if (!_.isNull(this.routeFeature))
            this.dataLayer.addFeatures(this.routeFeature);
    },

    /**
     * Forces Map to update itself.
     * Needed, when the div of this map getting resized
     */
    refresh: function () {
        this.map.updateSize();
    },

    /**
     * Draw markers given in markList
     */
    drawMarkers: function () {
        var markList = window.markList;
        for (var i = 0; i < markList.length; i++) {
            var mark = markList.at(i);
            var size = new OpenLayers.Size(21, 25);

            // Icons generated with: http://mapicons.nicolasmollet.com/numbers-letters/?style=default&custom_color=e89733
            var iconPath = 'img/mark.png';
            if (i === 0) {
                iconPath = 'img/startmark.png';
            } else if (i == markList.length - 1) {
                iconPath = 'img/targetmark.png';
            }

            var feature = new OpenLayers.Feature.Vector(
                new OpenLayers.Geometry.Point(mark.get('lonlat').lon, mark.get('lonlat').lat),
                {mark: mark},
                {externalGraphic: iconPath, graphicHeight: size.h, graphicWidth: size.w, graphicXOffset: -(size.w/2), graphicYOffset: -size.h, graphicOpacity: 0.7 }
            );
            this.dataLayer.addFeatures(feature);
        }
    },

    /**
     * Draws the route defined by list of vertices
     * PARAM: List of Vertices
     */
    drawRoute: function (vertexString) {
        // exit, when there is nothing to parse
        if (_.isNull(vertexString) || _.isUndefined(vertexString) || vertexString.length === 0)
            return;

        // parse string of vertices
        var pointList = [];

        if (_.isString(vertexString))
            vertexString = JSON.parse(vertexString);

        var proj = new OpenLayers.Projection("EPSG:4326");
        for (var i = 0; i < vertexString.way.length; i++) {
            // transform points
            var p = vertexString.way[i];
            var point = new OpenLayers.Geometry.Point(p.ln / 1e7, p.lt / 1e7);
            point.transform(proj, this.map.getProjectionObject());

            pointList.push(point);
        }

        //this.drawMarkers();
        // draw route on a layer and add it to map
        var geometry = new OpenLayers.Geometry.LineString(pointList);
        this.routeFeature = new OpenLayers.Feature.Vector(geometry, null, {
            strokeColor: "#0000ff",
            strokeOpacity: 1.7,
            strokeWidth: 2.5
        });
        this.dataLayer.addFeatures(this.routeFeature);
        this.currentRouteString = vertexString;
    },

    setCenter: function(lonlat, bb) {
        this.map.setCenter(this.transformFrom1984(new OpenLayers.LonLat(lonlat.lon, lonlat.lat)));

        if (arguments.length >= 2) {
            var bounds = new OpenLayers.Bounds(bb[2], bb[0], bb[3], bb[1]).transform(new OpenLayers.Projection("EPSG:4326"), this.map.getProjectionObject());
            this.map.zoomToExtent(bounds);
        }
    },

    /**
     * Zoom into map, so that the whole route is visible
     */
    zoomToRoute: function() {
        if (!_.isNull(this.dataLayer.getDataExtent())) {
            this.map.zoomToExtent(this.dataLayer.getDataExtent());
        }
    },

    getLonLatFromPos: function (posX, posY) {
        var pixel = new OpenLayers.Pixel(posX, posY);
        var lonlat = this.map.getLonLatFromPixel(pixel);

        // convert to "correct" projection
        var proj = new OpenLayers.Projection("EPSG:4326");
        lonlat.transform(this.map.getProjectionObject(), proj);
        return lonlat;
    },

    transformTo1984: function (lonlat) {
        var proj = new OpenLayers.Projection("EPSG:4326");
        var lonlatClone = lonlat.clone();
        lonlatClone.transform(this.map.getProjectionObject(), proj);

        return lonlatClone;
    },

    transformFrom1984: function (lonlat) {
        var proj = new OpenLayers.Projection("EPSG:4326");
        var lonlatClone = lonlat.clone();
        lonlatClone.transform(proj, this.map.getProjectionObject());
        return lonlatClone;
    }
});
<|endoftext|>


<commit_before>function ddg_spice_is_it_up(res)
{
    var out = '';

    var responses = new Array();
    responses[1] = "1xx_Informational";
    responses[2] = "2xx_Success";
    responses[3] = "3xx_Redirection";
    responses[4] = "4xx_Client_Error";
    responses[5] = "5xx_Server_Error";

    if (res['status_code'] === 1) {
        out = "" + res['domain'] + " seems up. ";
        out += "It took us " + res['response_time'] + " seconds to get a ";

        out += "" + res['response_code'] + "";

        out += " response from " + res['response_ip'];
    } else {
        out = "" + res['domain'] + " seems down. We did not get a response from it.";
    }

	items = new Array();
	items[0] = new Array();
	items[0]['a'] = out;
	items[0]['h'] = '';
	items[0]['s'] = 'IsItUp';
	items[0]['u'] = 'http://isitup.org/' + res['domain'];
	nra(items);
}
<commit_msg>Improved visual format.<commit_after>function ddg_spice_is_it_up(res)
{
    var out = '';

    var responses = new Array();
    responses[1] = "1xx_Informational";
    responses[2] = "2xx_Success";
    responses[3] = "3xx_Redirection";
    responses[4] = "4xx_Client_Error";
    responses[5] = "5xx_Server_Error";

    if (res['status_code'] === 1) {
        out = "" + res['domain'] + " seems up.";

        out += "

";

        out += "It took us " + res['response_time'] + " seconds to get a ";

        out += "" + res['response_code'] + "";

        out += " response from " + res['response_ip'] + ".";
    } else {
        out = "" + res['domain'] + " seems down. We did not get a response from it.";
    }

	items = new Array();
	items[0] = new Array();
	items[0]['a'] = out;
	items[0]['h'] = '';
	items[0]['s'] = 'Is it up?';
	items[0]['u'] = 'http://isitup.org/' + res['domain'];
	nra(items);
}
<|endoftext|>


<commit_before>const _ = require('lodash');
const juice = require('juice');
const template = require('./template');
const settingsCache = require('../../services/settings/cache');
const urlUtils = require('../../../shared/url-utils');
const moment = require('moment-timezone');
const cheerio = require('cheerio');
const api = require('../../api');
const {URL} = require('url');
const mobiledocLib = require('../../lib/mobiledoc');
const htmlToText = require('html-to-text');
const {isUnsplashImage, isLocalContentImage} = require('@tryghost/kg-default-cards/lib/utils');
const logging = require('../../../shared/logging');

const ALLOWED_REPLACEMENTS = ['first_name'];

const getSite = () => {
    const publicSettings = settingsCache.getPublic();
    return Object.assign({}, publicSettings, {
        url: urlUtils.urlFor('home', true),
        iconUrl: publicSettings.icon ? urlUtils.urlFor('image', {image: publicSettings.icon}, true) : null
    });
};

/**
 * createUnsubscribeUrl
 *
 * Takes a member uuid and returns the url that should be used to unsubscribe
 * In case of no member uuid, generates the preview unsubscribe url - `?preview=1`
 *
 * @param {string} uuid
 */
const createUnsubscribeUrl = (uuid) => {
    const siteUrl = urlUtils.getSiteUrl();
    const unsubscribeUrl = new URL(siteUrl);
    unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
    if (uuid) {
        unsubscribeUrl.searchParams.set('uuid', uuid);
    } else {
        unsubscribeUrl.searchParams.set('preview', '1');
    }

    return unsubscribeUrl.href;
};

// NOTE: serialization is needed to make sure we are using current API and do post transformations
//       such as image URL transformation from relative to absolute
const serializePostModel = async (model, apiVersion = 'v4') => {
    // fetch mobiledoc rather than html and plaintext so we can render email-specific contents
    const frame = {options: {context: {user: true}, formats: 'mobiledoc'}};
    const docName = 'posts';

    await api.shared
        .serializers
        .handle
        .output(model, {docName: docName, method: 'read'}, api[apiVersion].serializers.output, frame);

    return frame.response[docName][0];
};

// removes %% wrappers from unknown replacement strings in email content
const normalizeReplacementStrings = (email) => {
    // we don't want to modify the email object in-place
    const emailContent = _.pick(email, ['html', 'plaintext']);

    const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
    const REPLACEMENT_STRING_REGEX = /\{(?<recipientProperty>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;

    ['html', 'plaintext'].forEach((format) => {
        emailContent[format] = emailContent[format].replace(EMAIL_REPLACEMENT_REGEX, (replacementMatch, replacementStr) => {
            const match = replacementStr.match(REPLACEMENT_STRING_REGEX);

            if (match) {
                const {recipientProperty} = match.groups;

                if (ALLOWED_REPLACEMENTS.includes(recipientProperty)) {
                    // keeps wrapping %% for later replacement with real data
                    return replacementMatch;
                }
            }

            // removes %% so output matches user supplied content
            return replacementStr;
        });
    });

    return emailContent;
};

// parses email content and extracts an array of replacements with desired fallbacks
const parseReplacements = (email) => {
    const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
    const REPLACEMENT_STRING_REGEX = /\{(?<recipientProperty>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;

    const replacements = [];

    ['html', 'plaintext'].forEach((format) => {
        let result;
        while ((result = EMAIL_REPLACEMENT_REGEX.exec(email[format])) !== null) {
            const [replacementMatch, replacementStr] = result;
            const match = replacementStr.match(REPLACEMENT_STRING_REGEX);

            if (match) {
                const {recipientProperty, fallback} = match.groups;

                if (ALLOWED_REPLACEMENTS.includes(recipientProperty)) {
                    const id = `replacement_${replacements.length + 1}`;

                    replacements.push({
                        format,
                        id,
                        match: replacementMatch,
                        recipientProperty: `member_${recipientProperty}`,
                        fallback
                    });
                }
            }
        }
    });

    return replacements;
};

const serialize = async (postModel, options = {isBrowserPreview: false, apiVersion: 'v4'}) => {
    const post = await serializePostModel(postModel, options.apiVersion);

    const timezone = settingsCache.get('timezone');
    const momentDate = post.published_at ? moment(post.published_at) : moment();
    post.published_at = momentDate.tz(timezone).format('DD MMM YYYY');

    post.authors = post.authors && post.authors.map(author => author.name).join(',');
    if (post.posts_meta) {
        post.email_subject = post.posts_meta.email_subject;
    }

    // we use post.excerpt as a hidden piece of text that is picked up by some email
    // clients as a "preview" when listing emails. Our current plaintext/excerpt
    // generation outputs links as "Link [https://url/]" which isn't desired in the preview
    if (!post.custom_excerpt && post.excerpt) {
        post.excerpt = post.excerpt.replace(/\s\[http(.*?)\]/g, '');
    }

    post.html = mobiledocLib.mobiledocHtmlRenderer.render(JSON.parse(post.mobiledoc), {target: 'email'});
    // same options as used in Post model for generating plaintext but without `wordwrap: 80`
    // to avoid replacement strings being split across lines and for mail clients to handle
    // word wrapping based on user preferences
    post.plaintext = htmlToText.fromString(post.html, {
        wordwrap: false,
        ignoreImage: true,
        hideLinkHrefIfSameAsText: true,
        preserveNewlines: true,
        returnDomByDefault: true,
        uppercaseHeadings: false
    });

    // Outlook will render feature images at full-size breaking the layout.
    // Content images fix this by rendering max 600px images - do the same for feature image here
    if (isUnsplashImage(post.feature_image)) {
        // Unsplash images have a minimum size so assuming 1200px is safe
        const unsplashUrl = new URL(post.feature_image);
        unsplashUrl.searchParams.set('w', 1200);

        post.feature_image = unsplashUrl.href;
        post.feature_image_width = 600;
    } else {
        const {imageSize} = require('../../lib/image');
        try {
            const size = await imageSize.getImageSizeFromUrl(post.feature_image);

            if (size.width >= 600) {
                // keep original image, just set a fixed width
                post.feature_image_width = 600;
            }

            if (isLocalContentImage(post.feature_image, urlUtils.getSiteUrl())) {
                // we can safely request a 1200px image - Ghost will serve the original if it's smaller
                post.feature_image = post.feature_image.replace(/\/content\/images\//, '/content/images/size/w1200/');
            }
        } catch (err) {
            // log and proceed. Using original feature_image without fixed width isn't fatal.
            logging.error(err);
        }
    }

    const templateSettings = {
        showSiteHeader: settingsCache.get('newsletter_show_header'),
        bodyFontCategory: settingsCache.get('newsletter_body_font_category'),
        showBadge: settingsCache.get('newsletter_show_badge'),
        footerContent: settingsCache.get('newsletter_footer_content'),
        accentColor: settingsCache.get('accent_color')
    };
    let htmlTemplate = template({post, site: getSite(), templateSettings});
    if (options.isBrowserPreview) {
        const previewUnsubscribeUrl = createUnsubscribeUrl();
        htmlTemplate = htmlTemplate.replace('%recipient.unsubscribe_url%', previewUnsubscribeUrl);
    }

    // Inline css to style attributes, turn on support for pseudo classes.
    const juiceOptions = {inlinePseudoElements: true};
    let juicedHtml = juice(htmlTemplate, juiceOptions);

    // convert juiced HTML to a DOM-like interface for further manipulation
    // happens after inlining of CSS so we can change element types without worrying about styling
    let _cheerio = cheerio.load(juicedHtml);
    // force all links to open in new tab
    _cheerio('a').attr('target','_blank');
    // convert figure and figcaption to div so that Outlook applies margins
    _cheerio('figure, figcaption').each((i, elem) => (elem.tagName = 'div'));
    juicedHtml = _cheerio.html();

    // Fix any unsupported chars in Outlook
    juicedHtml = juicedHtml.replace(/'/g, ''');

    // Clean up any unknown replacements strings to get our final content
    const {html, plaintext} = normalizeReplacementStrings({
        html: juicedHtml,
        plaintext: post.plaintext
    });

    return {
        subject: post.email_subject || post.title,
        html,
        plaintext
    };
};

module.exports = {
    serialize,
    createUnsubscribeUrl,
    parseReplacements
};
<commit_msg>🐛 Fixed logs showing an error when sending an email with no feature image<commit_after>const _ = require('lodash');
const juice = require('juice');
const template = require('./template');
const settingsCache = require('../../services/settings/cache');
const urlUtils = require('../../../shared/url-utils');
const moment = require('moment-timezone');
const cheerio = require('cheerio');
const api = require('../../api');
const {URL} = require('url');
const mobiledocLib = require('../../lib/mobiledoc');
const htmlToText = require('html-to-text');
const {isUnsplashImage, isLocalContentImage} = require('@tryghost/kg-default-cards/lib/utils');
const logging = require('../../../shared/logging');

const ALLOWED_REPLACEMENTS = ['first_name'];

const getSite = () => {
    const publicSettings = settingsCache.getPublic();
    return Object.assign({}, publicSettings, {
        url: urlUtils.urlFor('home', true),
        iconUrl: publicSettings.icon ? urlUtils.urlFor('image', {image: publicSettings.icon}, true) : null
    });
};

/**
 * createUnsubscribeUrl
 *
 * Takes a member uuid and returns the url that should be used to unsubscribe
 * In case of no member uuid, generates the preview unsubscribe url - `?preview=1`
 *
 * @param {string} uuid
 */
const createUnsubscribeUrl = (uuid) => {
    const siteUrl = urlUtils.getSiteUrl();
    const unsubscribeUrl = new URL(siteUrl);
    unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
    if (uuid) {
        unsubscribeUrl.searchParams.set('uuid', uuid);
    } else {
        unsubscribeUrl.searchParams.set('preview', '1');
    }

    return unsubscribeUrl.href;
};

// NOTE: serialization is needed to make sure we are using current API and do post transformations
//       such as image URL transformation from relative to absolute
const serializePostModel = async (model, apiVersion = 'v4') => {
    // fetch mobiledoc rather than html and plaintext so we can render email-specific contents
    const frame = {options: {context: {user: true}, formats: 'mobiledoc'}};
    const docName = 'posts';

    await api.shared
        .serializers
        .handle
        .output(model, {docName: docName, method: 'read'}, api[apiVersion].serializers.output, frame);

    return frame.response[docName][0];
};

// removes %% wrappers from unknown replacement strings in email content
const normalizeReplacementStrings = (email) => {
    // we don't want to modify the email object in-place
    const emailContent = _.pick(email, ['html', 'plaintext']);

    const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
    const REPLACEMENT_STRING_REGEX = /\{(?<recipientProperty>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;

    ['html', 'plaintext'].forEach((format) => {
        emailContent[format] = emailContent[format].replace(EMAIL_REPLACEMENT_REGEX, (replacementMatch, replacementStr) => {
            const match = replacementStr.match(REPLACEMENT_STRING_REGEX);

            if (match) {
                const {recipientProperty} = match.groups;

                if (ALLOWED_REPLACEMENTS.includes(recipientProperty)) {
                    // keeps wrapping %% for later replacement with real data
                    return replacementMatch;
                }
            }

            // removes %% so output matches user supplied content
            return replacementStr;
        });
    });

    return emailContent;
};

// parses email content and extracts an array of replacements with desired fallbacks
const parseReplacements = (email) => {
    const EMAIL_REPLACEMENT_REGEX = /%%(\{.*?\})%%/g;
    const REPLACEMENT_STRING_REGEX = /\{(?<recipientProperty>\w*?)(?:,? *(?:"|")(?<fallback>.*?)(?:"|"))?\}/;

    const replacements = [];

    ['html', 'plaintext'].forEach((format) => {
        let result;
        while ((result = EMAIL_REPLACEMENT_REGEX.exec(email[format])) !== null) {
            const [replacementMatch, replacementStr] = result;
            const match = replacementStr.match(REPLACEMENT_STRING_REGEX);

            if (match) {
                const {recipientProperty, fallback} = match.groups;

                if (ALLOWED_REPLACEMENTS.includes(recipientProperty)) {
                    const id = `replacement_${replacements.length + 1}`;

                    replacements.push({
                        format,
                        id,
                        match: replacementMatch,
                        recipientProperty: `member_${recipientProperty}`,
                        fallback
                    });
                }
            }
        }
    });

    return replacements;
};

const serialize = async (postModel, options = {isBrowserPreview: false, apiVersion: 'v4'}) => {
    const post = await serializePostModel(postModel, options.apiVersion);

    const timezone = settingsCache.get('timezone');
    const momentDate = post.published_at ? moment(post.published_at) : moment();
    post.published_at = momentDate.tz(timezone).format('DD MMM YYYY');

    post.authors = post.authors && post.authors.map(author => author.name).join(',');
    if (post.posts_meta) {
        post.email_subject = post.posts_meta.email_subject;
    }

    // we use post.excerpt as a hidden piece of text that is picked up by some email
    // clients as a "preview" when listing emails. Our current plaintext/excerpt
    // generation outputs links as "Link [https://url/]" which isn't desired in the preview
    if (!post.custom_excerpt && post.excerpt) {
        post.excerpt = post.excerpt.replace(/\s\[http(.*?)\]/g, '');
    }

    post.html = mobiledocLib.mobiledocHtmlRenderer.render(JSON.parse(post.mobiledoc), {target: 'email'});
    // same options as used in Post model for generating plaintext but without `wordwrap: 80`
    // to avoid replacement strings being split across lines and for mail clients to handle
    // word wrapping based on user preferences
    post.plaintext = htmlToText.fromString(post.html, {
        wordwrap: false,
        ignoreImage: true,
        hideLinkHrefIfSameAsText: true,
        preserveNewlines: true,
        returnDomByDefault: true,
        uppercaseHeadings: false
    });

    // Outlook will render feature images at full-size breaking the layout.
    // Content images fix this by rendering max 600px images - do the same for feature image here
    if (post.feature_image) {
        if (isUnsplashImage(post.feature_image)) {
            // Unsplash images have a minimum size so assuming 1200px is safe
            const unsplashUrl = new URL(post.feature_image);
            unsplashUrl.searchParams.set('w', 1200);

            post.feature_image = unsplashUrl.href;
            post.feature_image_width = 600;
        } else {
            const {imageSize} = require('../../lib/image');
            try {
                const size = await imageSize.getImageSizeFromUrl(post.feature_image);

                if (size.width >= 600) {
                    // keep original image, just set a fixed width
                    post.feature_image_width = 600;
                }

                if (isLocalContentImage(post.feature_image, urlUtils.getSiteUrl())) {
                    // we can safely request a 1200px image - Ghost will serve the original if it's smaller
                    post.feature_image = post.feature_image.replace(/\/content\/images\//, '/content/images/size/w1200/');
                }
            } catch (err) {
                // log and proceed. Using original feature_image without fixed width isn't fatal.
                logging.error(err);
            }
        }
    }

    const templateSettings = {
        showSiteHeader: settingsCache.get('newsletter_show_header'),
        bodyFontCategory: settingsCache.get('newsletter_body_font_category'),
        showBadge: settingsCache.get('newsletter_show_badge'),
        footerContent: settingsCache.get('newsletter_footer_content'),
        accentColor: settingsCache.get('accent_color')
    };
    let htmlTemplate = template({post, site: getSite(), templateSettings});
    if (options.isBrowserPreview) {
        const previewUnsubscribeUrl = createUnsubscribeUrl();
        htmlTemplate = htmlTemplate.replace('%recipient.unsubscribe_url%', previewUnsubscribeUrl);
    }

    // Inline css to style attributes, turn on support for pseudo classes.
    const juiceOptions = {inlinePseudoElements: true};
    let juicedHtml = juice(htmlTemplate, juiceOptions);

    // convert juiced HTML to a DOM-like interface for further manipulation
    // happens after inlining of CSS so we can change element types without worrying about styling
    let _cheerio = cheerio.load(juicedHtml);
    // force all links to open in new tab
    _cheerio('a').attr('target','_blank');
    // convert figure and figcaption to div so that Outlook applies margins
    _cheerio('figure, figcaption').each((i, elem) => (elem.tagName = 'div'));
    juicedHtml = _cheerio.html();

    // Fix any unsupported chars in Outlook
    juicedHtml = juicedHtml.replace(/'/g, ''');

    // Clean up any unknown replacements strings to get our final content
    const {html, plaintext} = normalizeReplacementStrings({
        html: juicedHtml,
        plaintext: post.plaintext
    });

    return {
        subject: post.email_subject || post.title,
        html,
        plaintext
    };
};

module.exports = {
    serialize,
    createUnsubscribeUrl,
    parseReplacements
};
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var protobuf = require('pomelo-protobuf');
var Constants = require('../util/constants');
var crypto = require('crypto');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);

module.exports = function(app, opts) {
  return new Component(app, opts);
};

var Component = function(app, opts) {
  this.app = app;
  opts = opts || {};
  this.watchers = {};
  this.serverProtos = {};
  this.clientProtos = {};
  this.version = "";

  var env = app.get(Constants.RESERVED.ENV);
  var originServerPath = path.join(app.getBase(), Constants.FILEPATH.SERVER_PROTOS);
  var presentServerPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.SERVER_PROTOS));
  var originClientPath = path.join(app.getBase(), Constants.FILEPATH.CLIENT_PROTOS);
  var presentClientPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.CLIENT_PROTOS));

  this.serverProtosPath = opts.serverProtos || (fs.existsSync(originClientPath) ? Constants.FILEPATH.SERVER_PROTOS : presentServerPath);
  this.clientProtosPath = opts.clientProtos || (fs.existsSync(originServerPath) ? Constants.FILEPATH.CLIENT_PROTOS : presentClientPath);

  this.setProtos(Constants.RESERVED.SERVER, path.join(app.getBase(), this.serverProtosPath));
  this.setProtos(Constants.RESERVED.CLIENT, path.join(app.getBase(), this.clientProtosPath));

  protobuf.init({encoderProtos:this.serverProtos, decoderProtos:this.clientProtos});
};

var pro = Component.prototype;

pro.name = '__protobuf__';

pro.encode = function(key, msg) {
  return protobuf.encode(key, msg);
};

pro.encode2Bytes = function(key, msg) {
  return protobuf.encode2Bytes(key, msg);
};

pro.decode = function(key, msg) {
  return protobuf.decode(key, msg);
};

pro.getProtos = function() {
  return {
    server : this.serverProtos,
    client : this.clientProtos,
    version : this.version
  };
};

pro.getVersion = function() {
  return this.version;
};

pro.setProtos = function(type, path) {
  if(!fs.existsSync(path)) {
    return;
  }

  if(type === Constants.RESERVED.SERVER) {
    this.serverProtos = protobuf.parse(require(path));
  }

  if(type === Constants.RESERVED.CLIENT) {
    this.clientProtos = protobuf.parse(require(path));
  }

  var protoStr = JSON.stringify(this.clientProtos) + JSON.stringify(this.serverProtos);
  this.version = crypto.createHash('md5').update(protoStr).digest('base64');

  //Watch file
  var watcher = fs.watch(path, this.onUpdate.bind(this, type, path));
  if (this.watchers[type]) {
    this.watchers[type].close();
  }
  this.watchers[type] = watcher;
};

pro.onUpdate = function(type, path, event) {
  if(event !== 'change') {
    return;
  }

  var self = this;
  fs.readFile(path, 'utf8' ,function(err, data) {
    try {
      var protos = protobuf.parse(JSON.parse(data));
      if(type === Constants.RESERVED.SERVER) {
        protobuf.setEncoderProtos(protos);
        self.serverProtos = protos;
      } else {
        protobuf.setDecoderProtos(protos);
        self.clientProtos = protos;
      }

      var protoStr = JSON.stringify(self.clientProtos) + JSON.stringify(self.serverProtos);
      self.version = crypto.createHash('md5').update(protoStr).digest('base64');
      logger.info('change proto file , type : %j, path : %j, version : %j', type, path, self.version);
    } catch(e) {
      logger.warn("change proto file error! path : %j", path);
      logger.warn(e);
    }
  });
};

pro.stop = function(force, cb) {
  for (var type in this.watchers) {
    this.watchers[type].close();
  }
  this.watchers = {};
  process.nextTick(cb);
};
<commit_msg>protobuf: fix wrong variable `originServerPath`/`originClientPath`<commit_after>var fs = require('fs');
var path = require('path');
var protobuf = require('pomelo-protobuf');
var Constants = require('../util/constants');
var crypto = require('crypto');
var logger = require('pomelo-logger').getLogger('pomelo', __filename);

module.exports = function(app, opts) {
  return new Component(app, opts);
};

var Component = function(app, opts) {
  this.app = app;
  opts = opts || {};
  this.watchers = {};
  this.serverProtos = {};
  this.clientProtos = {};
  this.version = "";

  var env = app.get(Constants.RESERVED.ENV);
  var originServerPath = path.join(app.getBase(), Constants.FILEPATH.SERVER_PROTOS);
  var presentServerPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.SERVER_PROTOS));
  var originClientPath = path.join(app.getBase(), Constants.FILEPATH.CLIENT_PROTOS);
  var presentClientPath = path.join(Constants.FILEPATH.CONFIG_DIR, env, path.basename(Constants.FILEPATH.CLIENT_PROTOS));

  this.serverProtosPath = opts.serverProtos || (fs.existsSync(originServerPath) ? Constants.FILEPATH.SERVER_PROTOS : presentServerPath);
  this.clientProtosPath = opts.clientProtos || (fs.existsSync(originClientPath) ? Constants.FILEPATH.CLIENT_PROTOS : presentClientPath);

  this.setProtos(Constants.RESERVED.SERVER, path.join(app.getBase(), this.serverProtosPath));
  this.setProtos(Constants.RESERVED.CLIENT, path.join(app.getBase(), this.clientProtosPath));

  protobuf.init({encoderProtos:this.serverProtos, decoderProtos:this.clientProtos});
};

var pro = Component.prototype;

pro.name = '__protobuf__';

pro.encode = function(key, msg) {
  return protobuf.encode(key, msg);
};

pro.encode2Bytes = function(key, msg) {
  return protobuf.encode2Bytes(key, msg);
};

pro.decode = function(key, msg) {
  return protobuf.decode(key, msg);
};

pro.getProtos = function() {
  return {
    server : this.serverProtos,
    client : this.clientProtos,
    version : this.version
  };
};

pro.getVersion = function() {
  return this.version;
};

pro.setProtos = function(type, path) {
  if(!fs.existsSync(path)) {
    return;
  }

  if(type === Constants.RESERVED.SERVER) {
    this.serverProtos = protobuf.parse(require(path));
  }

  if(type === Constants.RESERVED.CLIENT) {
    this.clientProtos = protobuf.parse(require(path));
  }

  var protoStr = JSON.stringify(this.clientProtos) + JSON.stringify(this.serverProtos);
  this.version = crypto.createHash('md5').update(protoStr).digest('base64');

  //Watch file
  var watcher = fs.watch(path, this.onUpdate.bind(this, type, path));
  if (this.watchers[type]) {
    this.watchers[type].close();
  }
  this.watchers[type] = watcher;
};

pro.onUpdate = function(type, path, event) {
  if(event !== 'change') {
    return;
  }

  var self = this;
  fs.readFile(path, 'utf8' ,function(err, data) {
    try {
      var protos = protobuf.parse(JSON.parse(data));
      if(type === Constants.RESERVED.SERVER) {
        protobuf.setEncoderProtos(protos);
        self.serverProtos = protos;
      } else {
        protobuf.setDecoderProtos(protos);
        self.clientProtos = protos;
      }

      var protoStr = JSON.stringify(self.clientProtos) + JSON.stringify(self.serverProtos);
      self.version = crypto.createHash('md5').update(protoStr).digest('base64');
      logger.info('change proto file , type : %j, path : %j, version : %j', type, path, self.version);
    } catch(e) {
      logger.warn("change proto file error! path : %j", path);
      logger.warn(e);
    }
  });
};

pro.stop = function(force, cb) {
  for (var type in this.watchers) {
    this.watchers[type].close();
  }
  this.watchers = {};
  process.nextTick(cb);
};
<|endoftext|>


<commit_before>/**
 * SmoothScroll
 * This helper script created by DWUser.com.  Copyright 2013 DWUser.com.
 * Dual-licensed under the GPL and MIT licenses.
 * All individual scripts remain property of their copyrighters.
 * Date: 10-Sep-2013
 * Version: 1.0.1
 */
if (!window['jQuery']) alert('The jQuery library must be included before the smoothscroll.js file.  The plugin will not work propery.');

/**
 * jQuery.ScrollTo - Easy element scrolling using jQuery.
 * Copyright (c) 2007-2013 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * @author Ariel Flesler
 * @version 1.4.3.1
 */
;(function($){var h=$.scrollTo=function(a,b,c){$(window).scrollTo(a,b,c)};h.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1,limit:true};h.window=function(a){return $(window)._scrollable()};$.fn._scrollable=function(){return this.map(function(){var a=this,isWin=!a.nodeName||$.inArray(a.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!isWin)return a;var b=(a.contentWindow||a).document||a.ownerDocument||a;return/webkit/i.test(navigator.userAgent)||b.compatMode=='BackCompat'?b.body:b.documentElement})};$.fn.scrollTo=function(e,f,g){if(typeof f=='object'){g=f;f=0}if(typeof g=='function')g={onAfter:g};if(e=='max')e=9e9;g=$.extend({},h.defaults,g);f=f||g.duration;g.queue=g.queue&&g.axis.length>1;if(g.queue)f/=2;g.offset=both(g.offset);g.over=both(g.over);return this._scrollable().each(function(){if(e==null)return;var d=this,$elem=$(d),targ=e,toff,attr={},win=$elem.is('html,body');switch(typeof targ){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px|%)?$/.test(targ)){targ=both(targ);break}targ=$(targ,this);if(!targ.length)return;case'object':if(targ.is||targ.style)toff=(targ=$(targ)).offset()}$.each(g.axis.split(''),function(i,a){var b=a=='x'?'Left':'Top',pos=b.toLowerCase(),key='scroll'+b,old=d[key],max=h.max(d,a);if(toff){attr[key]=toff[pos]+(win?0:old-$elem.offset()[pos]);if(g.margin){attr[key]-=parseInt(targ.css('margin'+b))||0;attr[key]-=parseInt(targ.css('border'+b+'Width'))||0}attr[key]+=g.offset[pos]||0;if(g.over[pos])attr[key]+=targ[a=='x'?'width':'height']()*g.over[pos]}else{var c=targ[pos];attr[key]=c.slice&&c.slice(-1)=='%'?parseFloat(c)/100*max:c}if(g.limit&&/^\d+$/.test(attr[key]))attr[key]=attr[key]<=0?0:Math.min(attr[key],max);if(!i&&g.queue){if(old!=attr[key])animate(g.onAfterFirst);delete attr[key]}});animate(g.onAfter);function animate(a){$elem.animate(attr,f,g.easing,a&&function(){a.call(this,e,g)})}}).end()};h.max=function(a,b){var c=b=='x'?'Width':'Height',scroll='scroll'+c;if(!$(a).is('html,body'))return a[scroll]-$(a)[c.toLowerCase()]();var d='client'+c,html=a.ownerDocument.documentElement,body=a.ownerDocument.body;return Math.max(html[scroll],body[scroll])-Math.min(html[d],body[d])};function both(a){return typeof a=='object'?a:{top:a,left:a}}})(jQuery);

/**
 * jQuery.LocalScroll
 * Copyright (c) 2007-2010 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 05/31/2010
 * @author Ariel Flesler
 * @version 1.2.8b
 **/
;(function(b){function g(a,e,d){var h=e.hash.slice(1),f=document.getElementById(h)||document.getElementsByName(h)[0];if(f){a&&a.preventDefault();var c=b(d.target);if(!(d.lock&&c.is(":animated")||d.onBefore&&!1===d.onBefore(a,f,c))){d.stop&&c._scrollable().stop(!0);if(d.hash){var a=f.id==h?"id":"name",g=b("<a> </a>").attr(a,h).css({position:"absolute",top:b(window).scrollTop(),left:b(window).scrollLeft()});f[a]="";b("body").prepend(g);location=e.hash;g.remove();f[a]=h}c.scrollTo(f,d).trigger("notify.serialScroll",
[f])}}}var i=location.href.replace(/#.*/,""),c=b.localScroll=function(a){b("body").localScroll(a)};c.defaults={duration:1E3,axis:"y",event:"click",stop:!0,target:window,reset:!0};c.hash=function(a){if(location.hash){a=b.extend({},c.defaults,a);a.hash=!1;if(a.reset){var e=a.duration;delete a.duration;b(a.target).scrollTo(0,a);a.duration=e}g(0,location,a)}};b.fn.localScroll=function(a){function e(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,"")==i&&(!a.filter||b(this).is(a.filter))}
a=b.extend({},c.defaults,a);return a.lazy?this.bind(a.event,function(d){var c=b([d.target,d.target.parentNode]).filter(e)[0];c&&g(d,c,a)}):this.find("a,area").filter(e).bind(a.event,function(b){g(b,this,a)}).end().end()}})(jQuery);

// Initialize all .smoothScroll links
jQuery(function($){ $.localScroll({filter:'.smoothScroll'}); });
<commit_msg>Delete smoothscroll.js<commit_after><|endoftext|>


<commit_before>//Translated from ai1_player.rb using babyruby2js
'use strict';

var main = require('../../main');

var allHeuristics = require('./AllHeuristics');
var BoardAnalyser = require('./boan/BoardAnalyser');
var inherits = require('util').inherits;
var Genes = require('../../Genes');
var Grid = require('../../Grid');
var InfluenceMap = require('./boan/InfluenceMap');
var Player = require('../../Player');
var PotentialTerritory = require('./boan/PotentialTerritory');
var Stone = require('../../Stone');
var ZoneFiller = require('./boan/ZoneFiller');

var sOK = main.sOK, sINVALID = main.sINVALID, sBLUNDER = main.sBLUNDER;

var NO_MOVE = -1; // used for i coordinate of "not yet known" best moves

/** @class
 *  public read-only attribute: goban, inf, ter, enemyColor, genes
 *  TODO:
 *  - do not fill my own territory (potential territory recognition will use analyser.enlarge method)
 *  - identify all foolish moves (like NoEasyPrisoner but once for all) in a map that all heuristics can use
 *  - foresee a poursuit = on attack/defense (and/or use a reverse-killer?)
 *  - an eye shape constructor
 */
function Ai1Player(goban, color, genes) {
    if (genes === undefined) genes = null;
    Player.call(this, false, goban);
    this.inf = new InfluenceMap(this.goban);
    this.ter = new PotentialTerritory(this.goban);
    this.boan = new BoardAnalyser();
    this.gsize = this.goban.gsize;
    this.stateGrid = new Grid(this.gsize);
    this.scoreGrid = new Grid(this.gsize);

    this.genes = (( genes ? genes : new Genes() ));
    this.minimumScore = this.getGene('smaller-move', 0.03, 0.01, 0.1);

    this.heuristics = [];
    var heuristics = allHeuristics();
    for (var i = 0; i < heuristics.length; i++) {
        var h = new (heuristics[i])(this);
        this.heuristics.push(h);
    }
    this.setColor(color);
    // genes need to exist before we create heuristics so passing genes below is done
    // to keep things coherent
    this.prepareGame(this.genes);
}
inherits(Ai1Player, Player);
module.exports = Ai1Player;

Ai1Player.BoardAnalyser = BoardAnalyser;
Ai1Player.PotentialTerritory = PotentialTerritory;
Ai1Player.ZoneFiller = ZoneFiller;

Ai1Player.prototype.getHeuristic = function (heuristicName) {
    for (var n = this.heuristics.length - 1; n >= 0; n--) {
        var h = this.heuristics[n];
        if (h.constructor.name === heuristicName) return h;
    }
    throw new Error('Invalid heuristic name: ' + heuristicName);
};

Ai1Player.prototype.prepareGame = function (genes) {
    this.genes = genes;
    this.numMoves = 0;
};

Ai1Player.prototype.setColor = function (color) {
    Player.prototype.setColor.call(this, color);
    this.enemyColor = 1 - color;
    for (var i = 0; i < this.heuristics.length; i++) {
        this.heuristics[i].initColor();
    }
};

Ai1Player.prototype.getGene = function (name, defVal, lowLimit, highLimit) {
    if (lowLimit === undefined) lowLimit = null;
    if (highLimit === undefined) highLimit = null;
    return this.genes.get(this.constructor.name + '-' + name, defVal, lowLimit, highLimit);
};

function score2str(i, j, score) {
    return Grid.xy2move(i, j) + ':' + score.toFixed(3);
}

Ai1Player.prototype._foundSecondBestMove = function(i, j, score) {
    if (main.debug) {
        main.log.debug('=> ' + score2str(i,j,score) + ' becomes 2nd best move');
        if (this.secondBestI !== NO_MOVE) main.log.debug(' (replaces ' + score2str(this.secondBestI, this.secondBestJ, this.secondBestScore) + ')');
    }
    this.secondBestScore = score;
    this.secondBestI = i; this.secondBestJ = j;
};

Ai1Player.prototype._foundBestMove = function(i, j, score) {
    if (main.debug) {
        if (this.numBestTwins > 1) {
            main.log.debug('=> TWIN ' + score2str(i, j, score) + ' replaces equivalent best move ' + score2str(this.bestI, this.bestJ, this.bestScore));
        } else if (this.bestI !== NO_MOVE) {
            main.log.debug('=> ' + score2str(i, j, score) + ' becomes the best move');
        }
    }
    if (this.numBestTwins === 1) {
        this._foundSecondBestMove(this.bestI, this.bestJ, this.bestScore);
    }
    this.bestScore = score;
    this.bestI = i; this.bestJ = j;
};

Ai1Player.prototype._keepBestMoves = function(i, j, score) {
    // Keep the best move and the 2nd best move
    if (score < this.bestScore) {
        this._foundSecondBestMove(i, j, score);
    } else if (score > this.bestScore) {
        this.numBestTwins = 1; // number of moves with same best score (we randomly pick one of them)
        this._foundBestMove(i, j, score);
    } else { // score === this.bestScore
        this.numBestTwins++;
        if (Math.random() * this.numBestTwins >= 1) return; // keep current twin if it does not win
        this._foundBestMove(i, j, score);
    }
};

// Returns the move chosen (e.g. c4 or pass)
// You can also check:
//   player.bestScore to see the score of the move returned
//   player.secondBestScore
Ai1Player.prototype.getMove = function () {
    this.numMoves++;
    if (this.numMoves >= this.gsize * this.gsize) { // force pass after too many moves
        main.log.error('Forcing AI pass since we already played ' + this.numMoves);
        return 'pass';
    }
    this._prepareEval();

    // init grids (and mark invalid moves)
    var stateYx = this.stateGrid.yx;
    var scoreYx = this.scoreGrid.yx;
    var i,j;
    for (j = 1; j <= this.gsize; j++) {
        for (i = 1; i <= this.gsize; i++) {
            if (!Stone.validMove(this.goban, i, j, this.color)) {
                stateYx[j][i] = sINVALID;
                continue;
            }
            stateYx[j][i] = sOK;
            scoreYx[j][i] = 0;
        }
    }
    // do eval using each heuristic (NB: order is important)
    for (var n = 0; n < this.heuristics.length; n++) {
        this.heuristics[n].evalBoard(stateYx, scoreYx);
    }
    // now collect best score (and 2nd best)
    for (j = 1; j <= this.gsize; j++) {
        for (i = 1; i <= this.gsize; i++) {
            if (stateYx[j][i] >= sOK && scoreYx[j][i] > this.secondBestScore) {
                this._keepBestMoves(i, j, scoreYx[j][i]);
            }
        }
    }
    if (this.bestScore <= this.minimumScore) {
        return 'pass';
    }
    return Grid.xy2move(this.bestI, this.bestJ);
};

Ai1Player.prototype._collectGroupInfo = function () {
    // var allGroups = this.ter.allGroups;
    // for (var ndx in allGroups) {
    //     var g = allGroups[ndx], gi = g._info;
    // }
};

Ai1Player.prototype._prepareEval = function () {
    this.currentMove = this.goban.moveNumber();
    this.bestScore = this.secondBestScore = this.minimumScore;
    this.bestI = this.secondBestI = NO_MOVE;
    this.survey = null;

    this.inf.buildMap();
    this.ter.guessTerritories();
    this._collectGroupInfo();

    // get "raw" group info
    this.boan.analyse(this.color, this.goban);
};

/** Called by heuristics if they decide to stop looking further (rare cases) */
Ai1Player.prototype.markMoveAsBlunder = function (i, j, reason) {
    this.stateGrid.yx[j][i] = sBLUNDER;
    main.log.debug(Grid.xy2move(i, j) + ' seen as blunder: ' + reason);
};
Ai1Player.prototype.isBlunderMove = function (i, j) {
    return this.stateGrid.yx[j][i] === sBLUNDER;
};

/** For tests */
Ai1Player.prototype._testMoveEval = function (i, j) {
    if (this.currentMove !== this.goban.moveNumber()) this.getMove();
    var stateYx = this.stateGrid.yx;
    var scoreYx = this.scoreGrid.yx;
    // to get eval "again", set the state back to OK even if it got marked invalid later
    if (Stone.validMove(this.goban, i, j, this.color)) stateYx[j][i] = sOK;
    var score = 0, survey = {};
    for (var n = 0; n < this.heuristics.length; n++) {
        var h = this.heuristics[n];
        scoreYx[j][i] = 0;
        h.evalBoard(stateYx, scoreYx);
        var s = scoreYx[j][i];
        if (s) survey[h.constructor.name] = s;
        score += s;
    }
    this.survey = survey;
    return score;
};

/** For tests */
Ai1Player.prototype.testMoveEval = function (i, j) {
    var score = this._testMoveEval(i, j);

    this._foundBestMove(i, j, score);
    this.secondBestI = NO_MOVE;
    return score;
};

/** For tests */
Ai1Player.prototype.testHeuristic = function (i, j, heuristicName) {
    if (this.currentMove !== this.goban.moveNumber()) this.getMove();
    var stateYx = this.stateGrid.yx;
    var scoreYx = this.scoreGrid.yx;
    this.getMove();
    stateYx[j][i] = sOK;
    scoreYx[j][i] = 0;
    var h = this.getHeuristic(heuristicName);
    h.evalBoard(stateYx, scoreYx);
    return scoreYx[j][i];
};

Ai1Player.prototype.getMoveSurveyText = function (rank) {
    var survey, score, move;
    switch (rank) {
    case 1:
        if (this.bestI === NO_MOVE) break;
        this._testMoveEval(this.bestI, this.bestJ);
        survey = this.survey; score = this.bestScore;
        move = Grid.xy2move(this.bestI, this.bestJ);
        break;
    case 2:
        if (this.secondBestI === NO_MOVE) break;
        this._testMoveEval(this.secondBestI, this.secondBestJ);
        survey = this.survey; score = this.secondBestScore;
        move = Grid.xy2move(this.secondBestI, this.secondBestJ);
        break;
    }
    if (!survey) return '';
    var txt = 'Stats of ' + move + ' (' + score.toFixed(3) + '):\n';
    for (var h in survey) {
        if (survey[h] === 0) continue;
        txt += '- ' + h + ': ' + survey[h].toFixed(3) + '\n';
    }
    return txt;
};

<commit_msg>Ai1Player renamed Frankie<commit_after>//Translated from ai1_player.rb using babyruby2js
'use strict';

var main = require('../../main');

var allHeuristics = require('./AllHeuristics');
var BoardAnalyser = require('./boan/BoardAnalyser');
var inherits = require('util').inherits;
var Genes = require('../../Genes');
var Grid = require('../../Grid');
var InfluenceMap = require('./boan/InfluenceMap');
var Player = require('../../Player');
var PotentialTerritory = require('./boan/PotentialTerritory');
var Stone = require('../../Stone');
var ZoneFiller = require('./boan/ZoneFiller');

var sOK = main.sOK, sINVALID = main.sINVALID, sBLUNDER = main.sBLUNDER;

var NO_MOVE = -1; // used for i coordinate of "not yet known" best moves

/** @class
 *  public read-only attribute: goban, inf, ter, enemyColor, genes
 *  TODO:
 *  - do not fill my own territory (potential territory recognition will use analyser.enlarge method)
 *  - identify all foolish moves (like NoEasyPrisoner but once for all) in a map that all heuristics can use
 *  - foresee a poursuit = on attack/defense (and/or use a reverse-killer?)
 *  - an eye shape constructor
 */
function Frankie(goban, color, genes) {
    if (genes === undefined) genes = null;
    Player.call(this, false, goban);
    this.inf = new InfluenceMap(this.goban);
    this.ter = new PotentialTerritory(this.goban);
    this.boan = new BoardAnalyser();
    this.gsize = this.goban.gsize;
    this.stateGrid = new Grid(this.gsize);
    this.scoreGrid = new Grid(this.gsize);

    this.genes = (( genes ? genes : new Genes() ));
    this.minimumScore = this.getGene('smaller-move', 0.03, 0.01, 0.1);

    this.heuristics = [];
    var heuristics = allHeuristics();
    for (var i = 0; i < heuristics.length; i++) {
        var h = new (heuristics[i])(this);
        this.heuristics.push(h);
    }
    this.setColor(color);
    // genes need to exist before we create heuristics so passing genes below is done
    // to keep things coherent
    this.prepareGame(this.genes);
}
inherits(Frankie, Player);
module.exports = Frankie;

Frankie.BoardAnalyser = BoardAnalyser;
Frankie.PotentialTerritory = PotentialTerritory;
Frankie.ZoneFiller = ZoneFiller;

Frankie.prototype.getHeuristic = function (heuristicName) {
    for (var n = this.heuristics.length - 1; n >= 0; n--) {
        var h = this.heuristics[n];
        if (h.constructor.name === heuristicName) return h;
    }
    throw new Error('Invalid heuristic name: ' + heuristicName);
};

Frankie.prototype.prepareGame = function (genes) {
    this.genes = genes;
    this.numMoves = 0;
};

Frankie.prototype.setColor = function (color) {
    Player.prototype.setColor.call(this, color);
    this.enemyColor = 1 - color;
    for (var i = 0; i < this.heuristics.length; i++) {
        this.heuristics[i].initColor();
    }
};

Frankie.prototype.getGene = function (name, defVal, lowLimit, highLimit) {
    if (lowLimit === undefined) lowLimit = null;
    if (highLimit === undefined) highLimit = null;
    return this.genes.get(this.constructor.name + '-' + name, defVal, lowLimit, highLimit);
};

function score2str(i, j, score) {
    return Grid.xy2move(i, j) + ':' + score.toFixed(3);
}

Frankie.prototype._foundSecondBestMove = function(i, j, score) {
    if (main.debug) {
        main.log.debug('=> ' + score2str(i,j,score) + ' becomes 2nd best move');
        if (this.secondBestI !== NO_MOVE) main.log.debug(' (replaces ' + score2str(this.secondBestI, this.secondBestJ, this.secondBestScore) + ')');
    }
    this.secondBestScore = score;
    this.secondBestI = i; this.secondBestJ = j;
};

Frankie.prototype._foundBestMove = function(i, j, score) {
    if (main.debug) {
        if (this.numBestTwins > 1) {
            main.log.debug('=> TWIN ' + score2str(i, j, score) + ' replaces equivalent best move ' + score2str(this.bestI, this.bestJ, this.bestScore));
        } else if (this.bestI !== NO_MOVE) {
            main.log.debug('=> ' + score2str(i, j, score) + ' becomes the best move');
        }
    }
    if (this.numBestTwins === 1) {
        this._foundSecondBestMove(this.bestI, this.bestJ, this.bestScore);
    }
    this.bestScore = score;
    this.bestI = i; this.bestJ = j;
};

Frankie.prototype._keepBestMoves = function(i, j, score) {
    // Keep the best move and the 2nd best move
    if (score < this.bestScore) {
        this._foundSecondBestMove(i, j, score);
    } else if (score > this.bestScore) {
        this.numBestTwins = 1; // number of moves with same best score (we randomly pick one of them)
        this._foundBestMove(i, j, score);
    } else { // score === this.bestScore
        this.numBestTwins++;
        if (Math.random() * this.numBestTwins >= 1) return; // keep current twin if it does not win
        this._foundBestMove(i, j, score);
    }
};

// Returns the move chosen (e.g. c4 or pass)
// You can also check:
//   player.bestScore to see the score of the move returned
//   player.secondBestScore
Frankie.prototype.getMove = function () {
    this.numMoves++;
    if (this.numMoves >= this.gsize * this.gsize) { // force pass after too many moves
        main.log.error('Forcing AI pass since we already played ' + this.numMoves);
        return 'pass';
    }
    this._prepareEval();

    // init grids (and mark invalid moves)
    var stateYx = this.stateGrid.yx;
    var scoreYx = this.scoreGrid.yx;
    var i,j;
    for (j = 1; j <= this.gsize; j++) {
        for (i = 1; i <= this.gsize; i++) {
            if (!Stone.validMove(this.goban, i, j, this.color)) {
                stateYx[j][i] = sINVALID;
                continue;
            }
            stateYx[j][i] = sOK;
            scoreYx[j][i] = 0;
        }
    }
    // do eval using each heuristic (NB: order is important)
    for (var n = 0; n < this.heuristics.length; n++) {
        this.heuristics[n].evalBoard(stateYx, scoreYx);
    }
    // now collect best score (and 2nd best)
    for (j = 1; j <= this.gsize; j++) {
        for (i = 1; i <= this.gsize; i++) {
            if (stateYx[j][i] >= sOK && scoreYx[j][i] > this.secondBestScore) {
                this._keepBestMoves(i, j, scoreYx[j][i]);
            }
        }
    }
    if (this.bestScore <= this.minimumScore) {
        return 'pass';
    }
    return Grid.xy2move(this.bestI, this.bestJ);
};

Frankie.prototype._collectGroupInfo = function () {
    // var allGroups = this.ter.allGroups;
    // for (var ndx in allGroups) {
    //     var g = allGroups[ndx], gi = g._info;
    // }
};

Frankie.prototype._prepareEval = function () {
    this.currentMove = this.goban.moveNumber();
    this.bestScore = this.secondBestScore = this.minimumScore;
    this.bestI = this.secondBestI = NO_MOVE;
    this.survey = null;

    this.inf.buildMap();
    this.ter.guessTerritories();
    this._collectGroupInfo();

    // get "raw" group info
    this.boan.analyse(this.color, this.goban);
};

/** Called by heuristics if they decide to stop looking further (rare cases) */
Frankie.prototype.markMoveAsBlunder = function (i, j, reason) {
    this.stateGrid.yx[j][i] = sBLUNDER;
    main.log.debug(Grid.xy2move(i, j) + ' seen as blunder: ' + reason);
};
Frankie.prototype.isBlunderMove = function (i, j) {
    return this.stateGrid.yx[j][i] === sBLUNDER;
};

/** For tests */
Frankie.prototype._testMoveEval = function (i, j) {
    if (this.currentMove !== this.goban.moveNumber()) this.getMove();
    var stateYx = this.stateGrid.yx;
    var scoreYx = this.scoreGrid.yx;
    // to get eval "again", set the state back to OK even if it got marked invalid later
    if (Stone.validMove(this.goban, i, j, this.color)) stateYx[j][i] = sOK;
    var score = 0, survey = {};
    for (var n = 0; n < this.heuristics.length; n++) {
        var h = this.heuristics[n];
        scoreYx[j][i] = 0;
        h.evalBoard(stateYx, scoreYx);
        var s = scoreYx[j][i];
        if (s) survey[h.constructor.name] = s;
        score += s;
    }
    this.survey = survey;
    return score;
};

/** For tests */
Frankie.prototype.testMoveEval = function (i, j) {
    var score = this._testMoveEval(i, j);

    this._foundBestMove(i, j, score);
    this.secondBestI = NO_MOVE;
    return score;
};

/** For tests */
Frankie.prototype.testHeuristic = function (i, j, heuristicName) {
    if (this.currentMove !== this.goban.moveNumber()) this.getMove();
    var stateYx = this.stateGrid.yx;
    var scoreYx = this.scoreGrid.yx;
    this.getMove();
    stateYx[j][i] = sOK;
    scoreYx[j][i] = 0;
    var h = this.getHeuristic(heuristicName);
    h.evalBoard(stateYx, scoreYx);
    return scoreYx[j][i];
};

Frankie.prototype.getMoveSurveyText = function (rank) {
    var survey, score, move;
    switch (rank) {
    case 1:
        if (this.bestI === NO_MOVE) break;
        this._testMoveEval(this.bestI, this.bestJ);
        survey = this.survey; score = this.bestScore;
        move = Grid.xy2move(this.bestI, this.bestJ);
        break;
    case 2:
        if (this.secondBestI === NO_MOVE) break;
        this._testMoveEval(this.secondBestI, this.secondBestJ);
        survey = this.survey; score = this.secondBestScore;
        move = Grid.xy2move(this.secondBestI, this.secondBestJ);
        break;
    }
    if (!survey) return '';
    var txt = 'Stats of ' + move + ' (' + score.toFixed(3) + '):\n';
    for (var h in survey) {
        if (survey[h] === 0) continue;
        txt += '- ' + h + ': ' + survey[h].toFixed(3) + '\n';
    }
    return txt;
};

<|endoftext|>


<commit_before>function selectCategory(category) {
  $('#languages div').each(function(i, div) {
    div = $(div);
    if (div.hasClass(category)) {
      var code = div.find('code');
      if (!code.hasClass('hljs')) {
        hljs.highlightBlock(code.get(0));
      }
      div.show();
    } else {
      div.hide();
    }
  });
}

function initCategories() {
  var categories = {};
  $('#languages div').each(function(i, div) {
    if (!div.className) {
      div.className = 'other';
    };
    div.className.split(' ').forEach(function(c) {
      categories[c] = (categories[c] || 0) + 1;
    });
  });
  var ul = $('#categories');
  var category_names = Object.keys(categories);
  category_names.sort(function(a, b) {
    if (a === 'common' || b === 'other') {
      return -1;
    } else if (b === 'common' || a === 'other') {
      return 1;
    } else if (a  b) {
      return 1;
    } else {
      return 0;
    }
  });
  category_names.forEach(function(c) {
    ul.append('
' + c + ' (' + categories[c] +')
');
  });
  $('#categories li').click(function(e) {
    $('#categories li').removeClass('current');
    $(this).addClass('current');
    selectCategory($(this).data('category'));
  });
  $('#categories li:first-child').click();
}

function selectStyle(style) {
  $('link[title]').each(function(i, link) {
    link.disabled = (link.title != style);
  });
}

function resizeStyleSwitcher() {
  var ul = $('#styles');
  ul.height($(window).height() - ul.position().top - 20);
  ul.perfectScrollbar('update');
}

function initStyleSwitcher() {
  var ul = $('#styles');
  $('link[title]').each(function(i, link) {
    ul.append('
' + link.title + '
');
  });
  $('#styles li').click(function(e) {
    $('#styles li').removeClass('current');
    $(this).addClass('current');
    selectStyle($(this).text());
  });
  $('#styles li:first-child').click();
  ul.perfectScrollbar();
  $(window).resize(resizeStyleSwitcher);
  resizeStyleSwitcher();
}

$(document).ready(function() {
  initCategories();
  initStyleSwitcher();
});

<commit_msg>'all' category instead of 'other' in demo.<commit_after>function selectCategory(category) {
  $('#languages div').each(function(i, div) {
    div = $(div);
    if (div.hasClass(category)) {
      var code = div.find('code');
      if (!code.hasClass('hljs')) {
        hljs.highlightBlock(code.get(0));
      }
      div.show();
    } else {
      div.hide();
    }
  });
}

function initCategories() {
  var categories = {};
  $('#languages div').each(function(i, div) {
    div.className += ' all';
    div.className.split(' ').filter(Boolean).forEach(function(c) {
      categories[c] = (categories[c] || 0) + 1;
    });
  });
  var ul = $('#categories');
  var category_names = Object.keys(categories);
  category_names.sort(function(a, b) {
    if (a === 'common' || b === 'all') {
      return -1;
    } else if (b === 'common' || a === 'all') {
      return 1;
    } else if (a  b) {
      return 1;
    } else {
      return 0;
    }
  });
  category_names.forEach(function(c) {
    ul.append('
' + c + ' (' + categories[c] +')
');
  });
  $('#categories li').click(function(e) {
    $('#categories li').removeClass('current');
    $(this).addClass('current');
    selectCategory($(this).data('category'));
  });
  $('#categories li:first-child').click();
}

function selectStyle(style) {
  $('link[title]').each(function(i, link) {
    link.disabled = (link.title != style);
  });
}

function resizeStyleSwitcher() {
  var ul = $('#styles');
  ul.height($(window).height() - ul.position().top - 20);
  ul.perfectScrollbar('update');
}

function initStyleSwitcher() {
  var ul = $('#styles');
  $('link[title]').each(function(i, link) {
    ul.append('
' + link.title + '
');
  });
  $('#styles li').click(function(e) {
    $('#styles li').removeClass('current');
    $(this).addClass('current');
    selectStyle($(this).text());
  });
  $('#styles li:first-child').click();
  ul.perfectScrollbar();
  $(window).resize(resizeStyleSwitcher);
  resizeStyleSwitcher();
}

$(document).ready(function() {
  initCategories();
  initStyleSwitcher();
});

<|endoftext|>


<commit_before>import { defaultBorderSettings } from './defaults'

export default function ElementFocuser(doc, borderDrawer) {
	const momentaryBorderTime = 2000

	let borderType = defaultBorderSettings.borderType  // cached for simplicity
	let managingBorders = true  // draw and remove borders by default

	let currentElementInfo = null
	let borderRemovalTimer = null

	//
	// Options handling
	//

	// Take a local copy of the border type option at the start (this means
	// that 'gets' of options don't need to be done asynchronously in the rest
	// of the code).
	browser.storage.sync.get(defaultBorderSettings, function(items) {
		borderType = items['borderType']
	})

	browser.storage.onChanged.addListener(function(changes) {
		if ('borderType' in changes) {
			borderType =
				changes.borderType.newValue ?? defaultBorderSettings.borderType
			borderTypeChange()
		}
	})

	//
	// Public API
	//

	// Set focus on the selected landmark element. It takes an element info
	// object, as returned by the various LandmarksFinder functions.
	//
	// Note: this should only be called if landmarks were found. The check
	//       for this is done in the main content script, as it involves UI
	//       activity, and couples finding and focusing.
	this.focusElement = function(elementInfo) {
		if (managingBorders) this.clear()

		// Ensure that the element is focusable
		const originalTabindex = elementInfo.element.getAttribute('tabindex')
		if (originalTabindex === null || originalTabindex === '0') {
			elementInfo.element.setAttribute('tabindex', '-1')
		}

		elementInfo.element.scrollIntoView()  // always go to the top of it
		elementInfo.element.focus()

		// Add the border and set a timer to remove it (if required by user)
		if (managingBorders && borderType !== 'none') {
			borderDrawer.addBorder(elementInfo)

			if (borderType === 'momentary') {
				clearTimeout(borderRemovalTimer)
				borderRemovalTimer = setTimeout(function() {
					borderDrawer.removeBorderOn(currentElementInfo.element)
				}, momentaryBorderTime)
			}
		}

		// Restore tabindex value
		if (originalTabindex === null) {
			elementInfo.element.removeAttribute('tabindex')
		} else if (originalTabindex === '0') {
			elementInfo.element.setAttribute('tabindex', '0')
		}

		currentElementInfo = elementInfo
	}

	// By default, this object will ask for borders to be drawn and removed
	// according to user preferences (and reflect changes in preferences). If
	// it shouldn't (i.e. because all borders are being shown, and managed by
	// other code) then this can be turned off - though it will still manage
	// element focusing.
	this.manageBorders = function(canManageBorders) {
		managingBorders = canManageBorders
		if (!canManageBorders) {
			clearTimeout(borderRemovalTimer)
		}
	}

	this.isManagingBorders = function() {
		return managingBorders
	}

	this.clear = function() {
		if (currentElementInfo) {
			resetEverything()
		}
	}

	// When the document is changed, the currently-focused element may have
	// been removed, or at least changed size/position.
	// Note: this doesn't call the border drawer to refresh all borders, as
	//       this object is mainly concerned with just the current one, but
	//       after a mutation, any borders that are drawn should be refreshed.
	this.refreshFocusedElement = function() {
		if (currentElementInfo) {
			if (!doc.body.contains(currentElementInfo.element)) {
				resetEverything()
			}
		}
	}

	//
	// Private API
	//

	// Used internally when we know we have a currently selected element
	function resetEverything() {
		clearTimeout(borderRemovalTimer)
		borderDrawer.removeBorderOn(currentElementInfo.element)
		currentElementInfo = null
	}

	// Should a border be added/removed?
	function borderTypeChange() {
		if (currentElementInfo && managingBorders) {
			if (borderType === 'persistent') {
				borderDrawer.addBorder(currentElementInfo)
			} else {
				borderDrawer.removeBorderOn(currentElementInfo.element)
			}
		}
	}
}
<commit_msg>fix: Replace persistent border after un-show-all (#452)<commit_after>import { defaultBorderSettings } from './defaults'

export default function ElementFocuser(doc, borderDrawer) {
	const momentaryBorderTime = 2000

	let borderType = defaultBorderSettings.borderType  // cached for simplicity
	let managingBorders = true  // draw and remove borders by default

	let currentElementInfo = null
	let borderRemovalTimer = null

	//
	// Options handling
	//

	// Take a local copy of the border type option at the start (this means
	// that 'gets' of options don't need to be done asynchronously in the rest
	// of the code).
	browser.storage.sync.get(defaultBorderSettings, function(items) {
		borderType = items['borderType']
	})

	browser.storage.onChanged.addListener(function(changes) {
		if ('borderType' in changes) {
			borderType =
				changes.borderType.newValue ?? defaultBorderSettings.borderType
			borderTypeChange()
		}
	})

	//
	// Public API
	//

	// Set focus on the selected landmark element. It takes an element info
	// object, as returned by the various LandmarksFinder functions.
	//
	// Note: this should only be called if landmarks were found. The check
	//       for this is done in the main content script, as it involves UI
	//       activity, and couples finding and focusing.
	this.focusElement = function(elementInfo) {
		if (managingBorders) this.clear()

		// Ensure that the element is focusable
		const originalTabindex = elementInfo.element.getAttribute('tabindex')
		if (originalTabindex === null || originalTabindex === '0') {
			elementInfo.element.setAttribute('tabindex', '-1')
		}

		elementInfo.element.scrollIntoView()  // always go to the top of it
		elementInfo.element.focus()

		// Add the border and set a timer to remove it (if required by user)
		if (managingBorders && borderType !== 'none') {
			borderDrawer.addBorder(elementInfo)

			if (borderType === 'momentary') {
				clearTimeout(borderRemovalTimer)
				borderRemovalTimer = setTimeout(function() {
					borderDrawer.removeBorderOn(currentElementInfo.element)
				}, momentaryBorderTime)
			}
		}

		// Restore tabindex value
		if (originalTabindex === null) {
			elementInfo.element.removeAttribute('tabindex')
		} else if (originalTabindex === '0') {
			elementInfo.element.setAttribute('tabindex', '0')
		}

		currentElementInfo = elementInfo
	}

	// By default, this object will ask for borders to be drawn and removed
	// according to user preferences (and reflect changes in preferences). If
	// it shouldn't (i.e. because all borders are being shown, and managed by
	// other code) then this can be turned off - though it will still manage
	// element focusing.
	this.manageBorders = function(canManageBorders) {
		managingBorders = canManageBorders
		if (!canManageBorders) {
			clearTimeout(borderRemovalTimer)
		} else if (borderType === 'persistent') {
			// When we stop showing all landmarks at once, ensure the last
			// single one is put back if it was permanent.
			borderDrawer.addBorder(currentElementInfo)
		}
	}

	this.isManagingBorders = function() {
		return managingBorders
	}

	this.clear = function() {
		if (currentElementInfo) {
			resetEverything()
		}
	}

	// When the document is changed, the currently-focused element may have
	// been removed, or at least changed size/position.
	// Note: this doesn't call the border drawer to refresh all borders, as
	//       this object is mainly concerned with just the current one, but
	//       after a mutation, any borders that are drawn should be refreshed.
	this.refreshFocusedElement = function() {
		if (currentElementInfo) {
			if (!doc.body.contains(currentElementInfo.element)) {
				resetEverything()
			}
		}
	}

	//
	// Private API
	//

	// Used internally when we know we have a currently selected element
	function resetEverything() {
		clearTimeout(borderRemovalTimer)
		borderDrawer.removeBorderOn(currentElementInfo.element)
		currentElementInfo = null
	}

	// Should a border be added/removed?
	function borderTypeChange() {
		if (currentElementInfo && managingBorders) {
			if (borderType === 'persistent') {
				borderDrawer.addBorder(currentElementInfo)
			} else {
				borderDrawer.removeBorderOn(currentElementInfo.element)
			}
		}
	}
}
<|endoftext|>


<commit_before>//ckeditor config
<commit_msg>Restore ckeditor/config.js<commit_after>CKEDITOR.editorConfig = function( config )
{

// Define changes to default configuration here. For example:

  /* Filebrowser routes */
  // The location of an external file browser, that should be launched when "Browse Server" button is pressed.
  config.filebrowserBrowseUrl = "/ckeditor/attachment_files";

  // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Flash dialog.
  config.filebrowserFlashBrowseUrl = "/ckeditor/attachment_files";

  // The location of a script that handles file uploads in the Flash dialog.
  config.filebrowserFlashUploadUrl = "/ckeditor/attachment_files";

  // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Link tab of Image dialog.
  config.filebrowserImageBrowseLinkUrl = "/ckeditor/pictures";

  // The location of an external file browser, that should be launched when "Browse Server" button is pressed in the Image dialog.
  config.filebrowserImageBrowseUrl = "/ckeditor/pictures";

  // The location of a script that handles file uploads in the Image dialog.
  config.filebrowserImageUploadUrl = "/ckeditor/pictures";

  // The location of a script that handles file uploads.
  config.filebrowserUploadUrl = "/ckeditor/attachment_files";

  //Enable spellcheck
  config.disableNativeSpellChecker = false;

  config.allowedContent = true;

  // Toolbar groups configuration.
  config.toolbar = [
    { name: 'document', groups: [ 'mode', 'document', 'doctools' ], items: [ 'Source'] },
    { name: 'clipboard', groups: [ 'clipboard', 'undo' ], items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
    // { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ], items: [ 'Find', 'Replace', '-', 'SelectAll', '-', 'Scayt' ] },
    // { name: 'forms', items: [ 'Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField' ] },
    { name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
    { name: 'insert', items: [ 'Image', 'Flash', 'Table', 'HorizontalRule', 'SpecialChar' ] },
    { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ] },
    '/',
    { name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
    { name: 'colors', items: [ 'TextColor', 'BGColor' ] },
    { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] }
  ];

  config.toolbar_mini = [
    { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ] },
    { name: 'styles', items: [ 'Font', 'FontSize' ] },
    { name: 'colors', items: [ 'TextColor', 'BGColor' ] },
    { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] },
    { name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule', 'SpecialChar' ] }
  ];

  config.toolbar_simple = [
     { name: 'paragraph', groups: [ 'list', 'indent', 'blocks', 'align', 'bidi' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock' ] },
    { name: 'styles', items: [ 'Font', 'FontSize' ] },
    { name: 'colors', items: [ 'TextColor', 'BGColor' ] },
    { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] },
  ];

  config.toolbar_module_author = [
    { name: 'clipboard', groups: [ 'clipboard', 'undo' ], items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
    { name: 'styles', items: ['Format'] },
    { name: 'paragraph', groups: [ 'list', 'indent', 'blocks'], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote'] },
    { name: 'insert', items: [ 'Image', 'Table', 'SpecialChar' ] },
    { name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
    { name: 'basicstyles', groups: [ 'basicstyles'], items: [ 'Bold', 'Italic'] },
  ];
  config.toolbar_module_admin = [
    { name: 'document', groups: [ 'mode', 'document', 'doctools' ], items: [ 'Source'] },
    { name: 'clipboard', groups: [ 'clipboard', 'undo' ], items: [ 'Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Undo', 'Redo' ] },
    // { name: 'editing', groups: [ 'find', 'selection', 'spellchecker' ], items: [ 'Find', 'Replace', '-', 'SelectAll', '-', 'Scayt' ] },
   //  { name: 'links', items: [ 'Link', 'Unlink', 'Anchor' ] },
    { name: 'insert', items: [ 'Image', 'Table', 'HorizontalRule', 'SpecialChar' ] },
    { name: 'paragraph', groups: [ 'list', 'indent', 'blocks' ], items: [ 'NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv' ] },
    '/',
    { name: 'styles', items: [ 'Styles', 'Format', 'Font', 'FontSize' ] },
   { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ], items: [ 'Bold', 'Italic', 'Underline', 'Strike', 'Subscript', 'Superscript', '-', 'RemoveFormat' ] }
  ];
};
<|endoftext|>


<commit_before>/*
 * jQuery One Page Nav Plugin
 * http://github.com/davist11/jQuery-One-Page-Nav
 *
 * Copyright (c) 2010 Trevor Davis (http://trevordavis.net)
 * Dual licensed under the MIT and GPL licenses.
 * Uses the same license as jQuery, see:
 * http://jquery.org/license
 *
 * @version 2.0
 *
 * Example usage:
 * $('#nav').onePageNav({
 *   currentClass: 'current',
 *   changeHash: false,
 *   scrollSpeed: 750
 * });
 */

;(function($, window, document, undefined){

	// our plugin constructor
	var OnePageNav = function(elem, options){
		this.elem = elem;
		this.$elem = $(elem);
		this.options = options;
		this.metadata = this.$elem.data('plugin-options');
		this.$nav = this.$elem.find('a');
		this.$win = $(window);
		this.sections = {};
		this.didScroll = false;
		this.$doc = $(document);
		this.docHeight = this.$doc.height();
	};

	// the plugin prototype
	OnePageNav.prototype = {
		defaults: {
			currentClass: 'current',
			changeHash: false,
			easing: 'swing',
			filter: '',
			scrollSpeed: 750,
			scrollOffset: 0,
			scrollThreshold: 0.5,
			begin: false,
			end: false
		},

		init: function() {
			var self = this;

			// Introduce defaults that can be extended either
			// globally or using an object literal.
			self.config = $.extend({}, self.defaults, self.options, self.metadata);

			//Filter any links out of the nav
			if(self.config.filter !== '') {
				self.$nav = self.$nav.filter(self.config.filter);
			}

			//Handle clicks on the nav
			self.$nav.on('click.onePageNav', $.proxy(self.handleClick, self));

			//Get the section positions
			self.getPositions();

			//Handle scroll changes
			self.bindInterval();

			//Update the positions on resize too
			self.$win.on('resize.onePageNav', $.proxy(self.getPositions, self));

			return this;
		},

		adjustNav: function(self, $parent) {
			self.$elem.find('.' + self.config.currentClass).removeClass(self.config.currentClass);
			$parent.addClass(self.config.currentClass);
		},

		bindInterval: function() {
			var self = this;
			var docHeight;

			self.$win.on('scroll.onePageNav', function() {
				self.didScroll = true;
			});

			self.t = setInterval(function() {
				docHeight = self.$doc.height();

				//If it was scrolled
				if(self.didScroll) {
					self.didScroll = false;
					self.scrollChange();
				}

				//If the document height changes
				if(docHeight !== self.docHeight) {
					self.docHeight = docHeight;
					self.getPositions();
				}
			}, 250 );
		},

		getHash: function($link) {
			return $link.attr('href').split("#")[1];
		},

		getPositions: function() {
			var self = this;
			var linkHref;
			var topPos;

			self.$nav.each(function() {
				linkHref = self.getHash($(this));
				topPos = $("#" + linkHref).offset().top;

				self.sections[linkHref] = Math.round(topPos) - self.config.scrollOffset;
			});
		},

		getSection: function(windowPos) {
			var returnValue = null;
			var windowHeight = Math.round(this.$win.height() * this.config.scrollThreshold);

			for(var section in this.sections) {
				if((this.sections[section] - windowHeight) < windowPos) {
					returnValue = section;
				}
			}

			return returnValue;
		},

		handleClick: function(e) {
			var self = this;
			var $link = $(e.currentTarget);
			var $parent = $link.parent();
			var newLoc = '#' + self.getHash($link);

			if(!$parent.hasClass(self.config.currentClass)) {
				//Start callback
				if(self.config.begin) {
					self.config.begin();
				}

				//Change the highlighted nav item
				self.adjustNav(self, $parent);

				//Removing the auto-adjust on scroll
				self.unbindInterval();

				//Scroll to the correct position
				$.scrollTo(newLoc, self.config.scrollSpeed, {
					axis: 'y',
					easing: self.config.easing,
					offset: {
						top: -self.config.scrollOffset
					},
					onAfter: function() {
						//Do we need to change the hash?
						if(self.config.changeHash) {
							window.location.hash = newLoc;
						}

						//Add the auto-adjust on scroll back in
						self.bindInterval();

						//End callback
						if(self.config.end) {
							self.config.end();
						}
					}
				});
			}

			e.preventDefault();
		},

		scrollChange: function() {
			var windowTop = this.$win.scrollTop();
			var position = this.getSection(windowTop);
			var $parent;

			//If the position is set
			if(position !== null) {
				$parent = this.$elem.find('a[href$="#' + position + '"]').parent();

				//Change the highlighted nav item
				this.adjustNav(this, $parent);
			}
		},

		unbindInterval: function() {
			clearInterval(this.t);
			this.$win.unbind('scroll.onePageNav');
		}
	};

	OnePageNav.defaults = OnePageNav.prototype.defaults;

	$.fn.onePageNav = function(options) {
		return this.each(function() {
			new OnePageNav(this, options).init();
		});
	};

})( jQuery, window , document );
<commit_msg>updating jquery nav plugin to add anchor url to URL bar after clicking<commit_after>/*
 * jQuery One Page Nav Plugin
 * http://github.com/davist11/jQuery-One-Page-Nav
 *
 * Copyright (c) 2010 Trevor Davis (http://trevordavis.net)
 * Dual licensed under the MIT and GPL licenses.
 * Uses the same license as jQuery, see:
 * http://jquery.org/license
 *
 * @version 2.0
 *
 * Example usage:
 * $('#nav').onePageNav({
 *   currentClass: 'current',
 *   changeHash: false,
 *   scrollSpeed: 750
 * });
 */

;(function($, window, document, undefined){

	// our plugin constructor
	var OnePageNav = function(elem, options){
		this.elem = elem;
		this.$elem = $(elem);
		this.options = options;
		this.metadata = this.$elem.data('plugin-options');
		this.$nav = this.$elem.find('a');
		this.$win = $(window);
		this.sections = {};
		this.didScroll = false;
		this.$doc = $(document);
		this.docHeight = this.$doc.height();
	};

	// the plugin prototype
	OnePageNav.prototype = {
		defaults: {
			currentClass: 'current',
			changeHash: false,
			easing: 'swing',
			filter: '',
			scrollSpeed: 750,
			scrollOffset: 0,
			scrollThreshold: 0.5,
			begin: false,
			end: false
		},

		init: function() {
			var self = this;

			// Introduce defaults that can be extended either
			// globally or using an object literal.
			self.config = $.extend({}, self.defaults, self.options, self.metadata);

			//Filter any links out of the nav
			if(self.config.filter !== '') {
				self.$nav = self.$nav.filter(self.config.filter);
			}

			//Handle clicks on the nav
			self.$nav.on('click.onePageNav', $.proxy(self.handleClick, self));

			//Get the section positions
			self.getPositions();

			//Handle scroll changes
			self.bindInterval();

			//Update the positions on resize too
			self.$win.on('resize.onePageNav', $.proxy(self.getPositions, self));

			return this;
		},

		adjustNav: function(self, $parent) {
			self.$elem.find('.' + self.config.currentClass).removeClass(self.config.currentClass);
			$parent.addClass(self.config.currentClass);
		},

		bindInterval: function() {
			var self = this;
			var docHeight;

			self.$win.on('scroll.onePageNav', function() {
				self.didScroll = true;
			});

			self.t = setInterval(function() {
				docHeight = self.$doc.height();

				//If it was scrolled
				if(self.didScroll) {
					self.didScroll = false;
					self.scrollChange();
				}

				//If the document height changes
				if(docHeight !== self.docHeight) {
					self.docHeight = docHeight;
					self.getPositions();
				}
			}, 250 );
		},

		getHash: function($link) {
			return $link.attr('href').split("#")[1];
		},

		getPositions: function() {
			var self = this;
			var linkHref;
			var topPos;

			self.$nav.each(function() {
				linkHref = self.getHash($(this));
				topPos = $("#" + linkHref).offset().top;

				self.sections[linkHref] = Math.round(topPos) - self.config.scrollOffset;
			});
		},

		getSection: function(windowPos) {
			var returnValue = null;
			var windowHeight = Math.round(this.$win.height() * this.config.scrollThreshold);

			for(var section in this.sections) {
				if((this.sections[section] - windowHeight) < windowPos) {
					returnValue = section;
				}
			}

			return returnValue;
		},

		handleClick: function(e) {
			var self = this;
			var $link = $(e.currentTarget);
			var $parent = $link.parent();
			var newLoc = '#' + self.getHash($link);

			if(!$parent.hasClass(self.config.currentClass)) {
				//Start callback
				if(self.config.begin) {
					self.config.begin();
				}

				//Change the highlighted nav item
				self.adjustNav(self, $parent);

				//Removing the auto-adjust on scroll
				self.unbindInterval();

				//Scroll to the correct position
				$.scrollTo(newLoc, self.config.scrollSpeed, {
					axis: 'y',
					easing: self.config.easing,
					offset: {
						top: -self.config.scrollOffset
					},
					onAfter: function() {
						//Do we need to change the hash?
						if(self.config.changeHash) {
							window.location.hash = newLoc;
						}

						//Add the auto-adjust on scroll back in
						self.bindInterval();

						//End callback
						if(self.config.end) {
							self.config.end();
						}
					}
				});
			}

		},

		scrollChange: function() {
			var windowTop = this.$win.scrollTop();
			var position = this.getSection(windowTop);
			var $parent;

			//If the position is set
			if(position !== null) {
				$parent = this.$elem.find('a[href$="#' + position + '"]').parent();

				//Change the highlighted nav item
				this.adjustNav(this, $parent);
			}
		},

		unbindInterval: function() {
			clearInterval(this.t);
			this.$win.unbind('scroll.onePageNav');
		}
	};

	OnePageNav.defaults = OnePageNav.prototype.defaults;

	$.fn.onePageNav = function(options) {
		return this.each(function() {
			new OnePageNav(this, options).init();
		});
	};

})( jQuery, window , document );
<|endoftext|>