<commit_before>const http = require('http');
const qs = require('querystring');
const {start, stop} = require('../index');
const {getStaticOptions} = require('../utils');

describe('Optimizer server tests', () => {
  beforeEach(() => start());
  afterEach(() => stop());

  const VALID_REQUEST = `<html></html>`;

  it('should reject non POST requests', () => {
    const req = request({method: 'GET', body: VALID_REQUEST});
    return expect(req).rejects.toMatchObject({
      statusCode: 400,
      body: expect.stringMatching(/This server only accepts POST requests/),
    });
  });

  it('should reject requests not to the root', () => {
    const req = request({path: '/api', body: VALID_REQUEST});
    return expect(req).rejects.toMatchObject({
      statusCode: 400,
      body: expect.stringMatching(/This server only accepts requests made to/),
    });
  });

  it('should reject requests without a body', () => {
    const req = request();
    return expect(req).rejects.toMatchObject({
      statusCode: 400,
      body: expect.stringMatching(/This server requires HTML in the request body/),
    });
  });

  it('should run the provided HTML throught the optimizer', () => {
    const req = request({body: VALID_REQUEST, query: {canonical: 'http://example.com'}});
    return expect(req).resolves.toMatchObject({
      body: expect.stringMatching('<!doctype html>'),
    });
  });
});

describe('Configuration tests', () => {
  it('should ignore all env vars without AMP_OPTIMIZER prefix', () => {
    const options = getStaticOptions({TEST_VALUE: 'TEST_VALUE'});
    expect(options).toEqual({});
  });

  it('should camelCase and pass along intended configuration', () => {
    const options = getStaticOptions({
      AMP_OPTIMIZER_TEST_VALUE: 'TEST_VALUE',
    });
    expect(options).toEqual({testValue: 'TEST_VALUE'});
  });
});

async function request({path = '/', method = 'POST', body, query} = {}) {
  if (query) {
    path += `?${qs.stringify(query)}`;
  }
  return new Promise((resolve, reject) => {
    const options = {
      headers: {'Content-Type': 'text/html'},
      hostname: 'localhost',
      method,
      path,
      port: 3000,
    };
    const req = http.request(options, (res) => {
      let data = [];
      res.on('data', (chunk) => {
        data.push(chunk);
      });
      res.on('end', () => {
        const body = data.join('');
        if (res.statusCode === 400 || res.statusCode === 500) {
          reject({statusCode: res.statusCode, body});
        }
        resolve({statusCode: res.statusCode, body});
      });
      res.on('error', (err) => reject(err));
    });
    req.on('error', (err) => reject(err));
    req.end(body);
  });
}
<commit_msg>rena,e<commit_after>const http = require('http');
const qs = require('querystring');
const {start, stop} = require('../index');
const {getStaticOptions} = require('../utils');

describe('server tests', () => {
  beforeEach(() => start());
  afterEach(() => stop());

  const VALID_REQUEST = `<html></html>`;

  it('should reject non POST requests', () => {
    const req = request({method: 'GET', body: VALID_REQUEST});
    return expect(req).rejects.toMatchObject({
      statusCode: 400,
      body: expect.stringMatching(/This server only accepts POST requests/),
    });
  });

  it('should reject requests not to the root', () => {
    const req = request({path: '/api', body: VALID_REQUEST});
    return expect(req).rejects.toMatchObject({
      statusCode: 400,
      body: expect.stringMatching(/This server only accepts requests made to/),
    });
  });

  it('should reject requests without a body', () => {
    const req = request();
    return expect(req).rejects.toMatchObject({
      statusCode: 400,
      body: expect.stringMatching(/This server requires HTML in the request body/),
    });
  });

  it('should run the provided HTML throught the optimizer', () => {
    const req = request({body: VALID_REQUEST, query: {canonical: 'http://example.com'}});
    return expect(req).resolves.toMatchObject({
      body: expect.stringMatching('<!doctype html>'),
    });
  });
});

describe('Configuration tests', () => {
  it('should ignore all env vars without AMP_OPTIMIZER prefix', () => {
    const options = getStaticOptions({TEST_VALUE: 'TEST_VALUE'});
    expect(options).toEqual({});
  });

  it('should camelCase and pass along intended configuration', () => {
    const options = getStaticOptions({
      AMP_OPTIMIZER_TEST_VALUE: 'TEST_VALUE',
    });
    expect(options).toEqual({testValue: 'TEST_VALUE'});
  });
});

async function request({path = '/', method = 'POST', body, query} = {}) {
  if (query) {
    path += `?${qs.stringify(query)}`;
  }
  return new Promise((resolve, reject) => {
    const options = {
      headers: {'Content-Type': 'text/html'},
      hostname: 'localhost',
      method,
      path,
      port: 3000,
    };
    const req = http.request(options, (res) => {
      let data = [];
      res.on('data', (chunk) => {
        data.push(chunk);
      });
      res.on('end', () => {
        const body = data.join('');
        if (res.statusCode === 400 || res.statusCode === 500) {
          reject({statusCode: res.statusCode, body});
        }
        resolve({statusCode: res.statusCode, body});
      });
      res.on('error', (err) => reject(err));
    });
    req.on('error', (err) => reject(err));
    req.end(body);
  });
}
<|endoftext|>


<commit_before>'use strict';

const { isEmpty, getOr } = require('lodash/fp');
const { ApolloServer } = require('apollo-server-koa');
const {
  ApolloServerPluginLandingPageLocalDefault,
  ApolloServerPluginLandingPageProductionDefault,
} = require('apollo-server-core');
const depthLimit = require('graphql-depth-limit');
const { graphqlUploadKoa } = require('graphql-upload');

module.exports = async strapi => {
  // Generate the GraphQL schema for the content API
  const schema = strapi
    .plugin('graphql')
    .service('schema')
    .generateContentAPISchema();

  if (isEmpty(schema)) {
    strapi.log.warn('The GraphQL schema has not been generated because it is empty');

    return;
  }

  const config = getOr({}, 'config', strapi.plugins.graphql);
  const apolloServerConfig = getOr({}, 'apolloServer', config);

  const serverParams = {
    // Schema
    schema,

    // Initialize loaders for this request.
    context: ({ ctx }) => {
      // TODO: set loaders in the context not globally
      strapi
        .plugin('graphql')
        .service('old')
        ['data-loaders'].initializeLoader();

      return {
        context: ctx,
      };
    },

    // Format & validation
    formatError: err => {
      const formatError = getOr(null, 'formatError', config);

      return typeof formatError === 'function' ? formatError(err) : err;
    },
    validationRules: [depthLimit(config.depthLimit)],

    // Misc
    cors: false,
    uploads: false,
    bodyParserConfig: true,

    plugins: [
      // Specify which GraphQL landing page we want for the different env.
      process.env.NODE_ENV !== 'production'
        ? ApolloServerPluginLandingPageLocalDefault({ footer: false })
        : ApolloServerPluginLandingPageProductionDefault({ footer: false }),
    ],
    ...apolloServerConfig,
  };

  // Create a new Apollo server
  const server = new ApolloServer(serverParams);

  // Register the upload middleware
  useUploadMiddleware(strapi, config);

  try {
    // Since Apollo-Server v3, server.start() must be called before using server.applyMiddleware()
    await server.start();
  } catch (e) {
    strapi.log.error('Failed to start the Apollo server', e.message);
  }

  // Link the Apollo server & the Strapi app
  server.applyMiddleware({
    app: strapi.app,
    path: config.endpoint,
  });

  // Register destroy behavior
  strapi.plugins.graphql.destroy = async () => {
    await server.stop();
  };
};

/**
 * Register the upload middleware powered by graphql-upload in Strapi
 * @param {object} strapi
 * @param {object} config
 */
const useUploadMiddleware = (strapi, config) => {
  const uploadMiddleware = graphqlUploadKoa();

  strapi.app.use((ctx, next) => {
    if (ctx.path === config.endpoint) {
      return uploadMiddleware(ctx, next);
    }

    return next();
  });
};
<commit_msg>Cleanup<commit_after>'use strict';

const { isEmpty, getOr } = require('lodash/fp');
const { ApolloServer } = require('apollo-server-koa');
const {
  ApolloServerPluginLandingPageLocalDefault,
  ApolloServerPluginLandingPageProductionDefault,
} = require('apollo-server-core');
const depthLimit = require('graphql-depth-limit');
const { graphqlUploadKoa } = require('graphql-upload');

module.exports = async strapi => {
  // Generate the GraphQL schema for the content API
  const schema = strapi
    .plugin('graphql')
    .service('schema')
    .generateContentAPISchema();

  if (isEmpty(schema)) {
    strapi.log.warn('The GraphQL schema has not been generated because it is empty');

    return;
  }

  const config = getOr({}, 'config', strapi.plugin('graphql'));
  const apolloServerConfig = getOr({}, 'apolloServer', config);

  const serverParams = {
    // Schema
    schema,

    // Initialize loaders for this request.
    context: ({ ctx }) => {
      // TODO: set loaders in the context not globally
      strapi
        .plugin('graphql')
        .service('old')
        ['data-loaders'].initializeLoader();

      return {
        context: ctx,
      };
    },

    // Format & validation
    formatError: err => {
      const formatError = getOr(null, 'formatError', config);

      return typeof formatError === 'function' ? formatError(err) : err;
    },
    validationRules: [depthLimit(config.depthLimit)],

    // Misc
    cors: false,
    uploads: false,
    bodyParserConfig: true,

    plugins: [
      // Specify which GraphQL landing page we want for the different env.
      process.env.NODE_ENV !== 'production'
        ? ApolloServerPluginLandingPageLocalDefault({ footer: false })
        : ApolloServerPluginLandingPageProductionDefault({ footer: false }),
    ],
    ...apolloServerConfig,
  };

  // Create a new Apollo server
  const server = new ApolloServer(serverParams);

  // Register the upload middleware
  useUploadMiddleware(strapi, config);

  try {
    // Since Apollo-Server v3, server.start() must be called before using server.applyMiddleware()
    await server.start();
  } catch (e) {
    strapi.log.error('Failed to start the Apollo server', e.message);
  }

  // Link the Apollo server & the Strapi app
  server.applyMiddleware({
    app: strapi.app,
    path: config.endpoint,
  });

  // Register destroy behavior
  // todo[v4]: expose this in strapi-server.js (destroy attribute)
  strapi.plugins.graphql.destroy = async () => {
    await server.stop();
  };
};

