<commit_before>'use strict';

const P = require('bluebird');
const _ = require('underscore');
const domino = require('domino');
const striptags = require('striptags');
const api = require('./api-util');
const mwapi = require('./mwapi');
const dateUtil = require('./dateUtil');
const HTTPError = require('./util').HTTPError;
const parsoid = require('./parsoid-access');

const DEFAULT_THUMB_SIZE = 320;

const structuredCaptionsQuery = {
    action: 'wbgetentities',
    format: 'json',
    formatversion: 2,
    sites: 'commonswiki',
    props: 'labels|info',
};

/**
 * get page object from api response
 * @param  {Object} response  MWAPI response
 * @param  {Bool} dontThrow if true throw HTTP error 404
 * @return {Object}           Page Object
 */
function getPageObject(response, dontThrow) {
    const page = response.body.query.pages[0];
    if (!page.pageid || page.missing === true) {
        if (dontThrow) {
            page.notfound = true;
            return page;
        } else {
            throw new HTTPError({
                status: 404,
                type: 'not_found',
                title: 'No picture of the day for this date',
                detail: 'There is no picture of the day for this date.'
            });
        }
    }
    return page;
}

/** @param {!domino.Document} doc
    @return {void} */
function removeLangLabels(doc) {
    const labels = doc.querySelectorAll('span[class*=langlabel-]') || [];
    labels.forEach((element) => {
        element.parentNode.removeChild(element);
    });
}

/** @param {!domino.Document} doc
    @return {!Object.<string, string>} Map of languages to descriptions */
function queryDescriptions(doc) {
    const descriptions = {};
    const descriptionElements = doc.querySelectorAll('.description[class*=lang-]') || [];
    descriptionElements.forEach((element) => {
        descriptions[element.lang] = element.innerHTML.trim();
    });
    return descriptions;
}

/**
 * Converts a metadata array to a standard JS key-value object.
 * @param {!Object[]} meta A metadata array from Commons ([ { name: foo, value: bar }, ... ])
 * @return {!Object} a consolidated object ({ foo: bar, ... })
 */
function metadataToObject(meta) {
    return Object.assign(...meta.map(item => ({ [item.name]: item.value })));
}

/**
 * Provides a consistent structure for extended metadata (extmetadata) properties returned from the
 * MW API.  As returned directly from the MW API, these values may be strings or objects, and may
 * or may not specify the language in which they are provided.  This provides some structure to help
 * clients consume the data more easily.
 * The result language will be chosen according to the following algorithm:
 * 1) If a key for preferredLang exists, use preferredLang;
 * 2) Else, if the key exists in English, use English;
 * 3) Else, use the first available language.
 * The return value will include the following properties:
 * html (required): the original value text in the chosen language, likely (but not necessarily)
 *   HTML-formatted;
 * text (required): the original value text with HTML-formatting stripped;
 * lang (optional): the chosen language code, if language codes were provided

 * @param  {!Object|string} val Value of the extmetadata property
 * @param  {!string} preferredLang preferred language (most likely the request language)
 * @return {?Object} structured values for the extmetadata property
 */
function structureExtMetadataValue(val, preferredLang) {
    let html;
    let lang;

    if (typeof val === 'object') {
        if (val[preferredLang]) {
            lang = preferredLang;
        } else if (val.en) {
            lang = 'en';
        } else if (Object.keys(val)[0]) {
            lang = Object.keys(val)[0];
        } else {
            return;
        }
        html = val[lang];
    } else if (typeof val === 'string') {
        html = val;
    } else {
        return;
    }

    return {
        html,
        text: striptags(html).trim(),
        lang
    };
}

/**
 * Get structured artist info from extmetadata (to the extent possible).
 * We take a rather conservative approach here:
 * 1) In the 'html' property, we always return the full HTML string.
 * 2) If the field contains only plain text, we assume this is an artist name and
 *    return it in the 'name' property.
 * 3) If the HTML string is of a specific common form, we parse the user name and
 *    user page from it, and return them in the 'name' and 'user_page' fields respectively.
 *    That form is "Foo".
 * @param {!Object|string} val The Artist value from extmetadata
 * @param {!string} preferredLang "artist" metadata value from the MW API
 * @return {?Object} structured artist info
 */
function getStructuredArtistInfo(val, preferredLang) {
    const result = structureExtMetadataValue(val, preferredLang);
    if (!result) {
        return;
    }
    const doc = domino.createDocument(result.html);
    if (!doc.body.textContent) {
        return;
    }
    let name;
    let userPage;
    if (doc.body.textContent === result.html) {
        name = doc.body.textContent;
    } else {
        const a = doc.body.querySelector('a');
        if (a && a.outerHTML === result.html && a.getAttribute('title') === `User:${doc.body.textContent}`
            // TODO: Update to handle all Wikimedia project domains
            && a.getAttribute('href') === `//commons.wikimedia.org/wiki/${a.title}`) {
            name = doc.body.textContent;
            userPage = `https:${a.getAttribute('href')}`;
        }
    }
    return Object.assign(result, { name, user_page: userPage });
}

/**
 * Provides consistent structuring of extmetadata values. See comments on
 * structureExtMetadataProperty for the specific steps applied to each value.
 * @param {!Object} extMetadata extmetadata object from MW API
 * @param {!string} preferredLang preferred language (most likely the request language)
 * @return {?Object} set of structured extmetadata values
 */
function structureExtMetadataValues(extMetadata, preferredLang) {
    return Object.keys(extMetadata).reduce((acc, cur) => {
        const val = extMetadata[cur].value;
        if (cur === 'Artist') {
            // Special-case handling for the Artist property (conditionally including a
            // derived Commons username and User page URL), which is used for something
            // or other in the Android app.
            acc[cur] = getStructuredArtistInfo(val, preferredLang);
        } else {
            acc[cur] = structureExtMetadataValue(val, preferredLang);
        }
        return acc;
    }, {});
}

/**
 * Provides consistent structuring of wbentities values for the structured captions field
 * @param  {!Object} wbentity object data from wbgetentities API action
 * @return {!Object}          structured captions for given wbentity
 */
function structureCaptions(wbentity) {
    const captions = wbentity && wbentity.labels;
    if (captions) {
        Object.keys(captions).map(index => {
            captions[index] = captions[index].value;
        });
    }
    return captions;
}

/**
 * Make image info result
 * @param {?Object[]} ids query results for Commons page ids (for media endpoint)
 * @param {!Object[]} wbentities mediainfo entities from SDC (wbgetentities)
 * @param {!Object[]} meta metadata query results to be parsed and formatted
 * @param {!Object} siteinfo Site info
 * @param {!string} preferredLang language code of the preferred language for metadata values
 * @param {?Object} desc Description Value pre-processed (needed for Picture of the day)
 * @return {!Object} Object of items identified by canonical title
 */
function makeResults(ids, wbentities, meta, siteinfo, preferredLang, desc = null) {
    const idLookup = ids && ids.reduce((acc, cur) => {
        if (cur.pageid) {
            acc[cur.title] = cur;
        }
        return acc;
    }, {});
    return meta.filter(item => item.imageinfo).map((item) => {
        const imageInfo = item.imageinfo[0];
        const meta = imageInfo.metadata && metadataToObject(imageInfo.metadata);
        const ext = imageInfo.extmetadata
            && structureExtMetadataValues(imageInfo.extmetadata, preferredLang);
        const isAudio = imageInfo.mediatype === 'AUDIO';
        const isSvg = imageInfo.mime && imageInfo.mime.includes('svg');
        const canonicalTitle = mwapi.getCanonicalFileTitle(mwapi.getDbTitle(item.title, siteinfo));
        const lookupTitle = mwapi.getCanonicalFileTitle(item.title);
        const wb_entity_id = idLookup && idLookup[lookupTitle] && `M${idLookup[lookupTitle].pageid}`;
        const wbentity = wbentities && wbentities[0] && wbentities[0][wb_entity_id];
        const captions = structureCaptions(wbentity);

        return {
            titles: {
                canonical: canonicalTitle,
                normalized: item.title,
                display: item.pageprops && item.pageprops.displaytitle || item.title
            },
            thumbnail: {
                source: imageInfo.thumburl,
                width: imageInfo.thumbwidth,
                height: imageInfo.thumbheight,
                mime: imageInfo.thumbmime
            },
            original: {
                source: imageInfo.url,
                width: isAudio || isSvg ? undefined : imageInfo.width,
                height: isAudio || isSvg ? undefined : imageInfo.height,
                mime: imageInfo.mime,
            },
            page_count: imageInfo.pagecount,
            file_page: imageInfo.descriptionurl,
            duration: meta && (meta.length || meta.playtime_seconds) || undefined,
            artist: ext && ext.Artist,
            credit: ext && ext.Credit,
            // Use original value for each of these since it's well standardized data
            license: {
                type: ext && ext.LicenseShortName && ext.LicenseShortName.html,
                code: ext && ext.License && ext.License.html,
                url: ext && ext.LicenseUrl && ext.LicenseUrl.html
            },
            description: desc && structureExtMetadataValue(desc) || ext && ext.ImageDescription,
            wb_entity_id,
            structured: {
                captions:  captions && Object.keys(captions).length ? captions : {},
            }
        };
    }).reduce((res, item) => Object.assign(res, { [item.titles.canonical]: item }), {});
}

