<commit_before>/* globals Templates, jml */
Templates.resultsDisplay = {
    styles: ({
        $pRaw, escapeQuotedCSS, escapeCSS, tableWithFixedHeaderAndFooter, checkedFieldIndexes
    }) => {
        const color = !$pRaw('color') || $pRaw('color') === '#'
            ? $pRaw('colorName')
            : $pRaw('color');
        const bgcolor = !$pRaw('bgcolor') || $pRaw('bgcolor') === '#'
            ? $pRaw('bgcolorName')
            : $pRaw('bgcolor');
        return ['style', [
            {
                'y': 'td, th',
                'n': 'td',
                '0': 'td',
                'undefined': 'td'
            }[$pRaw('header')] + `{
    font-style: ${$pRaw('fontstyle')};
    font-variant: ${$pRaw('fontvariant')};
    font-weight: ${$pRaw('fontweight')};
    ${$pRaw('fontsize') ? `font-size: ${$pRaw('fontsize')};` : ''}
    font-family: ${escapeQuotedCSS($pRaw('fontSeq'))};

    font-stretch: ${$pRaw('fontstretch')};
    letter-spacing: ${$pRaw('letterspacing')};
    line-height: ${$pRaw('lineheight')};
    ${color ? `color: ${color};` : ''}
}
${escapeCSS($pRaw('pagecss') || '')}
` +
            (tableWithFixedHeaderAndFooter
                ? `
html, body, body > div {
    height: 100%; /* Needed to ensure descendent heights retain 100%; could be avoided if didn't want percent on table height */
}
.anchor-table-header {
    overflow-x: hidden; /* Not sure why we're getting extra here, but... */
    position: relative; /* Ensures we are still flowing, but provides anchor for absolutely positioned thead below (absolute positioning positions relative to nearest non-static ancestor; clear demo at https://www.w3schools.com/cssref/playit.asp?filename=playcss_position&preval=fixed ) */
    padding-top: 2em; /* Provides space for the header (the one that is absolutely positioned below relative to here) */
    padding-bottom: 2em; /* Provides space for the footer (the one that is absolutely positioned below relative to here) */
    background-color: white; /* Header background (not just for div text inside header, but for whole header area) */
    height: 90%; /* Percent of the whole screen taken by the table */
}
.anchor-table-body {
    overflow-y: auto; /* Provides scrollbars when text fills up beyond the height */
    height: 100%; /* If < 100%, the header anchor background color will seep below table */
}

.thead .th, .tfoot .th {
    line-height: 0; /* th div will have own line-height; reducing here avoids fattening it by an inner div */
    color: transparent; /* Hides the non-div duplicated header text */
    white-space: nowrap; /* Ensures column header text uses up full width without overlap (esp. wrap no longer seems to work); this must be applied outside of the div */
    border: none; /* We don't want a border for this invisible section */
}
.thead .th div.th-inner, .tfoot .th div.th-inner { /* divs are used as th is supposedly problematic */
    position: absolute; /* Puts relative to nearest non-static ancestor (our relative header anchor) */
    color: initial; /* Header must have an explicit color or it will get transparent container color */
    line-height: normal; /* Revert ancestor line height of 0 */
}
.thead .th div.th-inner {
    top: 0; /* Ensures our header stays fixed at top outside of normal flow of table */
}
.tfoot .th div.th-inner { /* divs are used as th is supposedly problematic */
    top: calc(100% - 2em); /* Ensures our header stays fixed at top outside of normal flow of table */
}
${checkedFieldIndexes.map((idx, i) => `
.td:nth-child(${i + 1}) {
    ${$pRaw('css' + (i + 1))}
}
`).join('')}

/* http://salzerdesign.com/test/fixedTable.html */
.interlintitle {
    ${escapeCSS($pRaw('interlintitle_css'))}
}
.zupa div.zupa1 {
    margin: 0 auto !important;
    width: 0 !important;
}
.zupa div.th-inner {
    width: 100%;
    margin-left: -50%;
    text-align: center;
}
`
                : '') +
            (bgcolor
                ? `
body {
    background-color: ${bgcolor};
}
`
                : '')
        ]];
    },
    main: ({
        tableData, schemaItems, $p, $pRaw, escapeQuotedCSS, escapeCSS, escapeHTML,
        heading, l, browseFieldSets, localizedFieldNames,
        interlinearSeparator = '

'
    }) => {
        let caption;
        const browseFieldSetIdx = browseFieldSets.findIndex((item, i) =>
            typeof $pRaw('start' + (i + 1) + '-1') === 'string'
        );
        const applicableBrowseFieldSet = browseFieldSets[browseFieldSetIdx];
        if ($pRaw('wishcaption') !== 'no') {
            /*
            // Works but displays in parentheses browse fields which
            //  may be non-applicable
            const buildRangePoint = (startOrEnd) => escapeHTML(
                browseFieldSets.reduce((txt, bfs, i) =>
                    (txt ? txt + ' (' : '') + bfs.map((bf, j) =>
                        (j > 0 ? l('comma-space') : '') + bf + ' ' +
                            $pRaw(startOrEnd + (i + 1) + '-' + (j + 1))
                    ).join('') + (txt ? ')' : ''), '')
            );
            */
            /*
            // Works but overly long
            const buildRangePoint = (startOrEnd) => escapeHTML(
                applicableBrowseFieldSet.map((bf, j) =>
                    (j > 0 ? l('comma-space') : '') + bf + ' ' +
                        $pRaw(startOrEnd + (browseFieldSetIdx + 1) + '-' + (j + 1))
                ).join('')
            );
            */
            const buildRanges = () => {
                const buildRangePoint = (startOrEnd) =>
                    applicableBrowseFieldSet.map((bf, j) =>
                        $pRaw(
                            startOrEnd + (browseFieldSetIdx + 1) + '-' + (j + 1)
                        )
                    );
                const start = buildRangePoint('start');
                const end = buildRangePoint('end');
                const endVals = [];
                const startStr = start.reduce((str, fieldValue, i) => {
                    const ret = str + fieldValue;
                    if (fieldValue === end[i]) {
                        return ret +
                            (i > 0 ? ',' : '') + // e.g., for "Genesis 7, 5-8"
                            ' '; // e.g., for 2nd space in "Surah 2 5-8"
                    }
                    endVals.push(end[i]);
                    return ret + ':';
                }, '').slice(0, -1);

                const rangeNames = applicableBrowseFieldSet.map((abfs) =>
                    abfs.browseFieldName).join(l('comma-space'));
                return escapeHTML(
                    startStr +
                    // l('to').toLowerCase() + ' ' +
                    '-' +
                    endVals.join(':') + ' (' + rangeNames + ')'
                );
            };
            caption = heading + ' ' + buildRanges();
        }

        const tableElems = ({
            table: [
                ['table', {'class': 'table', border: $pRaw('border') || '0'}],
                ['tr', {'class': 'tr'}],
                ['td', {'class': 'td'}],
                ['th', {'class': 'th'}],
                ['caption', {'class': 'caption'}],
                ['thead', {'class': 'thead'}],
                ['tbody', {'class': 'tbody'}],
                ['tfoot', {'class': 'tfoot'}]
                // ['colgroup', {'class': 'colgroup'}],
                // ['col', {'class': 'col'}]
            ],
            div: [
                ['div', {'class': 'table', style: 'display: table;'}],
                ['div', {'class': 'tr', style: 'display: table-row;'}],
                ['div', {'class': 'td', style: 'display: table-cell;'}],
                ['div', {'class': 'th', style: 'display: table-cell;'}],
                ['div', {'class': 'caption', style: 'display: table-caption;'}],
                ['div', {'class': 'thead', style: 'display: table-header-group;'}],
                ['div', {'class': 'tbody', style: 'display: table-row-group;'}],
                ['div', {'class': 'tfoot', style: 'display: table-footer-group;'}]
                // ['div', {'class': 'colgroup', style: 'display: table-column-group;'}],
                // ['div', {'class': 'col', style: 'display: table-column;'}]
            ],
            'json-array': 'json',
            'json-object': 'json'
        }[$pRaw('outputmode')]);
        if (tableElems === 'json') {
            alert('JSON support is currently not available');
            return;
        }
        const [
            tableElem, trElem, tdElem, thElem, captionElem, theadElem, tbodyElem, tfootElem
        ] = tableElems; // colgroupElem, colElem

        let num = 1;
        let field, checked;
        let checkedFields = [];
        do {
            field = $pRaw('field' + num);
            checked = $p.get('checked' + num, true);
            num++;
            if (field && checked === l('yes')) {
                checkedFields.push(field);
            }
        } while (field);
        checkedFields = checkedFields.filter((cf) => localizedFieldNames.includes(cf));
        const checkedFieldIndexes = checkedFields.map((cf) => localizedFieldNames.indexOf(cf));
        const allInterlinearColIndexes = checkedFieldIndexes.map((cfi, i) => {
            const interlin = $pRaw('interlin' + (i + 1));
            return interlin && interlin.split(/\s*,\s*/).map((col) =>
                parseInt(col, 10) - 1
            );
        });

        const tableWithFixedHeaderAndFooter = $pRaw('headerfooterfixed') === 'yes';
        const tableWrap = (children) =>
            tableWithFixedHeaderAndFooter
                ? ['div', {'class': 'anchor-table-header zupa'}, [
                    ['div', {'class': 'anchor-table-body'}, children]
                ]]
                : ['div', children];

        const addChildren = (el, children) => {
            el = JSON.parse(JSON.stringify(el));
            el.push(children);
            return el;
        };
        const addAtts = (el, newAtts) => [el[0], Object.assign({}, el[1], newAtts)];

        jml('div', [
            Templates.resultsDisplay.styles({
                $pRaw, escapeQuotedCSS, escapeCSS, tableWithFixedHeaderAndFooter, checkedFieldIndexes
            }),
            tableWrap([
                addChildren(tableElem, [
                    (caption ? addChildren(captionElem, [caption]) : ''),
                    /*
                    // Works but quirky, e.g., `color` doesn't work (as also confirmed per https://quirksmode.org/css/css2/columns.html)
                    addChildren(colgroupElem,
                        checkedFieldIndexes.map((idx, i) =>
                            addAtts(colElem, {style: $pRaw('css' + (i + 1))})
                        )
                    ),
                    */
                    ($pRaw('header') !== '0' ? addChildren(theadElem, [
                        addChildren(trElem,
                            checkedFields.map((cf, i) => {
                                const interlinearColIndexes = allInterlinearColIndexes[i];
                                cf = cf + (interlinearColIndexes
                                    ? l('comma-space') + interlinearColIndexes.map((idx) =>
                                        localizedFieldNames[idx]
                                    ).join(l('comma-space'))
                                    : ''
                                );
                                return addChildren(thElem, [
                                    cf,
                                    (tableWithFixedHeaderAndFooter ? ['div', {class: 'zupa1'}, [
                                        ['div', {class: 'th-inner'}, [
                                            ['span', [cf]]
                                        ]]
                                    ]] : '')
                                ]);
                            })
                        )
                    ]) : ''),
                    ($pRaw('footer') !== '0' ? addChildren(tfootElem, [
                        addChildren(trElem,
                            checkedFields.map((cf, i) => {
                                const interlinearColIndexes = allInterlinearColIndexes[i];
                                cf = cf + (interlinearColIndexes
                                    ? l('comma-space') + interlinearColIndexes.map((idx) =>
                                        localizedFieldNames[idx]
                                    ).join(l('comma-space'))
                                    : ''
                                );
                                return addChildren(thElem, [
                                    cf,
                                    (tableWithFixedHeaderAndFooter ? ['div', {class: 'zupa1'}, [
                                        ['div', {class: 'th-inner'}, [
                                            ['span', [cf]]
                                        ]]
                                    ]] : '')
                                ]);
                            })
                        )
                    ]) : ''),
                    addChildren(tbodyElem, [
                        /**/
                        ...tableData.map((tr, i) =>
                            addChildren(trElem,
                                checkedFieldIndexes.map((idx, j) => {
                                    const interlinearColIndexes = allInterlinearColIndexes[j];
                                    const showInterlinTitles = $pRaw('interlintitle') === '1' &&
                                        interlinearColIndexes;
                                    return addAtts(tdElem, {
                                        id: 'row' + (i + 1) + 'cell' + (j + 1),
                                        innerHTML:
                                            (showInterlinTitles
                                                ? '' +
                                                    localizedFieldNames[idx] +
                                                    '' + l('colon-space')
                                                : ''
                                            ) +
                                            tr[idx] + (interlinearColIndexes
                                                ? interlinearSeparator +
                                                    interlinearColIndexes.map((idx) =>
                                                        (showInterlinTitles
                                                            ? '' +
                                                                localizedFieldNames[idx] +
                                                                '' + l('colon-space')
                                                            : '') +
                                                        tr[idx]
                                                    ).join(interlinearSeparator)
                                                : ''
                                        )
                                    });
                                })
                        // */
                        /*
                                /*
                                // Todo: anchor1, etc.
                                // Todo: needs scroll into view for repeated anchor

                                // Todo: Add ranges within applicable browse field set
                                    (also need to cache JSON into IndexedDB or at
                                        least localStorage for now)
                                    start1-1, etc.
                                    end1-1, etc.
                                    rand
                                    context (highlight?)

                                // Todo: localizeParamNames (preference)?
                                // Todo: Later: Speech controls
                                // Todo: Later: search1, etc.
                                // Todo: Later: auto-fields
                                // Todo: Later: Handle transpose, in header, footer, and body
                                // Todo: Later: Support JSON types for `outputmode`, opening
                                //     new window with content-type set
                                */
                                /*
                                schemaItems.map(({title}, i) => {
                                    const fieldI = $pRaw('field' + (i + 1));
                                    if (fieldI) {
                                        const idx = schemaItems.findIndex(({title}) => title === fieldI);
                                        console.log('iiii', title, i, idx, fieldI);
                                        return idx === -1
                                            ? [tdElem, ['(empty)' + title]]
                                            : [tdElem, {
                                                innerHTML: tr[idx] // Todo: Only do for non-escaped!!!!
                                            }];
                                    }
                                    return 'ttt:' + title;
                                })
                                */
                    /**/
                            )
                        )
                    // */
                    ])
                ])
            ])
        ], document.body);
    }
};
<commit_msg>- Todo<commit_after>/* globals Templates, jml */
Templates.resultsDisplay = {
    styles: ({
        $pRaw, escapeQuotedCSS, escapeCSS, tableWithFixedHeaderAndFooter, checkedFieldIndexes
    }) => {
        const color = !$pRaw('color') || $pRaw('color') === '#'
            ? $pRaw('colorName')
            : $pRaw('color');
        const bgcolor = !$pRaw('bgcolor') || $pRaw('bgcolor') === '#'
            ? $pRaw('bgcolorName')
            : $pRaw('bgcolor');
        return ['style', [
            {
                'y': 'td, th',
                'n': 'td',
                '0': 'td',
                'undefined': 'td'
            }[$pRaw('header')] + `{
    font-style: ${$pRaw('fontstyle')};
    font-variant: ${$pRaw('fontvariant')};
    font-weight: ${$pRaw('fontweight')};
    ${$pRaw('fontsize') ? `font-size: ${$pRaw('fontsize')};` : ''}
    font-family: ${escapeQuotedCSS($pRaw('fontSeq'))};

    font-stretch: ${$pRaw('fontstretch')};
    letter-spacing: ${$pRaw('letterspacing')};
    line-height: ${$pRaw('lineheight')};
    ${color ? `color: ${color};` : ''}
}
${escapeCSS($pRaw('pagecss') || '')}
` +
            (tableWithFixedHeaderAndFooter
                ? `
html, body, body > div {
    height: 100%; /* Needed to ensure descendent heights retain 100%; could be avoided if didn't want percent on table height */
}
.anchor-table-header {
    overflow-x: hidden; /* Not sure why we're getting extra here, but... */
    position: relative; /* Ensures we are still flowing, but provides anchor for absolutely positioned thead below (absolute positioning positions relative to nearest non-static ancestor; clear demo at https://www.w3schools.com/cssref/playit.asp?filename=playcss_position&preval=fixed ) */
    padding-top: 2em; /* Provides space for the header (the one that is absolutely positioned below relative to here) */
    padding-bottom: 2em; /* Provides space for the footer (the one that is absolutely positioned below relative to here) */
    background-color: white; /* Header background (not just for div text inside header, but for whole header area) */
    height: 90%; /* Percent of the whole screen taken by the table */
}
.anchor-table-body {
    overflow-y: auto; /* Provides scrollbars when text fills up beyond the height */
    height: 100%; /* If < 100%, the header anchor background color will seep below table */
}

.thead .th, .tfoot .th {
    line-height: 0; /* th div will have own line-height; reducing here avoids fattening it by an inner div */
    color: transparent; /* Hides the non-div duplicated header text */
    white-space: nowrap; /* Ensures column header text uses up full width without overlap (esp. wrap no longer seems to work); this must be applied outside of the div */
    border: none; /* We don't want a border for this invisible section */
}
.thead .th div.th-inner, .tfoot .th div.th-inner { /* divs are used as th is supposedly problematic */
    position: absolute; /* Puts relative to nearest non-static ancestor (our relative header anchor) */
    color: initial; /* Header must have an explicit color or it will get transparent container color */
    line-height: normal; /* Revert ancestor line height of 0 */
}
.thead .th div.th-inner {
    top: 0; /* Ensures our header stays fixed at top outside of normal flow of table */
}
.tfoot .th div.th-inner { /* divs are used as th is supposedly problematic */
    top: calc(100% - 2em); /* Ensures our header stays fixed at top outside of normal flow of table */
}
${checkedFieldIndexes.map((idx, i) => `
.td:nth-child(${i + 1}) {
    ${$pRaw('css' + (i + 1))}
}
`).join('')}

/* http://salzerdesign.com/test/fixedTable.html */
.interlintitle {
    ${escapeCSS($pRaw('interlintitle_css'))}
}
.zupa div.zupa1 {
    margin: 0 auto !important;
    width: 0 !important;
}
.zupa div.th-inner {
    width: 100%;
    margin-left: -50%;
    text-align: center;
}
`
                : '') +
            (bgcolor
                ? `
body {
    background-color: ${bgcolor};
}
`
                : '')
        ]];
    },
    main: ({
        tableData, schemaItems, $p, $pRaw, escapeQuotedCSS, escapeCSS, escapeHTML,
        heading, l, browseFieldSets, localizedFieldNames,
        interlinearSeparator = '

'
    }) => {
        let caption;
        const browseFieldSetIdx = browseFieldSets.findIndex((item, i) =>
            typeof $pRaw('start' + (i + 1) + '-1') === 'string'
        );
        const applicableBrowseFieldSet = browseFieldSets[browseFieldSetIdx];
        if ($pRaw('wishcaption') !== 'no') {
            /*
            // Works but displays in parentheses browse fields which
            //  may be non-applicable
            const buildRangePoint = (startOrEnd) => escapeHTML(
                browseFieldSets.reduce((txt, bfs, i) =>
                    (txt ? txt + ' (' : '') + bfs.map((bf, j) =>
                        (j > 0 ? l('comma-space') : '') + bf + ' ' +
                            $pRaw(startOrEnd + (i + 1) + '-' + (j + 1))
                    ).join('') + (txt ? ')' : ''), '')
            );
            */
            /*
            // Works but overly long
            const buildRangePoint = (startOrEnd) => escapeHTML(
                applicableBrowseFieldSet.map((bf, j) =>
                    (j > 0 ? l('comma-space') : '') + bf + ' ' +
                        $pRaw(startOrEnd + (browseFieldSetIdx + 1) + '-' + (j + 1))
                ).join('')
            );
            */
            const buildRanges = () => {
                const buildRangePoint = (startOrEnd) =>
                    applicableBrowseFieldSet.map((bf, j) =>
                        $pRaw(
                            startOrEnd + (browseFieldSetIdx + 1) + '-' + (j + 1)
                        )
                    );
                const start = buildRangePoint('start');
                const end = buildRangePoint('end');
                const endVals = [];
                const startStr = start.reduce((str, fieldValue, i) => {
                    const ret = str + fieldValue;
                    if (fieldValue === end[i]) {
                        return ret +
                            (i > 0 ? ',' : '') + // e.g., for "Genesis 7, 5-8"
                            ' '; // e.g., for 2nd space in "Surah 2 5-8"
                    }
                    endVals.push(end[i]);
                    return ret + ':';
                }, '').slice(0, -1);

                const rangeNames = applicableBrowseFieldSet.map((abfs) =>
                    abfs.browseFieldName).join(l('comma-space'));
                return escapeHTML(
                    startStr +
                    // l('to').toLowerCase() + ' ' +
                    '-' +
                    endVals.join(':') + ' (' + rangeNames + ')'
                );
            };
            caption = heading + ' ' + buildRanges();
        }

        const tableElems = ({
            table: [
                ['table', {'class': 'table', border: $pRaw('border') || '0'}],
                ['tr', {'class': 'tr'}],
                ['td', {'class': 'td'}],
                ['th', {'class': 'th'}],
                ['caption', {'class': 'caption'}],
                ['thead', {'class': 'thead'}],
                ['tbody', {'class': 'tbody'}],
                ['tfoot', {'class': 'tfoot'}]
                // ['colgroup', {'class': 'colgroup'}],
                // ['col', {'class': 'col'}]
            ],
            div: [
                ['div', {'class': 'table', style: 'display: table;'}],
                ['div', {'class': 'tr', style: 'display: table-row;'}],
                ['div', {'class': 'td', style: 'display: table-cell;'}],
                ['div', {'class': 'th', style: 'display: table-cell;'}],
                ['div', {'class': 'caption', style: 'display: table-caption;'}],
                ['div', {'class': 'thead', style: 'display: table-header-group;'}],
                ['div', {'class': 'tbody', style: 'display: table-row-group;'}],
                ['div', {'class': 'tfoot', style: 'display: table-footer-group;'}]
                // ['div', {'class': 'colgroup', style: 'display: table-column-group;'}],
                // ['div', {'class': 'col', style: 'display: table-column;'}]
            ],
            'json-array': 'json',
            'json-object': 'json'
        }[$pRaw('outputmode')]);
        if (tableElems === 'json') {
            alert('JSON support is currently not available');
            return;
        }
        const [
            tableElem, trElem, tdElem, thElem, captionElem, theadElem, tbodyElem, tfootElem
        ] = tableElems; // colgroupElem, colElem

        let num = 1;
        let field, checked;
        let checkedFields = [];
        do {
            field = $pRaw('field' + num);
            checked = $p.get('checked' + num, true);
            num++;
            if (field && checked === l('yes')) {
                checkedFields.push(field);
            }
        } while (field);
        checkedFields = checkedFields.filter((cf) => localizedFieldNames.includes(cf));
        const checkedFieldIndexes = checkedFields.map((cf) => localizedFieldNames.indexOf(cf));
        const allInterlinearColIndexes = checkedFieldIndexes.map((cfi, i) => {
            const interlin = $pRaw('interlin' + (i + 1));
            return interlin && interlin.split(/\s*,\s*/).map((col) =>
                parseInt(col, 10) - 1
            );
        });

        const tableWithFixedHeaderAndFooter = $pRaw('headerfooterfixed') === 'yes';
        const tableWrap = (children) =>
            tableWithFixedHeaderAndFooter
                ? ['div', {'class': 'anchor-table-header zupa'}, [
                    ['div', {'class': 'anchor-table-body'}, children]
                ]]
                : ['div', children];

        const addChildren = (el, children) => {
            el = JSON.parse(JSON.stringify(el));
            el.push(children);
            return el;
        };
        const addAtts = (el, newAtts) => [el[0], Object.assign({}, el[1], newAtts)];

        jml('div', [
            Templates.resultsDisplay.styles({
                $pRaw, escapeQuotedCSS, escapeCSS, tableWithFixedHeaderAndFooter, checkedFieldIndexes
            }),
            tableWrap([
                addChildren(tableElem, [
                    (caption ? addChildren(captionElem, [caption]) : ''),
                    /*
                    // Works but quirky, e.g., `color` doesn't work (as also confirmed per https://quirksmode.org/css/css2/columns.html)
                    addChildren(colgroupElem,
                        checkedFieldIndexes.map((idx, i) =>
                            addAtts(colElem, {style: $pRaw('css' + (i + 1))})
                        )
                    ),
                    */
                    ($pRaw('header') !== '0' ? addChildren(theadElem, [
                        addChildren(trElem,
                            checkedFields.map((cf, i) => {
                                const interlinearColIndexes = allInterlinearColIndexes[i];
                                cf = cf + (interlinearColIndexes
                                    ? l('comma-space') + interlinearColIndexes.map((idx) =>
                                        localizedFieldNames[idx]
                                    ).join(l('comma-space'))
                                    : ''
                                );
                                return addChildren(thElem, [
                                    cf,
                                    (tableWithFixedHeaderAndFooter ? ['div', {class: 'zupa1'}, [
                                        ['div', {class: 'th-inner'}, [
                                            ['span', [cf]]
                                        ]]
                                    ]] : '')
                                ]);
                            })
                        )
                    ]) : ''),
                    ($pRaw('footer') !== '0' ? addChildren(tfootElem, [
                        addChildren(trElem,
                            checkedFields.map((cf, i) => {
                                const interlinearColIndexes = allInterlinearColIndexes[i];
                                cf = cf + (interlinearColIndexes
                                    ? l('comma-space') + interlinearColIndexes.map((idx) =>
                                        localizedFieldNames[idx]
                                    ).join(l('comma-space'))
                                    : ''
                                );
                                return addChildren(thElem, [
                                    cf,
                                    (tableWithFixedHeaderAndFooter ? ['div', {class: 'zupa1'}, [
                                        ['div', {class: 'th-inner'}, [
                                            ['span', [cf]]
                                        ]]
                                    ]] : '')
                                ]);
                            })
                        )
                    ]) : ''),
                    addChildren(tbodyElem, [
                        /**/
                        ...tableData.map((tr, i) =>
                            addChildren(trElem,
                                checkedFieldIndexes.map((idx, j) => {
                                    const interlinearColIndexes = allInterlinearColIndexes[j];
                                    const showInterlinTitles = $pRaw('interlintitle') === '1' &&
                                        interlinearColIndexes;
                                    return addAtts(tdElem, {
                                        id: 'row' + (i + 1) + 'cell' + (j + 1),
                                        innerHTML:
                                            (showInterlinTitles
                                                ? '' +
                                                    localizedFieldNames[idx] +
                                                    '' + l('colon-space')
                                                : ''
                                            ) +
                                            tr[idx] + (interlinearColIndexes
                                                ? interlinearSeparator +
                                                    interlinearColIndexes.map((idx) =>
                                                        (showInterlinTitles
                                                            ? '' +
                                                                localizedFieldNames[idx] +
                                                                '' + l('colon-space')
                                                            : '') +
                                                        tr[idx]
                                                    ).join(interlinearSeparator)
                                                : ''
                                        )
                                    });
                                })
                        // */
                        /*
                                /*
                                // Todo: anchor1, etc. (and add to it to indicate
                                //     field no. too for horizontal anchoring)
                                // Todo: needs scroll into view for repeated anchor

                                // Todo: Add ranges within applicable browse field set
                                    (also need to cache JSON into IndexedDB or at
                                        least localStorage for now)
                                    start1-1, etc.
                                    end1-1, etc.
                                    rand
                                    context (highlight?)

                                // Todo: localizeParamNames (preference)?
                                // Todo: Later: Speech controls
                                // Todo: Later: search1, etc.
                                // Todo: Later: auto-fields
                                // Todo: Later: Handle transpose, in header, footer, and body
                                // Todo: Later: Support JSON types for `outputmode`, opening
                                //     new window with content-type set
                                */
                                /*
                                schemaItems.map(({title}, i) => {
                                    const fieldI = $pRaw('field' + (i + 1));
                                    if (fieldI) {
                                        const idx = schemaItems.findIndex(({title}) => title === fieldI);
                                        console.log('iiii', title, i, idx, fieldI);
                                        return idx === -1
                                            ? [tdElem, ['(empty)' + title]]
                                            : [tdElem, {
                                                innerHTML: tr[idx] // Todo: Only do for non-escaped!!!!
                                            }];
                                    }
                                    return 'ttt:' + title;
                                })
                                */
                    /**/
                            )
                        )
                    // */
                    ])
                ])
            ])
        ], document.body);
    }
};
<|endoftext|>