/**
 * Register the upload middleware powered by graphql-upload in Strapi
 * @param {object} strapi
 * @param {object} config
 */
const useUploadMiddleware = (strapi, config) => {
  const uploadMiddleware = graphqlUploadKoa();

  strapi.app.use((ctx, next) => {
    if (ctx.path === config.endpoint) {
      return uploadMiddleware(ctx, next);
    }

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


<commit_before>const scaledPoint = (x, y, scale) => (
  new window.google.maps.Point(Math.round(x * scale), Math.round(y * scale))
)

const scaledSize = (x, y, scale) => (
  new window.google.maps.Size(Math.round(x * scale), Math.round(y * scale))
)

export const greyDotIcon = (scale = 1) => ({
  url: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='18px' height='19px' viewBox='0 0 18 19' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Epin-red%3C/title%3E%3Cg id='v2-final' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='map-pins' transform='translate%28-57.000000, -69.000000%29' fill='grey' stroke='%23FFFFFF' stroke-width='1.2'%3E%3Cg id='pin/sponsored/blue' transform='translate%2857.000000, 69.709999%29'%3E%3Cg id='pin-blue' transform='translate%280.820000, 0.820000%29'%3E%3Cg id='Group-21'%3E%3Ccircle id='pin-red' cx='8' cy='8' r='8'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E",
  anchor: scaledPoint(8, 8, scale),
  scaledSize: scaledSize(16, 16, scale),
})

export const redDotIcon = (scale = 1) => ({
  url: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='18px' height='19px' viewBox='0 0 18 19' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Epin-red%3C/title%3E%3Cg id='v2-final' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='map-pins' transform='translate%28-57.000000, -69.000000%29' fill='%23D32526' stroke='%23FFFFFF' stroke-width='1.2'%3E%3Cg id='pin/sponsored/blue' transform='translate%2857.000000, 69.709999%29'%3E%3Cg id='pin-blue' transform='translate%280.820000, 0.820000%29'%3E%3Cg id='Group-21'%3E%3Ccircle id='pin-red' cx='8' cy='8' r='8'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E",
  anchor: scaledPoint(8, 8, scale),
  scaledSize: scaledSize(16, 16, scale),
})

export const blackDotIcon = (scale = 1) => ({
  url: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='22px' height='22px' viewBox='0 0 22 22' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Epin-black%3C/title%3E%3Cdefs%3E%3Ccircle id='path-1' cx='8' cy='8' r='8'%3E%3C/circle%3E%3Cfilter x='-25.6%25' y='-19.4%25' width='151.3%25' height='151.3%25' filterUnits='objectBoundingBox' id='filter-2'%3E%3CfeMorphology radius='0.6' operator='dilate' in='SourceAlpha' result='shadowSpreadOuter1'%3E%3C/feMorphology%3E%3CfeOffset dx='0' dy='1' in='shadowSpreadOuter1' result='shadowOffsetOuter1'%3E%3C/feOffset%3E%3CfeGaussianBlur stdDeviation='1' in='shadowOffsetOuter1' result='shadowBlurOuter1'%3E%3C/feGaussianBlur%3E%3CfeComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'%3E%3C/feComposite%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.45 0' type='matrix' in='shadowBlurOuter1'%3E%3C/feColorMatrix%3E%3C/filter%3E%3C/defs%3E%3Cg id='v2-final' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='map-pins' transform='translate%28-55.000000, -180.000000%29'%3E%3Cg id='Group' transform='translate%2850.000000, 138.000000%29'%3E%3Cg id='pin-black' transform='translate%288.000000, 44.000000%29'%3E%3Cg id='Group-21'%3E%3Cg id='Combined-Shape-Copy-42'%3E%3Cuse fill='black' fill-opacity='1' filter='url%28%23filter-2%29' xlink:href='%23path-1'%3E%3C/use%3E%3Cuse stroke='%23FFFFFF' stroke-width='1.2' fill='%23000000' fill-rule='evenodd' xlink:href='%23path-1'%3E%3C/use%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E",
  anchor: scaledPoint(9, 8, scale),
  scaledSize: scaledSize(18, 18, scale),
})

export const blackDotIconWithBalloon = (scale = 1) => ({
  url: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='38' height='65' viewBox='0 0 38 65'%3E%3Cdefs%3E%3Cpath id='b' d='M12.598 31.638C5.396 30.078 0 23.669 0 16 0 7.163 7.163 0 16 0s16 7.163 16 16c0 7.248-4.82 13.37-11.43 15.338l-4.054 8.92-3.918-8.62z'/%3E%3Cfilter id='a' width='137.5%25' height='129.8%25' x='-18.8%25' y='-12.4%25' filterUnits='objectBoundingBox'%3E%3CfeMorphology in='SourceAlpha' operator='dilate' radius='1' result='shadowSpreadOuter1'/%3E%3CfeOffset dy='1' in='shadowSpreadOuter1' result='shadowOffsetOuter1'/%3E%3CfeGaussianBlur in='shadowOffsetOuter1' result='shadowBlurOuter1' stdDeviation='1.5'/%3E%3CfeComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'/%3E%3CfeColorMatrix in='shadowBlurOuter1' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.45 0'/%3E%3C/filter%3E%3Ccircle id='d' cx='8' cy='8' r='8'/%3E%3Cfilter id='c' width='151.3%25' height='151.3%25' x='-25.6%25' y='-19.4%25' filterUnits='objectBoundingBox'%3E%3CfeMorphology in='SourceAlpha' operator='dilate' radius='.6' result='shadowSpreadOuter1'/%3E%3CfeOffset dy='1' in='shadowSpreadOuter1' result='shadowOffsetOuter1'/%3E%3CfeGaussianBlur in='shadowOffsetOuter1' result='shadowBlurOuter1' stdDeviation='1'/%3E%3CfeComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'/%3E%3CfeColorMatrix in='shadowBlurOuter1' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.45 0'/%3E%3C/filter%3E%3C/defs%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg transform='translate%283 2%29'%3E%3Cuse fill='%23000' filter='url%28%23a%29' xlink:href='%23b'/%3E%3Cuse fill='%23D32526' stroke='%23FFF' stroke-width='2' xlink:href='%23b'/%3E%3C/g%3E%3Cg fill='%23000' transform='translate%2811 46%29'%3E%3Cuse filter='url%28%23c%29' xlink:href='%23d'/%3E%3Cuse stroke='%23FFF' stroke-width='1.2' xlink:href='%23d'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A",
  anchor: scaledPoint(16, 45, scale),
  scaledSize: scaledSize(32, 54, scale),
})
<commit_msg>feat(markerIcons): update the color of map pins to new brand red<commit_after>const scaledPoint = (x, y, scale) => (
  new window.google.maps.Point(Math.round(x * scale), Math.round(y * scale))
)

const scaledSize = (x, y, scale) => (
  new window.google.maps.Size(Math.round(x * scale), Math.round(y * scale))
)

export const greyDotIcon = (scale = 1) => ({
  url: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='18px' height='19px' viewBox='0 0 18 19' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Epin-red%3C/title%3E%3Cg id='v2-final' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='map-pins' transform='translate%28-57.000000, -69.000000%29' fill='grey' stroke='%23FFFFFF' stroke-width='1.2'%3E%3Cg id='pin/sponsored/blue' transform='translate%2857.000000, 69.709999%29'%3E%3Cg id='pin-blue' transform='translate%280.820000, 0.820000%29'%3E%3Cg id='Group-21'%3E%3Ccircle id='pin-red' cx='8' cy='8' r='8'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E",
  anchor: scaledPoint(8, 8, scale),
  scaledSize: scaledSize(16, 16, scale),
})

export const redDotIcon = (scale = 1) => ({
  url: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='18px' height='19px' viewBox='0 0 18 19' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Epin-red%3C/title%3E%3Cg id='v2-final' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='map-pins' transform='translate%28-57.000000, -69.000000%29' fill='%23ff132f' stroke='%23FFFFFF' stroke-width='1.2'%3E%3Cg id='pin/sponsored/blue' transform='translate%2857.000000, 69.709999%29'%3E%3Cg id='pin-blue' transform='translate%280.820000, 0.820000%29'%3E%3Cg id='Group-21'%3E%3Ccircle id='pin-red' cx='8' cy='8' r='8'%3E%3C/circle%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E",
  anchor: scaledPoint(8, 8, scale),
  scaledSize: scaledSize(16, 16, scale),
})

export const blackDotIcon = (scale = 1) => ({
  url: "data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='22px' height='22px' viewBox='0 0 22 22' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Ctitle%3Epin-black%3C/title%3E%3Cdefs%3E%3Ccircle id='path-1' cx='8' cy='8' r='8'%3E%3C/circle%3E%3Cfilter x='-25.6%25' y='-19.4%25' width='151.3%25' height='151.3%25' filterUnits='objectBoundingBox' id='filter-2'%3E%3CfeMorphology radius='0.6' operator='dilate' in='SourceAlpha' result='shadowSpreadOuter1'%3E%3C/feMorphology%3E%3CfeOffset dx='0' dy='1' in='shadowSpreadOuter1' result='shadowOffsetOuter1'%3E%3C/feOffset%3E%3CfeGaussianBlur stdDeviation='1' in='shadowOffsetOuter1' result='shadowBlurOuter1'%3E%3C/feGaussianBlur%3E%3CfeComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'%3E%3C/feComposite%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.45 0' type='matrix' in='shadowBlurOuter1'%3E%3C/feColorMatrix%3E%3C/filter%3E%3C/defs%3E%3Cg id='v2-final' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='map-pins' transform='translate%28-55.000000, -180.000000%29'%3E%3Cg id='Group' transform='translate%2850.000000, 138.000000%29'%3E%3Cg id='pin-black' transform='translate%288.000000, 44.000000%29'%3E%3Cg id='Group-21'%3E%3Cg id='Combined-Shape-Copy-42'%3E%3Cuse fill='black' fill-opacity='1' filter='url%28%23filter-2%29' xlink:href='%23path-1'%3E%3C/use%3E%3Cuse stroke='%23FFFFFF' stroke-width='1.2' fill='%23000000' fill-rule='evenodd' xlink:href='%23path-1'%3E%3C/use%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E",
  anchor: scaledPoint(9, 8, scale),
  scaledSize: scaledSize(18, 18, scale),
})

export const blackDotIconWithBalloon = (scale = 1) => ({
  url: "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='38' height='65' viewBox='0 0 38 65'%3E%3Cdefs%3E%3Cpath id='b' d='M12.598 31.638C5.396 30.078 0 23.669 0 16 0 7.163 7.163 0 16 0s16 7.163 16 16c0 7.248-4.82 13.37-11.43 15.338l-4.054 8.92-3.918-8.62z'/%3E%3Cfilter id='a' width='137.5%25' height='129.8%25' x='-18.8%25' y='-12.4%25' filterUnits='objectBoundingBox'%3E%3CfeMorphology in='SourceAlpha' operator='dilate' radius='1' result='shadowSpreadOuter1'/%3E%3CfeOffset dy='1' in='shadowSpreadOuter1' result='shadowOffsetOuter1'/%3E%3CfeGaussianBlur in='shadowOffsetOuter1' result='shadowBlurOuter1' stdDeviation='1.5'/%3E%3CfeComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'/%3E%3CfeColorMatrix in='shadowBlurOuter1' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.45 0'/%3E%3C/filter%3E%3Ccircle id='d' cx='8' cy='8' r='8'/%3E%3Cfilter id='c' width='151.3%25' height='151.3%25' x='-25.6%25' y='-19.4%25' filterUnits='objectBoundingBox'%3E%3CfeMorphology in='SourceAlpha' operator='dilate' radius='.6' result='shadowSpreadOuter1'/%3E%3CfeOffset dy='1' in='shadowSpreadOuter1' result='shadowOffsetOuter1'/%3E%3CfeGaussianBlur in='shadowOffsetOuter1' result='shadowBlurOuter1' stdDeviation='1'/%3E%3CfeComposite in='shadowBlurOuter1' in2='SourceAlpha' operator='out' result='shadowBlurOuter1'/%3E%3CfeColorMatrix in='shadowBlurOuter1' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.45 0'/%3E%3C/filter%3E%3C/defs%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg transform='translate%283 2%29'%3E%3Cuse fill='%23000' filter='url%28%23a%29' xlink:href='%23b'/%3E%3Cuse fill='%23ff132f' stroke='%23FFF' stroke-width='2' xlink:href='%23b'/%3E%3C/g%3E%3Cg fill='%23000' transform='translate%2811 46%29'%3E%3Cuse filter='url%28%23c%29' xlink:href='%23d'/%3E%3Cuse stroke='%23FFF' stroke-width='1.2' xlink:href='%23d'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A",
  anchor: scaledPoint(16, 45, scale),
  scaledSize: scaledSize(32, 54, scale),
})
<|endoftext|>


<commit_before>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor()
    {
        console.log("Init EAL");
        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    /**
     * @name init
     * @desc Gets extension settings and applies DOM manipulation
     */
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            // Needs to happen after user settings have been collected
            // and in the context of init();
            this.setSearchAndReplaceSettings();
            this.setWarningSettings();
            this.manipulateDOM();
        }.bind(this), 10);
    }

    /**
     * @name Set Search And Replace Settings
     * @desc
     */
    setSearchAndReplaceSettings()
    {
        // Check user is on their favourite block explorer, on fail target blank.
        this.target = (this.isBlockchainExplorerSite() ? '_self' : '_blank');

        // Register RegEx Patterns
        this.regExPatterns = [
            // Ethereum Address Regex
            /(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(\s|$)/gi,

            // ENS Address Regex
            /([a-z0-9][a-z0-9-\.]+[a-z0-9](?:\.eth))(\s|$)/gi
        ];

        // Register RegEx Matching Patterns
        this.matchPatterns = [
            // Ethereum Match Pattern
            /((?:0x)[0-9a-fA-F]{40})/gi,

            // ENS Match Pattern
            this.regExPatterns[1]
        ];

        // Register Replace Patterns
        this.replacePatterns = [
            // Ethereum Address Replace
            '$1$2$3',

            // ENS Address Replace
            '$1$2'
        ];
    }

    /**
     * @name Set Warning Settings
     * @desc
     */
    setWarningSettings()
    {
        // The block explorers that can handle ENS addresses
        this.ENSCompatiableExplorers = [
            "https://etherscan.io/address",
            "https://etherchain.org/account"
        ];

        // Does the user's favorite explorer support an ENS address
        for(var i=0; i<this.ENSCompatiableExplorers.length; i++){
            if(this.strBlockchainExplorer == this.ENSCompatiableExplorers[i]){
                this.ENSCompatiable = true;
                break;
            }
            this.ENSCompatiable = false;
        }

        // On failure give the user a warning.
        if(!this.ENSCompatiable){
            this.replacePatterns[1] = '$1';
        }
    }

    manipulateDOM()
    {
        if(true || this.intSettingsCount === this.intSettingsTotalCount) {
            if(this.blBlacklistDomains) {
                this.blacklistedDomainCheck();
            }
            this.convertAddressToLink();
        }
    }

    /**
     * @name Convert Address To Link
     * @desc Finds Ethereum addresses and converts to a link to a block explorer
     */
    convertAddressToLink()
    {
        var arrWhitelistedTags = ["code", "span", "p", "td", "li", "em", "i", "b", "strong", "small"];

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {
                this.convertAddresses(objNodes[x]);
            }
        }

        this.tidyUpSlots();

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    /**
     * @name Convert Addresses
     * @desc Takes a Node and checks if any of its children are textNodes. On success replace textNode with slot node
     * @desc slot node contains regex replaced content; see generateReplacementContent()
     * @param {Node} objNode
     */
    convertAddresses(objNode)
    {
        // Some nodes have non-textNode children
        // we need to ensure regex is applied only to text otherwise we will mess the html up
        for(var i=0; i < objNode.childNodes.length; i++){
            // Only check textNodes to prevent applying RegEx against element attributes
            if(objNode.childNodes[i].nodeType == 3){ // nodeType 3 = a text node

                var child = objNode.childNodes[i];
                var childContent = child.textContent;

                // Only start replacing stuff if the we get a RegEx match.
                if(this.isPatternMatched(childContent)) {
                    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
                    var replacement = document.createElement('slot');
                    replacement.setAttribute('class', 'ext-etheraddresslookup-temporary');
                    replacement.innerHTML = this.generateReplacementContent(childContent);
                    objNode.replaceChild(replacement, child);
                }
            }
        }
    }

    /**
     * @name Generate Replacement Content
     * @desc Takes string and replaces any regex pattern matches with the associated replace patterns
     * @param {string} content
     * @returns {string}
     */
    generateReplacementContent(content)
    {
        for(var i=0; i < this.regExPatterns.length; i++){
            content = content.replace(this.regExPatterns[i], this.replacePatterns[i]);
        }
        return content;
    }

    /**
     * @name Is Pattern Matched
     * @desc Checks content matches any of the object's matchPatterns
     * @param {string} content
     * @returns {boolean}
     */
    isPatternMatched(content)
    {
        for(var i=0; i < this.matchPatterns.length; i++){
            if(this.matchPatterns[i].exec(content) !== null){
                return true;
            }
        }
        return false;
    }

    /**
     * @name Is Blockchain Explorer Site
     * @desc Check if the current website is the user's selected block explorer
     * @returns {boolean}
     */
    isBlockchainExplorerSite()
    {
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;
        return (objBlockchainExplorer.hostname === window.location.hostname);
    }

    /**
     * @name Tidy Slots
     * @desc Searches document for slots adds the slot's child nodes to its parent then removes the slot
     */
    tidyUpSlots()
    {
        var slots = document.querySelectorAll("slot.ext-etheraddresslookup-temporary");
        for(var i=0; i < slots.length; i++){
            while(slots[i].childNodes.length > 0){
                slots[i].parentNode.appendChild(slots[i].firstChild);
            }
            slots[i].parentNode.removeChild(slots[i]);
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup();
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<commit_msg>Fixed Node positioning bug<commit_after>let objBrowser = chrome ? chrome : browser;

class EtherAddressLookup {

    constructor()
    {
        console.log("Init EAL");
        this.setDefaultExtensionSettings();
        this.init();
    }

    setDefaultExtensionSettings()
    {
        this.blHighlight = false;
        this.strBlockchainExplorer = "https://etherscan.io/address";

        this.intSettingsCount = 0;
        this.intSettingsTotalCount = 2;
    }

    /**
     * @name init
     * @desc Gets extension settings and applies DOM manipulation
     */
    init()
    {
        let objBrowser = chrome ? chrome : browser;
        //Get the highlight option for the user
        objBrowser.runtime.sendMessage({func: "highlight_option"}, function(objResponse) {
            if(objResponse && objResponse.hasOwnProperty("resp")) {
                this.blHighlight = (objResponse.resp == 1);
            }
            ++this.intSettingsCount;
        }.bind(this));

        //Get the blockchain explorer for the user
        objBrowser.runtime.sendMessage({func: "blockchain_explorer"}, function(objResponse) {
            this.strBlockchainExplorer = objResponse.resp;
            ++this.intSettingsCount;
        }.bind(this));

        //Update the DOM once all settings have been received...
        setTimeout(function() {
            // Needs to happen after user settings have been collected
            // and in the context of init();
            this.setSearchAndReplaceSettings();
            this.setWarningSettings();
            this.manipulateDOM();
        }.bind(this), 10);
    }

    /**
     * @name Set Search And Replace Settings
     * @desc
     */
    setSearchAndReplaceSettings()
    {
        // Check user is on their favourite block explorer, on fail target blank.
        this.target = (this.isBlockchainExplorerSite() ? '_self' : '_blank');

        // Register RegEx Patterns
        this.regExPatterns = [
            // Ethereum Address Regex
            /(^|\s|:|-)((?:0x)[0-9a-fA-F]{40})(\s|$)/gi,

            // ENS Address Regex
            /([a-z0-9][a-z0-9-\.]+[a-z0-9](?:\.eth))(\s|$)/gi
        ];

        // Register RegEx Matching Patterns
        this.matchPatterns = [
            // Ethereum Match Pattern
            /((?:0x)[0-9a-fA-F]{40})/gi,

            // ENS Match Pattern
            this.regExPatterns[1]
        ];

        // Register Replace Patterns
        this.replacePatterns = [
            // Ethereum Address Replace
            '$1$2$3',

            // ENS Address Replace
            '$1$2'
        ];
    }

    /**
     * @name Set Warning Settings
     * @desc
     */
    setWarningSettings()
    {
        // The block explorers that can handle ENS addresses
        this.ENSCompatiableExplorers = [
            "https://etherscan.io/address",
            "https://etherchain.org/account"
        ];

        // Does the user's favorite explorer support an ENS address
        for(var i=0; i<this.ENSCompatiableExplorers.length; i++){
            if(this.strBlockchainExplorer == this.ENSCompatiableExplorers[i]){
                this.ENSCompatiable = true;
                break;
            }
            this.ENSCompatiable = false;
        }

        // On failure give the user a warning.
        if(!this.ENSCompatiable){
            this.replacePatterns[1] = '$1$2';
        }
    }

    manipulateDOM()
    {
        if(true || this.intSettingsCount === this.intSettingsTotalCount) {
            if(this.blBlacklistDomains) {
                this.blacklistedDomainCheck();
            }
            this.convertAddressToLink();
        }
    }

    /**
     * @name Convert Address To Link
     * @desc Finds Ethereum addresses and converts to a link to a block explorer
     */
    convertAddressToLink()
    {
        var arrWhitelistedTags = ["code", "span", "p", "td", "li", "em", "i", "b", "strong", "small"];

        //Get the whitelisted nodes
        for(var i=0; i<arrWhitelistedTags.length; i++) {
            var objNodes = document.getElementsByTagName(arrWhitelistedTags[i]);
            //Loop through the whitelisted content
            for(var x=0; x<objNodes.length; x++) {
                this.convertAddresses(objNodes[x]);
            }
        }

        this.tidyUpSlots();

        if(this.blHighlight) {
            this.addHighlightStyle();
        }
    }

    /**
     * @name Convert Addresses
     * @desc Takes a Node and checks if any of its children are textNodes. On success replace textNode with slot node
     * @desc slot node contains regex replaced content; see generateReplacementContent()
     * @param {Node} objNode
     */
    convertAddresses(objNode)
    {
        // Some nodes have non-textNode children
        // we need to ensure regex is applied only to text otherwise we will mess the html up
        for(var i=0; i < objNode.childNodes.length; i++){
            // Only check textNodes to prevent applying RegEx against element attributes
            if(objNode.childNodes[i].nodeType == 3){ // nodeType 3 = a text node

                var child = objNode.childNodes[i];
                var childContent = child.textContent;

                // Only start replacing stuff if the we get a RegEx match.
                if(this.isPatternMatched(childContent)) {
                    // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/slot
                    var replacement = document.createElement('slot');
                    replacement.setAttribute('class', 'ext-etheraddresslookup-temporary');
                    replacement.innerHTML = this.generateReplacementContent(childContent);
                    objNode.replaceChild(replacement, child);
                }
            }
        }
    }

    /**
     * @name Generate Replacement Content
     * @desc Takes string and replaces any regex pattern matches with the associated replace patterns
     * @param {string} content
     * @returns {string}
     */
    generateReplacementContent(content)
    {
        for(var i=0; i < this.regExPatterns.length; i++){
            content = content.replace(this.regExPatterns[i], this.replacePatterns[i]);
        }
        return content;
    }

    /**
     * @name Is Pattern Matched
     * @desc Checks content matches any of the object's matchPatterns
     * @param {string} content
     * @returns {boolean}
     */
    isPatternMatched(content)
    {
        for(var i=0; i < this.matchPatterns.length; i++){
            if(this.matchPatterns[i].exec(content) !== null){
                return true;
            }
        }
        return false;
    }

    /**
     * @name Is Blockchain Explorer Site
     * @desc Check if the current website is the user's selected block explorer
     * @returns {boolean}
     */
    isBlockchainExplorerSite()
    {
        var objBlockchainExplorer = document.createElement("a");
        objBlockchainExplorer.href = this.strBlockchainExplorer;
        return (objBlockchainExplorer.hostname === window.location.hostname);
    }

    /**
     * @name Tidy Slots
     * @desc Searches document for slots adds the slot's child nodes to its parent then removes the slot
     */
    tidyUpSlots()
    {
        var slots = document.querySelectorAll("slot.ext-etheraddresslookup-temporary");
        for(var i=0; i < slots.length; i++){
            while(slots[i].childNodes.length > 0){
                slots[i].parentNode.insertBefore(slots[i].firstChild, slots[i]);
            }
            slots[i].parentNode.removeChild(slots[i]);
        }
    }

    //Removes the highlight style from Ethereum addresses
    removeHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-no_highlight");
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
        }
        return false;
    }

    //Adds the highlight style
    addHighlightStyle()
    {
        var objEtherAddresses = document.getElementsByClassName("ext-etheraddresslookup-link");
        for (var i = 0; i < objEtherAddresses.length; i++) {
            objEtherAddresses[i].classList.add("ext-etheraddresslookup-link-highlight");
            objEtherAddresses[i].classList.remove("ext-etheraddresslookup-link-no_highlight");
        }
        return false;
    }
}

window.addEventListener("load", function() {
    let objEtherAddressLookup = new EtherAddressLookup();
});

//Send message from the extension to here.
objBrowser.runtime.onMessage.addListener(
    function(request, sender, sendResponse) {
        let objEtherAddressLookup = new EtherAddressLookup();
        if(typeof request.func !== "undefined") {
            if(typeof objEtherAddressLookup[request.func] == "function") {
                objEtherAddressLookup[request.func]();
                sendResponse({status: "ok"});
                return true;
            }
        }
        sendResponse({status: "fail"});
    }
);
<|endoftext|>


<commit_before>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the about dialog.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Panel = require( 'SUN/Panel' );
  var CheckBox = require( 'SUN/CheckBox' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var TextPushButtonDeprecated = require( 'SUN/TextPushButtonDeprecated' );

  // strings
  var showPointersString = require( 'string!JOIST/showPointers' );
  var doneString = require( 'string!JOIST/done' );
  var settingsString = require( 'string!JOIST/title.settings' );

  /**
   * @param {Sim} sim
   * @constructor
   */
  function SettingsDialog( sim ) {
    var settingsDialog = this;

    //Use view, to help center and scale content
    ScreenView.call( this, {renderer: 'svg'} );

    var content = new VBox( { align: 'center', spacing: 50, children: [
      new Text( settingsString, { font: new PhetFont( 16 ) } ),
      new CheckBox( new Text( showPointersString ), sim.showPointersProperty ),
      new TextPushButtonDeprecated( doneString, {
        listener: function() {
          settingsDialog.doneListeners.forEach( function( listener ) {
            listener();
          } );
        }
      } )
    ]} );

    //Show a gray overlay that will help focus on the about dialog, and prevent clicks on the sim while the dialog is up
    this.addChild( new Panel( content, {centerX: this.layoutBounds.centerX, centerY: this.layoutBounds.centerY, xMargin: 20, yMargin: 20 } ) );

    function resize() {
      settingsDialog.layout( $( window ).width(), $( window ).height() );
    }

    //Fit to the window and render the initial scene
    $( window ).resize( resize );
    resize();
    this.doneListeners = [];
  }

  return inherit( ScreenView, SettingsDialog, {
    addDoneListener: function( listener ) {
      this.doneListeners.push( listener );
    }
  } );
} );
<commit_msg>replace TextPushButtonDeprecated in joist.SettingsDialog<commit_after>// Copyright 2002-2013, University of Colorado Boulder

/**
 * Shows the about dialog.
 *
 * @author Sam Reid
 */
define( function( require ) {
  'use strict';

  var VBox = require( 'SCENERY/nodes/VBox' );
  var Text = require( 'SCENERY/nodes/Text' );
  var inherit = require( 'PHET_CORE/inherit' );
  var ScreenView = require( 'JOIST/ScreenView' );
  var Panel = require( 'SUN/Panel' );
  var CheckBox = require( 'SUN/CheckBox' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var TextPushButton = require( 'SUN/buttons/TextPushButton' );

  // strings
  var showPointersString = require( 'string!JOIST/showPointers' );
  var doneString = require( 'string!JOIST/done' );
  var settingsString = require( 'string!JOIST/title.settings' );

  /**
   * @param {Sim} sim
   * @constructor
   */
  function SettingsDialog( sim ) {
    var settingsDialog = this;

    //Use view, to help center and scale content
    ScreenView.call( this, {renderer: 'svg'} );

    var content = new VBox( { align: 'center', spacing: 50, children: [
      new Text( settingsString, { font: new PhetFont( 16 ) } ),
      new CheckBox( new Text( showPointersString ), sim.showPointersProperty ),
      new TextPushButton( doneString, {
        listener: function() {
          settingsDialog.doneListeners.forEach( function( listener ) {
            listener();
          } );
        }
      } )
    ]} );

    //Show a gray overlay that will help focus on the about dialog, and prevent clicks on the sim while the dialog is up
    this.addChild( new Panel( content, {centerX: this.layoutBounds.centerX, centerY: this.layoutBounds.centerY, xMargin: 20, yMargin: 20 } ) );

    function resize() {
      settingsDialog.layout( $( window ).width(), $( window ).height() );
    }

    //Fit to the window and render the initial scene
    $( window ).resize( resize );
    resize();
    this.doneListeners = [];
  }

  return inherit( ScreenView, SettingsDialog, {
    addDoneListener: function( listener ) {
      this.doneListeners.push( listener );
    }
  } );
} );
<|endoftext|>


<commit_before>function getChannels(callback) {
  chrome.runtime.getBackgroundPage(function(page) {
    callback(page.channels);
  });
}

// TODO: Use underscore.js' template system to generate the table.
function createTableRow(name, isLive, viewers) {
  var color = isLive ? "red" : "gray";
  var baseUrl = "https://twitch.tv/";
  var tr = $("
");
  var a = $("");
  var tdChannel = $("").append(a.text(name));
  var tdViewers = $("");
  tdViewers.append($("")).append(
    viewers.toLocaleString());
  var tdRemoveChannel = $("");
  var iRemoveChannel = $("");
  var divRemoveChannelButton = $(
    "
");
  divRemoveChannelButton.data("name", name);
  tdRemoveChannel.append(
    divRemoveChannelButton.append(iRemoveChannel));
  tr.append(tdChannel, tdViewers, tdRemoveChannel);
  return tr;
}

function updateChannelList(channels) {
  var tbody = $("#channel-list tbody");
  tbody.empty();
  if (channels.count() === 0) {
    tbody.append($("
").append($("").text("No channels added yet")));
  } else {
    channels.foreach(function(channel) {
      tbody.append(
        createTableRow(channel.name, channel.isLive, channel.viewers));
    });
    // Hook up the remove button callbacks.
    $(".remove-channel").click(function() {
      channels.removeChannel($(this).data("name"));
    });
  }
}

function initializeUi() {
  var addChannel = $("#add-channel");
  var channelInput = $("#channel input");
  addChannel.click(function() {
    var channel = channelInput.val();
    getChannels(function(channels) {
      // Clear the input field if we successfully added the channel.
      if (channels.addChannel(channel)) {
        channelInput.val("");
      }
    });
  });
  $("#channel").submit(function(ev) {
    ev.preventDefault();
    // We just fake a click event on form submissions to re-use the click
    // signal handler.
    addChannel.click();
  });
  var showNotifications = $("#show-notifications");
  // TODO: Add a settings API to background.js that wraps calls to
  //       chrome.storage.
  chrome.storage.local.get({"show-notifications": false}, function(result) {
    var state = result["show-notifications"] ? "check" : "uncheck";
    // FIXME: There is a bug in semantic-ui's 1.12.2 button module that
    //        prevents the checkbox state to be set programmatically.
    showNotifications.checkbox(state);
  });
  showNotifications.checkbox("setting", "onChange", function() {
    var state = showNotifications.checkbox("is checked");
    // XXX: Remove this once the bug in semantic-ui is fixed.
    state = true;
    chrome.storage.local.set({"show-notifications": state});
  });
  channelInput.focus();
}

document.addEventListener("DOMContentLoaded", function() {
  initializeUi();

  getChannels(function(channels) {
    // Update the channel list on changes.
    channels.onchange(updateChannelList);
    // Update the channel list once on page load.
    updateChannelList(channels);
  });
});

<commit_msg>Use anchor tag for viewer count<commit_after>function getChannels(callback) {
  chrome.runtime.getBackgroundPage(function(page) {
    callback(page.channels);
  });
}

// TODO: Use underscore.js' template system to generate the table.
function createTableRow(name, isLive, viewers) {
  var color = isLive ? "red" : "gray";
  var baseUrl = "https://twitch.tv/";
  var tr = $("
");
  var a = $("");
  var tdChannel = $("").append(a.text(name));
  var tdViewers = $("");
  var aViewers = a.clone().text(viewers.toLocaleString());
  tdViewers.append($("")).append(aViewers);
  var tdRemoveChannel = $("");
  var iRemoveChannel = $("");
  var divRemoveChannelButton = $(
    "
");
  divRemoveChannelButton.data("name", name);
  tdRemoveChannel.append(
    divRemoveChannelButton.append(iRemoveChannel));
  tr.append(tdChannel, tdViewers, tdRemoveChannel);
  return tr;
}

function updateChannelList(channels) {
  var tbody = $("#channel-list tbody");
  tbody.empty();
  if (channels.count() === 0) {
    tbody.append($("
").append($("").text("No channels added yet")));
  } else {
    channels.foreach(function(channel) {
      tbody.append(
        createTableRow(channel.name, channel.isLive, channel.viewers));
    });
    // Hook up the remove button callbacks.
    $(".remove-channel").click(function() {
      channels.removeChannel($(this).data("name"));
    });
  }
}

function initializeUi() {
  var addChannel = $("#add-channel");
  var channelInput = $("#channel input");
  addChannel.click(function() {
    var channel = channelInput.val();
    getChannels(function(channels) {
      // Clear the input field if we successfully added the channel.
      if (channels.addChannel(channel)) {
        channelInput.val("");
      }
    });
  });
  $("#channel").submit(function(ev) {
    ev.preventDefault();
    // We just fake a click event on form submissions to re-use the click
    // signal handler.
    addChannel.click();
  });
  var showNotifications = $("#show-notifications");
  // TODO: Add a settings API to background.js that wraps calls to
  //       chrome.storage.
  chrome.storage.local.get({"show-notifications": false}, function(result) {
    var state = result["show-notifications"] ? "check" : "uncheck";
    // FIXME: There is a bug in semantic-ui's 1.12.2 button module that
    //        prevents the checkbox state to be set programmatically.
    showNotifications.checkbox(state);
  });
  showNotifications.checkbox("setting", "onChange", function() {
    var state = showNotifications.checkbox("is checked");
    // XXX: Remove this once the bug in semantic-ui is fixed.
    state = true;
    chrome.storage.local.set({"show-notifications": state});
  });
  channelInput.focus();
}

document.addEventListener("DOMContentLoaded", function() {
  initializeUi();

  getChannels(function(channels) {
    // Update the channel list on changes.
    channels.onchange(updateChannelList);
    // Update the channel list once on page load.
    updateChannelList(channels);
  });
});

<|endoftext|>


<commit_before>/*global Prism*/
var highlightCorrection = -1;

function highlightLines(jPre, lineNumber, secondLineNumber, connected) {
    var line, secondLine, lineHeight = parseFloat(jPre.css("line-height"));

    lineNumber = lineNumber + highlightCorrection;
    if (secondLineNumber !== undefined) {
        secondLineNumber = secondLineNumber + highlightCorrection;
    }

    line = jPre.find(".line-highlight").eq(0);
    secondLine = jPre.find(".line-highlight").eq(1);
    line.show();

    line.css("top", (lineNumber * lineHeight) + 'px');

    if (connected === true) {
        //create a bunch of new lines, which will cause this to highlight multiple lines
        line.text(new Array(secondLineNumber - lineNumber + 2).join(' \r\n'));		//plus 2 to account for the end and beginning
        secondLine.hide();
    } else if (secondLineNumber !== undefined) {
        line.text("\r\n");
        secondLine.css("top", (secondLineNumber * lineHeight) + 'px');
        secondLine.show();
    } else {
        line.text("\r\n");
        secondLine.hide();
    }
}

function getMouseOverLine(jPre, yOffset) {
    var lineHeight = parseInt(jPre.css("line-height"));
    return Math.round((yOffset + 1) / lineHeight);		//+1 to make mouseover have a more natural feel
}

function checkEndPointsForNum(first, second, test) {
    if (test === parseInt(first, 10) || test === parseInt(second, 10)) {
        return true;
    }
    return false;
}

function checkRangeForNum(first, second, test) {
    if (test >= parseInt(first, 10) && test <= parseInt(second, 10)) {
        return true;
    }
    return false;
}

//returns true if this comment is relevent to the selected line
function handleHighlightsAndComment(jComment, selectedLine, enclosingObject) {
    var dataString, splits;

    if (jComment !== undefined) {
        dataString = jComment.data("show");
    }

    if (typeof (dataString) == "string") {
        if (dataString.indexOf("-") != -1) {
            splits = dataString.split("-");
            if (checkRangeForNum(splits[0], splits[1], selectedLine)) {
                jComment.show();
                highlightLines(enclosingObject, parseInt(splits[0], 10), parseInt(splits[1], 10), true);
                return true;
            }
        } else if (dataString.indexOf("+") != -1) {
            splits = dataString.split("+");
            if (checkEndPointsForNum(splits[0], splits[1], selectedLine)) {
                jComment.show();
                highlightLines(enclosingObject, parseInt(splits[0], 10), parseInt(splits[1], 10));
                return true;
            }
        }
        else {
            console.log("not supported data string " + dataString);
        }
    }
    else {
        if (selectedLine == dataString) {
            jComment.show();
            highlightLines(enclosingObject, selectedLine);
            return true;
        }
    }
    highlightLines(enclosingObject, selectedLine);
    return false;
}

var isAnimating = false;

function adjustCodeCommentBoxForMousePosition(codeComment, y) {
    var height = parseInt(codeComment.css("height"), 10), currTop = parseInt(codeComment.css("top"), 10), newTop,
	rect, parent, preHeight, parentHeight;

	//rect is the code comment's rectangle relative to the viewport
	rect = codeComment.get(0).getBoundingClientRect()
    parent = codeComment.closest(".codeContainer");
	parentHeight = parseInt(parent.css("height"), 10);

    if (!isAnimating && currTop + 0.2 * height > y) {
        //scroll up
		isAnimating = true;
        newTop = Math.max(y - (0.3 * height), 15, currTop - (rect.top - 60),	//-60 because the nav bar's height is 50 and I want some space
			currTop - (codeComment.position().top - 10)		//jquery position
		);
        codeComment.animate({ top: newTop }, 150, "swing", function () { isAnimating = false; });
    }

    else if (!isAnimating && currTop + 0.8 * height < y) {
        //scroll down
		isAnimating = true;
        preHeight = parseInt(parent.find("pre").css("height"), 10);
        //the top should never be less than 10, but we don't want it to scroll down off the edge (10px margin)
        newTop = Math.max(10, Math.min((y - (0.7 * height)), preHeight - (height + 10), currTop + (window.innerHeight - rect.bottom) - 10,
			currTop + (parentHeight - (codeComment.position().top + height) ) - 10)
		);
        codeComment.animate({ top: newTop }, 150, "swing", function () { isAnimating = false; });
    }
}

Prism.hooks.add('after-highlight', function (env) {
    var numbersToHighlight, lineNumbers, container, mouseComments;
    if (env.code == "Loading…") {
        return;
    }
    numbersToHighlight = [];
    //console.log("call ");
    //console.log(env);

    container = $(env.element).closest(".codeContainer");

    mouseComments = container.find(".mouseComment");
    mouseComments.each(function (j, mc) {
        var i, show, range = false, lines;
        show = mc.getAttribute("data-show");
        lines = show.split(/[\+\-]/);
        if (show.indexOf("+") === -1) {
            range = true;
        }
        if (range && lines.length > 1) {
            for (i = parseInt(lines[0], 10) ; i <= parseInt(lines[1], 10) ; i++) {
                numbersToHighlight.push(i - 1);
            }
        } else {
            for (i = 0; i < lines.length; i++) {
                numbersToHighlight.push(parseInt(lines[i], 10) - 1);		//subtract 1 because these lines are 1 indexed, not 0-indexed
            }
        }
    });

    //console.log(numbersToHighlight);

    if (numbersToHighlight.length > 0) {
        lineNumbers = container.find(".line-numbers-rows span");
        lineNumbers.each(function (j, n) {
            if (-1 !== numbersToHighlight.indexOf(j)) {
                n.setAttribute("data-comment", "*");
                n.className = "starredBolded";
            }
        });
    }
});

$(document).ready(function () {
    console.log("codeCommentary.js");
	//highlightCorrection =
    $(".codeContainer").on("mousemove", "pre", function (e) {
        var enclosingObject = $(this);

		var y = e.pageY - enclosingObject.offset().top;		//cross-browser compatibility

        var selectedLine = getMouseOverLine(enclosingObject, y);

        var comments = enclosingObject.closest(".codeContainer").find(".mouseComment");
        comments.hide();

        if (comments.size() > 0) {
            comments.each(function (i, element) {
                if (handleHighlightsAndComment($(element), selectedLine, enclosingObject)) {
                    //break
                    return false;
                }
            });
        } else {
            handleHighlightsAndComment(undefined, selectedLine, enclosingObject);
        }
        adjustCodeCommentBoxForMousePosition(enclosingObject.closest(".codeContainer").find(".codeComment"), y);
    });

});<commit_msg>IE is not OBO anymore<commit_after>/*global Prism*/
var highlightCorrection = -1;
var ie_correction = 0;

function highlightLines(jPre, lineNumber, secondLineNumber, connected) {
    var line, secondLine, lineHeight = parseFloat(jPre.css("line-height"));

    lineNumber = lineNumber + highlightCorrection;
    if (secondLineNumber !== undefined) {
        secondLineNumber = secondLineNumber + highlightCorrection;
    }

    line = jPre.find(".line-highlight").eq(0);
    secondLine = jPre.find(".line-highlight").eq(1);
    line.show();

    line.css("top", (lineNumber * lineHeight) + 'px');

    if (connected === true) {
        //create a bunch of new lines, which will cause this to highlight multiple lines
        line.text(new Array(secondLineNumber - lineNumber + 2).join(' \r\n'));		//plus 2 to account for the end and beginning
        secondLine.hide();
    } else if (secondLineNumber !== undefined) {
        line.text("\r\n");
        secondLine.css("top", (secondLineNumber * lineHeight) + 'px');
        secondLine.show();
    } else {
        line.text("\r\n");
        secondLine.hide();
    }
}

function getMouseOverLine(jPre, yOffset) {
    var lineHeight = parseFloat(jPre.css("line-height"));
    return Math.round((yOffset + 1) / lineHeight) + ie_correction;		//+1 to make mouseover have a more natural feel
}

function checkEndPointsForNum(first, second, test) {
    if (test === parseInt(first, 10) || test === parseInt(second, 10)) {
        return true;
    }
    return false;
}

function checkRangeForNum(first, second, test) {
    if (test >= parseInt(first, 10) && test <= parseInt(second, 10)) {
        return true;
    }
    return false;
}

//returns true if this comment is relevent to the selected line
function handleHighlightsAndComment(jComment, selectedLine, enclosingObject) {
    var dataString, splits;

    if (jComment !== undefined) {
        dataString = jComment.data("show");
    }

    if (typeof (dataString) == "string") {
        if (dataString.indexOf("-") != -1) {
            splits = dataString.split("-");
            if (checkRangeForNum(splits[0], splits[1], selectedLine)) {
                jComment.show();
                highlightLines(enclosingObject, parseInt(splits[0], 10), parseInt(splits[1], 10), true);
                return true;
            }
        } else if (dataString.indexOf("+") != -1) {
            splits = dataString.split("+");
            if (checkEndPointsForNum(splits[0], splits[1], selectedLine)) {
                jComment.show();
                highlightLines(enclosingObject, parseInt(splits[0], 10), parseInt(splits[1], 10));
                return true;
            }
        }
        else {
            console.log("not supported data string " + dataString);
        }
    }
    else {
        if (selectedLine == dataString) {
            jComment.show();
            highlightLines(enclosingObject, selectedLine);
            return true;
        }
    }
    highlightLines(enclosingObject, selectedLine);
    return false;
}

var isAnimating = false;

function adjustCodeCommentBoxForMousePosition(codeComment, y) {
    var height = parseInt(codeComment.css("height"), 10), currTop = parseInt(codeComment.css("top"), 10), newTop,
	rect, parent, preHeight, parentHeight;

	//rect is the code comment's rectangle relative to the viewport
	rect = codeComment.get(0).getBoundingClientRect()
    parent = codeComment.closest(".codeContainer");
	parentHeight = parseInt(parent.css("height"), 10);

    if (!isAnimating && currTop + 0.2 * height > y) {
        //scroll up
		isAnimating = true;
        newTop = Math.max(y - (0.3 * height), 15, currTop - (rect.top - 60),	//-60 because the nav bar's height is 50 and I want some space
			currTop - (codeComment.position().top - 10)		//jquery position
		);
        codeComment.animate({ top: newTop }, 150, "swing", function () { isAnimating = false; });
    }

    else if (!isAnimating && currTop + 0.8 * height < y) {
        //scroll down
		isAnimating = true;
        preHeight = parseInt(parent.find("pre").css("height"), 10);
        //the top should never be less than 10, but we don't want it to scroll down off the edge (10px margin)
        newTop = Math.max(10, Math.min((y - (0.7 * height)), preHeight - (height + 10), currTop + (window.innerHeight - rect.bottom) - 10,
			currTop + (parentHeight - (codeComment.position().top + height) ) - 10)
		);
        codeComment.animate({ top: newTop }, 150, "swing", function () { isAnimating = false; });
    }
}

Prism.hooks.add('after-highlight', function (env) {
    var numbersToHighlight, lineNumbers, container, mouseComments;
    if (env.code == "Loading…") {
        return;
    }
    numbersToHighlight = [];
    //console.log("call ");
    //console.log(env);

    container = $(env.element).closest(".codeContainer");

    mouseComments = container.find(".mouseComment");
    mouseComments.each(function (j, mc) {
        var i, show, range = false, lines;
        show = mc.getAttribute("data-show");
        lines = show.split(/[\+\-]/);
        if (show.indexOf("+") === -1) {
            range = true;
        }
        if (range && lines.length > 1) {
            for (i = parseInt(lines[0], 10) ; i <= parseInt(lines[1], 10) ; i++) {
                numbersToHighlight.push(i - 1);
            }
        } else {
            for (i = 0; i < lines.length; i++) {
                numbersToHighlight.push(parseInt(lines[i], 10) - 1);		//subtract 1 because these lines are 1 indexed, not 0-indexed
            }
        }
    });

    //console.log(numbersToHighlight);

    if (numbersToHighlight.length > 0) {
        lineNumbers = container.find(".line-numbers-rows span");
        lineNumbers.each(function (j, n) {
            if (-1 !== numbersToHighlight.indexOf(j)) {
                n.setAttribute("data-comment", "*");
                n.className = "starredBolded";
            }
        });
    }
});

function isIE() {
	return (navigator.appVersion.indexOf("MSIE") + navigator.appVersion.indexOf("Trident")) >= -1;
}

$(document).ready(function () {
    console.log("codeCommentary.js");

	if (isIE()) {
		ie_correction = 1;
	}

    $(".codeContainer").on("mousemove", "pre", function (e) {
        var enclosingObject = $(this);

		var y = e.pageY - enclosingObject.offset().top;		//cross-browser compatibility

        var selectedLine = getMouseOverLine(enclosingObject, y);

        var comments = enclosingObject.closest(".codeContainer").find(".mouseComment");
        comments.hide();

        if (comments.size() > 0) {
            comments.each(function (i, element) {
                if (handleHighlightsAndComment($(element), selectedLine, enclosingObject)) {
                    //break
                    return false;
                }
            });
        } else {
            handleHighlightsAndComment(undefined, selectedLine, enclosingObject);
        }
        adjustCodeCommentBoxForMousePosition(enclosingObject.closest(".codeContainer").find(".codeComment"), y);
    });

});<|endoftext|>


<commit_before>/* global MO5 */

MO5("WSE.tools.ui").define("WSE.commands.alert", function (ui) {

    function alert (command, interpreter) {

        var title, message, doNext;

        title = command.getAttribute("title") || "Alert!";
        message = command.getAttribute("message") || "Alert!";
        message = module.tools.textToHtml(message);
        doNext = command.getAttribute("next") === "false" ? false : true;

        interpreter.bus.trigger("wse.interpreter.commands.alert", command);

        ui.alert(
            interpreter,
            {
                title: title,
                message: message,
                pause: true,
                doNext: doNext
            }
        );

        return {
            doNext: true
        };
    }

    return alert;

});
<commit_msg>Fixed: The alert command didn't work.<commit_after>/* global MO5 */

MO5("WSE.tools.ui", "WSE.tools").define("WSE.commands.alert", function (ui, tools) {

    function alert (command, interpreter) {

        var title, message, doNext;

        title = command.getAttribute("title") || "Alert!";
        message = command.getAttribute("message") || "Alert!";
        message = tools.textToHtml(message);
        doNext = command.getAttribute("next") === "false" ? false : true;

        interpreter.bus.trigger("wse.interpreter.commands.alert", command);

        ui.alert(
            interpreter,
            {
                title: title,
                message: message,
                pause: true,
                doNext: doNext
            }
        );

        return {
            doNext: true
        };
    }

    return alert;

});
<|endoftext|>


<commit_before>MLS.contentFilter = (function () {

    var $cf = $jQ('#content-filter'),
        pub = {

            /*============================
            =            Init            =
            ============================*/

            init: function () {
                $cf = $jQ('#content-filter');
                var $fs = $jQ('#filter-selections'),
                    $collapsible = $cf.find('.collapsible'),
                    // i = j = 0,
                    // $facet = null,
                    // param,
                    $facets = $cf.find('.facet');

                // collapse all but first dimension
                $collapsible.find('.facet-list').slideToggle('slow');

                // add data attributes to facets
                // for (i = 0; i < $facets.length; i++) {
                //     $facet = $facets.eq(i),
                //     url = $facet.children('a').attr('href'),

                //     params = url.split('?')[1].split('&');

                //     for (j=0; j< params.length; j++) {
                //         param = params[j].split('=');
                //         $facet.attr('data-' + param[0], param[1])
                //     }
                // }

                /*==========  bind click events  ==========*/

                // reset all
                $jQ('#clear-selections').on('click', pub.resetFilter);

                // dimension (expansion/collapse)
                $collapsible.find('.dimension-header').on('click', pub.dimensionClick);

                $facets.on('click', pub.processRequest);

                // remove filter
                $fs.find('a').on('click', pub.removeFilter);

                // compability services
                $jQ('.compatibility-filter').children('select').on('change', pub.processRequest);

            },

            /*-----  End of Init  ------*/

            finalize: function () {
                $cf = $jQ('#content-filter');
                var $fs = $jQ('#filter-selections'),
                    $collapsible = $cf.find('.collapsible'),
                    $facets = $cf.find('.facet');

                 /*==========  bind click events  ==========*/

                // reset all
                $jQ('#clear-selections').unbind('click', pub.resetFilter);

                // dimension (expansion/collapse)
                $collapsible.find('.dimension-header').unbind('click', pub.dimensionClick);

                // handle clicks on multi-facet
                // $facets.not('.multi').on('click', pub.facetClick);
                // $multis.on('click', pub.multiFacetClick);
                $facets.unbind('click', pub.processRequest);

                // remove filter
                $fs.find('a').unbind('click', pub.removeFilter);

                // compability services
                $jQ('.compatibility-filter').children('select').unbind('change', pub.processRequest);
            },

            reInit: function () {
                MLS.contentFilter.finalize();
                MLS.contentFilter.init();
            },

            /*=======================================
            =            dimension click            =
            =======================================*/

            dimensionClick: function (e) {
                e.preventDefault();
                $jQ(this).toggleClass('active').promise().done(function () {
                    // toggleElement($jQ(this).next(), false);
                    $jQ(this).next().slideToggle('slow');
                });
            },

            /*-----  End of dimension click  ------*/

            /*=============================================
            =            Remove selected facet            =
            =============================================*/

            removeFilter: function (e) {
                e.preventDefault();
                window.location.hash = '';
                pub.processRequest(e);
            },

            /*-----  End of Remove selected facet  ------*/

            /*=======================================
            =            process request            =
            =======================================*/

            processRequest : function (e) {
                e.preventDefault();

                var queryParams = [],
                    params = {},
                    param,
                    $elem = $jQ(this),
                    href = '',
                    j = 0;

                if (e.currentTarget.tagName === 'SELECT') { // for dropdowns
                    href = $elem.find('option:selected').attr('value');
                } else {
                    href = $elem.find('a').attr('href');
                }

                // update hash
                window.location.hash = href;

                // make params based on the url located in the a[href]
                queryParams = href.split('?')[1].split('&');

                for (j = 0; j < queryParams.length; j++) {
                    param = queryParams[j].split('=');
                    params[param[0]] = param[1];
                }

                // make request
                MLS.ajax.sendRequest(
                    MLS.ajax.endpoints.PRODUCT_LISTING,
                    params,
                    pub.updateResults
                );
            },

            /*-----  End of process request  ------*/

            /*===================================
            =            update grid            =
            ===================================*/

            updateResults : function (data) {

                if (data.hasOwnProperty('success')) {
                    // update results...
                    MLS.ui.updateContent('#main-column .content-grid', data.success.responseHTML);
                    contentGrid.init(); // reload contentGrid
                    // ... and result count ...
                    $jQ('#content-filter-count').find('strong').text(data.success.count);
                    // ... and filters
                    $cf.replaceWith(data.success.filtersHTML);
                    pub.init();
                } else {
                    MLS.ui.updateContent('#main-column .content-grid', data.error.responseHTML);
                }

            },

            /*-----  End of update grid  ------*/

        };
    return pub;
}());

<commit_msg>refactoring in progress<commit_after>MLS.contentFilter = (function () {

    var $cf = $jQ('#content-filter'),
        pub = {

            /*============================
            =            Init            =
            ============================*/

            init: function () {
                $cf = $jQ('#content-filter');
                var $fs = $jQ('#filter-selections'),
                    $collapsible = $cf.find('.collapsible'),
                    // i = j = 0,
                    // $facet = null,
                    // param,
                    $facets = $cf.find('.facet');

                // collapse all but first dimension
                $collapsible.find('.facet-list').slideToggle('slow');

                // add data attributes to facets
                // for (i = 0; i < $facets.length; i++) {
                //     $facet = $facets.eq(i),
                //     url = $facet.children('a').attr('href'),

                //     params = url.split('?')[1].split('&');

                //     for (j=0; j< params.length; j++) {
                //         param = params[j].split('=');
                //         $facet.attr('data-' + param[0], param[1])
                //     }
                // }

                /*==========  bind click events  ==========*/

                // reset all
                $jQ('#clear-selections').on('click', pub.resetFilter);

                // dimension (expansion/collapse)
                $collapsible.find('.dimension-header').on('click', pub.dimensionClick);

                $facets.on('click', pub.facetClick);

                // remove filter
                $fs.find('a').on('click', pub.removeFilter);

                // compability services
                $jQ('.compatibility-filter').children('select').on('change', pub.compabilitySelect);

                // type ahead (searc)
                $jQ('.compatibility-filter').children('input.type-ahead').on('keyup', pub.compabilitySearch);

            },
            /*-----  End of Init  ------*/

            finalize: function () {
                $cf = $jQ('#content-filter');
                var $fs = $jQ('#filter-selections'),
                    $collapsible = $cf.find('.collapsible'),
                    $facets = $cf.find('.facet');

                 /*==========  bind click events  ==========*/

                // reset all
                $jQ('#clear-selections').unbind('click', pub.resetFilter);

                // dimension (expansion/collapse)
                $collapsible.find('.dimension-header').unbind('click', pub.dimensionClick);

                $facets.unbind('click', pub.processRequest);

                // remove filter
                $fs.find('a').unbind('click', pub.removeFilter);

                // compability services
                $jQ('.compatibility-filter').children('select').unbind('change', pub.compabilitySelect);

            },

            reInit: function () {
                MLS.contentFilter.finalize();
                MLS.contentFilter.init();
            },

            /*=======================================
            =            dimension click            =
            =======================================*/

            dimensionClick: function (e) {
                e.preventDefault();
                $jQ(this).toggleClass('active').promise().done(function () {
                    // toggleElement($jQ(this).next(), false);
                    $jQ(this).next().slideToggle('slow');
                });
            },

            /*-----  End of dimension click  ------*/

            compabilitySearch: function (e) {
                if (e.keyCode === 13) {
                    console.log('input');
                    // pub.searchInput();
                }
            },

            compabilitySelect: function (e) {
                var $elem = e.currentTarget,
                    href = $elem.find('option:selected').attr('value');

                // update hash
                window.location.hash = href;
            },

            facetClick: function (e) {
                e.preventDefault();

                var $elem = e.currentTarget,
                    href = $elem.find('a').attr('href');

                // update hash
                window.location.hash = href;
            },

            /*=============================================
            =            Remove selected facet            =
            =============================================*/

            removeFilter: function (e) {
                e.preventDefault();
                window.location.hash = '';
                pub.processRequest(e);
            },

            /*-----  End of Remove selected facet  ------*/

            getParamsFromUrl: function (href) {
                var queryParams = [],
                    params = {},
                    param,
                    j = 0;
                // make params based on the url located in the a[href]
                queryParams = href.split('?')[1].split('&');

                for (j = 0; j < queryParams.length; j++) {
                    param = queryParams[j].split('=');
                    params[param[0]] = param[1];
                }

                return params;
            }

            /*=======================================
            =            process request            =
            =======================================*/

            processRequest : function (params) {

                // make request
                MLS.ajax.sendRequest(
                    MLS.ajax.endpoints.PRODUCT_LISTING,
                    params,
                    pub.updateResults
                );
            },

            /*-----  End of process request  ------*/

            /*===================================
            =            update grid            =
            ===================================*/

            updateResults : function (data) {

                if (data.hasOwnProperty('success')) {
                    // update results...
                    MLS.ui.updateContent('#main-column .content-grid', data.success.responseHTML);
                    contentGrid.init(); // reload contentGrid
                    // ... and result count ...
                    $jQ('#content-filter-count').find('strong').text(data.success.count);
                    // ... and filters
                    $cf.replaceWith(data.success.filtersHTML);
                    pub.init();
                } else {
                    MLS.ui.updateContent('#main-column .content-grid', data.error.responseHTML);
                }

            },

            /*-----  End of update grid  ------*/

        };
    return pub;
}());

<|endoftext|>


<commit_before>const followLinkModeSameTab = 1;
const followLinkModeNewTab = 2;

var followLink = {
  mode: 0,
  currentPattern: null,
  currentPosition: 0,
  selectedLink: null,
  matchedLinks: null,
  unmatchedLinks: null,
  hint: null
};

function eachElement(elements, callback) {
  for (var i = 0, len = elements.length; i < len; i++) {
    callback(elements[i]);
  }
}

function updateUi() {
  eachElement(followLink.unmatchedLinks, function (element) {
    element.classList.remove('follow-link-match');
    element.classList.remove('follow-link-selected');
  });
  eachElement(followLink.matchedLinks, function (element) {
    element.classList.add('follow-link-match');
    element.classList.remove('follow-link-selected');
  });

  if (followLink.mode > 0) {
    var modeString = followLink.mode == followLinkModeSameTab ? 'Follow Link' : 'Follow Link New Tab';

    if (followLink.selectedLink) {
      followLink.selectedLink.classList.remove('follow-link-match');
      followLink.selectedLink.classList.add('follow-link-selected');
      followLink.selectedLink.scrollIntoView({behavior: 'smooth'});
    }

    if (followLink.matchedLinks.length > 0) {
      followLink.hint.innerHTML = modeString + ' Pattern: ' + followLink.currentPattern + ' | Selected ' + (followLink.currentPosition + 1) + '/' + followLink.matchedLinks.length;
    } else if (followLink.currentPattern.length > 0) {
      followLink.hint.innerHTML = modeString + ' Pattern: ' + followLink.currentPattern + ' | Selected: 0';
    } else {
      followLink.hint.innerHTML = modeString + ' Pattern: ';
    }
    followLink.hint.classList.remove('follow-link-hidden');
  } else {
    followLink.hint.classList.add('follow-link-hidden');
  }
}

function matchLinks() {
  var re = followLink.mode && followLink.currentPattern ? new RegExp(followLink.currentPattern, 'i') : null;
  var allLinks = document.querySelectorAll("a");
  followLink.matchedLinks = Array();
  followLink.unmatchedLinks = Array();

  eachElement(allLinks, function (element) {
    if (re && element.innerHTML && element.innerHTML.match(re)) {
      followLink.matchedLinks.push(element);
    } else {
      followLink.unmatchedLinks.push(element);
    }
  });

  followLink.currentPosition = followLink.currentPosition % followLink.matchedLinks.length;

  if (followLink.matchedLinks.length > 0) {
    followLink.selectedLink = followLink.matchedLinks[followLink.currentPosition];
    followLink.selectedLink.classList.remove('follow-link-matched');
    followLink.selectedLink.classList.add('follow-link-selected');
  } else {
    followLink.selectedLink = null;
  }

  updateUi();
}

function navigateToSelectedLink() {
  if (followLink.selectedLink) {
    var url = followLink.selectedLink.href;
    chrome.runtime.sendMessage({url: url, newTab: followLink.mode == followLinkModeNewTab});
  }
}

function initMode(mode) {
  console.log('Entering follow link mode: ' + mode);
  followLink.mode = mode;
  followLink.currentPattern = '';
  followLink.currentPosition = 0;
  matchLinks();
}

function resetMode() {
  followLink.mode = 0;
  console.log('Leaving follow link mode');
  matchLinks();
}

function incPosition() {
  followLink.currentPosition += 1;
  matchLinks();
}

function removeLastCharFromPattern() {
  followLink.currentPosition = 0;
  followLink.currentPattern = followLink.currentPattern.slice(0, -1);
  matchLinks();
}

function addCharToPattern(char) {
  followLink.currentPosition = 0;
  followLink.currentPattern += char.toLocaleLowerCase();
  matchLinks();
}

function keyHandler(event) {
  if (!followLink.mode && event.code == 'KeyF' && event.ctrlKey == false) {
    initMode(event.shiftKey ? followLinkModeNewTab : followLinkModeSameTab);
    event.preventDefault();
  } else if (followLink.mode > 0 && event.code == 'Escape') {
    resetMode();
    event.preventDefault();
  } else if (followLink.mode > 0 && event.code == 'Enter') {
    navigateToSelectedLink();
    resetMode();
    event.preventDefault();
  } else if (followLink.mode > 0 && event.code == 'Tab') {
    incPosition();
    event.preventDefault();
  } else if (followLink.mode > 0 && event.code == 'Backspace') {
    removeLastCharFromPattern();
    event.preventDefault();
  } else if (followLink.mode > 0 && !event.ctrlKey) {
    addCharToPattern(String.fromCharCode(event.keyCode));
    event.preventDefault();
  }
}

document.addEventListener('keydown', keyHandler);
followLink.hint = document.createElement('div');
followLink.hint.classList.add('follow-link-hint');
followLink.hint.classList.add('follow-link-hidden');
document.body.appendChild(followLink.hint);<commit_msg>focus selected link, let default event handler open links in same tab<commit_after>const followLinkModeSameTab = 1;
const followLinkModeNewTab = 2;

var followLink = {
  mode: 0,
  currentPattern: null,
  currentPosition: 0,
  selectedLink: null,
  matchedLinks: null,
  unmatchedLinks: null,
  hint: null
};

function eachElement(elements, callback) {
  for (var i = 0, len = elements.length; i < len; i++) {
    callback(elements[i]);
  }
}

function updateUi() {
  eachElement(followLink.unmatchedLinks, function (element) {
    element.classList.remove('follow-link-match');
    element.classList.remove('follow-link-selected');
  });
  eachElement(followLink.matchedLinks, function (element) {
    element.classList.add('follow-link-match');
    element.classList.remove('follow-link-selected');
  });

  if (followLink.mode > 0) {
    var modeString = followLink.mode == followLinkModeSameTab ? 'Follow Link' : 'Follow Link New Tab';

    if (followLink.selectedLink) {
      followLink.selectedLink.classList.remove('follow-link-match');
      followLink.selectedLink.classList.add('follow-link-selected');
      followLink.selectedLink.focus();
    }

    if (followLink.matchedLinks.length > 0) {
      followLink.hint.innerHTML = modeString + ' Pattern: ' + followLink.currentPattern + ' | Selected ' + (followLink.currentPosition + 1) + '/' + followLink.matchedLinks.length;
    } else if (followLink.currentPattern.length > 0) {
      followLink.hint.innerHTML = modeString + ' Pattern: ' + followLink.currentPattern + ' | Selected: 0';
    } else {
      followLink.hint.innerHTML = modeString + ' Pattern: ';
    }
    followLink.hint.classList.remove('follow-link-hidden');
  } else {
    followLink.hint.classList.add('follow-link-hidden');
  }
}

function matchLinks() {
  var re = followLink.mode && followLink.currentPattern ? new RegExp(followLink.currentPattern, 'i') : null;
  var allLinks = document.querySelectorAll("a");
  followLink.matchedLinks = Array();
  followLink.unmatchedLinks = Array();

  eachElement(allLinks, function (element) {
    if (re && element.innerHTML && element.innerHTML.match(re)) {
      followLink.matchedLinks.push(element);
    } else {
      followLink.unmatchedLinks.push(element);
    }
  });

  followLink.currentPosition = followLink.currentPosition % followLink.matchedLinks.length;

  if (followLink.matchedLinks.length > 0) {
    followLink.selectedLink = followLink.matchedLinks[followLink.currentPosition];
    followLink.selectedLink.classList.remove('follow-link-matched');
    followLink.selectedLink.classList.add('follow-link-selected');
  } else {
    followLink.selectedLink = null;
  }

  updateUi();
}

function navigateToSelectedLink() {
  if (followLink.selectedLink) {
    var url = followLink.selectedLink.href;
    chrome.runtime.sendMessage({url: url, newTab: followLink.mode == followLinkModeNewTab});
  }
}

function initMode(mode) {
  console.log('Entering follow link mode: ' + mode);
  followLink.mode = mode;
  followLink.currentPattern = '';
  followLink.currentPosition = 0;
  matchLinks();
}

function resetMode() {
  followLink.mode = 0;
  console.log('Leaving follow link mode');
  matchLinks();
}

function incPosition() {
  followLink.currentPosition += 1;
  matchLinks();
}

function removeLastCharFromPattern() {
  followLink.currentPosition = 0;
  followLink.currentPattern = followLink.currentPattern.slice(0, -1);
  matchLinks();
}

function addCharToPattern(char) {
  followLink.currentPosition = 0;
  followLink.currentPattern += char.toLocaleLowerCase();
  matchLinks();
}

function keyHandler(event) {
  if (!followLink.mode && event.code == 'KeyF' && event.ctrlKey == false) {
    initMode(event.shiftKey ? followLinkModeNewTab : followLinkModeSameTab);
    event.preventDefault();
  } else if (followLink.mode > 0 && event.code == 'Escape') {
    resetMode();
    event.preventDefault();
  } else if (followLink.mode > 0 && event.code == 'Enter') {
    if (followLink.mode == followLinkModeNewTab) {
      navigateToSelectedLink();
      resetMode();
      event.preventDefault();
    } else {
      resetMode();
      // let the default event handler click on the link
    }
  } else if (followLink.mode > 0 && event.code == 'Tab') {
    incPosition();
    event.preventDefault();
  } else if (followLink.mode > 0 && event.code == 'Backspace') {
    removeLastCharFromPattern();
    event.preventDefault();
  } else if (followLink.mode > 0 && !event.ctrlKey) {
    addCharToPattern(String.fromCharCode(event.keyCode));
    event.preventDefault();
  }
}

document.addEventListener('keydown', keyHandler);
followLink.hint = document.createElement('div');
followLink.hint.classList.add('follow-link-hint');
followLink.hint.classList.add('follow-link-hidden');
document.body.appendChild(followLink.hint);<|endoftext|>