/**
 * Normalize response for Featured Image endpoint
 *   1) title = titles.display && remove title
 *   2) rename original to image
 * @param  {Object} potd Picture of the day returned from makeResults()
 * @return {Object}      POTD normalized and compativle with featured image spec
 */
function normalizePotdResponse(potd) {
    potd.title = potd.titles.display;
    delete potd.titles;

    potd.image = potd.original;
    delete potd.original;

    return potd;
}

/**
 * Get Picture of the day description from Parsoid response
 * @param  {Object} req  req Server request
 * @param  {Object} body parsoid body response
 * @return {Object}      POTD description value
 */
function getPotdDescriptionValue(req, body) {
    const doc = domino.createDocument(body);
    removeLangLabels(doc);
    return queryDescriptions(doc);
}

/**
 * Builds the request to get the Picture of the day of a given date.
 * @param  {[type]} req      [description]
 * @param {!Date} date for which day the featured image of theday is requested
 * @param  {[type]} siteinfo [description]
 * @return {!Promise} a promise resolving as an JSON object containing the response
 */
function requestPictureOfTheDay(req, date, siteinfo) {
    const commonsDomain = api.getCommonsDomain(req.params.domain);
    const isoDate = dateUtil.formatISODate(date);
    let page;
    const filter = [
        'Artist',
        'LicenseShortName',
        'License',
        'LicenseUrl',
        'Credit',
    ];

    const imageInfoProps = [
        'url',
        'dimensions',
        'extmetadata',
    ];

    const query = {
        action: 'query',
        format: 'json',
        formatversion: 2,
        generator: 'images',
        prop: 'imageinfo|revisions',
        iiextmetadatafilter: filter.join('|'),
        iiextmetadatamultilang: true,
        iimetadataversion: 'latest',
        iiprop: imageInfoProps.join('|'),
        iiurlwidth: mwapi.CARD_THUMB_FEATURE_SIZE,
        rawcontinue: '',
        titles: `Template:Potd/${isoDate}`
    };

    return api.mwApiGet(req, query, commonsDomain).then((response) => {
        mwapi.checkForQueryPagesInResponse(req, response);
        page = getPageObject(response);

        const parsoidReq = Object.create(req);
        parsoidReq.params = Object.create(req.params);
        parsoidReq.params.title = page.title;
        parsoidReq.params.domain = commonsDomain;

        const structuredQuery = Object.assign(structuredCaptionsQuery, { titles: page.title });

        return P.join(
            parsoid.getParsoidHtml(parsoidReq),
            api.mwApiGet(req, structuredQuery, commonsDomain),
            (html, structured) => {
                const description = getPotdDescriptionValue(req, html.body);
                const lang = req.params.domain[0];
                const entities = structured.body.entities;
                const results = makeResults([page], [entities],[page], siteinfo, lang,
                    description);
                const potd = _.values(results)[0];

                return {
                    payload: normalizePotdResponse(potd),
                    meta: parsoid.getRevAndTidFromEtag(html.headers)
                };
            });
    });
}

/**
 * Gets metadata about media items from MW API.
 *
 * Batches requests by sets of 50 since we may be dealing with large title sets, and 50 titles is
 * the max we can specify at a time through the query title parameter.
 *
 * https://www.mediawiki.org/wiki/API:Query#Specifying_pages
 */
function getMetadataFromApi(req, titles, siteinfo) {
    const preferredLang = req.params.domain[0];
    const commonsDomain = api.getCommonsDomain(req.params.domain);

    const imageInfoProps = [
        'url',
        'mime',
        'size',
        'thumbmime',
        'metadata', // needed for 'duration' (is this really necessary?)
        'extmetadata'
    ];

    const metaDataQuery = {
        action: 'query',
        format: 'json',
        formatversion: 2,
        prop: 'imageinfo|pageprops',
        iiprop: imageInfoProps.join('|'),
        iiurlwidth: DEFAULT_THUMB_SIZE,
        iimetadataversion: 'latest',
        iiextmetadatamultilang: '',
        continue: ''
    };

    // Pass clones (.slice(0)) of titles to both MW API calls, to prevent it from being consumed by
    // the first before the second gets to it...
    return P.join(
        api.mwApiGetQueryBatched(req, { action: 'query' }, titles.slice(0), commonsDomain),
        api.mwApiGetEntitiesBatched(req, structuredCaptionsQuery, titles.slice(0), commonsDomain),
        api.mwApiGetQueryBatched(req, metaDataQuery, titles.slice(0)),
        (ids, structured, meta) => makeResults(ids && ids.pages, structured, meta && meta.pages,
            siteinfo, preferredLang));
}

module.exports = {
    getMetadataFromApi,
    getStructuredArtistInfo,
    requestPictureOfTheDay,
    testing: {
        structureExtMetadataValue,
        makeResults
    }
};
<commit_msg>Media: Fix TypeError when processing pages with only Mathoid images<commit_after>'use strict';

const P = require('bluebird');
const _ = require('underscore');
const domino = require('domino');
const striptags = require('striptags');
const api = require('./api-util');
const mwapi = require('./mwapi');
const dateUtil = require('./dateUtil');
const HTTPError = require('./util').HTTPError;
const parsoid = require('./parsoid-access');

const DEFAULT_THUMB_SIZE = 320;

const structuredCaptionsQuery = {
    action: 'wbgetentities',
    format: 'json',
    formatversion: 2,
    sites: 'commonswiki',
    props: 'labels|info',
};

/**
 * get page object from api response
 * @param  {Object} response  MWAPI response
 * @param  {Bool} dontThrow if true throw HTTP error 404
 * @return {Object}           Page Object
 */
function getPageObject(response, dontThrow) {
    const page = response.body.query.pages[0];
    if (!page.pageid || page.missing === true) {
        if (dontThrow) {
            page.notfound = true;
            return page;
        } else {
            throw new HTTPError({
                status: 404,
                type: 'not_found',
                title: 'No picture of the day for this date',
                detail: 'There is no picture of the day for this date.'
            });
        }
    }
    return page;
}

/** @param {!domino.Document} doc
    @return {void} */
function removeLangLabels(doc) {
    const labels = doc.querySelectorAll('span[class*=langlabel-]') || [];
    labels.forEach((element) => {
        element.parentNode.removeChild(element);
    });
}

/** @param {!domino.Document} doc
    @return {!Object.<string, string>} Map of languages to descriptions */
function queryDescriptions(doc) {
    const descriptions = {};
    const descriptionElements = doc.querySelectorAll('.description[class*=lang-]') || [];
    descriptionElements.forEach((element) => {
        descriptions[element.lang] = element.innerHTML.trim();
    });
    return descriptions;
}

/**
 * Converts a metadata array to a standard JS key-value object.
 * @param {!Object[]} meta A metadata array from Commons ([ { name: foo, value: bar }, ... ])
 * @return {!Object} a consolidated object ({ foo: bar, ... })
 */
function metadataToObject(meta) {
    return Object.assign(...meta.map(item => ({ [item.name]: item.value })));
}

/**
 * Provides a consistent structure for extended metadata (extmetadata) properties returned from the
 * MW API.  As returned directly from the MW API, these values may be strings or objects, and may
 * or may not specify the language in which they are provided.  This provides some structure to help
 * clients consume the data more easily.
 * The result language will be chosen according to the following algorithm:
 * 1) If a key for preferredLang exists, use preferredLang;
 * 2) Else, if the key exists in English, use English;
 * 3) Else, use the first available language.
 * The return value will include the following properties:
 * html (required): the original value text in the chosen language, likely (but not necessarily)
 *   HTML-formatted;
 * text (required): the original value text with HTML-formatting stripped;
 * lang (optional): the chosen language code, if language codes were provided

 * @param  {!Object|string} val Value of the extmetadata property
 * @param  {!string} preferredLang preferred language (most likely the request language)
 * @return {?Object} structured values for the extmetadata property
 */
function structureExtMetadataValue(val, preferredLang) {
    let html;
    let lang;

    if (typeof val === 'object') {
        if (val[preferredLang]) {
            lang = preferredLang;
        } else if (val.en) {
            lang = 'en';
        } else if (Object.keys(val)[0]) {
            lang = Object.keys(val)[0];
        } else {
            return;
        }
        html = val[lang];
    } else if (typeof val === 'string') {
        html = val;
    } else {
        return;
    }

    return {
        html,
        text: striptags(html).trim(),
        lang
    };
}

/**
 * Get structured artist info from extmetadata (to the extent possible).
 * We take a rather conservative approach here:
 * 1) In the 'html' property, we always return the full HTML string.
 * 2) If the field contains only plain text, we assume this is an artist name and
 *    return it in the 'name' property.
 * 3) If the HTML string is of a specific common form, we parse the user name and
 *    user page from it, and return them in the 'name' and 'user_page' fields respectively.
 *    That form is "Foo".
 * @param {!Object|string} val The Artist value from extmetadata
 * @param {!string} preferredLang "artist" metadata value from the MW API
 * @return {?Object} structured artist info
 */