<commit_before>/** @author Michael Murphy */
requirejs.config({
   // By default load any module IDs from js/lib
   baseUrl: '/js/lib',
   // except, if the module ID starts with "app",
   // load it from the js/app directory. paths
   // config is relative to the baseUrl, and
   // never includes a ".js" extension since
   // the paths config could be for a directory.
   paths: {
      app: '../app',
      util: '../util',
      user: '../user',
      course: '../course',
      text: 'text',
      templates: '../../templates',
      api: '../../api'
   },
   // The shim configuration is simple to use:
   // (1) one states the dependencies (deps), if any, (which may be from the
   //     paths configuration, or may be valid paths themselves).
   // (2) (optionally) specify the global variable name from the file you're
   //     shimming, which should be exported to your module functions that
   //     require it. (If you don't specify the exports, then you'll need to
   //     just use the global, as nothing will get passed into your
   //     require/define functions.)
   shim: {
      'jquery-ui': ['jquery'],
      'jquery.magnific-popup' : ['jquery', 'jquery-ui'],
      'bootstrap': ['jquery']
   }
});

// Start the main app logic.
define(function (require) {
   var App = require('app/app');
   var Radio = require('backbone.radio');
   require('user/module');
   require('course/module');
   require('app/chart');
   require('app/modifyAssignment');
   require('app/modifyCategory');
   require('app/newAssignment');
   require('app/modifyClass');
   require('app/modifyStudent');
   require('app/addNewStudent')
   require('bootstrap');
   require('domReady!');

   App.start({});
   return App;
});
<commit_msg>fixed how query engine loads<commit_after>/** @author Michael Murphy */
requirejs.config({
   // By default load any module IDs from js/lib
   baseUrl: '/js/lib',
   // except, if the module ID starts with "app",
   // load it from the js/app directory. paths
   // config is relative to the baseUrl, and
   // never includes a ".js" extension since
   // the paths config could be for a directory.
   paths: {
      app: '../app',
      util: '../util',
      user: '../user',
      course: '../course',
      text: 'text',
      templates: '../../templates',
      api: '../../api'
   },
   // The shim configuration is simple to use:
   // (1) one states the dependencies (deps), if any, (which may be from the
   //     paths configuration, or may be valid paths themselves).
   // (2) (optionally) specify the global variable name from the file you're
   //     shimming, which should be exported to your module functions that
   //     require it. (If you don't specify the exports, then you'll need to
   //     just use the global, as nothing will get passed into your
   //     require/define functions.)
   shim: {
      'jquery-ui': ['jquery'],
      'jquery.magnific-popup' : ['jquery', 'jquery-ui'],
      'bootstrap': ['jquery'],
      'query-engine': {
         exports: 'queryEngine'
      }
   }
});

