<commit_before>"use strict";

const defaultOptions = {
    enableHeadingId: true,
    autoNumberingRegex: "^\\[\\]\\(\\=numbering([\\s\\S]*?)\\=\\)",
    autoNumbering: {
        "enable": false,
        "pattern": [],
        "defaultSuffix": ". ",
        "defaultPrefix": "",
        "defaultStart": 1,
        "defaultSeparator": "."
    },
    includeLevel: [1, 2, 3, 4, 5, 6],
    tocContainerClass: "toc",
    tocRegex: "^\\[\\]\\(toc\\)",
    excludeFromTocRegex: "\\[\\]\\(notoc\\)",
    defaultListElement: "ul",
    listElements: ["ul", "ul", "ul", "ul", "ul", "ul"],
    defaultListElementAttributeSet: { style: "list-style-type: none;" },
    listElementAttributeSets: [],
    idPrefix: "headings."
}; //defaultOptions
defaultOptions.bulletedListType = defaultOptions.defaultListElement;

module.exports = (md, options) => {

    const util = require("util");
    const autoNumbering = require("./autoNumbering");
    const autoNumberingParser = require("./autoNumbering.optionParser");

    if (options) {
        const isObject = obj => { return obj && obj.constructor == Object; };
        function clone(source) {
            let target = {};
            for (let index in source)
                if (source.hasOwnProperty(index))
                    if (isObject(source[index]))
                        target[index] = clone(source[index]);
                    else
                        target[index] = source[index];
            return target;
        } //clone
        options = clone(options);
    } else
        options = {};

    populateWithDefault(options, defaultOptions);

    // no magic function names:
    const tocFunctionNames = { open: "tocOpen", close: "tocClose", body: "tocBody" };
    const ruleName = "toc"; // works with null, but let's care about other plug-ins
    let firstTime = true;
    let usedIds = { headings: {}, toc: {}, excludeFromToc: {} };
    let idCounts = { headings: 0, toc: 0 };
    let idSet = [];

    md.core.ruler.before("normalize", "detectAutoNumbering", state => {
        let regexp;
        try {
            regexp = new RegExp(options.autoNumberingRegex);
        } catch (ex) {
            state.src = util.format(
                "
Invalid auto-numbering Regular Expression: %s

%s
",
                ex.toString(),
                options.autoNumberingRegex);
            return;
        } //exception
        let failedJsonParse = false;
        let match;
        try {
            match = regexp.exec(state.src);
            if (!match) return;
            if (!match.length) return;
            if (match.length < 2) return;
            let privilegedOptions = JSON.parse(match[1]);
            populateWithDefault(privilegedOptions, options.autoNumbering);
            options.autoNumbering = privilegedOptions;
        } catch (ex) {
            // alternative:
            let privilegedOptions;
            try {
                privilegedOptions = autoNumberingParser(match[1]);
                if (privilegedOptions) {
                    populateWithDefault(privilegedOptions, options.autoNumbering);
                    options.autoNumbering = privilegedOptions;
                    return;
                } // else let exception go
            } catch (newFormatException) {
                // continue with JSON
            }
            // end alternative
            failedJsonParse = true;
            const errorString = ex.toString();
            const errorTerms = errorString.split(' ');
            let errorPosition;
            for (const term of errorTerms) {
                errorPosition = parseInt(term);
                if (errorPosition) break;
            } //loop
            let matchText = match[1] && match[1].length > 0 ? match[1] : '';
            if (errorPosition && matchText) {
                matchText = [
                    matchText.slice(0, errorPosition),
                    " ▸",
                    matchText.slice(errorPosition)].join('');
            } //if
            state.src = util.format(
                "
Invalid auto-numbering JSON structure:

%s:
<big>
%s
</big>",
                errorString,
                matchText);
        } finally {
            if (match && !failedJsonParse)
                state.src = state.src.slice(match[0].length, state.src.length);
        } //exception
    }); //md.core.ruler.before

    md.core.ruler.before("inline", "buildToc", state => {
        if (!options.enableHeadingId)   // inconsistent with having toc/no-toc tags,
            return;                     // so leave them as is
        let tocRegexp = options.tocRegex;
        if (tocRegexp.constructor != RegExp)
            tocRegexp = new RegExp(options.tocRegex, "m");
        // extra global check saves time if there is no match:
        let doToc = false;
        if (tocRegexp) {
            const match = tocRegexp.exec(state.src);
            doToc = match != null;
        } //if
        let excludeFromTocRegex = options.excludeFromTocRegex;
        if (excludeFromTocRegex.constructor != RegExp)
            excludeFromTocRegex = new RegExp(options.excludeFromTocRegex, "m");
        //
        usedIds = { headings: {}, toc: {}, excludeFromToc: {} };
        idSet = [];
        buildIdSet(idSet, state.tokens, excludeFromTocRegex);
        addIdAttributes();
        if (!doToc) return;
        createToc(state);
        detectAndPushToc(tocRegexp);
        firstTime = false;
    }); //md.core.ruler.before

    const slugify = (s, used) => {
        let slug = options.idPrefix +
            s.replace(/ /g, '-')
                .replace(/[^A-Za-z0-9\-\.\_]/g, match => { return match.codePointAt().toString(16); }).toLowerCase();
        while (used[slug])
            slug += '.';
        used[slug] = slug;
        return slug;
    } // idHeadersSlugify

    function headingLevel(token) { return token.tag && parseInt(token.tag.substr(1, 1)); }

    function buildIdSet(idSet, tokens, excludeFromTocRegex) {
        const autoNumberingMethods = autoNumbering(options, headingLevel);
        const autoSet = autoNumberingMethods.initializer(tokens);
        for (let index = 1; index < tokens.length; ++index) {
            const token = tokens[index];
            const headingTextToken = tokens[index - 1];
            if (token.type !== "heading_close" || headingTextToken.type !== "inline") continue;
            let excludeFromToc = false;
            if (excludeFromTocRegex) {
                const oldContent = headingTextToken.content;
                headingTextToken.content = headingTextToken.content.replace(excludeFromTocRegex, "");
                excludeFromToc = oldContent !== headingTextToken.content;
                if (excludeFromToc)
                    usedIds.excludeFromToc[index] = token;
            } //if
            idSet.push({
                id: slugify(headingTextToken.content, usedIds),
                prefix: autoNumberingMethods.iterator(excludeFromToc, autoSet, token)
            });
        } //loop
    } //buildIdSet

    function addIdAttributes() {
        if (!firstTime) return;
        //const headingOpenPrevious = md.renderer.rules.heading_open; //SA??? removed to disable VSCode classes and id attributes
        md.renderer.rules.heading_open = function (tokens, index, options, object, renderer) {
            tokens[index].attrs = tokens[index].attrs || [];
            let title = tokens[index + 1].children.reduce((accumulator, child) => {
                return accumulator + child.content;
            }, "");
            // some not-so-pathological cases create redundant md.renderer.rules.heading_open call, so:
            let prefix = '';
            if (idSet[idCounts.headings]) {
                const headingSlug = idSet[idCounts.headings].id;
                tokens[index].attrs.push(["id", headingSlug]);
                prefix = idSet[idCounts.headings].prefix;
                ++idCounts.headings;
            } //if
            // if (headingOpenPrevious) //SA??? removed to disable VSCode classes and id attributes
            //     return headingOpenPrevious.apply(this, arguments) + prefix;
            // else
            return renderer.renderToken.apply(renderer, arguments) + prefix;
            //SA!!! APPEND text to return to add prefix to heading content
        }; //md.renderer.rules.heading_open
    } //addIdAttributes

    function createToc(state) {
        md.renderer.rules[tocFunctionNames.body] = function (tokens, index) {
            return createTocTree(0, state.tokens)[1];
        }; //body
    } //createToc

    function detectAndPushToc(tocRegexp) {
        if (!firstTime) return;
        md.inline.ruler.before("text", ruleName, function toc(state, silent) {
            if (silent) return false;
            const match = tocRegexp.exec(state.src);
            if (!match) return false;
            if (match.length < 1) return false;
            state.push(tocFunctionNames.body, ruleName, 0);
            state.src = "";
            return true;
        }); //md.inline.ruler.before
    } //detectAndPushToc

    function createTocTree(tokenIndex, tokens) {
        let headings = [],
            listItemContent = "",
            currentLevel,
            subHeadings,
            currentTokenIndex = tokenIndex;
        const size = tokens.length;
        idCounts = { headings: 0, toc: 0 };
        while (currentTokenIndex < size) {
            const token = tokens[currentTokenIndex];
            const heading = tokens[currentTokenIndex - 1];
            if (!heading) { currentTokenIndex++; continue; }
            const level = headingLevel(token);
            if (token.type !== "heading_close" || heading.type !== "inline") {
                currentTokenIndex++;
                continue;
            } //if
            if (usedIds.excludeFromToc[currentTokenIndex] == token || options.includeLevel.indexOf(level) == -1) {
                currentTokenIndex++;
                ++idCounts.toc; // one id is skipped //SA???
                continue;
            } //if
            if (currentLevel) {
                if (level > currentLevel) {
                    subHeadings = createTocTree(currentTokenIndex, tokens, usedIds, idCounts, idSet);
                    listItemContent += subHeadings[1];
                    currentTokenIndex = subHeadings[0];
                    continue;
                } //if
                if (level < currentLevel) {
                    listItemContent += "
";
                    headings.push(listItemContent);
                    return [currentTokenIndex, listElement(currentLevel, options, headings)];
                } //if
                if (level == currentLevel) {
                    listItemContent += "
";
                    headings.push(listItemContent);
                } //if
            } else
                currentLevel = level; // We init with the first found level
            const tocSlug = idSet[idCounts.toc].id; // SA??? bug here
            const prefix = idSet[idCounts.toc].prefix;
            ++idCounts.toc;
            listItemContent = util.format("
%s", prefix, tocSlug);
            if (options.itemPrefixes)
                if (options.itemPrefixes[currentLevel - 1])
                    headingContent = options.itemPrefixes[currentLevel - 1] + headingContent;
            listItemContent += heading.content;
            listItemContent += "";
            currentTokenIndex++;
        } //loop
        listItemContent += "
";
        headings.push(listItemContent);
        return [tokens.length, listElement(currentLevel, options, headings)];
    } //createTocTree

    function listElement(level, options, headings) {
        let listTag = options.defaultListElement;
        if (options.listElements)
            if (options.listElements[level - 1])
                listTag = options.listElements[level - 1];
        let elementAttributes = "";
        if (options.listElementAttributeSets)
            if (options.listElementAttributeSets[level - 1])
                for (let index in options.listElementAttributeSets[level - 1])
                    elementAttributes += util.format(" %s=\"%s\"", index, options.listElementAttributeSets[level - 1][index]);
        if (options.listElementAttributeSets)
            for (const index in options.defaultListElementAttributeSet)
                if (options.listElementAttributeSets.length < 1)
                    elementAttributes += " " + `${index}="${options.defaultListElementAttributeSet[index]}"`;
        return `<${listTag}${elementAttributes} class="${options.tocContainerClass}">${headings.join("")}</${listTag}>`;
    } //listElement

    function populateWithDefault(value, defaultValue) { // special edition: it does not populate Array
        if (!defaultValue) return;
        if (!value) return;
        if (defaultValue.constructor == Object && value.constructor == Object) {
            for (const index in defaultValue)
                if (!(index in value))
                    value[index] = defaultValue[index];
                else
                    populateWithDefault(value[index], defaultValue[index]);
        } else
            value = defaultValue;
    } //populateWithDefault

}; //module.exports
<commit_msg>TOC: removed unused rule names<commit_after>"use strict";

const defaultOptions = {
    enableHeadingId: true,
    autoNumberingRegex: "^\\[\\]\\(\\=numbering([\\s\\S]*?)\\=\\)",
    autoNumbering: {
        "enable": false,
        "pattern": [],
        "defaultSuffix": ". ",
        "defaultPrefix": "",
        "defaultStart": 1,
        "defaultSeparator": "."
    },
    includeLevel: [1, 2, 3, 4, 5, 6],
    tocContainerClass: "toc",
    tocRegex: "^\\[\\]\\(toc\\)",
    excludeFromTocRegex: "\\[\\]\\(notoc\\)",
    defaultListElement: "ul",
    listElements: ["ul", "ul", "ul", "ul", "ul", "ul"],
    defaultListElementAttributeSet: { style: "list-style-type: none;" },
    listElementAttributeSets: [],
    idPrefix: "headings."
}; //defaultOptions
defaultOptions.bulletedListType = defaultOptions.defaultListElement;

module.exports = (md, options) => {

    const util = require("util");
    const autoNumbering = require("./autoNumbering");
    const autoNumberingParser = require("./autoNumbering.optionParser");

    if (options) {
        const isObject = obj => { return obj && obj.constructor == Object; };
        function clone(source) {
            let target = {};
            for (let index in source)
                if (source.hasOwnProperty(index))
                    if (isObject(source[index]))
                        target[index] = clone(source[index]);
                    else
                        target[index] = source[index];
            return target;
        } //clone
        options = clone(options);
    } else
        options = {};

    populateWithDefault(options, defaultOptions);

    // no magic function names:
    const ruleName = "toc"; // works with null, but let's care about other plug-ins
    let firstTime = true;
    let usedIds = { headings: {}, toc: {}, excludeFromToc: {} };
    let idCounts = { headings: 0, toc: 0 };
    let idSet = [];

    md.core.ruler.before("normalize", "detectAutoNumbering", state => {
        let regexp;
        try {
            regexp = new RegExp(options.autoNumberingRegex);
        } catch (ex) {
            state.src = util.format(
                "
Invalid auto-numbering Regular Expression: %s

%s
",
                ex.toString(),
                options.autoNumberingRegex);
            return;
        } //exception
        let failedJsonParse = false;
        let match;
        try {
            match = regexp.exec(state.src);
            if (!match) return;
            if (!match.length) return;
            if (match.length < 2) return;
            let privilegedOptions = JSON.parse(match[1]);
            populateWithDefault(privilegedOptions, options.autoNumbering);
            options.autoNumbering = privilegedOptions;
        } catch (ex) {
            // alternative:
            let privilegedOptions;
            try {
                privilegedOptions = autoNumberingParser(match[1]);
                if (privilegedOptions) {
                    populateWithDefault(privilegedOptions, options.autoNumbering);
                    options.autoNumbering = privilegedOptions;
                    return;
                } // else let exception go
            } catch (newFormatException) {
                // continue with JSON
            }
            // end alternative
            failedJsonParse = true;
            const errorString = ex.toString();
            const errorTerms = errorString.split(' ');
            let errorPosition;
            for (const term of errorTerms) {
                errorPosition = parseInt(term);
                if (errorPosition) break;
            } //loop
            let matchText = match[1] && match[1].length > 0 ? match[1] : '';
            if (errorPosition && matchText) {
                matchText = [
                    matchText.slice(0, errorPosition),
                    " ▸",
                    matchText.slice(errorPosition)].join('');
            } //if
            state.src = util.format(
                "
Invalid auto-numbering JSON structure:

%s:
<big>
%s
</big>",
                errorString,
                matchText);
        } finally {
            if (match && !failedJsonParse)
                state.src = state.src.slice(match[0].length, state.src.length);
        } //exception
    }); //md.core.ruler.before

    md.core.ruler.before("inline", "buildToc", state => {
        if (!options.enableHeadingId)   // inconsistent with having toc/no-toc tags,
            return;                     // so leave them as is
        let tocRegexp = options.tocRegex;
        if (tocRegexp.constructor != RegExp)
            tocRegexp = new RegExp(options.tocRegex, "m");
        // extra global check saves time if there is no match:
        let doToc = false;
        if (tocRegexp) {
            const match = tocRegexp.exec(state.src);
            doToc = match != null;
        } //if
        let excludeFromTocRegex = options.excludeFromTocRegex;
        if (excludeFromTocRegex.constructor != RegExp)
            excludeFromTocRegex = new RegExp(options.excludeFromTocRegex, "m");
        //
        usedIds = { headings: {}, toc: {}, excludeFromToc: {} };
        idSet = [];
        buildIdSet(idSet, state.tokens, excludeFromTocRegex);
        addIdAttributes();
        if (!doToc) return;
        createToc(state);
        detectAndPushToc(tocRegexp);
        firstTime = false;
    }); //md.core.ruler.before

    const slugify = (s, used) => {
        let slug = options.idPrefix +
            s.replace(/ /g, '-')
                .replace(/[^A-Za-z0-9\-\.\_]/g, match => { return match.codePointAt().toString(16); }).toLowerCase();
        while (used[slug])
            slug += '.';
        used[slug] = slug;
        return slug;
    } // idHeadersSlugify

    function headingLevel(token) { return token.tag && parseInt(token.tag.substr(1, 1)); }

    function buildIdSet(idSet, tokens, excludeFromTocRegex) {
        const autoNumberingMethods = autoNumbering(options, headingLevel);
        const autoSet = autoNumberingMethods.initializer(tokens);
        for (let index = 1; index < tokens.length; ++index) {
            const token = tokens[index];
            const headingTextToken = tokens[index - 1];
            if (token.type !== "heading_close" || headingTextToken.type !== "inline") continue;
            let excludeFromToc = false;
            if (excludeFromTocRegex) {
                const oldContent = headingTextToken.content;
                headingTextToken.content = headingTextToken.content.replace(excludeFromTocRegex, "");
                excludeFromToc = oldContent !== headingTextToken.content;
                if (excludeFromToc)
                    usedIds.excludeFromToc[index] = token;
            } //if
            idSet.push({
                id: slugify(headingTextToken.content, usedIds),
                prefix: autoNumberingMethods.iterator(excludeFromToc, autoSet, token)
            });
        } //loop
    } //buildIdSet

    function addIdAttributes() {
        if (!firstTime) return;
        //const headingOpenPrevious = md.renderer.rules.heading_open; //SA??? removed to disable VSCode classes and id attributes
        md.renderer.rules.heading_open = function (tokens, index, options, object, renderer) {
            tokens[index].attrs = tokens[index].attrs || [];
            let title = tokens[index + 1].children.reduce((accumulator, child) => {
                return accumulator + child.content;
            }, "");
            // some not-so-pathological cases create redundant md.renderer.rules.heading_open call, so:
            let prefix = '';
            if (idSet[idCounts.headings]) {
                const headingSlug = idSet[idCounts.headings].id;
                tokens[index].attrs.push(["id", headingSlug]);
                prefix = idSet[idCounts.headings].prefix;
                ++idCounts.headings;
            } //if
            // if (headingOpenPrevious) //SA??? removed to disable VSCode classes and id attributes
            //     return headingOpenPrevious.apply(this, arguments) + prefix;
            // else
            return renderer.renderToken.apply(renderer, arguments) + prefix;
            //SA!!! APPEND text to return to add prefix to heading content
        }; //md.renderer.rules.heading_open
    } //addIdAttributes

    function createToc(state) {
        md.renderer.rules[ruleName] = function (tokens, index) {
            return createTocTree(0, state.tokens)[1];
        }; //body
    } //createToc

    function detectAndPushToc(tocRegexp) {
        if (!firstTime) return;
        md.inline.ruler.before("text", ruleName, function toc(state, silent) {
            if (silent) return false;
            const match = tocRegexp.exec(state.src);
            if (!match) return false;
            if (match.length < 1) return false;
            state.push(ruleName, 0);
            state.src = "";
            return true;
        }); //md.inline.ruler.before
    } //detectAndPushToc

    function createTocTree(tokenIndex, tokens) {
        let headings = [],
            listItemContent = "",
            currentLevel,
            subHeadings,
            currentTokenIndex = tokenIndex;
        const size = tokens.length;
        idCounts = { headings: 0, toc: 0 };
        while (currentTokenIndex < size) {
            const token = tokens[currentTokenIndex];
            const heading = tokens[currentTokenIndex - 1];
            if (!heading) { currentTokenIndex++; continue; }
            const level = headingLevel(token);
            if (token.type !== "heading_close" || heading.type !== "inline") {
                currentTokenIndex++;
                continue;
            } //if
            if (usedIds.excludeFromToc[currentTokenIndex] == token || options.includeLevel.indexOf(level) == -1) {
                currentTokenIndex++;
                ++idCounts.toc; // one id is skipped //SA???
                continue;
            } //if
            if (currentLevel) {
                if (level > currentLevel) {
                    subHeadings = createTocTree(currentTokenIndex, tokens, usedIds, idCounts, idSet);
                    listItemContent += subHeadings[1];
                    currentTokenIndex = subHeadings[0];
                    continue;
                } //if
                if (level < currentLevel) {
                    listItemContent += "
";
                    headings.push(listItemContent);
                    return [currentTokenIndex, listElement(currentLevel, options, headings)];
                } //if
                if (level == currentLevel) {
                    listItemContent += "
";
                    headings.push(listItemContent);
                } //if
            } else
                currentLevel = level; // We init with the first found level
            const tocSlug = idSet[idCounts.toc].id; // SA??? bug here
            const prefix = idSet[idCounts.toc].prefix;
            ++idCounts.toc;
            listItemContent = util.format("
%s", prefix, tocSlug);
            if (options.itemPrefixes)
                if (options.itemPrefixes[currentLevel - 1])
                    headingContent = options.itemPrefixes[currentLevel - 1] + headingContent;
            listItemContent += heading.content;
            listItemContent += "";
            currentTokenIndex++;
        } //loop
        listItemContent += "
";
        headings.push(listItemContent);
        return [tokens.length, listElement(currentLevel, options, headings)];
    } //createTocTree

    function listElement(level, options, headings) {
        let listTag = options.defaultListElement;
        if (options.listElements)
            if (options.listElements[level - 1])
                listTag = options.listElements[level - 1];
        let elementAttributes = "";
        if (options.listElementAttributeSets)
            if (options.listElementAttributeSets[level - 1])
                for (let index in options.listElementAttributeSets[level - 1])
                    elementAttributes += util.format(" %s=\"%s\"", index, options.listElementAttributeSets[level - 1][index]);
        if (options.listElementAttributeSets)
            for (const index in options.defaultListElementAttributeSet)
                if (options.listElementAttributeSets.length < 1)
                    elementAttributes += " " + `${index}="${options.defaultListElementAttributeSet[index]}"`;
        return `<${listTag}${elementAttributes} class="${options.tocContainerClass}">${headings.join("")}</${listTag}>`;
    } //listElement

    function populateWithDefault(value, defaultValue) { // special edition: it does not populate Array
        if (!defaultValue) return;
        if (!value) return;
        if (defaultValue.constructor == Object && value.constructor == Object) {
            for (const index in defaultValue)
                if (!(index in value))
                    value[index] = defaultValue[index];
                else
                    populateWithDefault(value[index], defaultValue[index]);
        } else
            value = defaultValue;
    } //populateWithDefault

}; //module.exports
<|endoftext|>


<commit_before>import Ember from 'ember';
import { validator, buildValidations } from 'ember-cp-validations';

const Validations = buildValidations({
  pilotName: {
    descriptionKey: 'pilot',
    validators: [
      validator('length', { max: 255 }),
    ],
    debounce: 500,
  },
  copilotId: {
    descriptionKey: 'copilot',
    validators: [
      validator('not-equal', { on: 'pilotId', messageKey: 'pilots-must-not-be-equal' }),
    ],
    debounce: 0,
  },
  copilotName: {
    descriptionKey: 'copilot',
    validators: [
      validator('length', { max: 255 }),
    ],
    debounce: 500,
  },
  registration: {
    descriptionKey: 'registration',
    validators: [
      validator('length', { max: 32 }),
    ],
    debounce: 500,
  },
  competitionId: {
    descriptionKey: 'competition-id',
    validators: [
      validator('length', { max: 5 }),
    ],
    debounce: 500,
  },
});

export default Ember.Component.extend(Validations, {
  result: null,
  clubMembers: null,
  aircraftModels: null,

  status: Ember.computed.readOnly('result.status'),
  flight: Ember.computed.alias('result.flight'),
  trace: Ember.computed.alias('result.trace'),
  airspaces: Ember.computed.readOnly('result.airspaces'),

  pilotId: Ember.computed.alias('flight.pilotId'),
  pilotName: Ember.computed.alias('flight.pilotName'),
  showPilotNameInput: Ember.computed.equal('pilotId', null),

  copilotId: Ember.computed.alias('flight.copilotId'),
  copilotName: Ember.computed.alias('flight.copilotName'),
  showCopilotNameInput: Ember.computed.equal('copilotId', null),

  modelId: Ember.computed.alias('flight.modelId'),
  registration: Ember.computed.alias('flight.registration'),
  competitionId: Ember.computed.alias('flight.competitionId'),

  igcStartTime: computedDate('trace.igc_start_time'),
  takeoffTime: computedDate('flight.takeoffTime'),
  scoreStartTime: computedDate('flight.scoreStartTime'),
  scoreEndTime: computedDate('flight.scoreEndTime'),
  landingTime: computedDate('flight.landingTime'),
  igcEndTime: computedDate('trace.igc_end_time'),

  success: Ember.computed.equal('status', 0),

  init() {
    this._super(...arguments);

    if (this.get('flight')) {
      this.set('pilotId', this.get('flight.pilot.id'));
      this.set('copilotId', this.get('flight.copilot.id'));
      this.set('modelId', this.get('flight.model.id'));
    }

    this.set('result.validations', this.get('validations'));
  },

  actions: {
    setTakeoffTime(value) {
      this.set('takeoffTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.takeoffTime > times.scoreStartTime) this.set('scoreStartTime', times.takeoffTime);
      if (times.takeoffTime > times.scoreEndTime) this.set('scoreEndTime', times.takeoffTime);
      if (times.takeoffTime > times.landingTime) this.set('landingTime', times.takeoffTime);
    },

    setScoreStartTime(value) {
      this.set('scoreStartTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.scoreStartTime < times.takeoffTime) this.set('takeoffTime', times.scoreStartTime);
      if (times.scoreStartTime > times.scoreEndTime) this.set('scoreEndTime', times.scoreStartTime);
      if (times.scoreStartTime > times.landingTime) this.set('landingTime', times.scoreStartTime);
    },

    setScoreEndTime(value) {
      this.set('scoreEndTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.scoreEndTime < times.takeoffTime) this.set('takeoffTime', times.scoreEndTime);
      if (times.scoreEndTime < times.scoreStartTime) this.set('scoreStartTime', times.scoreEndTime);
      if (times.scoreEndTime > times.landingTime) this.set('landingTime', times.scoreEndTime);
    },

    setLandingTime(value) {
      this.set('landingTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.landingTime < times.takeoffTime) this.set('takeoffTime', times.landingTime);
      if (times.landingTime < times.scoreStartTime) this.set('scoreStartTime', times.landingTime);
      if (times.landingTime < times.scoreEndTime) this.set('scoreEndTime', times.landingTime);
    },
  },
});

/**
 * Converts from ISO 8601 strings to Date instances and vice-versa.
 */
function computedDate(aliasKey) {
  return Ember.computed(aliasKey, {
    get() {
      let str = this.get(aliasKey);
      if (str) {
        return new Date(str);
      }
    },
    set(key, value) {
      let date = value ? value.toISOString() : value;
      this.set(aliasKey, date);
      return value;
    },
  });
}
<commit_msg>components/upload-result-form: Use isNone() macro for pilot/copilot ids<commit_after>import Ember from 'ember';
import { validator, buildValidations } from 'ember-cp-validations';

import isNone from '../computed/is-none';

const Validations = buildValidations({
  pilotName: {
    descriptionKey: 'pilot',
    validators: [
      validator('length', { max: 255 }),
    ],
    debounce: 500,
  },
  copilotId: {
    descriptionKey: 'copilot',
    validators: [
      validator('not-equal', { on: 'pilotId', messageKey: 'pilots-must-not-be-equal' }),
    ],
    debounce: 0,
  },
  copilotName: {
    descriptionKey: 'copilot',
    validators: [
      validator('length', { max: 255 }),
    ],
    debounce: 500,
  },
  registration: {
    descriptionKey: 'registration',
    validators: [
      validator('length', { max: 32 }),
    ],
    debounce: 500,
  },
  competitionId: {
    descriptionKey: 'competition-id',
    validators: [
      validator('length', { max: 5 }),
    ],
    debounce: 500,
  },
});

export default Ember.Component.extend(Validations, {
  result: null,
  clubMembers: null,
  aircraftModels: null,

  status: Ember.computed.readOnly('result.status'),
  flight: Ember.computed.alias('result.flight'),
  trace: Ember.computed.alias('result.trace'),
  airspaces: Ember.computed.readOnly('result.airspaces'),

  pilotId: Ember.computed.alias('flight.pilotId'),
  pilotName: Ember.computed.alias('flight.pilotName'),
  showPilotNameInput: isNone('pilotId'),

  copilotId: Ember.computed.alias('flight.copilotId'),
  copilotName: Ember.computed.alias('flight.copilotName'),
  showCopilotNameInput: isNone('copilotId'),

  modelId: Ember.computed.alias('flight.modelId'),
  registration: Ember.computed.alias('flight.registration'),
  competitionId: Ember.computed.alias('flight.competitionId'),

  igcStartTime: computedDate('trace.igc_start_time'),
  takeoffTime: computedDate('flight.takeoffTime'),
  scoreStartTime: computedDate('flight.scoreStartTime'),
  scoreEndTime: computedDate('flight.scoreEndTime'),
  landingTime: computedDate('flight.landingTime'),
  igcEndTime: computedDate('trace.igc_end_time'),

  success: Ember.computed.equal('status', 0),

  init() {
    this._super(...arguments);

    if (this.get('flight')) {
      this.set('pilotId', this.get('flight.pilot.id'));
      this.set('copilotId', this.get('flight.copilot.id'));
      this.set('modelId', this.get('flight.model.id'));
    }

    this.set('result.validations', this.get('validations'));
  },

  actions: {
    setTakeoffTime(value) {
      this.set('takeoffTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.takeoffTime > times.scoreStartTime) this.set('scoreStartTime', times.takeoffTime);
      if (times.takeoffTime > times.scoreEndTime) this.set('scoreEndTime', times.takeoffTime);
      if (times.takeoffTime > times.landingTime) this.set('landingTime', times.takeoffTime);
    },

    setScoreStartTime(value) {
      this.set('scoreStartTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.scoreStartTime < times.takeoffTime) this.set('takeoffTime', times.scoreStartTime);
      if (times.scoreStartTime > times.scoreEndTime) this.set('scoreEndTime', times.scoreStartTime);
      if (times.scoreStartTime > times.landingTime) this.set('landingTime', times.scoreStartTime);
    },

    setScoreEndTime(value) {
      this.set('scoreEndTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.scoreEndTime < times.takeoffTime) this.set('takeoffTime', times.scoreEndTime);
      if (times.scoreEndTime < times.scoreStartTime) this.set('scoreStartTime', times.scoreEndTime);
      if (times.scoreEndTime > times.landingTime) this.set('landingTime', times.scoreEndTime);
    },

    setLandingTime(value) {
      this.set('landingTime', value);

      let times = this.getProperties('takeoffTime', 'scoreStartTime', 'scoreEndTime', 'landingTime');
      if (times.landingTime < times.takeoffTime) this.set('takeoffTime', times.landingTime);
      if (times.landingTime < times.scoreStartTime) this.set('scoreStartTime', times.landingTime);
      if (times.landingTime < times.scoreEndTime) this.set('scoreEndTime', times.landingTime);
    },
  },
});

/**
 * Converts from ISO 8601 strings to Date instances and vice-versa.
 */
function computedDate(aliasKey) {
  return Ember.computed(aliasKey, {
    get() {
      let str = this.get(aliasKey);
      if (str) {
        return new Date(str);
      }
    },
    set(key, value) {
      let date = value ? value.toISOString() : value;
      this.set(aliasKey, date);
      return value;
    },
  });
}
<|endoftext|>


<commit_before>/* globals bufferpm, chrome, safari */

// Put together a query string for the iframe
var buildSrc = function(data, config) {

  var src = data.local ?
    config.overlay.localendpoint :
    config.overlay.endpoint;

  var qs = '';

  config.attributes.forEach(function(attr, i){
    if ( !data[ attr.name ] ) return;
    if ( qs.length ) qs += '&';
    qs += attr.name + '=' + attr.encode( data[attr.name] );
  });

  if (qs.length) src += '?' + qs;

  return src;
};

var openPopUp = function(src, port, doneCallback) {

  // Safari doesn't allow window.open when popups are disabled
  if (typeof safari !== 'undefined') {
    port.emit('buffer_safari_open', src);
  } else {
    window.open(src, null, 'height=600,width=850');
  }

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind("buffermessage", function(overlaydata) {
    bufferpm.unbind("buffermessage");
    setTimeout(function () {
      doneCallback(overlaydata);
    }, 0);
    window.focus();
  });
};

/**
 * This is a list of domains that have a strict Content Security Policy
 * This is a temporary work around while we figure out a long term solution
 * to this issue: https://github.com/bufferapp/buffer-chrome/issues/12
 */
var CSPWhitelist = [
  'twitter.com',
  'github.com',
  'education.github.com',
  'medium.com',
  'www.npmjs.org',
  'www.npmjs.com' // DEC 2014
];

// Build that overlay!
// Triggered by code working from the button up
var bufferOverlay = function(data, config, port, doneCallback) {

  if( ! doneCallback ) doneCallback = function () {};
  if( ! config ) return;

  var src = buildSrc(data, config);
  var domain = window.location.hostname;

  if (xt.options['buffer.op.tpc-disabled'] ||
    CSPWhitelist.indexOf(domain) > -1 ) {
    return openPopUp(src, port, doneCallback);
  }

  // Create the iframe and add the footer:
  var iframe = document.createElement('iframe');

  iframe.allowtransparency = 'true';
  iframe.scrolling = 'no';
  iframe.id = 'buffer_overlay';
  iframe.name = 'buffer_overlay';
  iframe.style.cssText = config.overlay.getCSS();

  iframe.src = src;

  // Testing CSP issues w/ sandboxed iframe
  // var qs = src.split('?')[1];
  // iframe.src = xt.data.get('data/shared/extension.html?' + qs);

  document.body.appendChild(iframe);

  var styleTag = createStyleTag();
  document.body.appendChild(styleTag);

  var dashboardButton = createDashboardButton();
  document.body.appendChild(dashboardButton);

  var cancelButton = createCancelButton();
  document.body.appendChild(cancelButton);

  $(document).on('click', '.buffer-floating-cancel-btn', function() {
      closePopup(document, doneCallback);
  });

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind('buffermessage', function(overlaydata) {
      closePopup(document, doneCallback, overlaydata);
  });

  /**
   * Listen to ESC key and close the popup when hit.
   */
  $(document).keyup(function(e) {
    if (e.keyCode == 27) {
        closePopup(document, doneCallback);
    }
  });

  // Remove the loading image when we hear from the other side
  bufferpm.bind('buffer_loaded', function(data) {
    bufferpm.unbind('buffer_loaded');
    iframe.style.backgroundImage = 'none';

    if ($(".buffer-floating-cancel-btn").length) {
        $(".buffer-floating-cancel-btn").remove();
    }
  });
};

function closePopup(document, doneCallback, overlayData) {

    if ($("#buffer_overlay").length) {
        $("#buffer_overlay").remove();
    }

    if ($(".buffer-floating-cancel-btn").length) {
        $(".buffer-floating-cancel-btn").remove();
    }

    if ($(".buffer-floating-btn").length) {
        $(".buffer-floating-btn").remove();
    }

    bufferpm.unbind('buffermessage');
    bufferpm.unbind('buffer_addbutton');

    setTimeout(function () {
      doneCallback(overlayData);
    }, 0);

    window.focus();
}

var createStyleTag = function() {
  var style = document.createElement('style');
  style.type = 'text/css';

  var content = document.createTextNode([
    '.buffer-floating-btn:hover {',
      'text-decoration: none;',
      'color: #323b43;',
      'cursor: pointer;',
    '}'
  ].join(''));

  style.appendChild(content);

  return style;
};

var createDashboardButton = function() {

  var css = [
    'position: fixed;',
    'top: 10px;',
    'right: 10px;',
    'z-index: 2147483647;',
    'padding: 8px 10px 8px 32px;',
    'background-color: #fff;',
    'background-image: url(https://d389zggrogs7qo.cloudfront.net/images/bookmarklet_icon.png);',
    'background-repeat: no-repeat;',
    'background-size: 15px;',
    'background-position: 11px 12px;',
    'color: #323b43;',
    'border: 0;',
    'text-decoration: none;',
    'border-radius: 2px;',
    'text-decoration: none;',
    'font-size: 14px;',
    'line-height: 1.6;',
    'font-family: "Open Sans", Roboto, Helvetica, Arial;',
    'box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);'
  ].join('');

  var button = document.createElement('a');
  button.href = 'https://buffer.com/app';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-floating-btn');
  button.setAttribute('style', css);

  var text = document.createTextNode('Go to Buffer');
  button.appendChild(text);

  return button;
};

var createCancelButton = function() {

  var css = [
    'position: fixed;',
    'top: 10px;',
    'left: 10px;',
    'z-index: 2147483647;',
    'padding: 8px 10px 8px 10px;',
    'background-color: #fff;',
    'background-repeat: no-repeat;',
    'color: #323b43;',
    'border: 0;',
    'text-decoration: none;',
    'border-radius: 2px;',
    'text-decoration: none;',
    'font-size: 14px;',
    'line-height: 1.6;',
    'font-family: "Open Sans", Roboto, Helvetica, Arial;',
    'box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);'
  ].join('');

  var button = document.createElement('button');
  button.setAttribute('class', 'buffer-floating-btn buffer-floating-cancel-btn');
  button.setAttribute('style', css);

  var text = document.createTextNode('Cancel');
  button.appendChild(text);

  return button;
};

// getOverlayConfig returns the configuration object for use by bufferOverlay
var getOverlayConfig = function(postData){

  var config = {};

  // Set this to true for using a local server while testing
  config.local = false;

  var segments = window.location.pathname.split('/');

  config.pocketWeb = ( window.location.host.indexOf('getpocket') !== -1 && segments[2] === 'read' );

  // Specification for gathering data for the overlay
  config.attributes = [
    {
      name: "url",
      get: function (cb) {
        if(config.pocketWeb){
          var li = document.getElementsByClassName('original')[0];
          var link = li.getElementsByTagName('a')[0].href;
          cb(link);
        }
        else{
          cb(window.location.href);
        }
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "text",
      get: function (cb) {
        if (document.getSelection().toString().length) {
          return cb('"' + document.getSelection().toString() + '"');
        }

        if (config.pocketWeb){
          var headline = document.querySelectorAll('.reader_head h1')[0];
          var title = headline && headline.textContent;
          return cb(title);
        }

        var ogTitle = document.head.querySelector('meta[property="og:title"]');
        if (ogTitle && ogTitle.content && ogTitle.content.length) {
          return cb(ogTitle.content);
        }

        cb(document.title);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_tweet_id",
      get: function (cb) {
        cb(postData.retweeted_tweet_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_id",
      get: function (cb) {
        cb(postData.retweeted_user_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_name",
      get: function (cb) {
        cb(postData.retweeted_user_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_display_name",
      get: function (cb) {
        cb(postData.retweeted_user_display_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "picture",
      get: function (cb) {
        cb(postData.image);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "embed",
      get: function (cb) {
        cb(postData.embed);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "local",
      get: function (cb) {
        cb(config.local);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "version",
      get: function (cb) {
        cb(postData.version);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "placement",
      get: function (cb) {
        if( postData.placement ) cb(postData.placement);
        else cb('general');
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    }
  ];

  var loadingImgRel = 'img/white-loading-gif-small.gif';
  //TODO - Change to static.buffer when it's set up!
  var loadingImg = typeof chrome !== 'undefined' ?
    chrome.extension.getURL('data/shared/' +  loadingImgRel) :
    document.location.protocol === 'http:' ?
      'http://static.bufferapp.com/images/extensions/' + loadingImgRel :
      'https://d389zggrogs7qo.cloudfront.net/images/extensions/' + loadingImgRel;

  config.overlay = {
    endpoint: "https://buffer.com/add/",
    localendpoint: "https://local.buffer.com/add/",
    getCSS: function () {
      return [
        'border: none;',
        'height: 100%;',
        'width: 100%;',
        'position: fixed!important;',
        'z-index: 2147483646;',
        'top:0;',
        'left:0;',
        'display: block!important;',
        'max-width: 100%!important;',
        'max-height: 100%!important;',
        'padding: 0!important;',
        'background: rgba(0, 0, 0, 0.1) url(' + loadingImg +') no-repeat center center;',
        'background-size: 40px;'
      ].join('');
    }
  };

  return config;
};

// Method for handling the async firing of the cb
var executeAfter = function(done, count, args, cb) {
  if(done === count) {
    setTimeout(function(){
      cb.apply( null, args );
    }, 0);
  }
};

// Asynchronously gather data about the page and from embedded sources,
// like Twitter or Facebook. Currently the async is a bit over the top,
// and not used, but if we need async down the line, it's there.
var getData = function (postData, cb) {
  var config = getOverlayConfig( postData );
  var count = config.attributes.length;
  var done = 0;
  var data = {};

  config.attributes.forEach(function(attr, i){
    attr.get(function(d){
      done += 1;
      data[ attr.name ] = d;
      executeAfter(done, count, [ data, config ], cb);
    });
  });
};

// bufferData is triggered by the buffer_click listener in
// the buffer-browser-embed file, where it's passed a port
// to communicate with the extension and data sent from the
// background page.
var bufferData = function (port, postData) {

  if (window.top !== window) return;

  // Transform the data somewhat, and then create an overlay.
  // When it's done, fire buffer_done back to the extension
  var createOverlay = function (data, config) {
    if( data.embed ) {
      if( typeof data.embed === "object" ) {
        for( var i in data.embed ) {
          if( data.embed.hasOwnProperty(i) ) {
            data[i] = data.embed[i];
          }
        }
        if( data.embed.text && !data.embed.url ) {
          data.url = null;
        }
        data.embed = null;
      } else {
        data.text = data.embed;
        data.url = null;
        data.embed = null;
      }
    }
    bufferOverlay(data, config, port, function (overlaydata) {
      port.emit("buffer_done", overlaydata);
    });
  };

  // It all starts here.
  // createOverlay is the callback that should fire after getData has
  // gathered all the necessaries
  getData(postData, createOverlay);
};
<commit_msg>Strip superfluous quotes from selected text<commit_after>/* globals bufferpm, chrome, safari */

// Put together a query string for the iframe
var buildSrc = function(data, config) {

  var src = data.local ?
    config.overlay.localendpoint :
    config.overlay.endpoint;

  var qs = '';

  config.attributes.forEach(function(attr, i){
    if ( !data[ attr.name ] ) return;
    if ( qs.length ) qs += '&';
    qs += attr.name + '=' + attr.encode( data[attr.name] );
  });

  if (qs.length) src += '?' + qs;

  return src;
};

var openPopUp = function(src, port, doneCallback) {

  // Safari doesn't allow window.open when popups are disabled
  if (typeof safari !== 'undefined') {
    port.emit('buffer_safari_open', src);
  } else {
    window.open(src, null, 'height=600,width=850');
  }

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind("buffermessage", function(overlaydata) {
    bufferpm.unbind("buffermessage");
    setTimeout(function () {
      doneCallback(overlaydata);
    }, 0);
    window.focus();
  });
};

/**
 * This is a list of domains that have a strict Content Security Policy
 * This is a temporary work around while we figure out a long term solution
 * to this issue: https://github.com/bufferapp/buffer-chrome/issues/12
 */
var CSPWhitelist = [
  'twitter.com',
  'github.com',
  'education.github.com',
  'medium.com',
  'www.npmjs.org',
  'www.npmjs.com' // DEC 2014
];

// Build that overlay!
// Triggered by code working from the button up
var bufferOverlay = function(data, config, port, doneCallback) {

  if( ! doneCallback ) doneCallback = function () {};
  if( ! config ) return;

  var src = buildSrc(data, config);
  var domain = window.location.hostname;

  if (xt.options['buffer.op.tpc-disabled'] ||
    CSPWhitelist.indexOf(domain) > -1 ) {
    return openPopUp(src, port, doneCallback);
  }

  // Create the iframe and add the footer:
  var iframe = document.createElement('iframe');

  iframe.allowtransparency = 'true';
  iframe.scrolling = 'no';
  iframe.id = 'buffer_overlay';
  iframe.name = 'buffer_overlay';
  iframe.style.cssText = config.overlay.getCSS();

  iframe.src = src;

  // Testing CSP issues w/ sandboxed iframe
  // var qs = src.split('?')[1];
  // iframe.src = xt.data.get('data/shared/extension.html?' + qs);

  document.body.appendChild(iframe);

  var styleTag = createStyleTag();
  document.body.appendChild(styleTag);

  var dashboardButton = createDashboardButton();
  document.body.appendChild(dashboardButton);

  var cancelButton = createCancelButton();
  document.body.appendChild(cancelButton);

  $(document).on('click', '.buffer-floating-cancel-btn', function() {
      closePopup(document, doneCallback);
  });

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind('buffermessage', function(overlaydata) {
      closePopup(document, doneCallback, overlaydata);
  });

  /**
   * Listen to ESC key and close the popup when hit.
   */
  $(document).keyup(function(e) {
    if (e.keyCode == 27) {
        closePopup(document, doneCallback);
    }
  });

  // Remove the loading image when we hear from the other side
  bufferpm.bind('buffer_loaded', function(data) {
    bufferpm.unbind('buffer_loaded');
    iframe.style.backgroundImage = 'none';

    if ($(".buffer-floating-cancel-btn").length) {
        $(".buffer-floating-cancel-btn").remove();
    }
  });
};

function closePopup(document, doneCallback, overlayData) {

    if ($("#buffer_overlay").length) {
        $("#buffer_overlay").remove();
    }

    if ($(".buffer-floating-cancel-btn").length) {
        $(".buffer-floating-cancel-btn").remove();
    }

    if ($(".buffer-floating-btn").length) {
        $(".buffer-floating-btn").remove();
    }

    bufferpm.unbind('buffermessage');
    bufferpm.unbind('buffer_addbutton');

    setTimeout(function () {
      doneCallback(overlayData);
    }, 0);

    window.focus();
}

var createStyleTag = function() {
  var style = document.createElement('style');
  style.type = 'text/css';

  var content = document.createTextNode([
    '.buffer-floating-btn:hover {',
      'text-decoration: none;',
      'color: #323b43;',
      'cursor: pointer;',
    '}'
  ].join(''));

  style.appendChild(content);

  return style;
};

var createDashboardButton = function() {

  var css = [
    'position: fixed;',
    'top: 10px;',
    'right: 10px;',
    'z-index: 2147483647;',
    'padding: 8px 10px 8px 32px;',
    'background-color: #fff;',
    'background-image: url(https://d389zggrogs7qo.cloudfront.net/images/bookmarklet_icon.png);',
    'background-repeat: no-repeat;',
    'background-size: 15px;',
    'background-position: 11px 12px;',
    'color: #323b43;',
    'border: 0;',
    'text-decoration: none;',
    'border-radius: 2px;',
    'text-decoration: none;',
    'font-size: 14px;',
    'line-height: 1.6;',
    'font-family: "Open Sans", Roboto, Helvetica, Arial;',
    'box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);'
  ].join('');

  var button = document.createElement('a');
  button.href = 'https://buffer.com/app';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-floating-btn');
  button.setAttribute('style', css);

  var text = document.createTextNode('Go to Buffer');
  button.appendChild(text);

  return button;
};

var createCancelButton = function() {

  var css = [
    'position: fixed;',
    'top: 10px;',
    'left: 10px;',
    'z-index: 2147483647;',
    'padding: 8px 10px 8px 10px;',
    'background-color: #fff;',
    'background-repeat: no-repeat;',
    'color: #323b43;',
    'border: 0;',
    'text-decoration: none;',
    'border-radius: 2px;',
    'text-decoration: none;',
    'font-size: 14px;',
    'line-height: 1.6;',
    'font-family: "Open Sans", Roboto, Helvetica, Arial;',
    'box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);'
  ].join('');

  var button = document.createElement('button');
  button.setAttribute('class', 'buffer-floating-btn buffer-floating-cancel-btn');
  button.setAttribute('style', css);

  var text = document.createTextNode('Cancel');
  button.appendChild(text);

  return button;
};

// getOverlayConfig returns the configuration object for use by bufferOverlay
var getOverlayConfig = function(postData){

  var config = {};

  // Set this to true for using a local server while testing
  config.local = false;

  var segments = window.location.pathname.split('/');

  config.pocketWeb = ( window.location.host.indexOf('getpocket') !== -1 && segments[2] === 'read' );

  // Specification for gathering data for the overlay
  config.attributes = [
    {
      name: "url",
      get: function (cb) {
        if(config.pocketWeb){
          var li = document.getElementsByClassName('original')[0];
          var link = li.getElementsByTagName('a')[0].href;
          cb(link);
        }
        else{
          cb(window.location.href);
        }
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "text",
      get: function (cb) {
        var selectedText = document.getSelection().toString();
        var quoteChars;

        if (selectedText) {
          // If quotes surround the selected text, strip them away
          quoteChars = ['"', '“', '”', '\'', '‘', '’', '«', '»'];
          if (quoteChars.indexOf(selectedText[0]) != -1 && quoteChars.indexOf(selectedText[selectedText.length - 1]) != -1) {
            selectedText = selectedText.slice(1, selectedText.length - 1);
          }

          return cb('"' + selectedText + '"');
        }

        if (config.pocketWeb){
          var headline = document.querySelectorAll('.reader_head h1')[0];
          var title = headline && headline.textContent;
          return cb(title);
        }

        var ogTitle = document.head.querySelector('meta[property="og:title"]');
        if (ogTitle && ogTitle.content && ogTitle.content.length) {
          return cb(ogTitle.content);
        }

        cb(document.title);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_tweet_id",
      get: function (cb) {
        cb(postData.retweeted_tweet_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_id",
      get: function (cb) {
        cb(postData.retweeted_user_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_name",
      get: function (cb) {
        cb(postData.retweeted_user_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_display_name",
      get: function (cb) {
        cb(postData.retweeted_user_display_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "picture",
      get: function (cb) {
        cb(postData.image);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "embed",
      get: function (cb) {
        cb(postData.embed);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "local",
      get: function (cb) {
        cb(config.local);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "version",
      get: function (cb) {
        cb(postData.version);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "placement",
      get: function (cb) {
        if( postData.placement ) cb(postData.placement);
        else cb('general');
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    }
  ];

  var loadingImgRel = 'img/white-loading-gif-small.gif';
  //TODO - Change to static.buffer when it's set up!
  var loadingImg = typeof chrome !== 'undefined' ?
    chrome.extension.getURL('data/shared/' +  loadingImgRel) :
    document.location.protocol === 'http:' ?
      'http://static.bufferapp.com/images/extensions/' + loadingImgRel :
      'https://d389zggrogs7qo.cloudfront.net/images/extensions/' + loadingImgRel;

  config.overlay = {
    endpoint: "https://buffer.com/add/",
    localendpoint: "https://local.buffer.com/add/",
    getCSS: function () {
      return [
        'border: none;',
        'height: 100%;',
        'width: 100%;',
        'position: fixed!important;',
        'z-index: 2147483646;',
        'top:0;',
        'left:0;',
        'display: block!important;',
        'max-width: 100%!important;',
        'max-height: 100%!important;',
        'padding: 0!important;',
        'background: rgba(0, 0, 0, 0.1) url(' + loadingImg +') no-repeat center center;',
        'background-size: 40px;'
      ].join('');
    }
  };

  return config;
};

// Method for handling the async firing of the cb
var executeAfter = function(done, count, args, cb) {
  if(done === count) {
    setTimeout(function(){
      cb.apply( null, args );
    }, 0);
  }
};

// Asynchronously gather data about the page and from embedded sources,
// like Twitter or Facebook. Currently the async is a bit over the top,
// and not used, but if we need async down the line, it's there.
var getData = function (postData, cb) {
  var config = getOverlayConfig( postData );
  var count = config.attributes.length;
  var done = 0;
  var data = {};

  config.attributes.forEach(function(attr, i){
    attr.get(function(d){
      done += 1;
      data[ attr.name ] = d;
      executeAfter(done, count, [ data, config ], cb);
    });
  });
};

// bufferData is triggered by the buffer_click listener in
// the buffer-browser-embed file, where it's passed a port
// to communicate with the extension and data sent from the
// background page.
var bufferData = function (port, postData) {

  if (window.top !== window) return;

  // Transform the data somewhat, and then create an overlay.
  // When it's done, fire buffer_done back to the extension
  var createOverlay = function (data, config) {
    if( data.embed ) {
      if( typeof data.embed === "object" ) {
        for( var i in data.embed ) {
          if( data.embed.hasOwnProperty(i) ) {
            data[i] = data.embed[i];
          }
        }
        if( data.embed.text && !data.embed.url ) {
          data.url = null;
        }
        data.embed = null;
      } else {
        data.text = data.embed;
        data.url = null;
        data.embed = null;
      }
    }
    bufferOverlay(data, config, port, function (overlaydata) {
      port.emit("buffer_done", overlaydata);
    });
  };

  // It all starts here.
  // createOverlay is the callback that should fire after getData has
  // gathered all the necessaries
  getData(postData, createOverlay);
};
<|endoftext|>


<commit_before>(function() {
  'use strict';

  var concurrentConfig = {
    ionic: {
      tasks: [],
      options: {
        limit: 5,
        logConcurrentOutput: true
      }
    },
    server: [
      'compass:server',
      'copy:styles',
      'copy:vendor',
      'copy:fonts',
      'jadengtemplatecache'
    ],
    test: [
      'compass',
      'copy:styles',
      'copy:vendor',
      'copy:fonts',
      'jadengtemplatecache'
    ],
    dist: {
      options: {
        limit: 5
      },
      tasks: [
        'compass:dist',
        'copy:dist',
        'copy:vendor',
        'copy:fonts',
        'jadengtemplatecache'
      ]
    },
    staging: [
      'compass:staging',
      'copy:styles',
      'copy:vendor',
      'copy:fonts',
      'jadengtemplatecache'
    ]
  };

  return module.exports = concurrentConfig;
})();
<commit_msg>do not jadengtemplatecache<commit_after>(function() {
  'use strict';

  var concurrentConfig = {
    ionic: {
      tasks: [],
      options: {
        limit: 5,
        logConcurrentOutput: true
      }
    },
    server: [
      'compass:server',
      'copy:styles',
      'copy:vendor',
      'copy:fonts',
      'jadengtemplatecache'
    ],
    test: [
      'compass',
      'copy:styles',
      'copy:vendor',
      'copy:fonts',
      'jadengtemplatecache'
    ],
    dist: {
      options: {
        limit: 5
      },
      tasks: [
        'compass:dist',
        'copy:dist',
        'copy:vendor',
        'copy:fonts',
        // 'jadengtemplatecache'
      ]
    },
    staging: [
      'compass:staging',
      'copy:styles',
      'copy:vendor',
      'copy:fonts',
      'jadengtemplatecache'
    ]
  };

  return module.exports = concurrentConfig;
})();
<|endoftext|>


<commit_before>var _ = require('underscore');
var express = require('express');

var db = require('../helpers/db');
var isBadRequest = require('../helpers/request-checker');

var router = express.Router();

var createTagQuery = function(id, tags) {
  var query = 'INSERT INTO tags ("place_id", "characteristic_id") VALUES ';

  for (var tag in tags) {
    query += ('(' + id + ', ' + tags[tag] + ')');

    if (tags[tag] === _.last(tags)) {
      query += ';';
    } else {
      query += ', ';
    }
  }

  return query;
};

router.get('/', function(req, res) {
  var query = "SELECT place_id, places.name, countries.name AS country, description, places.image, latitude, longitude, link, enabled FROM places LEFT JOIN countries ON countries.country_id = places.country_id ORDER BY place_id ASC";

  db.query(query)
    .then(function(result) {
      res.status(200).send(result.rows);
    })
    .catch(function(error) {
      res.status(500).send(error);
    });
});

router.post('/', function(req, res) {
  var values = [
    req.body.country_id,
    req.body.name,
    req.body.description,
    req.body.image,
    req.body.latitude,
    req.body.longitude,
    req.body.link,
    req.body.enabled,
    req.body.tags
  ];

  if (isBadRequest(values)) {
    res.status(400).send("Bad Request");
    return;
  }

  var query = 'INSERT INTO places ("country_id", "name", "description", "image", "latitude", "longitude", "link", "enabled") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING place_id';

  db.query(query, _.initial(values))
    .then(function(result) {
      var row = result.rows[0];
      var tags = _.last(values);
      var query = createTagQuery(row.place_id, tags);

      db.query(query)
        .then(function(result) {
          res.status(201).send(row);
        })
        .catch(function(error) {
          res.status(500).send(error);
        });
    })
    .catch(function(error) {
      res.status(500).send(error);
    });
});

router.put('/:id', function(req, res) {
  var values = [
    req.body.name,
    req.body.description,
    req.body.image,
    req.body.latitude,
    req.body.longitude,
    req.body.link,
    req.body.enabled
  ];

  if (isBadRequest(values)) {
    res.status(400).send("Bad Request");
    return;
  }

  var id = req.params.id;
  var tags = req.body.tags;
  var shouldUpdateTags = !(_.isUndefined(tags));

  var tagPromise = new Promise(function(resolve, reject) {

    if (!shouldUpdateTags) {
      resolve("Not updating tags");
    } else {
      var tagQuery = 'DELETE FROM tags WHERE tags.place_id = $1';
      var tagValues = [id];

      var errorCallback = function(error) {
        res.status(500).send(error);
        reject(error);
      };

      db.query(tagQuery, tagValues)
        .then(function(result) {
          tagQuery = createTagQuery(id, tags);

          db.query(tagQuery)
            .then(function(result) {
              resolve("Done updating tags");
            })
            .catch(errorCallback);
        })
        .catch(errorCallback);
    }
  });

  tagPromise.then(function(result) {
    console.log(result);

    var query = "UPDATE places SET name = $1, description = $2, image = $3, latitude = $4, longitude = $5, link = $6, enabled = $7 WHERE place_id = " + id ;

    db.query(query, values)
    .then(function(result) {
      res.status(200).end();
    })
    .catch(function(error) {
      res.status(500).send(error);
    });
  });
});

module.exports = router;
<commit_msg>Fix error when there are no tags to insert<commit_after>var _ = require('underscore');
var express = require('express');

var db = require('../helpers/db');
var isBadRequest = require('../helpers/request-checker');

var router = express.Router();

var createTagQuery = function(id, tags) {
  var query = 'INSERT INTO tags ("place_id", "characteristic_id") VALUES ';

  for (var tag in tags) {
    query += ('(' + id + ', ' + tags[tag] + ')');

    if (tags[tag] === _.last(tags)) {
      query += ';';
    } else {
      query += ', ';
    }
  }

  return query;
};

router.get('/', function(req, res) {
  var query = "SELECT place_id, places.name, countries.name AS country, description, places.image, latitude, longitude, link, enabled FROM places LEFT JOIN countries ON countries.country_id = places.country_id ORDER BY place_id ASC";

  db.query(query)
    .then(function(result) {
      res.status(200).send(result.rows);
    })
    .catch(function(error) {
      res.status(500).send(error);
    });
});

router.post('/', function(req, res) {
  var values = [
    req.body.country_id,
    req.body.name,
    req.body.description,
    req.body.image,
    req.body.latitude,
    req.body.longitude,
    req.body.link,
    req.body.enabled,
    req.body.tags
  ];

  if (isBadRequest(values)) {
    res.status(400).send("Bad Request");
    return;
  }

  var query = 'INSERT INTO places ("country_id", "name", "description", "image", "latitude", "longitude", "link", "enabled") VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING place_id';

  db.query(query, _.initial(values))
    .then(function(result) {
      var row = result.rows[0];
      var tags = _.last(values);
      var query = createTagQuery(row.place_id, tags);

      db.query(query)
        .then(function(result) {
          res.status(201).send(row);
        })
        .catch(function(error) {
          res.status(500).send(error);
        });
    })
    .catch(function(error) {
      res.status(500).send(error);
    });
});

router.put('/:id', function(req, res) {
  var values = [
    req.body.name,
    req.body.description,
    req.body.image,
    req.body.latitude,
    req.body.longitude,
    req.body.link,
    req.body.enabled
  ];

  if (isBadRequest(values)) {
    res.status(400).send("Bad Request");
    return;
  }

  var id = req.params.id;
  var tags = req.body.tags;
  var shouldUpdateTags = !(_.isUndefined(tags));

  var tagPromise = new Promise(function(resolve, reject) {

    if (!shouldUpdateTags) {
      resolve("Not updating tags");
    } else {
      var tagQuery = 'DELETE FROM tags WHERE tags.place_id = $1';
      var tagValues = [id];

      var errorCallback = function(error) {
        res.status(500).send(error);
        reject(error);
      };

      db.query(tagQuery, tagValues)
        .then(function(result) {
          tagQuery = createTagQuery(id, tags);

          if (tagQuery === "") {
            resolve("Done updating tags");
            return;
          }

          db.query(tagQuery)
            .then(function(result) {
              resolve("Done updating tags");
            })
            .catch(errorCallback);
        })
        .catch(errorCallback);
    }
  });

  tagPromise.then(function(result) {
    var query = "UPDATE places SET name = $1, description = $2, image = $3, latitude = $4, longitude = $5, link = $6, enabled = $7 WHERE place_id = " + id;

    db.query(query, values)
      .then(function(result) {
        res.status(200).end();
      })
      .catch(function(error) {
        res.status(500).send(error);
      });
  });
});

module.exports = router;
<|endoftext|>


<commit_before>"use strict";

let PageOrder = Vue.component(
	'page-order',
	{
		template: `

Ordering

To make an order, please email me with the product names you would like to buy and I will arrange a Paypal invoice — no Paypal account required. (Something more automated should be coming soon!) Anything listed in the <router-link :to="\{ path: '/catalog'}">Catalog</router-link> or further down on this page is available to buy both online and in person at anime conventions. Let me know if you want priority shipping.

All prices are in US Dollars.

Customers not satisfied with the quality of their purchased magnets should contact me within one week of receiving them, along with a photo or scan of the unsatisfactory magnets. Such magnets may be eligible for free replacement. (Please save the packaging if you have a complaint, as the batch number can help me understand what problems you might be having.)

Shipping

Shipping rates: $3 to the US ($6.50 for priority), $9 to Canada, and $13 elsewhere. Priority to other countries is much more expensive and not much faster than first class mail, but can be arranged if required — please email me for a price estimate.

Packages to the US usually arrive 2-4 days after shipping, and international orders usually arrive in 7-10 days, though packages can of course be randomly delayed, particularly when customs become involved. If prompt arrival is important, consider that priority mail has guarantees for delivery dates whereas first class mail does not.

Orders will be shipped within one or two days, but orders involving custom work will be shipped when the custom work is done.

I am not responsible for VAT or any other import fees, so please be aware of your country’s policies for international mail. (I am also unwilling to lie about the value of the package on custom forms.)

Magnets

Anything listed as in-stock in the <router-link :to="\{ path: '/catalog'}">General Catalog</router-link> is available to order online or at anime conventions where I have an Artist Alley table. (For a list of conventions I am attending, see the <router-link :to="\{ path: '/'}">Info</router-link> section.) Retired characters are not available for purchase except as a special order, which follow the procedure for POD magnets specified in the <router-link :to="\{ path: '/custom'}">Custom</router-link> section.

Substitutions are not possible for characters in a set. Nor are partial set sales possible, except in the case of already-orphaned magnets. (See below for information about orphans.)

Grab Bags

I have a variety of blind grab bags available online, as well as at conventions that allow them, as an affordable way to acquire characters in bulk. You will not be able to see what characters are included until you open the bag, but I sometimes limit the category of the bag to help narrow things down. (See the <router-link :to="\{ path: '/catalog'}">Catalog</router-link> to learn about what characters are available in each of the six color-coded categories.)

Grab Bags are $5, and include at least 3 characters at a value of at least $10.

Super Grab Bags are $20, and include at least 13 characters at a value of at least $50.

I make grab bags in batches known to contain no duplicate products. So while I don’t know exactly what each bag contains, I am theoretically able to prevent duplicates when grab bags are ordered together (provided they aren’t ordered in too-great of a quantity). If you want bags from a specific batch please let me know when you order.

All bags contain new, undamaged products.

Grab Bags Currrently In Stock

							Category
							Type
							Qty
							Batch #

							Anime / Manga
							Super Grab Bag
							4x
							A1

							Grab Bag
							10x
							A2

							7x
							A3

							Game and Pokémon
							Super Grab Bag
							1x
							V0

							4x
							V1

							Grab Bag
							18x
							V2

							14x
							V3

							Western Animation
							Super Grab Bag
							1x
							W1

							Grab Bag
							2x
							W2

							all categories
							Super Grab Bag
							1x
							00

Combos

At conventions I offer discounts for certain combinations of sets. These are now also available online!

All 8 Classic Doctors: $28

First Doctor through Eighth Doctor. Save $4!

All Doctor Who characters: $48

First Doctor through Twelfth Doctor plus DW Companions Set 2. (DW Companions Set 1 is sold out and retired, unfortunately.) Save $12!

All My Little Pony characters: $49

Every My Little Pony character I have in stock. Save $23!

All Pokémon characters: $85

Every Pokémon character I have in stock. Currently includes 15 trainers and 37 Pokémon! Save $54!

All Anime/Manga characters: $282

All in-stock Anime/Manga characters (currently 98 total), 25% off! Save $94!

All Video Game characters: $157.50

All in-stock Video Game characters (currently 53 total), 25% off! Save $52.50!

EVERY MAGNET I HAVE IN STOCK: $490

WOW! All in-stock characters for $1.75 each! This is currently 280 characters! I’ll even throw in some extra, never-released characters! THIS IS MORE THAN 50% OFF! YOU’LL DROWN IN MAGNETS!

Discounted Sets

Previously convention-exclusive, discounted sets are now available online! Discounted sets are not flawed enough to be discarded but are flawed enough to warrant being sold at a reduced rate. Sometimes it’s as simple as a fleck of dust under the lamination layer that causes a bubble, or a misalignment of the cut edge by 1-1.5 mm.

Coming Soon!

Orphaned Singless

Sets with damaged pieces may leave orphaned characters/heads/bodies/accessories which are otherwise flawless. Such pieces were normally used at conventions are part of the “demo board” but are now available online! NOTE: Unlike magnets from the convention demo board, these orphans are in new condition unless mentioned otherwise.

Coming Soon!

		`
	}
);
<commit_msg>Inventory updates, and introduced discount sets and orphans<commit_after>"use strict";

let PageOrder = Vue.component(
	'page-order',
	{
		template: `

Ordering

To make an order, please email me with the product names you would like to buy and I will arrange a Paypal invoice — no Paypal account required. (Something more automated should be coming soon!) Anything listed in the <router-link :to="\{ path: '/catalog'}">Catalog</router-link> or further down on this page is available to buy both online and in person at anime conventions. Let me know if you want priority shipping.

All prices are in US Dollars.

Customers not satisfied with the quality of their purchased magnets should contact me within one week of receiving them, along with a photo or scan of the unsatisfactory magnets. Such magnets may be eligible for free replacement. (Please save the packaging if you have a complaint, as the batch number can help me understand what problems you might be having.)

Shipping

Shipping rates: $3 to the US ($6.50 for priority), $9 to Canada, and $13 elsewhere. Priority to other countries is much more expensive and not much faster than first class mail, but can be arranged if required — please email me for a price estimate.

Packages to the US usually arrive 2-4 days after shipping, and international orders usually arrive in 7-10 days, though packages can of course be randomly delayed, particularly when customs become involved. If prompt arrival is important, consider that priority mail has guarantees for delivery dates whereas first class mail does not.

Orders will be shipped within one or two days, but orders involving custom work will be shipped when the custom work is done.

I am not responsible for VAT or any other import fees, so please be aware of your country’s policies for international mail. (I am also unwilling to lie about the value of the package on custom forms.)

Magnets

Anything listed as in-stock in the <router-link :to="\{ path: '/catalog'}">General Catalog</router-link> is available to order online or at anime conventions where I have an Artist Alley table. (For a list of conventions I am attending, see the <router-link :to="\{ path: '/'}">Info</router-link> section.) Retired characters are not available for purchase except as a special order, which follow the procedure for POD magnets specified in the <router-link :to="\{ path: '/custom'}">Custom</router-link> section.

Substitutions are not possible for characters in a set. Nor are partial set sales possible, except in the case of already-orphaned magnets. (See below for information about orphans.)

Grab Bags

I have a variety of blind grab bags available online, as well as at conventions that allow them, as an affordable way to acquire characters in bulk. You will not be able to see what characters are included until you open the bag, but I sometimes limit the category of the bag to help narrow things down. (See the <router-link :to="\{ path: '/catalog'}">Catalog</router-link> to learn about what characters are available in each of the six color-coded categories.)

Grab Bags are $5, and include at least 3 characters at a value of at least $10.

Super Grab Bags are $20, and include at least 13 characters at a value of at least $50.

I make grab bags in batches known to contain no duplicate products. So while I don’t know exactly what each bag contains, I am theoretically able to prevent duplicates when grab bags are ordered together (provided they aren’t ordered in too-great of a quantity). If you want bags from a specific batch please let me know when you order.

All bags contain new, undamaged products.

Grab Bags Currrently In Stock

							Category
							Type
							Qty
							Batch #

							Anime / Manga
							Super Grab Bag
							1x
							A1

							Grab Bag
							5x
							A2

							7x
							A3

							Game and Pokémon
							Super Grab Bag
							1x
							V0

							3x
							V1

							Grab Bag
							16x
							V2

							14x
							V3

							Western Animation
							Super Grab Bag
							1x
							W1

							Grab Bag
							2x
							W2

							all categories
							Super Grab Bag
							1x
							00

Combos

At conventions I offer discounts for certain combinations of sets. These are now also available online!

All 8 Classic Doctors: $28

First Doctor through Eighth Doctor. Save $4!

All Remaining My Little Pony characters: $25

Every My Little Pony character I have in stock. Save $17!

All Pokémon characters: $80

Every Pokémon character I have in stock. Currently includes 12 trainers and 35 Pokémon! Save $40!

All Anime/Manga characters: $241.50

All in-stock Anime/Manga characters (currently 83 total), 25% off! Save $80.50!

All Video Game characters: $136.50

All in-stock Video Game characters (currently 46 total), 25% off! Save $45.50!

EVERY MAGNET I HAVE IN STOCK: $407.75

WOW! All in-stock characters for $1.75 each! This is currently 233 characters! I’ll even throw in some extra, never-released characters! THIS IS MORE THAN 50% OFF! YOU’LL DROWN IN MAGNETS!

Discounted Sets

Previously convention-exclusive, discounted sets are now available online! Discounted sets are not flawed enough to be discarded but are flawed enough to warrant being sold at a reduced rate. Sometimes it’s as simple as a fleck of dust under the lamination layer that causes a bubble, or a misalignment of the cut edge by 1-1.5 mm.

Dragonball Z Set 1: $6 (normally $8)

Miscellaneous small defects

Hunter x Hunter Set: $6 (normally $8)

Slightly used

Dragonball Z Set 2: $4 (normally $8)

Misaligned cut

Mass Effect Set 1: $5 (normally $8)

Misaligned cut

Harry Potter: $3 (normally $4)

Red dot in hair

Ninth Doctor: $3 (normally $4)

Dot under lamination layer

Firefly Set 2: $8 (normally $12)

Dust under lamination layer; torn paper under lamination layer

Deadpool: $2 (normally $4)

Ripped foot

Orphaned Singless

Sets with damaged pieces may leave orphaned characters/heads/bodies/accessories which are otherwise flawless. Such pieces were normally used at conventions are part of the “demo board” but are now available online! NOTE: Unlike magnets from the convention demo board, these orphans are in new condition unless mentioned otherwise.

Yuu Yuu Hakusho Hiei Set, Hiei only: $3 (two available)

normally bundled with Hiei for $8

Spirited Away Set 2, No Face only: $2 (two available)

normally bundled with "Mouse and Bird" for $6

Final Fantasy XIII Set, Lightning only: $3 (two available)

normally bundled with Hope for $8

Mass Effect Set 1, Garrus only: $3 (six available)

normally bundled with Wrex for $8

Korra Set, Bolin only: $2 (two available)

normally bundled with Mako and Korra for $12; in addition, Bolin's foot has been cut into slightly

Chrono Trigger Set 2, Magus only: $3

normally bundled with Frog and Ayla for $12

Firefly Set 3, Kaylee and Simon only: $5

normally bundled with River and bloody knives for $12

Firefly Set 3, Kaylee and Simon with River's body: $6

normally bundled with River's head and bloody knives for $12

Lizbeth from Pokémon (unreleased): $1 (many available)

never released character!

Gym Leader Skyla from Pokémon (unreleased): $1 (many available)

never released character!

		`
	}
);
<|endoftext|>


<commit_before>/**
 * @file MonthPicker component
 * @author sunday(sunday.wei@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';

import TouchRipple from '../TouchRipple';

class MonthPicker extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.defaultTable = {
            row_number: 4,
            col_number: 3
        };

        this.state = {
            selectYear: props.year,
            selectMonth: props.month,
            currentYear: moment(props.value).format('YYYY'),
            currentMonth: moment(props.value).format('MM'),
            selectDay: props.day
        };

        this.previousLevel = ::this.previousLevel;
        this.selectDate = ::this.selectDate;
        this.previousYear = ::this.previousYear;
        this.nextYear = ::this.nextYear;

    }

    previousLevel() {
        this.props.previousClick && this.props.previousClick('year');
    }

    selectDate(s_month) {
        let {selectYear} = this.state;
        this.setState({
            currentYear: selectYear,
            currentMonth: s_month,
            selectMonth: s_month
        }, () => {
            this.props.onChange && this.props.onChange({year: selectYear, month: s_month});
        });
    }

    previousYear() {
        let {currentYear, currentMonth, selectYear, selectMonth} = this.state;
        selectYear = +selectYear - 1;
        if (Number(currentYear) === Number(selectYear)) {
            selectMonth = currentMonth;
        } else {
            selectMonth = undefined;
        }
        this.setState({
            selectYear: selectYear,
            selectMonth: selectMonth
        });
    }

    nextYear() {
        let {currentYear, currentMonth, selectYear, selectMonth} = this.state;
        selectYear = +selectYear + 1;

        if (Number(currentYear) === Number(selectYear)) {
            selectMonth = currentMonth;
        } else {
            selectMonth = undefined;
        }
        this.setState({
            selectYear: selectYear,
            selectMonth: selectMonth
        });
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.props.value || nextProps.year !== this.props.year || nextProps.month !== this.props.month) {
            const value = nextProps.value;
            this.setState({
                selectYear: nextProps.year,
                selectMonth: nextProps.month,
                currentYear: moment(value).format('YYYY'),
                currentMonth: moment(value).format('MM')
            });
        }
    }

    componentDidMount() {
        const {value, year, month} = this.props;
        if (value && year && month) {
            this.setState({
                selectYear: year,
                selectMonth: month,
                currentYear: moment(value).format('YYYY'),
                currentMonth: moment(value).format('MM')
            });

        }
    }

    render() {

        const {className, maxValue, minValue} = this.props;

        let {selectYear, selectMonth, currentYear} = this.state;

        const {previousYear, nextYear, selectDate, previousLevel} = this;
        let current_months = [],
            ul_list = [];
        let MonthEn = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

        for (let i = 0; i < MonthEn.length; i++) {
            let Months = (
                <li className={`${(currentYear == selectYear) && (Number(selectMonth) == (i + 1)) ? 'active' : ''}
                                ${(maxValue && (moment(maxValue).format('YYYY') == selectYear) && (+(moment(maxValue).format('MM'))) < (i + 1)) ||
                (minValue && (moment(minValue).format('YYYY') == selectYear) && (+(moment(minValue).format('MM'))) > (i + 1)) ? 'item-gray' : 'current-years'}
                                `}
                    key={'current' + i}
                    onClick={() => {
                        if ((maxValue && (moment(maxValue).format('YYYY') == selectYear) && (+(moment(maxValue).format('MM'))) < (i + 1)) ||
                            (minValue && (moment(minValue).format('YYYY') == selectYear) && (+(moment(minValue).format('MM'))) > (i + 1))) {
                            return;
                        } else {
                            selectDate(i + 1);
                        }
                    }}>

                        {MonthEn[i]}
                        <TouchRipple/>

);
            current_months.push(Months);
        }
        if (current_months.length > 0) {
            for (let i = 0; i < this.defaultTable.row_number; i++) {
                let li_list = [],
                    start_index = i * this.defaultTable.col_number,
                    end_index = (i + 1) * this.defaultTable.col_number;
                for (let j = start_index; j < end_index; j++) {
                    li_list.push(current_months[j]);
                }
                ul_list.push(li_list);
            }
        }
        selectYear = selectYear.toString();
        let leftNextYear = maxValue && (moment(maxValue).format('YYYY') <= +selectYear);
        let rightPreYear = minValue && (moment(minValue).format('YYYY') >= +selectYear);

        return (

                    {
                        rightPreYear ?
                            null
                            :

                                <TouchRipple/>

                    }

                    {selectYear}
                    {
                        leftNextYear ?
                            null :

                                <TouchRipple/>

                    }

                        {
                            ul_list.map((item, key) => {
                                return (
{item}
);
                            })
                        }

        );
    }
};

MonthPicker.propTypes = {
    className: PropTypes.string,
    value: PropTypes.any,
    maxValue: PropTypes.any,
    minValue: PropTypes.any,
    year: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    month: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    day: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    onChange: PropTypes.func,
    previousClick: PropTypes.func
};

export default MonthPicker;<commit_msg>update MonthPicker<commit_after>/**
 * @file MonthPicker component
 * @author sunday(sunday.wei@derbysoft.com)
 */

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';

import TouchRipple from '../TouchRipple';

class MonthPicker extends Component {

    constructor(props, ...restArgs) {

        super(props, ...restArgs);

        this.defaultTable = {
            row_number: 4,
            col_number: 3
        };

        this.state = {
            selectYear: props.year,
            selectMonth: props.month,
            currentYear: moment(props.value).format('YYYY'),
            currentMonth: moment(props.value).format('MM'),
            selectDay: props.day
        };

        this.previousLevel = ::this.previousLevel;
        this.selectDate = ::this.selectDate;
        this.previousYear = ::this.previousYear;
        this.nextYear = ::this.nextYear;

    }

    previousLevel() {
        this.props.previousClick && this.props.previousClick('year');
    }

    selectDate(s_month) {
        let {selectYear} = this.state;
        this.setState({
            currentYear: selectYear,
            currentMonth: s_month,
            selectMonth: s_month
        }, () => {
            this.props.onChange && this.props.onChange({year: selectYear, month: s_month});
        });
    }

    previousYear() {
        let {currentYear, currentMonth, selectYear, selectMonth} = this.state;
        selectYear = +selectYear - 1;
        if (Number(currentYear) === Number(selectYear)) {
            selectMonth = currentMonth;
        } else {
            selectMonth = undefined;
        }
        this.setState({
            selectYear: selectYear,
            selectMonth: selectMonth
        });
    }

    nextYear() {
        let {currentYear, currentMonth, selectYear, selectMonth} = this.state;
        selectYear = +selectYear + 1;

        if (Number(currentYear) === Number(selectYear)) {
            selectMonth = currentMonth;
        } else {
            selectMonth = undefined;
        }
        this.setState({
            selectYear: selectYear,
            selectMonth: selectMonth
        });
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.value !== this.props.value || nextProps.year !== this.props.year || nextProps.month !== this.props.month) {
            const value = nextProps.value;
            this.setState({
                selectYear: nextProps.year,
                selectMonth: nextProps.month,
                currentYear: moment(value).format('YYYY'),
                currentMonth: moment(value).format('MM')
            });
        }
    }

    componentDidMount() {
        const {value, year, month} = this.props;
        if (value && year && month) {
            this.setState({
                selectYear: year,
                selectMonth: month,
                currentYear: moment(value).format('YYYY'),
                currentMonth: moment(value).format('MM')
            });

        }
    }

    render() {

        const {className, maxValue, minValue} = this.props;

        let {selectYear, selectMonth, currentYear} = this.state;

        const {previousYear, nextYear, selectDate, previousLevel} = this;
        let current_months = [],
            ul_list = [];
        let MonthEn = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

        for (let i = 0; i < MonthEn.length; i++) {
            let Months = (
                <li className={`${(currentYear == selectYear) && (Number(selectMonth) == (i + 1)) ? 'active' : ''}
                                ${(maxValue && (moment(maxValue).format('YYYY') == selectYear) && (+(moment(maxValue).format('MM'))) < (i + 1)) ||
                (minValue && (moment(minValue).format('YYYY') == selectYear) && (+(moment(minValue).format('MM'))) > (i + 1)) ? 'item-gray' : 'current-years'}
                                `}
                    key={'current' + i}
                    onClick={() => {
                        if ((maxValue && (moment(maxValue).format('YYYY') == selectYear) && (+(moment(maxValue).format('MM'))) < (i + 1)) ||
                            (minValue && (moment(minValue).format('YYYY') == selectYear) && (+(moment(minValue).format('MM'))) > (i + 1))) {
                            return;
                        } else {
                            selectDate(i + 1);
                        }
                    }}>

                        {MonthEn[i]}
                        <TouchRipple/>

);
            current_months.push(Months);
        }
        if (current_months.length > 0) {
            for (let i = 0; i < this.defaultTable.row_number; i++) {
                let li_list = [],
                    start_index = i * this.defaultTable.col_number,
                    end_index = (i + 1) * this.defaultTable.col_number;
                for (let j = start_index; j < end_index; j++) {
                    li_list.push(current_months[j]);
                }
                ul_list.push(li_list);
            }
        }
        selectYear = selectYear.toString();
        let leftNextYear = maxValue && (moment(maxValue).format('YYYY') <= +selectYear);
        let rightPreYear = minValue && (moment(minValue).format('YYYY') >= +selectYear);

        return (

                    {
                        rightPreYear ?
                            null
                            :

                                <TouchRipple/>

                    }

                    {selectYear}
                    {
                        leftNextYear ?
                            null :

                                <TouchRipple/>

                    }

                        {
                            ul_list.map((item, key) =>

                                    {item}

                            )
                        }

        );
    }
};

MonthPicker.propTypes = {
    className: PropTypes.string,
    value: PropTypes.any,
    maxValue: PropTypes.any,
    minValue: PropTypes.any,
    year: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    month: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    day: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    onChange: PropTypes.func,
    previousClick: PropTypes.func
};

export default MonthPicker;<|endoftext|>