function getStructuredArtistInfo(val, preferredLang) {
    const result = structureExtMetadataValue(val, preferredLang);
    if (!result) {
        return;
    }
    const doc = domino.createDocument(result.html);
    if (!doc.body.textContent) {
        return;
    }
    let name;
    let userPage;
    if (doc.body.textContent === result.html) {
        name = doc.body.textContent;
    } else {
        const a = doc.body.querySelector('a');
        if (a && a.outerHTML === result.html && a.getAttribute('title') === `User:${doc.body.textContent}`
            // TODO: Update to handle all Wikimedia project domains
            && a.getAttribute('href') === `//commons.wikimedia.org/wiki/${a.title}`) {
            name = doc.body.textContent;
            userPage = `https:${a.getAttribute('href')}`;
        }
    }
    return Object.assign(result, { name, user_page: userPage });
}

/**
 * Provides consistent structuring of extmetadata values. See comments on
 * structureExtMetadataProperty for the specific steps applied to each value.
 * @param {!Object} extMetadata extmetadata object from MW API
 * @param {!string} preferredLang preferred language (most likely the request language)
 * @return {?Object} set of structured extmetadata values
 */
function structureExtMetadataValues(extMetadata, preferredLang) {
    return Object.keys(extMetadata).reduce((acc, cur) => {
        const val = extMetadata[cur].value;
        if (cur === 'Artist') {
            // Special-case handling for the Artist property (conditionally including a
            // derived Commons username and User page URL), which is used for something
            // or other in the Android app.
            acc[cur] = getStructuredArtistInfo(val, preferredLang);
        } else {
            acc[cur] = structureExtMetadataValue(val, preferredLang);
        }
        return acc;
    }, {});
}

/**
 * Provides consistent structuring of wbentities values for the structured captions field
 * @param  {!Object} wbentity object data from wbgetentities API action
 * @return {!Object}          structured captions for given wbentity
 */
function structureCaptions(wbentity) {
    const captions = wbentity && wbentity.labels;
    if (captions) {
        Object.keys(captions).map(index => {
            captions[index] = captions[index].value;
        });
    }
    return captions;
}

/**
 * Make image info result
 * @param {?Object} ids query results for Commons page ids (for media endpoint)
 * @param {!Object[]} wbentities mediainfo entities from SDC (wbgetentities)
 * @param {!Object} meta metadata query results to be parsed and formatted
 * @param {!Object} siteinfo Site info
 * @param {!string} preferredLang language code of the preferred language for metadata values
 * @param {?Object} desc Description Value pre-processed (needed for Picture of the day)
 * @return {!Object} Object of items identified by canonical title
 */
function makeResults(ids, wbentities, meta, siteinfo, preferredLang, desc = null) {
    if (!meta || !meta.pages) {
        return {};
    }
    const idLookup = ids && ids.pages && ids.pages.reduce((acc, cur) => {
        if (cur.pageid) {
            acc[cur.title] = cur;
        }
        return acc;
    }, {});
    return meta.pages.filter(item => item.imageinfo).map((item) => {
        const imageInfo = item.imageinfo[0];
        const meta = imageInfo.metadata && metadataToObject(imageInfo.metadata);
        const ext = imageInfo.extmetadata
            && structureExtMetadataValues(imageInfo.extmetadata, preferredLang);
        const isAudio = imageInfo.mediatype === 'AUDIO';
        const isSvg = imageInfo.mime && imageInfo.mime.includes('svg');
        const canonicalTitle = mwapi.getCanonicalFileTitle(mwapi.getDbTitle(item.title, siteinfo));
        const lookupTitle = mwapi.getCanonicalFileTitle(item.title);
        const wb_entity_id = idLookup && idLookup[lookupTitle] && `M${idLookup[lookupTitle].pageid}`;
        const wbentity = wbentities && wbentities[0] && wbentities[0][wb_entity_id];
        const captions = structureCaptions(wbentity);

        return {
            titles: {
                canonical: canonicalTitle,
                normalized: item.title,
                display: item.pageprops && item.pageprops.displaytitle || item.title
            },
            thumbnail: {
                source: imageInfo.thumburl,
                width: imageInfo.thumbwidth,
                height: imageInfo.thumbheight,
                mime: imageInfo.thumbmime
            },
            original: {
                source: imageInfo.url,
                width: isAudio || isSvg ? undefined : imageInfo.width,
                height: isAudio || isSvg ? undefined : imageInfo.height,
                mime: imageInfo.mime,
            },
            page_count: imageInfo.pagecount,
            file_page: imageInfo.descriptionurl,
            duration: meta && (meta.length || meta.playtime_seconds) || undefined,
            artist: ext && ext.Artist,
            credit: ext && ext.Credit,
            // Use original value for each of these since it's well standardized data
            license: {
                type: ext && ext.LicenseShortName && ext.LicenseShortName.html,
                code: ext && ext.License && ext.License.html,
                url: ext && ext.LicenseUrl && ext.LicenseUrl.html
            },
            description: desc && structureExtMetadataValue(desc) || ext && ext.ImageDescription,
            wb_entity_id,
            structured: {
                captions:  captions && Object.keys(captions).length ? captions : {},
            }
        };
    }).reduce((res, item) => Object.assign(res, { [item.titles.canonical]: item }), {});
}

/**
 * Normalize response for Featured Image endpoint
 *   1) title = titles.display && remove title
 *   2) rename original to image
 * @param  {Object} potd Picture of the day returned from makeResults()
 * @return {Object}      POTD normalized and compativle with featured image spec
 */
function normalizePotdResponse(potd) {
    potd.title = potd.titles.display;
    delete potd.titles;

    potd.image = potd.original;
    delete potd.original;

    return potd;
}

/**
 * Get Picture of the day description from Parsoid response
 * @param  {Object} req  req Server request
 * @param  {Object} body parsoid body response
 * @return {Object}      POTD description value
 */
function getPotdDescriptionValue(req, body) {
    const doc = domino.createDocument(body);
    removeLangLabels(doc);
    return queryDescriptions(doc);
}

/**
 * Builds the request to get the Picture of the day of a given date.
 * @param  {[type]} req      [description]
 * @param {!Date} date for which day the featured image of theday is requested
 * @param  {[type]} siteinfo [description]
 * @return {!Promise} a promise resolving as an JSON object containing the response
 */
function requestPictureOfTheDay(req, date, siteinfo) {
    const commonsDomain = api.getCommonsDomain(req.params.domain);
    const isoDate = dateUtil.formatISODate(date);
    let page;
    const filter = [
        'Artist',
        'LicenseShortName',
        'License',
        'LicenseUrl',
        'Credit',
    ];

    const imageInfoProps = [
        'url',
        'dimensions',
        'extmetadata',
    ];

    const query = {
        action: 'query',
        format: 'json',
        formatversion: 2,
        generator: 'images',
        prop: 'imageinfo|revisions',
        iiextmetadatafilter: filter.join('|'),
        iiextmetadatamultilang: true,
        iimetadataversion: 'latest',
        iiprop: imageInfoProps.join('|'),
        iiurlwidth: mwapi.CARD_THUMB_FEATURE_SIZE,
        rawcontinue: '',
        titles: `Template:Potd/${isoDate}`
    };

    return api.mwApiGet(req, query, commonsDomain).then((response) => {
        mwapi.checkForQueryPagesInResponse(req, response);
        page = getPageObject(response);

        const parsoidReq = Object.create(req);
        parsoidReq.params = Object.create(req.params);
        parsoidReq.params.title = page.title;
        parsoidReq.params.domain = commonsDomain;

        const structuredQuery = Object.assign(structuredCaptionsQuery, { titles: page.title });

        return P.join(
            parsoid.getParsoidHtml(parsoidReq),
            api.mwApiGet(req, structuredQuery, commonsDomain),
            (html, structured) => {
                const description = getPotdDescriptionValue(req, html.body);
                const lang = req.params.domain[0];
                const entities = structured.body.entities;
                const results = makeResults([page], [entities],[page], siteinfo, lang,
                    description);
                const potd = _.values(results)[0];

                return {
                    payload: normalizePotdResponse(potd),
                    meta: parsoid.getRevAndTidFromEtag(html.headers)
                };
            });
    });
}

/**
 * Gets metadata about media items from MW API.
 *
 * Batches requests by sets of 50 since we may be dealing with large title sets, and 50 titles is
 * the max we can specify at a time through the query title parameter.
 *
 * https://www.mediawiki.org/wiki/API:Query#Specifying_pages
 */
function getMetadataFromApi(req, titles, siteinfo) {
    const preferredLang = req.params.domain[0];
    const commonsDomain = api.getCommonsDomain(req.params.domain);

    const imageInfoProps = [
        'url',
        'mime',
        'size',
        'thumbmime',
        'metadata', // needed for 'duration' (is this really necessary?)
        'extmetadata'
    ];

    const metaDataQuery = {
        action: 'query',
        format: 'json',
        formatversion: 2,
        prop: 'imageinfo|pageprops',
        iiprop: imageInfoProps.join('|'),
        iiurlwidth: DEFAULT_THUMB_SIZE,
        iimetadataversion: 'latest',
        iiextmetadatamultilang: '',
        continue: ''
    };

    // Pass clones (.slice(0)) of titles to both MW API calls, to prevent it from being consumed by
    // the first before the second gets to it...
    return P.join(
        api.mwApiGetQueryBatched(req, { action: 'query' }, titles.slice(0), commonsDomain),
        api.mwApiGetEntitiesBatched(req, structuredCaptionsQuery, titles.slice(0), commonsDomain),
        api.mwApiGetQueryBatched(req, metaDataQuery, titles.slice(0)),
        (ids, structured, meta) => makeResults(ids, structured, meta, siteinfo, preferredLang));
}