// Start the main app logic.
define(function (require) {
   var App = require('app/app');
   var Radio = require('backbone.radio');
   require('user/module');
   require('course/module');
   require('app/chart');
   require('app/modifyAssignment');
   require('app/modifyCategory');
   require('app/newAssignment');
   require('app/modifyClass');
   require('app/modifyStudent');
   require('app/addNewStudent')
   require('bootstrap');
   require('domReady!');

   App.start({});
   return App;
});
<|endoftext|>


<commit_before>var templates = [
    "root/externallib/text!root/plugins/events/events.html",
    "root/externallib/text!root/plugins/events/event.html"
];

define(templates, function (eventsTpl, eventTpl) {
    var plugin = {
        settings: {
            name: "events",
            type: "general",
            icon: "plugins/events/icon.png",
            menuURL: "#events/90",
            lang: {
                component: "core"
            }
        },

        routes: [
            ["events/:days", "show_events", "showEvents"],
            ["events/show/:id", "show_event", "showEvent"]
        ],

        // This is like a static variable where we store the last Events retrieved in JSON format.
        lastEvents: null,

        /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            return MM.util.wsAvailable('core_calendar_get_calendar_events') ||
                    MM.util.wsAvailable('local_mobile_core_calendar_get_calendar_events');
        },

        _getCalendarEventsSucces: function(response, days) {
            var daysIncrement = 90;
            var pageTitle = MM.lang.s("events") + "  " + days + " " + MM.lang.s("days");

            MM.plugins.events.lastEvents = typeof(response.events !== "undefined")? response.events : [];

            var d;
            // Formatting.
            for (var el in MM.plugins.events.lastEvents) {
                var event = MM.plugins.events.lastEvents[el];

                d = new Date(event.timestart * 1000);
                event.startdate = d.toLocaleDateString();
                event.starttime = d.toLocaleTimeString();

                d = new Date((event.timestart + event.timeduration) * 1000);
                event.enddate = d.toLocaleDateString();
                event.endtime = d.toLocaleTimeString();

                MM.plugins.events.lastEvents[el] = event;
            }

            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');

            var tpl = {events: MM.plugins.events.lastEvents};

            var html = MM.tpl.render(MM.plugins.events.templates.events.html, tpl);

            MM.panels.show('center', html, {title: pageTitle});
            $("#events-showmore").on(MM.clickType, function(e) {
                MM.plugins.events.showEvents(days + daysIncrement);
            });
            // Load the first user
            if (MM.deviceType == "tablet" && MM.plugins.events.lastEvents.length > 0) {
                $("#panel-center li:eq(0)").addClass("selected-row");
                MM.plugins.events.showEvent(0);
                $("#panel-center li:eq(0)").addClass("selected-row");
            }

        },

        _getCalendarEventsFailure: function(m) {
            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');
            if (typeof(m) !== "undefined" && m) {
                MM.popErrorMessage(m);
            }
        },

        /**
         * Display global and course events for all the user courses
         * TODO: Support groups events also
         *
         * @param  {integer} days The number of days for displaying events starting today
         */
        showEvents: function(days) {
            MM.panels.showLoading('center');

            days = parseInt(days, 10);

            if (MM.deviceType == "tablet") {
                MM.panels.showLoading('right');
            }
            // Adding loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').addClass('loading-row');

            // The core_calendar_get_calendar_events needs all the current user courses and groups.
            var params = {
                "options[userevents]": 1,
                "options[siteevents]": 1,
                "options[timestart]": MM.util.timestamp(),
                "options[timeend]": MM.util.timestamp() + (MM.util.SECONDS_DAY * days)
            };

            var courses = MM.db.where("courses", {siteid: MM.config.current_site.id});
            $.each(courses, function(index, course) {
                params["events[courseids][" + index + "]"] = course.get("courseid");
            });

            var wsFunction = "core_calendar_get_calendar_events";
            if (!MM.util.wsAvailable(wsFunction)) {
                wsFunction = 'local_mobile_core_calendar_get_calendar_events';
            }

            MM.moodleWSCall(wsFunction,
                params,
                function(r) {
                    MM.plugins.events._getCalendarEventsSucces(r, days)
                },
                null,
                MM.plugins.events._getCalendarEventsFailure
                );
        },

        /**
         * Displays a single event information
         *
         * @param  {integer} eventId The index position in the original events array retrieved.
         */
        showEvent: function(eventId) {
            var pageTitle = MM.lang.s("events");

            if (typeof(MM.plugins.events.lastEvents[eventId]) != "undefined") {
                var fullEvent = MM.plugins.events.lastEvents[eventId];
                var course = MM.db.get("courses", MM.config.current_site.id + "-" + fullEvent.courseid);
                if (course) {
                    fullEvent.courseName = MM.util.formatText(course.get("fullname"));
                }
                var tpl = {"event": fullEvent};
                var html = MM.tpl.render(MM.plugins.events.templates.event.html, tpl);
                MM.panels.show('right', html, {title: pageTitle});
            }
        },

        templates: {
            "event": {
                html: eventTpl
            },
            "events": {
                html: eventsTpl
            }
        }

    };

    MM.registerPlugin(plugin);

});<commit_msg>MOBILE-631 Correct formatting of date times (without seconds)<commit_after>var templates = [
    "root/externallib/text!root/plugins/events/events.html",
    "root/externallib/text!root/plugins/events/event.html"
];