module.exports = {
    getMetadataFromApi,
    getStructuredArtistInfo,
    requestPictureOfTheDay,
    testing: {
        structureExtMetadataValue,
        makeResults
    }
};
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux'
import { Button, Form, Input } from 'reactstrap';
import countries from 'countries-cities'

import { searchProperty } from '../actions/index.js'

require('dotenv').config()

class Example extends React.Component {
  constructor(props){
    super(props)
    this.state={
      cities: []
    }
  }
  render() {
    return (

        <Form className="row">

            <input
              type="text"
              className="form-control"
              placeholder="Find a Property that you want"
              ref="search"
              onChange={()=> this.searchProp()}/>

            <select onChange={()=> this.searchProp()}>
              {this.state.cities.map((city,index)=>{
                  return <option ref="city">{city}</option>
              })}
            </select>

        </Form>

    );
  }

  searchProp(){
    this.props.searchProperty(this.refs.search.value,this.refs.city.value)
  }

  componentDidMount(){
    let cities = countries.getCities('indonesia')
    this.setState({
      cities: cities
    })
  }
}

const mapDispatchToProps = (dispatch) =>{
  return {
    searchProperty: (prop,city) => dispatch(searchProperty(prop,city))
  }
}

export default connect (null,mapDispatchToProps)(Example)
<commit_msg>fix city search<commit_after>import React from 'react';
import { connect } from 'react-redux'
import { Button, Form, Input } from 'reactstrap';
import countries from 'countries-cities'

import { searchProperty } from '../actions/index.js'

require('dotenv').config()

class Example extends React.Component {
  constructor(props){
    super(props)
    this.state={
      cities: []
    }
  }
  render() {
    return (

        <Form className="row">

            <input
              type="text"
              className="form-control"
              placeholder="Find a Property that you want"
              ref="search"
              onChange={()=> this.searchProp()}/>

            <select onChange={()=> this.searchProp()} ref="city">
              <option disabled>Select City</option>
              {this.state.cities.map((city,index)=>{
                  return <option key ={index} value={city}>{city}</option>
              })}
            </select>

        </Form>

    );
  }

  searchProp(){
    this.props.searchProperty(this.refs.search.value,this.refs.city.value)
  }

  componentDidMount(){
    let cities = countries.getCities('indonesia')
    this.setState({
      cities: cities
    })
  }
}

const mapDispatchToProps = (dispatch) =>{
  return {
    searchProperty: (prop,city) => dispatch(searchProperty(prop,city))
  }
}

export default connect (null,mapDispatchToProps)(Example)
<|endoftext|>


<commit_before>export function RoutesConfig($stateProvider, $urlRouterProvider) {
	'ngInject';

	var getView = (viewName) => {
		return './views/app/pages/' + viewName + '/' + viewName + '.page.html';
	};

	$urlRouterProvider.otherwise('/');

	$stateProvider
		.state('app', {
			abstract: true,
			views: {
				header: {
					templateUrl: getView('header')
				},
				footer: {
					templateUrl: getView('footer')
				},
				main: {}
			}
		})
		.state('app.landing', {
            url: '/',
            data: {},
            views: {
                'main@': {
                    templateUrl: getView('landing')
                }
            }
        })
        .state('app.login', {
			url: '/login',
			data: {},//{auth: true} would require JWT auth for this route
			views: {
				'main@': {
					templateUrl: getView('login')
				}
			}
		})
        .state('app.register', {
            url: '/register',
            data: {},//{auth: true} would require JWT auth for this route
            views: {
                'main@': {
                    templateUrl: getView('register')
                }
            }
        });
}
<commit_msg>template string for getView<commit_after>export function RoutesConfig($stateProvider, $urlRouterProvider) {
	'ngInject';

	var getView = (viewName) => {
		return `./views/app/pages/${viewName}/${viewName}.page.html`;
	};

	$urlRouterProvider.otherwise('/');

	$stateProvider
		.state('app', {
			abstract: true,
			views: {
				header: {
					templateUrl: getView('header')
				},
				footer: {
					templateUrl: getView('footer')
				},
				main: {}
			}
		})
		.state('app.landing', {
            url: '/',
            data: {},
            views: {
                'main@': {
                    templateUrl: getView('landing')
                }
            }
        })
        .state('app.login', {
			url: '/login',
			data: {},//{auth: true} would require JWT auth for this route
			views: {
				'main@': {
					templateUrl: getView('login')
				}
			}
		})
        .state('app.register', {
            url: '/register',
            data: {},//{auth: true} would require JWT auth for this route
            views: {
                'main@': {
                    templateUrl: getView('register')
                }
            }
        });
}
<|endoftext|>


<commit_before>/*
 * @author     Martin Høgh <mh@mapcentia.com>
 * @copyright  2013-2019 MapCentia ApS
 * @license    http://www.gnu.org/licenses/#AGPL  GNU AFFERO GENERAL PUBLIC LICENSE 3
 */

'use strict';

/**
 *
 */

module.exports = {
    download: (sql, format) => {
        let request = new XMLHttpRequest();
        request.open('POST', '/api/sql/' + db, true);
        request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charseselectt=UTF-8');
        request.responseType = 'blob';
        request.onload = function () {
            if (request.status === 200) {
                let filename, type;
                switch (format) {
                    case "excel":
                        filename = 'file.xlsx';
                        type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                        break;
                    default:
                        filename = 'file.geojson';
                        type = 'application/json';
                        break;
                }
                let blob = new Blob([request.response], {type: type});
                let link = document.createElement('a');
                link.href = window.URL.createObjectURL(blob);
                link.download = filename;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
            }
            // some error handling should be done here...
        };

        let uri = 'format=' + format + '&client_encoding=UTF8&&srs=4326&q=' + sql;
        request.send(uri);
    }
};<commit_msg>Added CSV<commit_after>/*
 * @author     Martin Høgh <mh@mapcentia.com>
 * @copyright  2013-2019 MapCentia ApS
 * @license    http://www.gnu.org/licenses/#AGPL  GNU AFFERO GENERAL PUBLIC LICENSE 3
 */

'use strict';

/**
 *
 */

module.exports = {
    download: (sql, format) => {
        let request = new XMLHttpRequest();
        request.open('POST', '/api/sql/' + db, true);
        request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charseselectt=UTF-8');
        request.responseType = 'blob';
        request.onload = function () {
            if (request.status === 200) {
                let filename, type;
                switch (format) {
                    case "csv":
                        filename = 'file.csv';
                        type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                        break;
                    case "excel":
                        filename = 'file.xlsx';
                        type = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
                        break;
                    default:
                        filename = 'file.geojson';
                        type = 'application/json';
                        break;
                }
                let blob = new Blob([request.response], {type: type});
                let link = document.createElement('a');
                link.href = window.URL.createObjectURL(blob);
                link.download = filename;
                document.body.appendChild(link);
                link.click();
                document.body.removeChild(link);
            }
            // some error handling should be done here...
        };

        let uri = 'format=' + format + '&client_encoding=UTF8&&srs=4326&q=' + sql;
        request.send(uri);
    }
};
<|endoftext|>


<commit_before>var dataUrl = 'https://api.github.com/events';
var cacheDelayInput = document.getElementById('cache-delay');
var cacheFailInput = document.getElementById('cache-fail');
var networkDelayInput = document.getElementById('network-delay');
var networkFailInput = document.getElementById('network-fail');
var cacheStatus = document.getElementById('cache-status');
var networkStatus = document.getElementById('network-status');
var getDataButton = document.getElementById('getDataButton');
var dataElement = document.getElementById('data');

var cacheName = 'cache-then-network';
var gotNetworkData = false;
var networkFetchStartTime;
var cacheFetchStartTime;

navigator.serviceWorker.ready.then(function () {
  console.log('SW ready');
});

navigator.serviceWorker.addEventListener('statechange', function () {
  console.log('state change');
});

navigator.serviceWorker.addEventListener('controllerchange', function () {
  console.log('controller change');
});

if (navigator.serviceWorker.controller) {
  console.log('SW already registered');
} else {
  console.log('registering SW');
  navigator.serviceWorker.register('sw.js', {
    scope: './',
  }).then(function handleRegistered(registration) {
    console.log('SW registered');
    console.log('registration: ' + registration);
    console.log('registration.installing: ' + registration.installing);
    console.log('registration.waiting: ' + registration.waiting);
    console.log('registration.active: ' + registration.active);
    //console.log('registration.active.state: ' + registration.active.state);
    registration.update();
    //console.log('SW state: ' + navigator.serviceWorker.controller.state);
    //console.log('SW id: ' + navigator.serviceWorker.controller.id);
  });
}

function reset() {
  dataElement.textContent = '';
  cacheStatus.textContent = '';
  networkStatus.textContent = '';
  gotNetworkData = false;
}

function disableUI() {
  getDataButton.disabled = true;
  cacheDelayInput.disabled = true;
  cacheFailInput.disabled = true;
  networkDelayInput.disabled = true;
  networkFailInput.disabled = true;
  reset();
}

function enableUI() {
  getDataButton.disabled = false;
  cacheDelayInput.disabled = false;
  cacheFailInput.disabled = false;
  networkDelayInput.disabled = false;
  networkFailInput.disabled = false;
}

function handleFetchCompletion(res) {
  var shouldNetworkError = networkFailInput.checked;
  if (shouldNetworkError) {
    throw new Error('Network error');
  }

  res.json().then(function(data) {
    updatePage(data);
    gotNetworkData = true;
  });
}

function handleCacheFetchCompletion(res) {
  var shouldCacheError = cacheFailInput.checked;
  if (shouldCacheError || !res) {
    throw Error('Cache miss');
  }

  res.json().then(function(data) {
    if (!gotNetworkData) {
      updatePage(data);
    }
  });
}

function updatePage(data) {
  dataElement.textContent = 'User ' + data[0].actor.login + ' modified repo ' + data[0].repo.name;
}

getDataButton.addEventListener('click', function handleClick() {
  disableUI();

  // Initiate network fetch
  networkStatus.textContent = 'Fetching...';
  networkFetchStartTime = window.performance.now();
  var networkFetch = fetch(dataUrl, { mode: 'cors', cache: 'no-cache' }).then(function(res) {
    var networkDelay = networkDelayInput.value || 0;

    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          handleFetchCompletion(res);
          resolve();
        } catch (err) {
          reject(err);
        }
      }, networkDelay);
    });
  }).then(function() {
    var now = window.performance.now();
    var elapsed = Math.round(now - networkFetchStartTime);
    networkStatus.textContent = 'Success after ' + elapsed + 'ms';
  }).catch(function(err) {
    var now = window.performance.now();
    var elapsed = Math.round(now - networkFetchStartTime);
    networkStatus.textContent = err + ' after ' + elapsed + 'ms';
  });

  // Get cached data
  cacheStatus.textContent = 'Fetching...';
  cacheFetchStartTime = window.performance.now();
  var cacheFetch = caches.open(cacheName).then(function(cache) {
    return cache.match(dataUrl).then(function(res) {
      var cacheDelay = cacheDelayInput.value || 0;

      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            handleCacheFetchCompletion(res);
            resolve();
          } catch (err) {
            reject(err);
          }
        }, cacheDelay);
      });
    }).then(function() {
      var now = window.performance.now();
      var elapsed = Math.round(now - cacheFetchStartTime);
      cacheStatus.textContent = 'Success after ' + elapsed + 'ms';
    }).catch(function(err) {
      var now = window.performance.now();
      var elapsed = Math.round(now - cacheFetchStartTime);
      cacheStatus.textContent = err + ' after ' + elapsed + 'ms';
    });
  });

  Promise.all([networkFetch, cacheFetch]).then(enableUI);
});
<commit_msg>Fix lint issues<commit_after>var dataUrl = 'https://api.github.com/events';
var cacheDelayInput = document.getElementById('cache-delay');
var cacheFailInput = document.getElementById('cache-fail');
var networkDelayInput = document.getElementById('network-delay');
var networkFailInput = document.getElementById('network-fail');
var cacheStatus = document.getElementById('cache-status');
var networkStatus = document.getElementById('network-status');
var getDataButton = document.getElementById('getDataButton');
var dataElement = document.getElementById('data');

var cacheName = 'cache-then-network';
var gotNetworkData = false;
var networkFetchStartTime;
var cacheFetchStartTime;

navigator.serviceWorker.ready.then(function() {
  console.log('SW ready');
});

navigator.serviceWorker.addEventListener('statechange', function() {
  console.log('state change');
});

navigator.serviceWorker.addEventListener('controllerchange', function() {
  console.log('controller change');
});

if (navigator.serviceWorker.controller) {
  console.log('SW already registered');
} else {
  console.log('registering SW');
  navigator.serviceWorker.register('sw.js', {
    scope: './',
  }).then(function handleRegistered(registration) {
    console.log('SW registered');
    console.log('registration: ' + registration);
    console.log('registration.installing: ' + registration.installing);
    console.log('registration.waiting: ' + registration.waiting);
    console.log('registration.active: ' + registration.active);
    registration.update();
  });
}

function reset() {
  dataElement.textContent = '';
  cacheStatus.textContent = '';
  networkStatus.textContent = '';
  gotNetworkData = false;
}

function disableUI() {
  getDataButton.disabled = true;
  cacheDelayInput.disabled = true;
  cacheFailInput.disabled = true;
  networkDelayInput.disabled = true;
  networkFailInput.disabled = true;
  reset();
}

function enableUI() {
  getDataButton.disabled = false;
  cacheDelayInput.disabled = false;
  cacheFailInput.disabled = false;
  networkDelayInput.disabled = false;
  networkFailInput.disabled = false;
}

function handleFetchCompletion(res) {
  var shouldNetworkError = networkFailInput.checked;
  if (shouldNetworkError) {
    throw new Error('Network error');
  }

  res.json().then(function(data) {
    updatePage(data);
    gotNetworkData = true;
  });
}

function handleCacheFetchCompletion(res) {
  var shouldCacheError = cacheFailInput.checked;
  if (shouldCacheError || !res) {
    throw Error('Cache miss');
  }

  res.json().then(function(data) {
    if (!gotNetworkData) {
      updatePage(data);
    }
  });
}

function updatePage(data) {
  dataElement.textContent = 'User ' + data[0].actor.login + ' modified repo ' + data[0].repo.name;
}

getDataButton.addEventListener('click', function handleClick() {
  disableUI();

  // Initiate network fetch
  networkStatus.textContent = 'Fetching...';
  networkFetchStartTime = window.performance.now();
  var networkFetch = fetch(dataUrl, { mode: 'cors', cache: 'no-cache' }).then(function(res) {
    var networkDelay = networkDelayInput.value || 0;

    return new Promise(function(resolve, reject) {
      setTimeout(function() {
        try {
          handleFetchCompletion(res);
          resolve();
        } catch (err) {
          reject(err);
        }
      }, networkDelay);
    });
  }).then(function() {
    var now = window.performance.now();
    var elapsed = Math.round(now - networkFetchStartTime);
    networkStatus.textContent = 'Success after ' + elapsed + 'ms';
  }).catch(function(err) {
    var now = window.performance.now();
    var elapsed = Math.round(now - networkFetchStartTime);
    networkStatus.textContent = err + ' after ' + elapsed + 'ms';
  });

  // Get cached data
  cacheStatus.textContent = 'Fetching...';
  cacheFetchStartTime = window.performance.now();
  var cacheFetch = caches.open(cacheName).then(function(cache) {
    return cache.match(dataUrl).then(function(res) {
      var cacheDelay = cacheDelayInput.value || 0;

      return new Promise(function(resolve, reject) {
        setTimeout(function() {
          try {
            handleCacheFetchCompletion(res);
            resolve();
          } catch (err) {
            reject(err);
          }
        }, cacheDelay);
      });
    }).then(function() {
      var now = window.performance.now();
      var elapsed = Math.round(now - cacheFetchStartTime);
      cacheStatus.textContent = 'Success after ' + elapsed + 'ms';
    }).catch(function(err) {
      var now = window.performance.now();
      var elapsed = Math.round(now - cacheFetchStartTime);
      cacheStatus.textContent = err + ' after ' + elapsed + 'ms';
    });
  });

  Promise.all([networkFetch, cacheFetch]).then(enableUI);
});
<|endoftext|>


<commit_before>'use strict';

/**
 * @ngdoc directive
 * @name ngSwitch
 * @restrict EA
 *
 * @description
 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
 * as specified in the template.
 *
 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
 * matches the value obtained from the evaluated expression. In other words, you define a container element
 * (where you place the directive), place an expression on the **`on="..."` attribute**
 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
 * attribute is displayed.
 *
 *

 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
 * as literal string values to match against.
 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
 * value of the expression `$scope.someVal`.
 *

 * @animations
 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
 *
 * @usage
 *
 * ```
 * <ANY ng-switch="expression">
 *   <ANY ng-switch-when="matchValue1">...</ANY>
 *   <ANY ng-switch-when="matchValue2">...</ANY>
 *   <ANY ng-switch-default>...</ANY>
 * </ANY>
 * ```
 *
 *
 * @scope
 * @priority 800
 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
 * On child elements add:
 *
 * * `ngSwitchWhen`: the case statement to match against. If match then this
 *   case will be displayed. If the same match appears multiple times, all the
 *   elements will be displayed.
 * * `ngSwitchDefault`: the default case when no other case match. If there
 *   are multiple default cases, all of them will be displayed when no other
 *   case match.
 *
 *
 * @example
  <example module="ngAnimate" deps="angular-animate.js" animations="true">
    <file name="index.html">

        <select ng-model="selection" ng-options="item for item in items">
        </select>
        <tt>selection={{selection}}</tt>

Settings Div

Home Span

default

    </file>
    <file name="script.js">
      function Ctrl($scope) {
        $scope.items = ['settings', 'home', 'other'];
        $scope.selection = $scope.items[0];
      }
    </file>
    <file name="animations.css">
      .animate-switch-container {
        position:relative;
        background:white;
        border:1px solid black;
        height:40px;
        overflow:hidden;
      }

      .animate-switch {
        padding:10px;
      }

      .animate-switch.ng-animate {
        -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
        transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;

        position:absolute;
        top:0;
        left:0;
        right:0;
        bottom:0;
      }

      .animate-switch.ng-leave.ng-leave-active,
      .animate-switch.ng-enter {
        top:-50px;
      }
      .animate-switch.ng-leave,
      .animate-switch.ng-enter.ng-enter-active {
        top:0;
      }
    </file>
    <file name="protractor.js" type="protractor">
      var switchElem = element(by.css('[ng-switch]'));
      var select = element(by.model('selection'));

      it('should start in settings', function() {
        expect(switchElem.getText()).toMatch(/Settings Div/);
      });
      it('should change to home', function() {
        select.all(by.css('option')).get(1).click();
        expect(switchElem.getText()).toMatch(/Home Span/);
      });
      it('should select default', function() {
        select.all(by.css('option')).get(2).click();
        expect(switchElem.getText()).toMatch(/default/);
      });
    </file>
  </example>
 */