define(templates, function (eventsTpl, eventTpl) {
    var plugin = {
        settings: {
            name: "events",
            type: "general",
            icon: "plugins/events/icon.png",
            menuURL: "#events/90",
            lang: {
                component: "core"
            }
        },

        routes: [
            ["events/:days", "show_events", "showEvents"],
            ["events/show/:id", "show_event", "showEvent"]
        ],

        // This is like a static variable where we store the last Events retrieved in JSON format.
        lastEvents: null,

        /**
         * Determines is the plugin is visible.
         * It may check Moodle remote site version, device OS, device type, etc...
         * This function is called when a alink to a plugin functinality is going to be rendered.
         *
         * @return {bool} True if the plugin is visible for the site and device
         */
        isPluginVisible: function() {
            return MM.util.wsAvailable('core_calendar_get_calendar_events') ||
                    MM.util.wsAvailable('local_mobile_core_calendar_get_calendar_events');
        },

        _getCalendarEventsSucces: function(response, days) {
            var daysIncrement = 90;
            var pageTitle = MM.lang.s("events") + "  " + days + " " + MM.lang.s("days");

            MM.plugins.events.lastEvents = typeof(response.events !== "undefined")? response.events : [];

            var d;
            // Formatting.
            for (var el in MM.plugins.events.lastEvents) {
                var event = MM.plugins.events.lastEvents[el];

                d = new Date(event.timestart * 1000);
                event.startdate = d.toLocaleDateString();
                event.starttime = d.toLocaleTimeString(navigator.language, {hour: '2-digit', minute:'2-digit'});

                if (event.timeduration) {
                    d = new Date((event.timestart + event.timeduration) * 1000);
                    event.enddate = d.toLocaleDateString();
                    event.endtime = d.toLocaleTimeString(navigator.language, {hour: '2-digit', minute:'2-digit'});
                } else {
                    event.enddate = 0;
                    event.endtime = 0;
                }

                MM.plugins.events.lastEvents[el] = event;
            }

            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');

            var tpl = {events: MM.plugins.events.lastEvents};

            var html = MM.tpl.render(MM.plugins.events.templates.events.html, tpl);

            MM.panels.show('center', html, {title: pageTitle});
            $("#events-showmore").on(MM.clickType, function(e) {
                MM.plugins.events.showEvents(days + daysIncrement);
            });
            // Load the first user
            if (MM.deviceType == "tablet" && MM.plugins.events.lastEvents.length > 0) {
                $("#panel-center li:eq(0)").addClass("selected-row");
                MM.plugins.events.showEvent(0);
                $("#panel-center li:eq(0)").addClass("selected-row");
            }

        },

        _getCalendarEventsFailure: function(m) {
            // Removing loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').removeClass('loading-row');
            if (typeof(m) !== "undefined" && m) {
                MM.popErrorMessage(m);
            }
        },

        /**
         * Display global and course events for all the user courses
         * TODO: Support groups events also
         *
         * @param  {integer} days The number of days for displaying events starting today
         */
        showEvents: function(days) {
            MM.panels.showLoading('center');

            days = parseInt(days, 10);

            if (MM.deviceType == "tablet") {
                MM.panels.showLoading('right');
            }
            // Adding loading icon.
            $('a[href="' + MM.plugins.events.settings.menuURL + '"]', '#panel-left').addClass('loading-row');

            // The core_calendar_get_calendar_events needs all the current user courses and groups.
            var params = {
                "options[userevents]": 1,
                "options[siteevents]": 1,
                "options[timestart]": MM.util.timestamp(),
                "options[timeend]": MM.util.timestamp() + (MM.util.SECONDS_DAY * days)
            };

            var courses = MM.db.where("courses", {siteid: MM.config.current_site.id});
            $.each(courses, function(index, course) {
                params["events[courseids][" + index + "]"] = course.get("courseid");
            });

            var wsFunction = "core_calendar_get_calendar_events";
            if (!MM.util.wsAvailable(wsFunction)) {
                wsFunction = 'local_mobile_core_calendar_get_calendar_events';
            }

            MM.moodleWSCall(wsFunction,
                params,
                function(r) {
                    MM.plugins.events._getCalendarEventsSucces(r, days)
                },
                null,
                MM.plugins.events._getCalendarEventsFailure
                );
        },

        /**
         * Displays a single event information
         *
         * @param  {integer} eventId The index position in the original events array retrieved.
         */
        showEvent: function(eventId) {
            var pageTitle = MM.lang.s("events");

            if (typeof(MM.plugins.events.lastEvents[eventId]) != "undefined") {
                var fullEvent = MM.plugins.events.lastEvents[eventId];
                var course = MM.db.get("courses", MM.config.current_site.id + "-" + fullEvent.courseid);
                if (course) {
                    fullEvent.courseName = MM.util.formatText(course.get("fullname"));
                }
                var tpl = {"event": fullEvent};
                var html = MM.tpl.render(MM.plugins.events.templates.event.html, tpl);
                MM.panels.show('right', html, {title: pageTitle});
            }
        },

        templates: {
            "event": {
                html: eventTpl
            },
            "events": {
                html: eventsTpl
            }
        }

    };

    MM.registerPlugin(plugin);

});<|endoftext|>