var ngSwitchDirective = ['$animate', function($animate) {
  return {
    restrict: 'EA',
    require: 'ngSwitch',

    // asks for $scope to fool the BC controller module
    controller: ['$scope', function ngSwitchController() {
     this.cases = {};
    }],
    link: function(scope, element, attr, ngSwitchController) {
      var watchExpr = attr.ngSwitch || attr.on,
          selectedTranscludes = [],
          selectedElements = [],
          previousElements = [],
          selectedScopes = [];

      scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
        var i, ii;
        for (i = 0, ii = previousElements.length; i < ii; ++i) {
          previousElements[i].remove();
        }
        previousElements.length = 0;

        for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
          var selected = selectedElements[i];
          selectedScopes[i].$destroy();
          previousElements[i] = selected;
          $animate.leave(selected, function() {
            previousElements.splice(i, 1);
          });
        }

        selectedElements.length = 0;
        selectedScopes.length = 0;

        if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
          scope.$eval(attr.change);
          forEach(selectedTranscludes, function(selectedTransclude) {
            var selectedScope = scope.$new();
            selectedScopes.push(selectedScope);
            selectedTransclude.transclude(selectedScope, function(caseElement) {
              var anchor = selectedTransclude.element;

              selectedElements.push(caseElement);
              $animate.enter(caseElement, anchor.parent(), anchor);
            });
          });
        }
      });
    }
  };
}];

var ngSwitchWhenDirective = ngDirective({
  transclude: 'element',
  priority: 800,
  require: '^ngSwitch',
  link: function(scope, element, attrs, ctrl, $transclude) {
    ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
    ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
  }
});

var ngSwitchDefaultDirective = ngDirective({
  transclude: 'element',
  priority: 800,
  require: '^ngSwitch',
  link: function(scope, element, attr, ctrl, $transclude) {
    ctrl.cases['?'] = (ctrl.cases['?'] || []);
    ctrl.cases['?'].push({ transclude: $transclude, element: element });
   }
});
<commit_msg>docs(ngSwitch): update example to use a module<commit_after>'use strict';

/**
 * @ngdoc directive
 * @name ngSwitch
 * @restrict EA
 *
 * @description
 * The `ngSwitch` directive is used to conditionally swap DOM structure on your template based on a scope expression.
 * Elements within `ngSwitch` but without `ngSwitchWhen` or `ngSwitchDefault` directives will be preserved at the location
 * as specified in the template.
 *
 * The directive itself works similar to ngInclude, however, instead of downloading template code (or loading it
 * from the template cache), `ngSwitch` simply chooses one of the nested elements and makes it visible based on which element
 * matches the value obtained from the evaluated expression. In other words, you define a container element
 * (where you place the directive), place an expression on the **`on="..."` attribute**
 * (or the **`ng-switch="..."` attribute**), define any inner elements inside of the directive and place
 * a when attribute per element. The when attribute is used to inform ngSwitch which element to display when the on
 * expression is evaluated. If a matching expression is not found via a when attribute then an element with the default
 * attribute is displayed.
 *
 *

 * Be aware that the attribute values to match against cannot be expressions. They are interpreted
 * as literal string values to match against.
 * For example, **`ng-switch-when="someVal"`** will match against the string `"someVal"` not against the
 * value of the expression `$scope.someVal`.
 *

 * @animations
 * enter - happens after the ngSwitch contents change and the matched child element is placed inside the container
 * leave - happens just after the ngSwitch contents change and just before the former contents are removed from the DOM
 *
 * @usage
 *
 * ```
 * <ANY ng-switch="expression">
 *   <ANY ng-switch-when="matchValue1">...</ANY>
 *   <ANY ng-switch-when="matchValue2">...</ANY>
 *   <ANY ng-switch-default>...</ANY>
 * </ANY>
 * ```
 *
 *
 * @scope
 * @priority 800
 * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
 * On child elements add:
 *
 * * `ngSwitchWhen`: the case statement to match against. If match then this
 *   case will be displayed. If the same match appears multiple times, all the
 *   elements will be displayed.
 * * `ngSwitchDefault`: the default case when no other case match. If there
 *   are multiple default cases, all of them will be displayed when no other
 *   case match.
 *
 *
 * @example
  <example module="switchExample" deps="angular-animate.js" animations="true">
    <file name="index.html">

        <select ng-model="selection" ng-options="item for item in items">
        </select>
        <tt>selection={{selection}}</tt>

Settings Div

Home Span

default

    </file>
    <file name="script.js">
      angular.module('switchExample', ['ngAnimate'])
        .controller('ExampleController', ['$scope', function($scope) {
          $scope.items = ['settings', 'home', 'other'];
          $scope.selection = $scope.items[0];
        }]);
    </file>
    <file name="animations.css">
      .animate-switch-container {
        position:relative;
        background:white;
        border:1px solid black;
        height:40px;
        overflow:hidden;
      }

      .animate-switch {
        padding:10px;
      }

      .animate-switch.ng-animate {
        -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
        transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;

        position:absolute;
        top:0;
        left:0;
        right:0;
        bottom:0;
      }

      .animate-switch.ng-leave.ng-leave-active,
      .animate-switch.ng-enter {
        top:-50px;
      }
      .animate-switch.ng-leave,
      .animate-switch.ng-enter.ng-enter-active {
        top:0;
      }
    </file>
    <file name="protractor.js" type="protractor">
      var switchElem = element(by.css('[ng-switch]'));
      var select = element(by.model('selection'));

      it('should start in settings', function() {
        expect(switchElem.getText()).toMatch(/Settings Div/);
      });
      it('should change to home', function() {
        select.all(by.css('option')).get(1).click();
        expect(switchElem.getText()).toMatch(/Home Span/);
      });
      it('should select default', function() {
        select.all(by.css('option')).get(2).click();
        expect(switchElem.getText()).toMatch(/default/);
      });
    </file>
  </example>
 */
var ngSwitchDirective = ['$animate', function($animate) {
  return {
    restrict: 'EA',
    require: 'ngSwitch',

    // asks for $scope to fool the BC controller module
    controller: ['$scope', function ngSwitchController() {
     this.cases = {};
    }],
    link: function(scope, element, attr, ngSwitchController) {
      var watchExpr = attr.ngSwitch || attr.on,
          selectedTranscludes = [],
          selectedElements = [],
          previousElements = [],
          selectedScopes = [];

      scope.$watch(watchExpr, function ngSwitchWatchAction(value) {
        var i, ii;
        for (i = 0, ii = previousElements.length; i < ii; ++i) {
          previousElements[i].remove();
        }
        previousElements.length = 0;

        for (i = 0, ii = selectedScopes.length; i < ii; ++i) {
          var selected = selectedElements[i];
          selectedScopes[i].$destroy();
          previousElements[i] = selected;
          $animate.leave(selected, function() {
            previousElements.splice(i, 1);
          });
        }

        selectedElements.length = 0;
        selectedScopes.length = 0;

        if ((selectedTranscludes = ngSwitchController.cases['!' + value] || ngSwitchController.cases['?'])) {
          scope.$eval(attr.change);
          forEach(selectedTranscludes, function(selectedTransclude) {
            var selectedScope = scope.$new();
            selectedScopes.push(selectedScope);
            selectedTransclude.transclude(selectedScope, function(caseElement) {
              var anchor = selectedTransclude.element;

              selectedElements.push(caseElement);
              $animate.enter(caseElement, anchor.parent(), anchor);
            });
          });
        }
      });
    }
  };
}];

var ngSwitchWhenDirective = ngDirective({
  transclude: 'element',
  priority: 800,
  require: '^ngSwitch',
  link: function(scope, element, attrs, ctrl, $transclude) {
    ctrl.cases['!' + attrs.ngSwitchWhen] = (ctrl.cases['!' + attrs.ngSwitchWhen] || []);
    ctrl.cases['!' + attrs.ngSwitchWhen].push({ transclude: $transclude, element: element });
  }
});

var ngSwitchDefaultDirective = ngDirective({
  transclude: 'element',
  priority: 800,
  require: '^ngSwitch',
  link: function(scope, element, attr, ctrl, $transclude) {
    ctrl.cases['?'] = (ctrl.cases['?'] || []);
    ctrl.cases['?'].push({ transclude: $transclude, element: element });
   }
});
<|endoftext|>


<commit_before>// Toolkit for handling Node.js data streams

// Copyright (c) 2013 Laurent Fortin
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

var xstream = {};

xstream.version = '0.0.1';

xstream.isStream = function isStream(obj) {
  return obj instanceof stream.Readable || obj instanceof stream.Writable;
};

xstream.isReadable = function isReadable(obj) {
  return this.isStream(obj) && !!obj.readable;
};

xstream.isWritable = function isWritable(obj) {
  return this.isStream(obj) && !!obj.writable;
};

xstream.isTransform = function isTransform(obj) {
  return obj instanceof stream.Transform;
};

xstream.isFlowing = function isFlowing(obj) {
  return this.isReadable(obj) && !!obj._readableState.flowing;
};

xstream.dev = {};

xstream.dev.Null = function Null(mode) {
  if(mode === "read") {
    return fs.createReadStream("/dev/null");
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/null");
  }
  throw new Error("mode must me either 'read or 'write'");
};

xstream.dev.Zero = function Zero(mode, length) {
  if(mode === "read") {
    if(!Number(length)) {
      throw new Error("must provide length in bytes");
    }
    return fs.createReadStream("/dev/zero", {start: 0, end: length-1});
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/zero");
  }
  throw new Error("mode must me either 'read or 'write'");
};

xstream.dev.Full = function Full(mode, length) {
  if(mode === "read") {
    if(!Number(length)) {
      throw new Error("must provide length in bytes");
    }
    return fs.createReadStream("/dev/full", {start: 0, end: length-1});
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/full");
  }
  throw new Error("mode must me either 'read or 'write'");
};

xstream.dev.Random = function Random(mode, length) {
  if(mode === "read") {
    if(!Number(length)) {
      throw new Error("must provide length in bytes");
    }
    return fs.createReadStream("/dev/urandom", {start: 0, end: length-1});
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/urandom");
  }
  throw new Error("mode must me either 'read or 'write'");
};

module.exports = xstream;

<commit_msg>change object name<commit_after>// Toolkit for handling Node.js data streams

// Copyright (c) 2013 Laurent Fortin
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

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

var stk = {};

stk.version = '0.0.1';

stk.isStream = function isStream(obj) {
  return obj instanceof stream.Readable || obj instanceof stream.Writable;
};

stk.isReadable = function isReadable(obj) {
  return this.isStream(obj) && !!obj.readable;
};

stk.isWritable = function isWritable(obj) {
  return this.isStream(obj) && !!obj.writable;
};

stk.isTransform = function isTransform(obj) {
  return obj instanceof stream.Transform;
};

stk.isFlowing = function isFlowing(obj) {
  return this.isReadable(obj) && !!obj._readableState.flowing;
};

stk.dev = {};

stk.dev.Null = function Null(mode) {
  if(mode === "read") {
    return fs.createReadStream("/dev/null");
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/null");
  }
  throw new Error("mode must me either 'read or 'write'");
};

stk.dev.Zero = function Zero(mode, length) {
  if(mode === "read") {
    if(!Number(length)) {
      throw new Error("must provide length in bytes");
    }
    return fs.createReadStream("/dev/zero", {start: 0, end: length-1});
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/zero");
  }
  throw new Error("mode must me either 'read or 'write'");
};

stk.dev.Full = function Full(mode, length) {
  if(mode === "read") {
    if(!Number(length)) {
      throw new Error("must provide length in bytes");
    }
    return fs.createReadStream("/dev/full", {start: 0, end: length-1});
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/full");
  }
  throw new Error("mode must me either 'read or 'write'");
};

stk.dev.Random = function Random(mode, length) {
  if(mode === "read") {
    if(!Number(length)) {
      throw new Error("must provide length in bytes");
    }
    return fs.createReadStream("/dev/urandom", {start: 0, end: length-1});
  }
  if(mode === "write") {
    return fs.createWriteStream("/dev/urandom");
  }
  throw new Error("mode must me either 'read or 'write'");
};

module.exports = stk;

<|endoftext|>


<commit_before>/*========================================================================

   This file is part of the GOsa project -  http://gosa-project.org

   Copyright:
      (C) 2010-2017 GONICUS GmbH, Germany, http://www.gonicus.de

   License:
      LGPL-2.1: http://www.gnu.org/licenses/lgpl-2.1.html

   See the LICENSE file in the project's top-level directory for details.

======================================================================== */

/*
#asset(gosa/*)
*/

/**
 * SSE: Server-sent events (receives information from the server/backend)
 * */
qx.Class.define("gosa.io.Sse", {

  extend : qx.core.Object,

  type   : "singleton",

  construct: function(){
    this.base(arguments);
    this.__eventSource = null;
  },

  destruct : function(){
    this.__eventSource = null;
  },

  events: {
    "objectRemoved": "qx.event.type.Data",
    "objectCreated": "qx.event.type.Data",
    "objectModified": "qx.event.type.Data",
    "objectClosing": "qx.event.type.Data",
    "pluginUpdate": "qx.event.type.Data",
    "workflowUpdate": "qx.event.type.Data"
  },

  properties: {
    connected: {
      check: "Boolean",
      init: false,
      event: "changeConnected"
    }
  },

  members : {

    __eventSource: null,

    reconnect : function() {

      if (this.__eventSource) {
        this.__eventSource.close();
      }

      var uri = window.location.protocol +"//" + window.location.host + gosa.Config.sse;

      this.__eventSource = new EventSource(uri);
      var that = this;
      this.__eventSource.addEventListener("notification", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that._handleNotificationMessage(message);
      }, false);
      this.__eventSource.addEventListener("objectChange", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that._handleObjectChangeMessage(message);
      }, false);
      this.__eventSource.addEventListener("objectCloseAnnouncement", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that._handleObjectCloseAnnouncement(message);
      }, false);
      this.__eventSource.addEventListener("pluginUpdate", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that.fireDataEvent("pluginUpdate", message['namespace']);
      }, false);
      this.__eventSource.addEventListener("workflowUpdate", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that.fireDataEvent("workflowUpdate", message['id']);
      }, false);
      this.__eventSource.onerror = function (e) {
        var readyState = e.currentTarget.readyState;
        if (readyState != EventSource.OPEN) {
          // Connection was closed.
          this.setConnected(false);
        }
      }.bind(this);
      this.__eventSource.onopen = function(e) {
        var readyState = e.currentTarget.readyState;
        if (readyState == EventSource.OPEN) {
          // Connection was opened.
          this.setConnected(true);
        }
      }.bind(this);
    },

    /* Handle object change messages and fire up events
     * to inform other objects of the changes. E.g Lists.
     * */
    _handleObjectChangeMessage : function(info) {
      if(info['changeType'] == "update"){
        this.fireDataEvent("objectModified", info);
      }else if(info['changeType'] == "remove"){
        this.fireDataEvent("objectRemoved", info);
      }else if(info['changeType'] == "create"){
        this.fireDataEvent("objectCreated", info);
      }else{
        this.error("unhandled sse event: " + info['changeType']);
      }
    },

    _handleNotificationMessage : function(info) {
      var timeout = 5000;
      var icon = gosa.Config.getImagePath("status/dialog-information.png", 48);
      var title = "";
      if (info['timeout']) {
        timeout = info['timeout'];
      }
      if (info['icon']) {
        if (info['icon'] == "_no_icon_") {
          icon = gosa.Config.getImagePath("status/dialog-information.png", 48);
        } else if (info['icon'].substring(0, 7) == "base64:") {
          icon = "data:image/png;base64, " + info['icon'].substring(7);
        } else {
          icon = gosa.Config.getImagePath("status/" + info['icon'] + ".png", 48);
        }
      }
      if (info['title']) {
        title = "
" + info['title'] + "
";
      }

      if (gosa.Config.notifications && gosa.Config.notifications.checkPermission() == 0) {
        var data = qx.util.Base64.encode("
" + title + info['body'] + "
");
        var notification = gosa.Config.notifications.createHTMLNotification("data:text/html;charset=utf-8;base64," + data);
        notification.show();
        qx.event.Timer.once(function() {
          notification.cancel();
        }, this, timeout);

      } else {

        // Create bubble with timeout
        var popup = new qx.ui.basic.Atom(title + info["body"].replace(/\n/g, '
'), icon).set({
            backgroundColor : "#0A0A0A",
            textColor : "#F0F0F0",
            decorator : "scroll-knob",
            iconPosition : "left",
            padding : 8,
            paddingRight : 20,
            zIndex : 10000,
            opacity : 0.8,
            rich : true
        });
        popup.addListener("click", function(){ this.closePopup(popup); }, this);
        this.showPopup(popup);

        qx.event.Timer.once(function() {
          this.closePopup(popup);
        }, this, timeout);

      }
    },

    _handleObjectCloseAnnouncement : function(info) {
      this.fireDataEvent("objectClosing", info);
    },

    closePopup : function(popup) {
      //TODO: take care about multiple popups at one time (rearranging)

      if (!popup) {
        return;
      }

      var duration = 250;
      var desc = {duration: duration, keep: 100, timing: "ease-out", keyFrames : {
        0 : {"opacity" : 0.8},
        100 : {"opacity": 0, display: "none"}
      }};

      qx.bom.element.Animation.animate(popup.getContentElement().getDomElement(), desc);
      setTimeout(function() {if (popup){ popup.destroy();}}, duration + 100);
    },

    showPopup : function(popup) {
      //TODO: take care about multiple popups at one time (positioning)
      var doc = qx.core.Init.getApplication().getRoot();
      doc.add(popup, {right: 15, bottom: 15});
    }
  }
});
<commit_msg>fix error<commit_after>/*========================================================================

   This file is part of the GOsa project -  http://gosa-project.org

   Copyright:
      (C) 2010-2017 GONICUS GmbH, Germany, http://www.gonicus.de

   License:
      LGPL-2.1: http://www.gnu.org/licenses/lgpl-2.1.html

   See the LICENSE file in the project's top-level directory for details.

======================================================================== */