<commit_before>/*  jspsych-xab.js
 *	Josh de Leeuw
 *
 *  This plugin runs a single XAB trial, where X is an image presented in isolation, and A and B are choices, with A or B being equal to X.
 *	The subject's goal is to identify whether A or B is identical to X.
 *
 * documentation: docs.jspsych.org
 *
 */

jsPsych.plugins.xab = (function() {

  var plugin = {};

  jsPsych.pluginAPI.registerPreload('xab', 'stimuli', 'image',function(t){ return !t.is_html || t.is_html == 'undefined'});

  plugin.info = {
    name: 'xab',
    description: '',
    parameters: {
      stimulus: {
        type: [jsPsych.plugins.parameterType.STRING],
        array: true,
        default: undefined,
        no_function: false,
        description: ''
      },
      is_html: {
        type: [jsPsych.plugins.parameterType.BOOL],
        default: false,
        no_function: false,
        description: ''
      },
      left_key: {
        type: [jsPsych.plugins.parameterType.KEYCODE],
        default: 'q',
        no_function: false,
        description: ''
      },
      right_key: {
        type: [jsPsych.plugins.parameterType.KEYCODE],
        default: 'p',
        no_function: false,
        description: ''
      },
      prompt: {
        type: [jsPsych.plugins.parameterType.STRING],
        default: '',
        no_function: false,
        description: ''
      },
      timing_x: {
        type: [jsPsych.plugins.parameterType.INT],
        default: 1000,
        no_function: false,
        description: ''
      },
      timing_xab_gap: {
        type: [jsPsych.plugins.parameterType.INT],
        default: 1000,
        no_function: false,
        description: ''
      },
      timing_ab: {
        type: [jsPsych.plugins.parameterType.INT],
        default: -1,
        no_function: false,
        description: ''
      },
      timing_response: {
        type: [jsPsych.plugins.parameterType.INT],
        default: -1,
        no_function: false,
        description: ''
      }
    }
  }

  plugin.trial = function(display_element, trial) {

    // default trial values
    trial.left_key = trial.left_key || 81; // defaults to 'q'
    trial.right_key = trial.right_key || 80; // defaults to 'p'
    trial.timing_x = trial.timing_x || 1000; // defaults to 1000msec.
    trial.timing_xab_gap = trial.timing_xab_gap || 1000; // defaults to 1000msec.
    trial.timing_ab = trial.timing_ab || -1; // defaults to -1, meaning infinite time on AB. If a positive number is used, then AB will only be displayed for that length.
    trial.timing_response = trial.timing_response || -1; //
    trial.is_html = (typeof trial.is_html === 'undefined') ? false : trial.is_html;
    trial.prompt = (typeof trial.prompt === 'undefined') ? "" : trial.prompt;

    // if any trial variables are functions
    // this evaluates the function and replaces
    // it with the output of the function
    trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial);

    // unpack the stimuli array
    trial.x_path = trial.stimuli[0];

    // if there is only a pair of stimuli, then the first is the target and is shown twice.
    // if there is a triplet, then the first is X, the second is the target, and the third is foil (useful for non-exact-match XAB).
    if (trial.stimuli.length == 2) {
      trial.a_path = trial.stimuli[0];
      trial.b_path = trial.stimuli[1];
    } else {
      trial.a_path = trial.stimuli[1];
      trial.b_path = trial.stimuli[2];
    }

    // how we display the content depends on whether the content is
    // HTML code or an image path.
    if (!trial.is_html) {
      display_element.innerHTML = '';
    } else {
      display_element.innerHTML = '
'+trial.x_path+'
';
    }

    // start a timer of length trial.timing_x to move to the next part of the trial
    jsPsych.pluginAPI.setTimeout(function() {
      showBlankScreen();
    }, trial.timing_x);

    function showBlankScreen() {
      // remove the x stimulus
      display_element.innerHTML = '';

      // start timer
      jsPsych.pluginAPI.setTimeout(function() {
        showSecondStimulus();
      }, trial.timing_xab_gap);
    }

    function showSecondStimulus() {

      // randomize whether the target is on the left or the right
      var images = [trial.a_path, trial.b_path];
      var target_left = (Math.floor(Math.random() * 2) === 0); // 50% chance target is on left.
      if (!target_left) {
        images = [trial.b_path, trial.a_path];
      }

      if (!trial.is_html) {
        display_element.innerHTML = '';
      } else {
        display_element.innerHTML = '
'+trial.x_path+'
';
      }
      // show the options
      if (!trial.is_html) {
        display_element.innerHTML += '';
        display_element.innerHTML += '';
      } else {
        display_element.innerHTML += '
'+images[0]+'
';
        display_element.innerHTML += '
'+images[1]+'
';
      }

      if (trial.prompt !== "") {
        display_element.innerHTML += trial.prompt;
      }

      // if timing_ab is > 0, then we hide the stimuli after timing_ab milliseconds
      if (trial.timing_ab > 0) {
        jsPsych.pluginAPI.setTimeout(function() {
          var matches = display_element.querySelectorAll('.jspsych-xab-stimulus');
          for(var i=0; i<matches.length; i++){
            matches[i].style.visibility = 'hidden';
          }
        }, trial.timing_ab);
      }

      // if timing_response > 0, then we end the trial after timing_response milliseconds
      if (trial.timing_response > 0) {
        jsPsych.pluginAPI.setTimeout(function() {
          end_trial({
            rt: -1,
            correct: false,
            key: -1
          });
        }, trial.timing_response);
      }

      // create the function that triggers when a key is pressed.
      var after_response = function(info) {

        var correct = false; // true when the correct response is chosen

        if (info.key == trial.left_key) // 'q' key by default
        {
          if (target_left) {
            correct = true;
          }
        } else if (info.key == trial.right_key) // 'p' key by default
        {
          if (!target_left) {
            correct = true;
          }
        }

        info.correct = correct;

        end_trial(info);

      };

      var end_trial = function(info) {
        // kill any remaining setTimeout handlers
        jsPsych.pluginAPI.clearAllTimeouts();

        jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);

        // create object to store data from trial
        var trial_data = {
          "rt": info.rt,
          "correct": info.correct,
          "stimulus": JSON.stringify([trial.x_path, trial.a_path, trial.b_path]),
          "key_press": info.key
        };

        display_element.innerHTML = ''; // remove all

        // move on to the next trial after timing_post_trial milliseconds
        jsPsych.finishTrial(trial_data);
      }

      var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
        callback_function: after_response,
        valid_responses: [trial.left_key, trial.right_key],
        rt_method: 'date',
        persist: false,
        allow_held_key: false
      });
    }
  };

  return plugin;
})();
<commit_msg>fix extra presentation of X<commit_after>/*  jspsych-xab.js
 *	Josh de Leeuw
 *
 *  This plugin runs a single XAB trial, where X is an image presented in isolation, and A and B are choices, with A or B being equal to X.
 *	The subject's goal is to identify whether A or B is identical to X.
 *
 * documentation: docs.jspsych.org
 *
 */