/*
#asset(gosa/*)
*/

/**
 * SSE: Server-sent events (receives information from the server/backend)
 * */
qx.Class.define("gosa.io.Sse", {

  extend : qx.core.Object,

  type   : "singleton",

  construct: function(){
    this.base(arguments);
    this.__eventSource = null;
  },

  destruct : function(){
    this.__eventSource = null;
  },

  events: {
    "objectRemoved": "qx.event.type.Data",
    "objectCreated": "qx.event.type.Data",
    "objectModified": "qx.event.type.Data",
    "objectClosing": "qx.event.type.Data",
    "pluginUpdate": "qx.event.type.Data",
    "workflowUpdate": "qx.event.type.Data"
  },

  properties: {
    connected: {
      check: "Boolean",
      init: false,
      event: "changeConnected"
    }
  },

  members : {

    __eventSource: null,

    reconnect : function() {

      if (this.__eventSource) {
        this.__eventSource.close();
      }

      var uri = window.location.protocol +"//" + window.location.host + gosa.Config.sse;

      this.__eventSource = new EventSource(uri);
      var that = this;
      this.__eventSource.addEventListener("notification", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that._handleNotificationMessage(message);
      }, false);
      this.__eventSource.addEventListener("objectChange", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that._handleObjectChangeMessage(message);
      }, false);
      this.__eventSource.addEventListener("objectCloseAnnouncement", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that._handleObjectCloseAnnouncement(message);
      }, false);
      this.__eventSource.addEventListener("pluginUpdate", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that.fireDataEvent("pluginUpdate", message['namespace']);
      }, false);
      this.__eventSource.addEventListener("workflowUpdate", function (e) {
        var message = qx.lang.Json.parse(e.data);
        that.fireDataEvent("workflowUpdate", message['id']);
      }, false);
      this.__eventSource.onerror = function (e) {
        var readyState = e.currentTarget.readyState;
        if (readyState != EventSource.OPEN) {
          // Connection was closed.
          this.setConnected(false);
        }
      }.bind(this);
      this.__eventSource.onopen = function(e) {
        var readyState = e.currentTarget.readyState;
        if (readyState == EventSource.OPEN) {
          // Connection was opened.
          this.setConnected(true);
        }
      }.bind(this);
    },

    /* Handle object change messages and fire up events
     * to inform other objects of the changes. E.g Lists.
     * */
    _handleObjectChangeMessage : function(info) {
      if(info['changeType'] == "update"){
        this.fireDataEvent("objectModified", info);
      }else if(info['changeType'] == "remove"){
        this.fireDataEvent("objectRemoved", info);
      }else if(info['changeType'] == "create"){
        this.fireDataEvent("objectCreated", info);
      }else{
        this.error("unhandled sse event: " + info['changeType']);
      }
    },

    _handleNotificationMessage : function(info) {
      var timeout = 5000;
      var icon = gosa.Config.getImagePath("status/dialog-information.png", 48);
      var title = "";
      if (info['timeout']) {
        timeout = info['timeout'];
      }
      if (info['icon']) {
        if (info['icon'] == "_no_icon_") {
          icon = gosa.Config.getImagePath("status/dialog-information.png", 48);
        } else if (info['icon'].substring(0, 7) == "base64:") {
          icon = "data:image/png;base64, " + info['icon'].substring(7);
        } else {
          icon = gosa.Config.getImagePath("status/" + info['icon'] + ".png", 48);
        }
      }
      if (info['title']) {
        title = '
' + info['title'] + '
';
      }

      if (gosa.Config.notifications && gosa.Config.notifications.checkPermission() == 0) {
        var data = qx.util.Base64.encode("
" + title + info['body'] + "
");
        var notification = gosa.Config.notifications.createHTMLNotification("data:text/html;charset=utf-8;base64," + data);
        notification.show();
        qx.event.Timer.once(function() {
          notification.cancel();
        }, this, timeout);

      } else {

        // Create bubble with timeout
        var popup = new qx.ui.basic.Atom(title + info["body"].replace(/\n/g, '
'), icon).set({
            backgroundColor : "#0A0A0A",
            textColor : "#F0F0F0",
            decorator : "scroll-knob",
            iconPosition : "left",
            padding : 8,
            paddingRight : 20,
            zIndex : 10000,
            opacity : 0.8,
            rich : true
        });
        popup.addListener("click", function(){ this.closePopup(popup); }, this);
        this.showPopup(popup);

        qx.event.Timer.once(function() {
          this.closePopup(popup);
        }, this, timeout);

      }
    },

    _handleObjectCloseAnnouncement : function(info) {
      this.fireDataEvent("objectClosing", info);
    },

    closePopup : function(popup) {
      //TODO: take care about multiple popups at one time (rearranging)

      if (!popup) {
        return;
      }

      var duration = 250;
      var desc = {duration: duration, keep: 100, timing: "ease-out", keyFrames : {
        0 : {"opacity" : 0.8},
        100 : {"opacity": 0, display: "none"}
      }};

      qx.bom.element.Animation.animate(popup.getContentElement().getDomElement(), desc);
      setTimeout(function() {if (popup){ popup.destroy();}}, duration + 100);
    },

    showPopup : function(popup) {
      //TODO: take care about multiple popups at one time (positioning)
      var doc = qx.core.Init.getApplication().getRoot();
      doc.add(popup, {right: 15, bottom: 15});
    }
  }
});
<|endoftext|>


<commit_before>import collapse from '../common/collapse';
import containsText from '../common/containsText';
import {def_table} from '../support/constants';
import getElementsByTagName from '../common/getElementsByTagName';
import getText from '../common/getText';
import getValue from '../system/getValue';
import insertHtmlAfterEnd from '../common/insertHtmlAfterEnd';
import {pCC} from '../support/layout';
import parseDateAsTimestamp from '../system/parseDateAsTimestamp';
import setValue from '../system/setValue';
import {simpleCheckbox} from '../settings/simpleCheckbox';

var ladderResetPref = 'lastLadderReset';
var lastLadderReset;

function checkForPvPLadder(row) {
  if (containsText('PvP Ladder', row.children[1].children[0])) {
    var logTime = parseDateAsTimestamp(
      getText(row.children[1].children[2]).replace('Posted: ', ''));
    if (logTime > lastLadderReset) {
      setValue(ladderResetPref, logTime);
      lastLadderReset = logTime;
    }
  }
}

function testArticle(rowType) {return rowType > 1;}

function setupPref(prefName, rowInjector) {
  insertHtmlAfterEnd(rowInjector, simpleCheckbox(prefName));
}

export default function viewArchive() {
  lastLadderReset = getValue(ladderResetPref);
  var prefName = 'collapseNewsArchive';
  var theTables = getElementsByTagName(def_table, pCC);
  if (theTables.length > 2) {
    setupPref(prefName, theTables[0].rows[2]);
    collapse({
      prefName: prefName,
      theTable: theTables[2],
      headInd: 6,
      articleTest: testArticle,
      extraFn: checkForPvPLadder
    });
  }
}
<commit_msg>FIX new news archive format<commit_after>import collapse from '../common/collapse';
import containsText from '../common/containsText';
import {def_table} from '../support/constants';
import getElementsByTagName from '../common/getElementsByTagName';
import getText from '../common/getText';
import getValue from '../system/getValue';
import insertHtmlAfterEnd from '../common/insertHtmlAfterEnd';
import {pCC} from '../support/layout';
import parseDateAsTimestamp from '../system/parseDateAsTimestamp';
import setValue from '../system/setValue';
import {simpleCheckbox} from '../settings/simpleCheckbox';

var ladderResetPref = 'lastLadderReset';
var lastLadderReset;

function checkForPvPLadder(row) {
  if (containsText('PvP Ladder', row.children[1].children[0])) {
    var logTime = parseDateAsTimestamp(
      getText(row.children[1].children[2]).replace('Posted: ', ''));
    if (logTime > lastLadderReset) {
      setValue(ladderResetPref, logTime);
      lastLadderReset = logTime;
    }
  }
}

function testArticle(rowType) {return rowType > 1;}

function setupPref(prefName, rowInjector) {
  insertHtmlAfterEnd(rowInjector, simpleCheckbox(prefName));
}

export default function viewArchive() {
  lastLadderReset = getValue(ladderResetPref);
  var prefName = 'collapseNewsArchive';
  var theTables = getElementsByTagName(def_table, pCC);
  if (theTables.length > 2) {
    setupPref(prefName, theTables[0].rows[2]);
    collapse({
      prefName: prefName,
      theTable: theTables[2],
      headInd: 7,
      articleTest: testArticle,
      extraFn: checkForPvPLadder
    });
  }
}
<|endoftext|>