jsPsych.plugins.xab = (function() {

  var plugin = {};

  jsPsych.pluginAPI.registerPreload('xab', 'stimuli', 'image',function(t){ return !t.is_html || t.is_html == 'undefined'});

  plugin.info = {
    name: 'xab',
    description: '',
    parameters: {
      stimulus: {
        type: [jsPsych.plugins.parameterType.STRING],
        array: true,
        default: undefined,
        no_function: false,
        description: ''
      },
      is_html: {
        type: [jsPsych.plugins.parameterType.BOOL],
        default: false,
        no_function: false,
        description: ''
      },
      left_key: {
        type: [jsPsych.plugins.parameterType.KEYCODE],
        default: 'q',
        no_function: false,
        description: ''
      },
      right_key: {
        type: [jsPsych.plugins.parameterType.KEYCODE],
        default: 'p',
        no_function: false,
        description: ''
      },
      prompt: {
        type: [jsPsych.plugins.parameterType.STRING],
        default: '',
        no_function: false,
        description: ''
      },
      timing_x: {
        type: [jsPsych.plugins.parameterType.INT],
        default: 1000,
        no_function: false,
        description: ''
      },
      timing_xab_gap: {
        type: [jsPsych.plugins.parameterType.INT],
        default: 1000,
        no_function: false,
        description: ''
      },
      timing_ab: {
        type: [jsPsych.plugins.parameterType.INT],
        default: -1,
        no_function: false,
        description: ''
      },
      timing_response: {
        type: [jsPsych.plugins.parameterType.INT],
        default: -1,
        no_function: false,
        description: ''
      }
    }
  }

  plugin.trial = function(display_element, trial) {

    // default trial values
    trial.left_key = trial.left_key || 81; // defaults to 'q'
    trial.right_key = trial.right_key || 80; // defaults to 'p'
    trial.timing_x = trial.timing_x || 1000; // defaults to 1000msec.
    trial.timing_xab_gap = trial.timing_xab_gap || 1000; // defaults to 1000msec.
    trial.timing_ab = trial.timing_ab || -1; // defaults to -1, meaning infinite time on AB. If a positive number is used, then AB will only be displayed for that length.
    trial.timing_response = trial.timing_response || -1; //
    trial.is_html = (typeof trial.is_html === 'undefined') ? false : trial.is_html;
    trial.prompt = (typeof trial.prompt === 'undefined') ? "" : trial.prompt;

    // if any trial variables are functions
    // this evaluates the function and replaces
    // it with the output of the function
    trial = jsPsych.pluginAPI.evaluateFunctionParameters(trial);

    // unpack the stimuli array
    trial.x_path = trial.stimuli[0];

    // if there is only a pair of stimuli, then the first is the target and is shown twice.
    // if there is a triplet, then the first is X, the second is the target, and the third is foil (useful for non-exact-match XAB).
    if (trial.stimuli.length == 2) {
      trial.a_path = trial.stimuli[0];
      trial.b_path = trial.stimuli[1];
    } else {
      trial.a_path = trial.stimuli[1];
      trial.b_path = trial.stimuli[2];
    }

    // how we display the content depends on whether the content is
    // HTML code or an image path.
    if (!trial.is_html) {
      display_element.innerHTML = '';
    } else {
      display_element.innerHTML = '
'+trial.x_path+'
';
    }

    // start a timer of length trial.timing_x to move to the next part of the trial
    jsPsych.pluginAPI.setTimeout(function() {
      showBlankScreen();
    }, trial.timing_x);

    function showBlankScreen() {
      // remove the x stimulus
      display_element.innerHTML = '';

      // start timer
      jsPsych.pluginAPI.setTimeout(function() {
        showSecondStimulus();
      }, trial.timing_xab_gap);
    }

    function showSecondStimulus() {

      // randomize whether the target is on the left or the right
      var images = [trial.a_path, trial.b_path];
      var target_left = (Math.floor(Math.random() * 2) === 0); // 50% chance target is on left.
      if (!target_left) {
        images = [trial.b_path, trial.a_path];
      }

      // show the options
      if (!trial.is_html) {
        display_element.innerHTML += '';
        display_element.innerHTML += '';
      } else {
        display_element.innerHTML += '
'+images[0]+'
';
        display_element.innerHTML += '
'+images[1]+'
';
      }

      if (trial.prompt !== "") {
        display_element.innerHTML += trial.prompt;
      }

      // if timing_ab is > 0, then we hide the stimuli after timing_ab milliseconds
      if (trial.timing_ab > 0) {
        jsPsych.pluginAPI.setTimeout(function() {
          var matches = display_element.querySelectorAll('.jspsych-xab-stimulus');
          for(var i=0; i<matches.length; i++){
            matches[i].style.visibility = 'hidden';
          }
        }, trial.timing_ab);
      }

      // if timing_response > 0, then we end the trial after timing_response milliseconds
      if (trial.timing_response > 0) {
        jsPsych.pluginAPI.setTimeout(function() {
          end_trial({
            rt: -1,
            correct: false,
            key: -1
          });
        }, trial.timing_response);
      }

      // create the function that triggers when a key is pressed.
      var after_response = function(info) {

        var correct = false; // true when the correct response is chosen

        if (info.key == trial.left_key) // 'q' key by default
        {
          if (target_left) {
            correct = true;
          }
        } else if (info.key == trial.right_key) // 'p' key by default
        {
          if (!target_left) {
            correct = true;
          }
        }

        info.correct = correct;

        end_trial(info);

      };

      var end_trial = function(info) {
        // kill any remaining setTimeout handlers
        jsPsych.pluginAPI.clearAllTimeouts();

        jsPsych.pluginAPI.cancelKeyboardResponse(keyboardListener);

        // create object to store data from trial
        var trial_data = {
          "rt": info.rt,
          "correct": info.correct,
          "stimulus": JSON.stringify([trial.x_path, trial.a_path, trial.b_path]),
          "key_press": info.key
        };

        display_element.innerHTML = ''; // remove all

        // move on to the next trial after timing_post_trial milliseconds
        jsPsych.finishTrial(trial_data);
      }

      var keyboardListener = jsPsych.pluginAPI.getKeyboardResponse({
        callback_function: after_response,
        valid_responses: [trial.left_key, trial.right_key],
        rt_method: 'date',
        persist: false,
        allow_held_key: false
      });
    }
  };

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


<commit_before>/*
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

export default class Range {
    constructor(model, startPosition, endPosition = startPosition) {
        this._model = model;
        this._start = startPosition;
        this._end = endPosition;
    }

    moveStart(delta) {
        this._start = this._start.forwardsWhile(this._model, () => {
            delta -= 1;
            return delta >= 0;
        });
    }

    expandBackwardsWhile(predicate) {
        this._start = this._start.backwardsWhile(this._model, predicate);
    }

    get text() {
        let text = "";
        this._start.iteratePartsBetween(this._end, this._model, (part, startIdx, endIdx) => {
            const t = part.text.substring(startIdx, endIdx);
            text = text + t;
        });
        return text;
    }

    /**
     * Splits the model at the range boundaries and replaces with the given parts.
     * Should be run inside a `model.transform()` callback.
     * @param {Part[]} parts the parts to replace the range with
     * @return {Number} the net amount of characters added, can be negative.
     */
    replace(parts) {
        const newLength = parts.reduce((sum, part) => sum + part.text.length, 0);
        let oldLength = 0;
        this._start.iteratePartsBetween(this._end, this._model, (part, startIdx, endIdx) => {
            oldLength += endIdx - startIdx;
        });
        this._model.replaceRange(this._start, this._end, parts);
        return newLength - oldLength;
    }
}
<commit_msg>sort positions in Range constructor, so start always comes before end<commit_after>/*
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

export default class Range {
    constructor(model, positionA, positionB = positionA) {
        this._model = model;
        const bIsLarger = positionA.compare(positionB) < 0;
        this._start = bIsLarger ? positionA : positionB;
        this._end = bIsLarger ? positionB : positionA;
    }

    moveStart(delta) {
        this._start = this._start.forwardsWhile(this._model, () => {
            delta -= 1;
            return delta >= 0;
        });
    }

    expandBackwardsWhile(predicate) {
        this._start = this._start.backwardsWhile(this._model, predicate);
    }

    get text() {
        let text = "";
        this._start.iteratePartsBetween(this._end, this._model, (part, startIdx, endIdx) => {
            const t = part.text.substring(startIdx, endIdx);
            text = text + t;
        });
        return text;
    }

    /**
     * Splits the model at the range boundaries and replaces with the given parts.
     * Should be run inside a `model.transform()` callback.
     * @param {Part[]} parts the parts to replace the range with
     * @return {Number} the net amount of characters added, can be negative.
     */
    replace(parts) {
        const newLength = parts.reduce((sum, part) => sum + part.text.length, 0);
        let oldLength = 0;
        this._start.iteratePartsBetween(this._end, this._model, (part, startIdx, endIdx) => {
            oldLength += endIdx - startIdx;
        });
        this._model.replaceRange(this._start, this._end, parts);
        return newLength - oldLength;
    }
}
<|endoftext|>


<commit_before>
/**
 * Request.js
 *
 * Request class contains server only options
 *
 * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/.
 */

import {format as formatUrl} from 'url';
import Stream from 'stream';
import Headers, {exportNodeCompatibleHeaders} from './headers';
import Body, {clone, extractContentType, getTotalBytes} from './body';
import {isAbortSignal} from './utils/is';

const INTERNALS = Symbol('Request internals');

const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;

/**
 * Check if `obj` is an instance of Request.
 *
 * @param  {*} obj
 * @return {boolean}
 */
function isRequest(object) {
	return (
		typeof object === 'object' &&
		typeof object[INTERNALS] === 'object'
	);
}

/**
 * Wrapper around `new URL` to handle relative URLs (https://github.com/nodejs/node/issues/12682)
 *
 * @param  {string} urlStr
 * @return {void}
 */
function parseURL(urlString) {
	/*
		Check whether the URL is absolute or not

		Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
		Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
	*/
	if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlString)) {
		return new URL(urlString);
	}

	throw new TypeError('Only absolute URLs are supported');
}

/**
 * Request class
 *
 * @param   Mixed   input  Url or Request instance
 * @param   Object  init   Custom options
 * @return  Void
 */
export default class Request {
	constructor(input, init = {}) {
		let parsedURL;

		// Normalize input and force URL to be encoded as UTF-8 (https://github.com/bitinn/node-fetch/issues/245)
		if (!isRequest(input)) {
			if (input && input.href) {
				// In order to support Node.js' Url objects; though WHATWG's URL objects
				// will fall into this branch also (since their `toString()` will return
				// `href` property anyway)
				parsedURL = parseURL(input.href);
			} else {
				// Coerce input to a string before attempting to parse
				parsedURL = parseURL(`${input}`);
			}

			input = {};
		} else {
			parsedURL = parseURL(input.url);
		}

		let method = init.method || input.method || 'GET';
		method = method.toUpperCase();

		// eslint-disable-next-line no-eq-null, eqeqeq
		if ((init.body != null || isRequest(input) && input.body !== null) &&
			(method === 'GET' || method === 'HEAD')) {
			throw new TypeError('Request with GET/HEAD method cannot have body');
		}

		// eslint-disable-next-line no-eq-null, eqeqeq
		const inputBody = init.body != null ?
			init.body :
			(isRequest(input) && input.body !== null ?
				clone(input) :
				null);

		Body.call(this, inputBody, {
			timeout: init.timeout || input.timeout || 0,
			size: init.size || input.size || 0
		});

		const headers = new Headers(init.headers || input.headers || {});

		if (inputBody !== null && !headers.has('Content-Type')) {
			const contentType = extractContentType(inputBody);
			if (contentType) {
				headers.append('Content-Type', contentType);
			}
		}

		let signal = isRequest(input) ?
			input.signal :
			null;
		if ('signal' in init) {
			signal = init.signal;
		}

		if (signal !== null && !isAbortSignal(signal)) {
			throw new TypeError('Expected signal to be an instanceof AbortSignal');
		}

		this[INTERNALS] = {
			method,
			redirect: init.redirect || input.redirect || 'follow',
			headers,
			parsedURL,
			signal
		};

		// Node-fetch-only options
		this.follow = init.follow !== undefined ?
			init.follow : (input.follow !== undefined ?
				input.follow : 20);
		this.compress = init.compress !== undefined ?
			init.compress : (input.compress !== undefined ?
				input.compress : true);
		this.counter = init.counter || input.counter || 0;
		this.agent = init.agent || input.agent;
		this.highWaterMark = init.highWaterMark || input.highWaterMark;
	}

	get method() {
		return this[INTERNALS].method;
	}

	get url() {
		return formatUrl(this[INTERNALS].parsedURL);
	}

	get headers() {
		return this[INTERNALS].headers;
	}

	get redirect() {
		return this[INTERNALS].redirect;
	}

	get signal() {
		return this[INTERNALS].signal;
	}

	/**
	 * Clone this request
	 *
	 * @return  Request
	 */
	clone() {
		return new Request(this);
	}
}

Body.mixIn(Request.prototype);

Object.defineProperty(Request.prototype, Symbol.toStringTag, {
	value: 'Request',
	writable: false,
	enumerable: false,
	configurable: true
});

Object.defineProperties(Request.prototype, {
	method: {enumerable: true},
	url: {enumerable: true},
	headers: {enumerable: true},
	redirect: {enumerable: true},
	clone: {enumerable: true},
	signal: {enumerable: true}
});

/**
 * Convert a Request to Node.js http request options.
 *
 * @param   Request  A Request instance
 * @return  Object   The options object to be passed to http.request
 */
export function getNodeRequestOptions(request) {
	const {parsedURL} = request[INTERNALS];
	const headers = new Headers(request[INTERNALS].headers);

	// Fetch step 1.3
	if (!headers.has('Accept')) {
		headers.set('Accept', '*/*');
	}

	// Basic fetch
	if (!parsedURL.protocol || !parsedURL.hostname) {
		throw new TypeError('Only absolute URLs are supported');
	}

	if (!/^https?:$/.test(parsedURL.protocol)) {
		throw new TypeError('Only HTTP(S) protocols are supported');
	}

	if (
		request.signal &&
		request.body instanceof Stream.Readable &&
		!streamDestructionSupported
	) {
		throw new Error('Cancellation of streamed requests with AbortSignal is not supported');
	}

	// HTTP-network-or-cache fetch steps 2.4-2.7
	let contentLengthValue = null;
	if (request.body === null && /^(post|put)$/i.test(request.method)) {
		contentLengthValue = '0';
	}

	if (request.body !== null) {
		const totalBytes = getTotalBytes(request);
		if (typeof totalBytes === 'number') {
			contentLengthValue = String(totalBytes);
		}
	}

	if (contentLengthValue) {
		headers.set('Content-Length', contentLengthValue);
	}

	// HTTP-network-or-cache fetch step 2.11
	if (!headers.has('User-Agent')) {
		headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
	}

	// HTTP-network-or-cache fetch step 2.15
	if (request.compress && !headers.has('Accept-Encoding')) {
		headers.set('Accept-Encoding', 'gzip,deflate');
	}

	let {agent} = request;
	if (typeof agent === 'function') {
		agent = agent(parsedURL);
	}

	if (!headers.has('Connection') && !agent) {
		headers.set('Connection', 'close');
	}

	// HTTP-network fetch step 4.2
	// chunked encoding is handled by Node.js

	// manually spread the URL object instead of spread syntax
	const requestOptions = {
		path: parsedURL.pathname,
		pathname: parsedURL.pathname,
		hostname: parsedURL.hostname,
		protocol: parsedURL.protocol,
		port: parsedURL.port,
		hash: parsedURL.hash,
		search: parsedURL.search,
		query: parsedURL.query,
		href: parsedURL.href,
		method: request.method,
		headers: exportNodeCompatibleHeaders(headers),
		agent
	};

	return requestOptions;
}
<commit_msg>fix: Ensure search parameters are included in URL path (#759)<commit_after>
/**
 * Request.js
 *
 * Request class contains server only options
 *
 * All spec algorithm step numbers are based on https://fetch.spec.whatwg.org/commit-snapshots/ae716822cb3a61843226cd090eefc6589446c1d2/.
 */

import {format as formatUrl} from 'url';
import Stream from 'stream';
import Headers, {exportNodeCompatibleHeaders} from './headers';
import Body, {clone, extractContentType, getTotalBytes} from './body';
import {isAbortSignal} from './utils/is';

const INTERNALS = Symbol('Request internals');

const streamDestructionSupported = 'destroy' in Stream.Readable.prototype;

/**
 * Check if `obj` is an instance of Request.
 *
 * @param  {*} obj
 * @return {boolean}
 */
function isRequest(object) {
	return (
		typeof object === 'object' &&
		typeof object[INTERNALS] === 'object'
	);
}

/**
 * Wrapper around `new URL` to handle relative URLs (https://github.com/nodejs/node/issues/12682)
 *
 * @param  {string} urlStr
 * @return {void}
 */
function parseURL(urlString) {
	/*
		Check whether the URL is absolute or not

		Scheme: https://tools.ietf.org/html/rfc3986#section-3.1
		Absolute URL: https://tools.ietf.org/html/rfc3986#section-4.3
	*/
	if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.exec(urlString)) {
		return new URL(urlString);
	}

	throw new TypeError('Only absolute URLs are supported');
}

/**
 * Request class
 *
 * @param   Mixed   input  Url or Request instance
 * @param   Object  init   Custom options
 * @return  Void
 */
export default class Request {
	constructor(input, init = {}) {
		let parsedURL;

		// Normalize input and force URL to be encoded as UTF-8 (https://github.com/bitinn/node-fetch/issues/245)
		if (!isRequest(input)) {
			if (input && input.href) {
				// In order to support Node.js' Url objects; though WHATWG's URL objects
				// will fall into this branch also (since their `toString()` will return
				// `href` property anyway)
				parsedURL = parseURL(input.href);
			} else {
				// Coerce input to a string before attempting to parse
				parsedURL = parseURL(`${input}`);
			}

			input = {};
		} else {
			parsedURL = parseURL(input.url);
		}

		let method = init.method || input.method || 'GET';
		method = method.toUpperCase();

		// eslint-disable-next-line no-eq-null, eqeqeq
		if ((init.body != null || isRequest(input) && input.body !== null) &&
			(method === 'GET' || method === 'HEAD')) {
			throw new TypeError('Request with GET/HEAD method cannot have body');
		}

		// eslint-disable-next-line no-eq-null, eqeqeq
		const inputBody = init.body != null ?
			init.body :
			(isRequest(input) && input.body !== null ?
				clone(input) :
				null);

		Body.call(this, inputBody, {
			timeout: init.timeout || input.timeout || 0,
			size: init.size || input.size || 0
		});

		const headers = new Headers(init.headers || input.headers || {});

		if (inputBody !== null && !headers.has('Content-Type')) {
			const contentType = extractContentType(inputBody);
			if (contentType) {
				headers.append('Content-Type', contentType);
			}
		}

		let signal = isRequest(input) ?
			input.signal :
			null;
		if ('signal' in init) {
			signal = init.signal;
		}

		if (signal !== null && !isAbortSignal(signal)) {
			throw new TypeError('Expected signal to be an instanceof AbortSignal');
		}

		this[INTERNALS] = {
			method,
			redirect: init.redirect || input.redirect || 'follow',
			headers,
			parsedURL,
			signal
		};

		// Node-fetch-only options
		this.follow = init.follow !== undefined ?
			init.follow : (input.follow !== undefined ?
				input.follow : 20);
		this.compress = init.compress !== undefined ?
			init.compress : (input.compress !== undefined ?
				input.compress : true);
		this.counter = init.counter || input.counter || 0;
		this.agent = init.agent || input.agent;
		this.highWaterMark = init.highWaterMark || input.highWaterMark;
	}

	get method() {
		return this[INTERNALS].method;
	}

	get url() {
		return formatUrl(this[INTERNALS].parsedURL);
	}

	get headers() {
		return this[INTERNALS].headers;
	}

	get redirect() {
		return this[INTERNALS].redirect;
	}

	get signal() {
		return this[INTERNALS].signal;
	}

	/**
	 * Clone this request
	 *
	 * @return  Request
	 */
	clone() {
		return new Request(this);
	}
}

Body.mixIn(Request.prototype);

Object.defineProperty(Request.prototype, Symbol.toStringTag, {
	value: 'Request',
	writable: false,
	enumerable: false,
	configurable: true
});

Object.defineProperties(Request.prototype, {
	method: {enumerable: true},
	url: {enumerable: true},
	headers: {enumerable: true},
	redirect: {enumerable: true},
	clone: {enumerable: true},
	signal: {enumerable: true}
});

/**
 * Convert a Request to Node.js http request options.
 *
 * @param   Request  A Request instance
 * @return  Object   The options object to be passed to http.request
 */
export function getNodeRequestOptions(request) {
	const {parsedURL} = request[INTERNALS];
	const headers = new Headers(request[INTERNALS].headers);

	// Fetch step 1.3
	if (!headers.has('Accept')) {
		headers.set('Accept', '*/*');
	}

	// Basic fetch
	if (!parsedURL.protocol || !parsedURL.hostname) {
		throw new TypeError('Only absolute URLs are supported');
	}

	if (!/^https?:$/.test(parsedURL.protocol)) {
		throw new TypeError('Only HTTP(S) protocols are supported');
	}

	if (
		request.signal &&
		request.body instanceof Stream.Readable &&
		!streamDestructionSupported
	) {
		throw new Error('Cancellation of streamed requests with AbortSignal is not supported');
	}

	// HTTP-network-or-cache fetch steps 2.4-2.7
	let contentLengthValue = null;
	if (request.body === null && /^(post|put)$/i.test(request.method)) {
		contentLengthValue = '0';
	}

	if (request.body !== null) {
		const totalBytes = getTotalBytes(request);
		if (typeof totalBytes === 'number') {
			contentLengthValue = String(totalBytes);
		}
	}

	if (contentLengthValue) {
		headers.set('Content-Length', contentLengthValue);
	}

	// HTTP-network-or-cache fetch step 2.11
	if (!headers.has('User-Agent')) {
		headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
	}

	// HTTP-network-or-cache fetch step 2.15
	if (request.compress && !headers.has('Accept-Encoding')) {
		headers.set('Accept-Encoding', 'gzip,deflate');
	}

	let {agent} = request;
	if (typeof agent === 'function') {
		agent = agent(parsedURL);
	}

	if (!headers.has('Connection') && !agent) {
		headers.set('Connection', 'close');
	}

	// HTTP-network fetch step 4.2
	// chunked encoding is handled by Node.js

	// manually spread the URL object instead of spread syntax
	const requestOptions = {
		path: parsedURL.pathname + parsedURL.search,
		pathname: parsedURL.pathname,
		hostname: parsedURL.hostname,
		protocol: parsedURL.protocol,
		port: parsedURL.port,
		hash: parsedURL.hash,
		search: parsedURL.search,
		query: parsedURL.query,
		href: parsedURL.href,
		method: request.method,
		headers: exportNodeCompatibleHeaders(headers),
		agent
	};

	return requestOptions;
}
<|endoftext|>


<commit_before>define(function(require, exports, module) {
    "use strict";
    main.consumes = ["Plugin", "ui"];
    main.provides = ["fontawesome"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var ui = imports.ui;

        /***** Initialization *****/

        var plugin = new Plugin("Ajax.org", main.consumes);

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            var css = require("text!./css/font-awesome.css")
                .replace(/@\{base-path\}/g,  options.staticPrefix)
            ui.insertCss(css, false, plugin);
        }

        /***** Lifecycle *****/

        plugin.on("load", function(){
            load();
        });

        plugin.freezePublicAPI({});
        register(null, { "fontawesome" : plugin });
    }
});<commit_msg>Apply .eslintrc rules<commit_after>define(function(require, exports, module) {
    "use strict";
    main.consumes = ["Plugin", "ui"];
    main.provides = ["fontawesome"];
    return main;

    function main(options, imports, register) {
        var Plugin = imports.Plugin;
        var ui = imports.ui;

        /***** Initialization *****/

        var plugin = new Plugin("Ajax.org", main.consumes);

        var loaded = false;
        function load() {
            if (loaded) return false;
            loaded = true;

            var css = require("text!./css/font-awesome.css")
                .replace(/@\{base-path\}/g, options.staticPrefix);
            ui.insertCss(css, false, plugin);
        }

        /***** Lifecycle *****/

        plugin.on("load", function() {
            load();
        });

        plugin.freezePublicAPI({});
        register(null, { "fontawesome": plugin });
    }
});<|endoftext|>