<commit_before>// Inserts shortcut buttons after all of the following:
//     <input type="text" class="vDateField">
//     <input type="text" class="vTimeField">

var DateTimeShortcuts = {
    calendars: [],
    calendarInputs: [],
    clockInputs: [],
    calendarDivName1: 'calendarbox', // name of calendar
 that gets toggled
    calendarDivName2: 'calendarin',  // name of
 that contains calendar
    calendarLinkName: 'calendarlink',// name of the link that is used to toggle
    clockDivName: 'clockbox',        // name of clock
 that gets toggled
    clockLinkName: 'clocklink',      // name of the link that is used to toggle
    shortCutsClass: 'datetimeshortcuts', // class of the clock and cal shortcuts
    admin_media_prefix: '',
    init: function() {
        // Get admin_media_prefix by grabbing it off the window object. It's
        // set in the admin/base.html template, so if it's not there, someone's
        // overridden the template. In that case, we'll set a clearly-invalid
        // value in the hopes that someone will examine HTTP requests and see it.
        if (window.__admin_media_prefix__ != undefined) {
            DateTimeShortcuts.admin_media_prefix = window.__admin_media_prefix__;
        } else {
            DateTimeShortcuts.admin_media_prefix = '/missing-admin-media-prefix/';
        }

        var inputs = document.getElementsByTagName('input');
        for (i=0; i<inputs.length; i++) {
            var inp = inputs[i];
            if (inp.getAttribute('type') == 'text' && inp.className.match(/vTimeField/)) {
                DateTimeShortcuts.addClock(inp);
            }
            else if (inp.getAttribute('type') == 'text' && inp.className.match(/vDateField/)) {
                DateTimeShortcuts.addCalendar(inp);
            }
        }
    },
    // Add clock widget to a given field
    addClock: function(inp) {
        var num = DateTimeShortcuts.clockInputs.length;
        DateTimeShortcuts.clockInputs[num] = inp;

        // Shortcut links (clock icon and "Now" link)
        var shortcuts_span = document.createElement('span');
        shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
        inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
        var now_link = document.createElement('a');
        now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + get_format('TIME_INPUT_FORMATS')[0] + "'));");
        now_link.appendChild(document.createTextNode(gettext('Now')));
        var clock_link = document.createElement('a');
        clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
        clock_link.id = DateTimeShortcuts.clockLinkName + num;
        quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_clock.gif', 'alt', gettext('Clock'));
        shortcuts_span.appendChild(document.createTextNode('\240'));
        shortcuts_span.appendChild(now_link);
        shortcuts_span.appendChild(document.createTextNode('\240|\240'));
        shortcuts_span.appendChild(clock_link);

        // Create clock link div
        //
        // Markup looks like:
        //

        //
Choose a time

        //

        //
Now

        //
Midnight

        //
6 a.m.

        //
Noon

        //

        //
Cancel

        //

        var clock_box = document.createElement('div');
        clock_box.style.display = 'none';
        clock_box.style.position = 'absolute';
        clock_box.className = 'clockbox module';
        clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num);
        document.body.appendChild(clock_box);
        addEvent(clock_box, 'click', DateTimeShortcuts.cancelEventPropagation);

        quickElement('h2', clock_box, gettext('Choose a time'));
        time_list = quickElement('ul', clock_box, '');
        time_list.className = 'timelist';
        time_format = get_format('TIME_INPUT_FORMATS')[0];
        quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + time_format + "'));");
        quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,0,0,0,0).strftime('" + time_format + "'));");
        quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,6,0,0,0).strftime('" + time_format + "'));");
        quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,12,0,0,0).strftime('" + time_format + "'));");

        cancel_p = quickElement('p', clock_box, '');
        cancel_p.className = 'calendar-cancel';
        quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');');
    },
    openClock: function(num) {
        var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num)
        var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num)

        // Recalculate the clockbox position
        // is it left-to-right or right-to-left layout ?
        if (getStyle(document.body,'direction')!='rtl') {
            clock_box.style.left = findPosX(clock_link) + 17 + 'px';
        }
        else {
            // since style's width is in em, it'd be tough to calculate
            // px value of it. let's use an estimated px for now
            // TODO: IE returns wrong value for findPosX when in rtl mode
            //       (it returns as it was left aligned), needs to be fixed.
            clock_box.style.left = findPosX(clock_link) - 110 + 'px';
        }
        clock_box.style.top = findPosY(clock_link) - 30 + 'px';

        // Show the clock box
        clock_box.style.display = 'block';
        addEvent(window.document, 'click', function() { DateTimeShortcuts.dismissClock(num); return true; });
    },
    dismissClock: function(num) {
       document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none';
       window.document.onclick = null;
    },
    handleClockQuicklink: function(num, val) {
       DateTimeShortcuts.clockInputs[num].value = val;
       DateTimeShortcuts.clockInputs[num].focus();
       DateTimeShortcuts.dismissClock(num);
    },
    // Add calendar widget to a given field.
    addCalendar: function(inp) {
        var num = DateTimeShortcuts.calendars.length;

        DateTimeShortcuts.calendarInputs[num] = inp;

        // Shortcut links (calendar icon and "Today" link)
        var shortcuts_span = document.createElement('span');
        shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
        inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
        var today_link = document.createElement('a');
        today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
        today_link.appendChild(document.createTextNode(gettext('Today')));
        var cal_link = document.createElement('a');
        cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');');
        cal_link.id = DateTimeShortcuts.calendarLinkName + num;
        quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_calendar.gif', 'alt', gettext('Calendar'));
        shortcuts_span.appendChild(document.createTextNode('\240'));
        shortcuts_span.appendChild(today_link);
        shortcuts_span.appendChild(document.createTextNode('\240|\240'));
        shortcuts_span.appendChild(cal_link);

        // Create calendarbox div.
        //
        // Markup looks like:
        //
        //

        //

        //           ‹
        //           › February 2003
        //

        //

        //
        //

        //

        //          Yesterday | Today | Tomorrow
        //

        //
Cancel

        //

        var cal_box = document.createElement('div');
        cal_box.style.display = 'none';
        cal_box.style.position = 'absolute';
        cal_box.className = 'calendarbox module';
        cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num);
        document.body.appendChild(cal_box);
        addEvent(cal_box, 'click', DateTimeShortcuts.cancelEventPropagation);

        // next-prev links
        var cal_nav = quickElement('div', cal_box, '');
        var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev('+num+');');
        cal_nav_prev.className = 'calendarnav-previous';
        var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext('+num+');');
        cal_nav_next.className = 'calendarnav-next';

        // main box
        var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num);
        cal_main.className = 'calendar';
        DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num));
        DateTimeShortcuts.calendars[num].drawCurrent();

        // calendar shortcuts
        var shortcuts = quickElement('div', cal_box, '');
        shortcuts.className = 'calendar-shortcuts';
        quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);');
        shortcuts.appendChild(document.createTextNode('\240|\240'));
        quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
        shortcuts.appendChild(document.createTextNode('\240|\240'));
        quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);');

        // cancel bar
        var cancel_p = quickElement('p', cal_box, '');
        cancel_p.className = 'calendar-cancel';
        quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');');
    },
    openCalendar: function(num) {
        var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num)
        var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num)
        var inp = DateTimeShortcuts.calendarInputs[num];

        // Determine if the current value in the input has a valid date.
        // If so, draw the calendar with that date's year and month.
        if (inp.value) {
            var date_parts = inp.value.split('-');
            var year = date_parts[0];
            var month = parseFloat(date_parts[1]);
            if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
                DateTimeShortcuts.calendars[num].drawDate(month, year);
            }
        }

        // Recalculate the clockbox position
        // is it left-to-right or right-to-left layout ?
        if (getStyle(document.body,'direction')!='rtl') {
            cal_box.style.left = findPosX(cal_link) + 17 + 'px';
        }
        else {
            // since style's width is in em, it'd be tough to calculate
            // px value of it. let's use an estimated px for now
            // TODO: IE returns wrong value for findPosX when in rtl mode
            //       (it returns as it was left aligned), needs to be fixed.
            cal_box.style.left = findPosX(cal_link) - 180 + 'px';
        }
        cal_box.style.top = findPosY(cal_link) - 75 + 'px';

        cal_box.style.display = 'block';
        addEvent(window.document, 'click', function() { DateTimeShortcuts.dismissCalendar(num); return true; });
    },
    dismissCalendar: function(num) {
        document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'none';
        window.document.onclick = null;
    },
    drawPrev: function(num) {
        DateTimeShortcuts.calendars[num].drawPreviousMonth();
    },
    drawNext: function(num) {
        DateTimeShortcuts.calendars[num].drawNextMonth();
    },
    handleCalendarCallback: function(num) {
        format = get_format('DATE_INPUT_FORMATS')[0];
        // the format needs to be escaped a little
        format = format.replace('\\', '\\\\');
        format = format.replace('\r', '\\r');
        format = format.replace('\n', '\\n');
        format = format.replace('\t', '\\t');
        format = format.replace("'", "\\'");
        return ["function(y, m, d) { DateTimeShortcuts.calendarInputs[",
               num,
               "].value = new Date(y, m-1, d).strftime('",
               format,
               "');DateTimeShortcuts.calendarInputs[",
               num,
               "].focus();document.getElementById(DateTimeShortcuts.calendarDivName1+",
               num,
               ").style.display='none';}"].join('');
    },
    handleCalendarQuickLink: function(num, offset) {
       var d = new Date();
       d.setDate(d.getDate() + offset)
       DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]);
       DateTimeShortcuts.calendarInputs[num].focus();
       DateTimeShortcuts.dismissCalendar(num);
    },
    cancelEventPropagation: function(e) {
        if (!e) e = window.event;
        e.cancelBubble = true;
        if (e.stopPropagation) e.stopPropagation();
    }
}

addEvent(window, 'load', DateTimeShortcuts.init);
<commit_msg>Fixed #11414 -- Made sure the calendar and clock popup windows in the admin don't have a negative vertical position. Thanks uipko for the report and fix.<commit_after>// Inserts shortcut buttons after all of the following:
//     <input type="text" class="vDateField">
//     <input type="text" class="vTimeField">

var DateTimeShortcuts = {
    calendars: [],
    calendarInputs: [],
    clockInputs: [],
    calendarDivName1: 'calendarbox', // name of calendar
 that gets toggled
    calendarDivName2: 'calendarin',  // name of
 that contains calendar
    calendarLinkName: 'calendarlink',// name of the link that is used to toggle
    clockDivName: 'clockbox',        // name of clock
 that gets toggled
    clockLinkName: 'clocklink',      // name of the link that is used to toggle
    shortCutsClass: 'datetimeshortcuts', // class of the clock and cal shortcuts
    admin_media_prefix: '',
    init: function() {
        // Get admin_media_prefix by grabbing it off the window object. It's
        // set in the admin/base.html template, so if it's not there, someone's
        // overridden the template. In that case, we'll set a clearly-invalid
        // value in the hopes that someone will examine HTTP requests and see it.
        if (window.__admin_media_prefix__ != undefined) {
            DateTimeShortcuts.admin_media_prefix = window.__admin_media_prefix__;
        } else {
            DateTimeShortcuts.admin_media_prefix = '/missing-admin-media-prefix/';
        }

        var inputs = document.getElementsByTagName('input');
        for (i=0; i<inputs.length; i++) {
            var inp = inputs[i];
            if (inp.getAttribute('type') == 'text' && inp.className.match(/vTimeField/)) {
                DateTimeShortcuts.addClock(inp);
            }
            else if (inp.getAttribute('type') == 'text' && inp.className.match(/vDateField/)) {
                DateTimeShortcuts.addCalendar(inp);
            }
        }
    },
    // Add clock widget to a given field
    addClock: function(inp) {
        var num = DateTimeShortcuts.clockInputs.length;
        DateTimeShortcuts.clockInputs[num] = inp;

        // Shortcut links (clock icon and "Now" link)
        var shortcuts_span = document.createElement('span');
        shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
        inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
        var now_link = document.createElement('a');
        now_link.setAttribute('href', "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + get_format('TIME_INPUT_FORMATS')[0] + "'));");
        now_link.appendChild(document.createTextNode(gettext('Now')));
        var clock_link = document.createElement('a');
        clock_link.setAttribute('href', 'javascript:DateTimeShortcuts.openClock(' + num + ');');
        clock_link.id = DateTimeShortcuts.clockLinkName + num;
        quickElement('img', clock_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_clock.gif', 'alt', gettext('Clock'));
        shortcuts_span.appendChild(document.createTextNode('\240'));
        shortcuts_span.appendChild(now_link);
        shortcuts_span.appendChild(document.createTextNode('\240|\240'));
        shortcuts_span.appendChild(clock_link);

        // Create clock link div
        //
        // Markup looks like:
        //

        //
Choose a time

        //

        //
Now

        //
Midnight

        //
6 a.m.

        //
Noon

        //

        //
Cancel

        //

        var clock_box = document.createElement('div');
        clock_box.style.display = 'none';
        clock_box.style.position = 'absolute';
        clock_box.className = 'clockbox module';
        clock_box.setAttribute('id', DateTimeShortcuts.clockDivName + num);
        document.body.appendChild(clock_box);
        addEvent(clock_box, 'click', DateTimeShortcuts.cancelEventPropagation);

        quickElement('h2', clock_box, gettext('Choose a time'));
        time_list = quickElement('ul', clock_box, '');
        time_list.className = 'timelist';
        time_format = get_format('TIME_INPUT_FORMATS')[0];
        quickElement("a", quickElement("li", time_list, ""), gettext("Now"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date().strftime('" + time_format + "'));");
        quickElement("a", quickElement("li", time_list, ""), gettext("Midnight"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,0,0,0,0).strftime('" + time_format + "'));");
        quickElement("a", quickElement("li", time_list, ""), gettext("6 a.m."), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,6,0,0,0).strftime('" + time_format + "'));");
        quickElement("a", quickElement("li", time_list, ""), gettext("Noon"), "href", "javascript:DateTimeShortcuts.handleClockQuicklink(" + num + ", new Date(1970,1,1,12,0,0,0).strftime('" + time_format + "'));");

        cancel_p = quickElement('p', clock_box, '');
        cancel_p.className = 'calendar-cancel';
        quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissClock(' + num + ');');
    },
    openClock: function(num) {
        var clock_box = document.getElementById(DateTimeShortcuts.clockDivName+num)
        var clock_link = document.getElementById(DateTimeShortcuts.clockLinkName+num)

        // Recalculate the clockbox position
        // is it left-to-right or right-to-left layout ?
        if (getStyle(document.body,'direction')!='rtl') {
            clock_box.style.left = findPosX(clock_link) + 17 + 'px';
        }
        else {
            // since style's width is in em, it'd be tough to calculate
            // px value of it. let's use an estimated px for now
            // TODO: IE returns wrong value for findPosX when in rtl mode
            //       (it returns as it was left aligned), needs to be fixed.
            clock_box.style.left = findPosX(clock_link) - 110 + 'px';
        }
        clock_box.style.top = Math.max(0, findPosY(clock_link) - 30) + 'px';

        // Show the clock box
        clock_box.style.display = 'block';
        addEvent(window.document, 'click', function() { DateTimeShortcuts.dismissClock(num); return true; });
    },
    dismissClock: function(num) {
       document.getElementById(DateTimeShortcuts.clockDivName + num).style.display = 'none';
       window.document.onclick = null;
    },
    handleClockQuicklink: function(num, val) {
       DateTimeShortcuts.clockInputs[num].value = val;
       DateTimeShortcuts.clockInputs[num].focus();
       DateTimeShortcuts.dismissClock(num);
    },
    // Add calendar widget to a given field.
    addCalendar: function(inp) {
        var num = DateTimeShortcuts.calendars.length;

        DateTimeShortcuts.calendarInputs[num] = inp;

        // Shortcut links (calendar icon and "Today" link)
        var shortcuts_span = document.createElement('span');
        shortcuts_span.className = DateTimeShortcuts.shortCutsClass;
        inp.parentNode.insertBefore(shortcuts_span, inp.nextSibling);
        var today_link = document.createElement('a');
        today_link.setAttribute('href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
        today_link.appendChild(document.createTextNode(gettext('Today')));
        var cal_link = document.createElement('a');
        cal_link.setAttribute('href', 'javascript:DateTimeShortcuts.openCalendar(' + num + ');');
        cal_link.id = DateTimeShortcuts.calendarLinkName + num;
        quickElement('img', cal_link, '', 'src', DateTimeShortcuts.admin_media_prefix + 'img/admin/icon_calendar.gif', 'alt', gettext('Calendar'));
        shortcuts_span.appendChild(document.createTextNode('\240'));
        shortcuts_span.appendChild(today_link);
        shortcuts_span.appendChild(document.createTextNode('\240|\240'));
        shortcuts_span.appendChild(cal_link);

        // Create calendarbox div.
        //
        // Markup looks like:
        //
        //

        //

        //           ‹
        //           › February 2003
        //

        //

        //
        //

        //

        //          Yesterday | Today | Tomorrow
        //

        //
Cancel

        //

        var cal_box = document.createElement('div');
        cal_box.style.display = 'none';
        cal_box.style.position = 'absolute';
        cal_box.className = 'calendarbox module';
        cal_box.setAttribute('id', DateTimeShortcuts.calendarDivName1 + num);
        document.body.appendChild(cal_box);
        addEvent(cal_box, 'click', DateTimeShortcuts.cancelEventPropagation);

        // next-prev links
        var cal_nav = quickElement('div', cal_box, '');
        var cal_nav_prev = quickElement('a', cal_nav, '<', 'href', 'javascript:DateTimeShortcuts.drawPrev('+num+');');
        cal_nav_prev.className = 'calendarnav-previous';
        var cal_nav_next = quickElement('a', cal_nav, '>', 'href', 'javascript:DateTimeShortcuts.drawNext('+num+');');
        cal_nav_next.className = 'calendarnav-next';

        // main box
        var cal_main = quickElement('div', cal_box, '', 'id', DateTimeShortcuts.calendarDivName2 + num);
        cal_main.className = 'calendar';
        DateTimeShortcuts.calendars[num] = new Calendar(DateTimeShortcuts.calendarDivName2 + num, DateTimeShortcuts.handleCalendarCallback(num));
        DateTimeShortcuts.calendars[num].drawCurrent();

        // calendar shortcuts
        var shortcuts = quickElement('div', cal_box, '');
        shortcuts.className = 'calendar-shortcuts';
        quickElement('a', shortcuts, gettext('Yesterday'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', -1);');
        shortcuts.appendChild(document.createTextNode('\240|\240'));
        quickElement('a', shortcuts, gettext('Today'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', 0);');
        shortcuts.appendChild(document.createTextNode('\240|\240'));
        quickElement('a', shortcuts, gettext('Tomorrow'), 'href', 'javascript:DateTimeShortcuts.handleCalendarQuickLink(' + num + ', +1);');

        // cancel bar
        var cancel_p = quickElement('p', cal_box, '');
        cancel_p.className = 'calendar-cancel';
        quickElement('a', cancel_p, gettext('Cancel'), 'href', 'javascript:DateTimeShortcuts.dismissCalendar(' + num + ');');
    },
    openCalendar: function(num) {
        var cal_box = document.getElementById(DateTimeShortcuts.calendarDivName1+num)
        var cal_link = document.getElementById(DateTimeShortcuts.calendarLinkName+num)
        var inp = DateTimeShortcuts.calendarInputs[num];

        // Determine if the current value in the input has a valid date.
        // If so, draw the calendar with that date's year and month.
        if (inp.value) {
            var date_parts = inp.value.split('-');
            var year = date_parts[0];
            var month = parseFloat(date_parts[1]);
            if (year.match(/\d\d\d\d/) && month >= 1 && month <= 12) {
                DateTimeShortcuts.calendars[num].drawDate(month, year);
            }
        }

        // Recalculate the clockbox position
        // is it left-to-right or right-to-left layout ?
        if (getStyle(document.body,'direction')!='rtl') {
            cal_box.style.left = findPosX(cal_link) + 17 + 'px';
        }
        else {
            // since style's width is in em, it'd be tough to calculate
            // px value of it. let's use an estimated px for now
            // TODO: IE returns wrong value for findPosX when in rtl mode
            //       (it returns as it was left aligned), needs to be fixed.
            cal_box.style.left = findPosX(cal_link) - 180 + 'px';
        }
        cal_box.style.top = Math.max(0, findPosY(cal_link) - 75) + 'px';

        cal_box.style.display = 'block';
        addEvent(window.document, 'click', function() { DateTimeShortcuts.dismissCalendar(num); return true; });
    },
    dismissCalendar: function(num) {
        document.getElementById(DateTimeShortcuts.calendarDivName1+num).style.display = 'none';
        window.document.onclick = null;
    },
    drawPrev: function(num) {
        DateTimeShortcuts.calendars[num].drawPreviousMonth();
    },
    drawNext: function(num) {
        DateTimeShortcuts.calendars[num].drawNextMonth();
    },
    handleCalendarCallback: function(num) {
        format = get_format('DATE_INPUT_FORMATS')[0];
        // the format needs to be escaped a little
        format = format.replace('\\', '\\\\');
        format = format.replace('\r', '\\r');
        format = format.replace('\n', '\\n');
        format = format.replace('\t', '\\t');
        format = format.replace("'", "\\'");
        return ["function(y, m, d) { DateTimeShortcuts.calendarInputs[",
               num,
               "].value = new Date(y, m-1, d).strftime('",
               format,
               "');DateTimeShortcuts.calendarInputs[",
               num,
               "].focus();document.getElementById(DateTimeShortcuts.calendarDivName1+",
               num,
               ").style.display='none';}"].join('');
    },
    handleCalendarQuickLink: function(num, offset) {
       var d = new Date();
       d.setDate(d.getDate() + offset)
       DateTimeShortcuts.calendarInputs[num].value = d.strftime(get_format('DATE_INPUT_FORMATS')[0]);
       DateTimeShortcuts.calendarInputs[num].focus();
       DateTimeShortcuts.dismissCalendar(num);
    },
    cancelEventPropagation: function(e) {
        if (!e) e = window.event;
        e.cancelBubble = true;
        if (e.stopPropagation) e.stopPropagation();
    }
}

addEvent(window, 'load', DateTimeShortcuts.init);
<|endoftext|>


<commit_before>// Inserts shortcut buttons after all of the following:
//     <input type="text" class="vDateField">
//     <input type="text" class="vTimeField">

var DateTimeShortcuts = {
    calendars: [],
    calendarInputs: [],
    clockInputs: [],
    calendarDivName1: 'calendarbox', // name of calendar
 that gets toggled
    calendarDivName2: 'calendarin',  // name of
 that contains calendar
    calendarLinkName: 'calendarlink',// name of the link that is used to toggle
    clockDivName: 'clockbox',        // name of clock
 that gets toggled
    clockLinkName: 'clocklink',      // name of the link that is used to toggle
    admin_media_prefix: '',
    init: function() {
        // Deduce admin_media_prefix by looking at the


<commit_before>// # Content Helper
// Usage: `{{content}}`, `{{content words="20"}}`, `{{content characters="256"}}`
//
// Turns content html into a safestring so that the user doesn't have to
// escape it or tell handlebars to leave it alone with a triple-brace.
//
// Shows default or custom CTA when trying to see content without access
//
// Enables tag-safe truncation of content by characters or words.
//
// Dev flag feature: In case of restricted content access for member-only posts, shows CTA box

const {templates, hbs, SafeString} = require('../services/proxy');
const downsize = require('downsize');
const _ = require('lodash');
const createFrame = hbs.handlebars.createFrame;

function restrictedCta(options) {
    options = options || {};
    options.data = options.data || {};
    _.merge(this, {
        accentColor: (options.data.site && options.data.site.accent_color) || '#3db0ef'
    });
    const data = createFrame(options.data);
    return templates.execute('content-cta', this, {data});
}

module.exports = function content(options = {}) {
    let self = this;
    let args = arguments;

    const hash = options.hash || {};
    const truncateOptions = {};
    let runTruncate = false;

    for (const key of ['words', 'characters']) {
        if (Object.prototype.hasOwnProperty.call(hash, key)) {
            runTruncate = true;
            truncateOptions[key] = parseInt(hash[key], 10);
        }
    }

    if (this.html === null) {
        this.html = '';
    }

    if (!_.isUndefined(this.access) && !this.access) {
        return restrictedCta.apply(self, args);
    }

    if (runTruncate) {
        return new SafeString(
            downsize(this.html, truncateOptions)
        );
    }

    return new SafeString(this.html);
};
<commit_msg>Updated default accent color for content cta<commit_after>// # Content Helper
// Usage: `{{content}}`, `{{content words="20"}}`, `{{content characters="256"}}`
//
// Turns content html into a safestring so that the user doesn't have to
// escape it or tell handlebars to leave it alone with a triple-brace.
//
// Shows default or custom CTA when trying to see content without access
//
// Enables tag-safe truncation of content by characters or words.
//
// Dev flag feature: In case of restricted content access for member-only posts, shows CTA box

const {templates, hbs, SafeString} = require('../services/proxy');
const downsize = require('downsize');
const _ = require('lodash');
const createFrame = hbs.handlebars.createFrame;

function restrictedCta(options) {
    options = options || {};
    options.data = options.data || {};
    _.merge(this, {
        accentColor: (options.data.site && options.data.site.accent_color) || '#15171A'
    });
    const data = createFrame(options.data);
    return templates.execute('content-cta', this, {data});
}

module.exports = function content(options = {}) {
    let self = this;
    let args = arguments;

    const hash = options.hash || {};
    const truncateOptions = {};
    let runTruncate = false;

    for (const key of ['words', 'characters']) {
        if (Object.prototype.hasOwnProperty.call(hash, key)) {
            runTruncate = true;
            truncateOptions[key] = parseInt(hash[key], 10);
        }
    }

    if (this.html === null) {
        this.html = '';
    }

    if (!_.isUndefined(this.access) && !this.access) {
        return restrictedCta.apply(self, args);
    }

    if (runTruncate) {
        return new SafeString(
            downsize(this.html, truncateOptions)
        );
    }

    return new SafeString(this.html);
};
<|endoftext|>


<commit_before>// search.js
var React = require('react');
var ReactDOM = require('react-dom');
var ymirAPI =require('../static/js/ymirWrapper.js');
var Model = ymirAPI;

function getCookie(cname) {
    var name = cname + "=";
    var ca = document.cookie.split(';');
    for(var i=0; i<ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1);
        if (c.indexOf(name) == 0) return c.substring(name.length,c.length);
    }
    return "";
}

var Category = React.createClass({
	getInitialState: function(){
	    return {worlds:[]}
	},

	componentDidMount: function(){
	    var self = this;
	    Model(getCookie('worldId'))['Characters'].all().then(function(res){
	    	console.log(res);
	        var st=self.state;
	        st.worlds = res.data;
	        self.setState(st);
	    });
	},

	render: function(){
		var items = this.state.worlds.map((i,n)=>{
			return(

					{i.id}: {i.name}
					 | edit
					| delete

			);
		});
	    return(

{this.props.category} +

				<input className="search" placeholder="Search" />

					{items}

	    )
	},

	cookieFunction: function(id) {
		document.cookie = id;
	}
})

module.exports = Category;

<commit_msg>cookie<commit_after>// search.js
var React = require('react');
var ReactDOM = require('react-dom');
var ymirAPI =require('../static/js/ymirWrapper.js');
var Model = ymirAPI;

function getCookie(cname) {
    var name = cname + "=";
    var ca = document.cookie.split(';');
    for(var i=0; i<ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1);
        if (c.indexOf(name) == 0) return c.substring(name.length,c.length);
    }
    return "";
}

var Category = React.createClass({
	getInitialState: function(){
	    return {worlds:[]}
	},

	componentDidMount: function(){
	    var self = this;
	    Model(getCookie('worldId'))['Characters'].all().then(function(res){
	    	console.log(res);
	        var st=self.state;
	        st.worlds = res.data;
	        self.setState(st);
	    });
	},

	cookieFunction: function(id) {
		document.cookie = "characterEditId="+id;
		location.href=this.props.category+"Edit.html"
	},

	render: function(){
		var self = this;
		var items = this.state.worlds.map((i,n)=>{
			return(

					{i.id}: {i.name}
					 | edit
					| delete

			);
		});
	    return(

{this.props.category} +

				<input className="search" placeholder="Search" />

					{items}

	    )
	},

})

module.exports = Category;

<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {Link, browserHistory} from 'react-router';

export default class Students extends Component {
  constructor (props) {
    super(props);
    this.state = {
      students: [],
      baseQuery: {},
      letter: ''
    };
    this.fields = {
      _id: -1
    };
  }

  componentDidMount() {
    this.setState({letter: this.props.params.letter && this.props.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  componentWillReceiveProps(nextProps) {
    this.setState({letter: nextProps.params.letter && nextProps.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  handleLetterClick(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.getAttribute('href')}`);
  }
  handleSelectChange(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.value}`);
  }
  handleStudentClick(e) {
    e.preventDefault();
    browserHistory.push(`/student/${e.target.getAttribute('href')}`);
  }
  makeQuery(letter) {
    let regex = new RegExp(`^${letter}`, 'i');
    let query = {
      "name.last": regex
    };
    this.setState({baseQuery: query}, () => this.query(query, {
      limit: 0,
      sort: {
        "name.last":1,
        "name.first":1
      },
      fields: this.fields
    }));

  }
  query(q, p) {
    this.props.db.students.find(q, p).fetch((data) => {
      this.setState({students: data});
    });
  }

  render() {
    const letters = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    return (

              {letters.map((char, i) => (
                <li
                  className={this.state.letter === char ? 'current' : ''}
                  key={`${char}_${i}`}>
                   this.handleLetterClick(e)}>{char}

)
              )}

            <select className="pagination text-center show-for-small-only" role="navigation" aria-label="Pagination" onChange={(e) => this.handleSelectChange(e)}>
              {letters.map((char, i) => <option value={char} key={`${char}_${i}`}>{char}</option>)}
            </select>

              {this.state.students.length === 0 &&
Sorry, no results for {this.state.letter}.
}
              {this.state.students && this.state.students.map((student) => (

                  <Link to={`/student/${student.sid}`}>
                    <img className="student-image-small float-left" src={`${student.picture.thumbnail}`} alt={`${student.name.last} ${student.name.first}`}/>
                    {`${student.name.last} ${student.name.first}`}
                  </Link>

))
              }

    )
  }
}
<commit_msg>use large only<commit_after>import React, {Component} from 'react';
import {Link, browserHistory} from 'react-router';

export default class Students extends Component {
  constructor (props) {
    super(props);
    this.state = {
      students: [],
      baseQuery: {},
      letter: ''
    };
    this.fields = {
      _id: -1
    };
  }

  componentDidMount() {
    this.setState({letter: this.props.params.letter && this.props.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  componentWillReceiveProps(nextProps) {
    this.setState({letter: nextProps.params.letter && nextProps.params.letter.toUpperCase() || 'A'},
      () => this.makeQuery(this.state.letter));
  }
  handleLetterClick(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.getAttribute('href')}`);
  }
  handleSelectChange(e) {
    e.preventDefault();
    browserHistory.push(`/students/${e.target.value}`);
  }
  handleStudentClick(e) {
    e.preventDefault();
    browserHistory.push(`/student/${e.target.getAttribute('href')}`);
  }
  makeQuery(letter) {
    let regex = new RegExp(`^${letter}`, 'i');
    let query = {
      "name.last": regex
    };
    this.setState({baseQuery: query}, () => this.query(query, {
      limit: 0,
      sort: {
        "name.last":1,
        "name.first":1
      },
      fields: this.fields
    }));

  }
  query(q, p) {
    this.props.db.students.find(q, p).fetch((data) => {
      this.setState({students: data});
    });
  }

  render() {
    const letters = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
    return (

              {letters.map((char, i) => (
                <li
                  className={this.state.letter === char ? 'current' : ''}
                  key={`${char}_${i}`}>
                   this.handleLetterClick(e)}>{char}

)
              )}

            <select className="pagination text-center show-for-small-only" role="navigation" aria-label="Pagination" onChange={(e) => this.handleSelectChange(e)}>
              {letters.map((char, i) => <option value={char} key={`${char}_${i}`}>{char}</option>)}
            </select>

              {this.state.students.length === 0 &&
Sorry, no results for {this.state.letter}.
}
              {this.state.students && this.state.students.map((student) => (

                  <Link to={`/student/${student.sid}`}>
                    <img className="student-image-small float-left" src={`${student.picture.large}`} alt={`${student.name.last} ${student.name.first}`}/>
                    {`${student.name.last} ${student.name.first}`}
                  </Link>

))
              }

    )
  }
}
<|endoftext|>


<commit_before>const path = require('path')
const log = require('../../lib/logger')('fileScanner')
const { promisify } = require('util')
const fs = require('fs')
const stat = promisify(fs.stat)
const musicMeta = require('music-metadata')
const mp4info = require('../../lib/mp4info.js')
const getFiles = require('./getFiles')
const getPerms = require('../../lib/getPermutations')
const Library = require('../../Library')
const Media = require('../../Media')
const IPCMedia = require('../IPCMedia')
const MetaParser = require('../MetaParser')
const Scanner = require('../Scanner')
const { NodeVM } = require('vm2')

const videoExts = ['.cdg', '.mp4'].reduce((perms, ext) => perms.concat(getPerms(ext)), [])
const audioTypes = ['m4a', 'mp3']

class FileScanner extends Scanner {
  constructor (prefs) {
    super()
    this.paths = prefs.paths
  }

  async scan () {
    const offlinePaths = [] // pathIds
    const validMedia = [] // mediaIds
    let files = []
    let lastDir

    // count files to scan from all paths
    for (let i = 0; i < this.paths.result.length; i++) {
      const pathId = this.paths.result[i]
      const basePath = this.paths.entities[pathId].path

      this.emitStatus(`Listing folders (${i + 1} of ${this.paths.result.length})`, 0)

      try {
        log.info('Searching path: %s', basePath)
        let list = await getFiles(basePath, { pathId, basePath })
        list = list.filter(({ file }) => videoExts.includes(path.extname(file)))

        log.info('  => found %s files with valid extensions (cdg, mp4)', list.length)
        files = files.concat(list)
      } catch (err) {
        log.error(`  => ${err.message} (path offline)`)
        offlinePaths.push(pathId)
      }

      if (this.isCanceling) {
        return
      }
    } // end for

    log.info('Processing %s total files', files.length)

    // process files
    for (let i = 0; i < files.length; i++) {
      this.emitStatus(`Scanning media (${i + 1} of ${files.length})`, ((i + 1) / files.length) * 100)

      const { file, basePath } = files[i]
      const dir = path.dirname(file)

      if (lastDir !== dir) {
        lastDir = dir

        // (re)init parser with this folder's config, if any
        const cfg = getCfg(dir, basePath)
        this.parser = new MetaParser(cfg)
      }

      // emit progress
      log.info('[%s/%s] %s', i + 1, files.length, file)

      try {
        const mediaId = await this.processFile(files[i])
        validMedia.push(mediaId) // successfuly processed
      } catch (err) {
        log.warn(err.message + ': ' + file) // try the next file
      }

      if (this.isCanceling) {
        return
      }
    } // end for

    log.info('Scan finished with %s songs in library', validMedia.length)

    // cleanup
    {
      const invalidMedia = []
      const res = await Media.search()

      res.result.forEach(mediaId => {
        // was this media item just verified?
        if (validMedia.includes(mediaId)) {
          return
        }

        // is media item in an offline path?
        if (offlinePaths.includes(res.entities[mediaId].pathId)) {
          return
        }

        // looks like we need to remove it
        invalidMedia.push(mediaId)
      })

      if (invalidMedia.length) {
        log.info(`Removing ${invalidMedia.length} invalid media entries`)
        await IPCMedia.remove(invalidMedia)
      }
    }
  }

  async processFile ({ file, pathId, basePath }) {
    const relPath = file.substr(basePath.length + 1)

    {
      // already in database with the same path?
      const res = await Media.search({
        pathId,
        // try both slashes to be POSIX/Win agnostic
        relPath: [relPath.replace('/', '\\'), relPath.replace('\\', '/')],
      })

      log.info('  => %s result(s)', res.result.length)

      if (res.result.length) {
        // run meta parser and compare to metadata in db
        const cur = res.entities[res.result[0]]
        const { artist, title } = this.parser(path.parse(file).name)

        // did artistId or songId change?
        const match = await Library.matchSong(artist, title)

        if (cur.artistId !== match.artistId || cur.songId !== match.songId) {
          log.info('  => old: %s', JSON.stringify({ artist: cur.artist, title: cur.title }))
          log.info('  => new: %s', JSON.stringify({ artist: match.artist, title: match.title }))

          await IPCMedia.update({
            mediaId: cur.mediaId,
            songId: match.songId,
            dateUpdated: Math.round(new Date().getTime() / 1000), // seconds
          })
        } else {
          log.info('  => ok')
        }

        return cur.mediaId
      } // end if
    }

    // new media
    // -------------------------------
    const { artist, title } = this.parser(path.parse(file).name)
    const match = await Library.matchSong(artist, title)
    const media = {
      pathId,
      relPath,
      songId: match.songId,
      dateAdded: Math.round(new Date().getTime() / 1000), // seconds
    }

    log.info('  => new: %s', JSON.stringify({ artist: match.artist, title: match.title }))

    // need to look for an audio file?
    if (path.extname(file).toLowerCase() === '.cdg') {
      let audio

      // look for all uppercase and lowercase permutations of each
      // file extension since we may be on a case-sensitive fs
      for (const type of audioTypes) {
        for (const ext of getPerms(type)) {
          audio = file.substr(0, file.lastIndexOf('.') + 1) + ext

          // does file exist?
          try {
            await stat(audio)
            log.info('  => found %s audio', type)
            break
          } catch (err) {
            // try another permutation
            audio = null
          }
        } // end for

        if (audio) {
          try {
            const meta = await musicMeta.parseFile(audio, { duration: true })
            media.duration = Math.round(meta.format.duration)
            media.audioExt = path.extname(audio).replace('.', '')
            break
          } catch (err) {
            // try another type
            audio = null
            log.error(err)
          }
        }
      } // end for

      if (!audio) {
        throw new Error(`  => no valid audio file`)
      }
    } else if (path.extname(file).toLowerCase() === '.mp4') {
      // get video duration
      const info = await mp4info(file)
      media.duration = Math.round(info.duration)
    }

    if (!media.duration) {
      throw new Error(`  => could not determine duration`)
    }

    log.info('  => duration: %s:%s',
      Math.floor(media.duration / 60),
      Math.round(media.duration % 60, 10).toString().padStart(2, '0')
    )

    // resolves to a mediaId
    return IPCMedia.add(media)
  }
}

// search each parent dir (up to baseDir)
function getCfg (dir, baseDir) {
  dir = path.normalize(dir)
  baseDir = path.normalize(baseDir)
  const file = path.resolve(dir, '_kfconfig.js')

  try {
    const userScript = fs.readFileSync(file, 'utf-8')
    log.info('Found config at %s', file)

    try {
      const vm = new NodeVM({ wrapper: 'none' })
      return vm.run(userScript)
    } catch (err) {
      log.error(err)
    }
  } catch (err) {
    log.info('No config at %s', file)
  }

  if (dir === baseDir) {
    return
  }

  // try parent dir
  return getCfg(path.resolve(dir, '..'), baseDir)
}

module.exports = FileScanner
<commit_msg>Minor log tweak<commit_after>const path = require('path')
const log = require('../../lib/logger')('fileScanner')
const { promisify } = require('util')
const fs = require('fs')
const stat = promisify(fs.stat)
const musicMeta = require('music-metadata')
const mp4info = require('../../lib/mp4info.js')
const getFiles = require('./getFiles')
const getPerms = require('../../lib/getPermutations')
const Library = require('../../Library')
const Media = require('../../Media')
const IPCMedia = require('../IPCMedia')
const MetaParser = require('../MetaParser')
const Scanner = require('../Scanner')
const { NodeVM } = require('vm2')

const videoExts = ['.cdg', '.mp4'].reduce((perms, ext) => perms.concat(getPerms(ext)), [])
const audioTypes = ['m4a', 'mp3']

class FileScanner extends Scanner {
  constructor (prefs) {
    super()
    this.paths = prefs.paths
  }

  async scan () {
    const offlinePaths = [] // pathIds
    const validMedia = [] // mediaIds
    let files = []
    let lastDir

    // count files to scan from all paths
    for (let i = 0; i < this.paths.result.length; i++) {
      const pathId = this.paths.result[i]
      const basePath = this.paths.entities[pathId].path

      this.emitStatus(`Listing folders (${i + 1} of ${this.paths.result.length})`, 0)

      try {
        log.info('Searching path: %s', basePath)
        let list = await getFiles(basePath, { pathId, basePath })
        list = list.filter(({ file }) => videoExts.includes(path.extname(file)))

        log.info('  => found %s files with valid extensions (cdg, mp4)', list.length)
        files = files.concat(list)
      } catch (err) {
        log.error(`  => ${err.message} (path offline)`)
        offlinePaths.push(pathId)
      }

      if (this.isCanceling) {
        return
      }
    } // end for

    log.info('Processing %s total files', files.length)

    // process files
    for (let i = 0; i < files.length; i++) {
      this.emitStatus(`Scanning media (${i + 1} of ${files.length})`, ((i + 1) / files.length) * 100)

      const { file, basePath } = files[i]
      const dir = path.dirname(file)

      if (lastDir !== dir) {
        lastDir = dir

        // (re)init parser with this folder's config, if any
        const cfg = getCfg(dir, basePath)
        this.parser = new MetaParser(cfg)
      }

      // emit progress
      log.info('[%s/%s] %s', i + 1, files.length, file)

      try {
        const mediaId = await this.processFile(files[i])
        validMedia.push(mediaId) // successfuly processed
      } catch (err) {
        log.warn(err.message + ': ' + file) // try the next file
      }

      if (this.isCanceling) {
        return
      }
    } // end for

    log.info('Scan finished; found %s valid songs', validMedia.length)

    // cleanup
    {
      const invalidMedia = []
      const res = await Media.search()

      res.result.forEach(mediaId => {
        // was this media item just verified?
        if (validMedia.includes(mediaId)) {
          return
        }

        // is media item in an offline path?
        if (offlinePaths.includes(res.entities[mediaId].pathId)) {
          return
        }

        // looks like we need to remove it
        invalidMedia.push(mediaId)
      })

      if (invalidMedia.length) {
        log.info(`Removing ${invalidMedia.length} invalid media entries`)
        await IPCMedia.remove(invalidMedia)
      }
    }
  }

  async processFile ({ file, pathId, basePath }) {
    const relPath = file.substr(basePath.length + 1)

    {
      // already in database with the same path?
      const res = await Media.search({
        pathId,
        // try both slashes to be POSIX/Win agnostic
        relPath: [relPath.replace('/', '\\'), relPath.replace('\\', '/')],
      })

      log.info('  => %s result(s)', res.result.length)

      if (res.result.length) {
        // run meta parser and compare to metadata in db
        const cur = res.entities[res.result[0]]
        const { artist, title } = this.parser(path.parse(file).name)

        // did artistId or songId change?
        const match = await Library.matchSong(artist, title)

        if (cur.artistId !== match.artistId || cur.songId !== match.songId) {
          log.info('  => old: %s', JSON.stringify({ artist: cur.artist, title: cur.title }))
          log.info('  => new: %s', JSON.stringify({ artist: match.artist, title: match.title }))

          await IPCMedia.update({
            mediaId: cur.mediaId,
            songId: match.songId,
            dateUpdated: Math.round(new Date().getTime() / 1000), // seconds
          })
        } else {
          log.info('  => ok')
        }

        return cur.mediaId
      } // end if
    }

    // new media
    // -------------------------------
    const { artist, title } = this.parser(path.parse(file).name)
    const match = await Library.matchSong(artist, title)
    const media = {
      pathId,
      relPath,
      songId: match.songId,
      dateAdded: Math.round(new Date().getTime() / 1000), // seconds
    }

    log.info('  => new: %s', JSON.stringify({ artist: match.artist, title: match.title }))

    // need to look for an audio file?
    if (path.extname(file).toLowerCase() === '.cdg') {
      let audio

      // look for all uppercase and lowercase permutations of each
      // file extension since we may be on a case-sensitive fs
      for (const type of audioTypes) {
        for (const ext of getPerms(type)) {
          audio = file.substr(0, file.lastIndexOf('.') + 1) + ext

          // does file exist?
          try {
            await stat(audio)
            log.info('  => found %s audio', type)
            break
          } catch (err) {
            // try another permutation
            audio = null
          }
        } // end for

        if (audio) {
          try {
            const meta = await musicMeta.parseFile(audio, { duration: true })
            media.duration = Math.round(meta.format.duration)
            media.audioExt = path.extname(audio).replace('.', '')
            break
          } catch (err) {
            // try another type
            audio = null
            log.error(err)
          }
        }
      } // end for

      if (!audio) {
        throw new Error(`  => no valid audio file`)
      }
    } else if (path.extname(file).toLowerCase() === '.mp4') {
      // get video duration
      const info = await mp4info(file)
      media.duration = Math.round(info.duration)
    }

    if (!media.duration) {
      throw new Error(`  => could not determine duration`)
    }

    log.info('  => duration: %s:%s',
      Math.floor(media.duration / 60),
      Math.round(media.duration % 60, 10).toString().padStart(2, '0')
    )

    // resolves to a mediaId
    return IPCMedia.add(media)
  }
}

// search each parent dir (up to baseDir)
function getCfg (dir, baseDir) {
  dir = path.normalize(dir)
  baseDir = path.normalize(baseDir)
  const file = path.resolve(dir, '_kfconfig.js')

  try {
    const userScript = fs.readFileSync(file, 'utf-8')
    log.info('Found config at %s', file)

    try {
      const vm = new NodeVM({ wrapper: 'none' })
      return vm.run(userScript)
    } catch (err) {
      log.error(err)
    }
  } catch (err) {
    log.info('No config at %s', file)
  }

  if (dir === baseDir) {
    return
  }

  // try parent dir
  return getCfg(path.resolve(dir, '..'), baseDir)
}

module.exports = FileScanner
<|endoftext|>


<commit_before>// Retrive data for all steps of all files
// (map db data into meta objects for our needs)

'use strict';

var aFrom                         = require('es5-ext/array/from')
  , forEach                       = require('es5-ext/object/for-each')
  , capitalize                    = require('es5-ext/string/#/capitalize')
  , includes                      = require('es5-ext/string/#/contains')
  , Set                           = require('es6-set')
  , Map                           = require('es6-map')
  , deferred                      = require('deferred')
  , memoize                       = require('memoizee')
  , unserializeValue              = require('dbjs/_setup/unserialize/value')
  , resolveKeyPath                = require('dbjs/_setup/utils/resolve-key-path')
  , resolveEventKeys              = require('dbjs-persistence/lib/resolve-event-keys')
  , debugLoad                     = require('debug-ext')('load', 6)
  , humanize                      = require('debug-ext').humanize
  , resolveProcessingStepFullPath = require('../../utils/resolve-processing-step-full-path')
  , toDateInTz                    = require('../../utils/to-date-in-time-zone')
  , timeZone                      = require('../../db').timeZone;

var re = new RegExp('^processingSteps\\/map\\/([a-zA-Z0-9]+' +
	'(?:\\/steps\\/map\\/[a-zA-Z0-9]+)*)\\/([a-z0-9A-Z\\/]+)$');

module.exports = exports = memoize(function (driver, processingStepsMeta) {
	var storageStepsMap = new Map(), stepShortPathMap = new Map()
	  , serviceFullShortNameMap = new Map()
	  , startTime = Date.now();

	var result = { steps: new Map(), businessProcesses: new Map() };

	forEach(processingStepsMeta, function (meta, stepShortPath) {
		var stepPath = resolveProcessingStepFullPath(stepShortPath);
		stepShortPathMap.set(stepPath, stepShortPath);
		meta._services.forEach(function (serviceName) {
			var serviceFullName = 'businessProcess' + capitalize.call(serviceName)
			  , storage = driver.getStorage(serviceFullName);
			serviceFullShortNameMap.set(serviceFullName, serviceName);
			if (!storageStepsMap.has(storage)) storageStepsMap.set(storage, new Set());
			storageStepsMap.get(storage).add(stepPath);
		});

		result.steps.set(stepShortPath, new Map());
	});

	return deferred.map(aFrom(storageStepsMap), function (data) {
		var storage = data[0], stepPaths = data[1]
		  , serviceName = serviceFullShortNameMap.get(storage.name);

		var initStepDataset = function (stepPath, businessProcessId) {
			var stepShortPath = stepShortPathMap.get(stepPath);
			if (!result.steps.get(stepShortPath).get(businessProcessId)) {
				result.steps.get(stepShortPath).set(businessProcessId, {
					stepShortPath: stepShortPath,
					businessProcessId: businessProcessId,
					stepFullPath: 'processingSteps/map/' + stepPath
				});
			}
			return result.steps.get(stepShortPath).get(businessProcessId);
		};
		var initBpDataset = function (businessProcessId) {
			if (!result.businessProcesses.has(businessProcessId)) {
				result.businessProcesses.set(businessProcessId,  {
					businessProcessId: businessProcessId,
					serviceName: serviceName
				});
			}
			return result.businessProcesses.get(businessProcessId);
		};

		// Listen for new records
		storage.on('key:&', function (event) {
			if (event.data.value[0] !== '7') delete initBpDataset(event.ownerId)._existing;
			else initBpDataset(event.ownerId)._existing = true;
		});
		stepPaths.forEach(function (stepPath) {
			// Status
			forEach(exports.stepMetaMap, function (meta, stepKeyPath) {
				storage.on('key:processingSteps/map/' + stepPath + '/' + stepKeyPath, function (event) {
					if (event.type !== (meta.type || 'direct')) return;
					if (!meta.validate(event.data)) meta.delete(initStepDataset(stepPath, event.ownerId));
					else meta.set(initStepDataset(stepPath, event.ownerId), event.data);
				});
			});
		});
		forEach(exports.businessProcessMetaMap, function (meta, keyPath) {
			storage.on('key:' + keyPath, function (event) {
				if (event.type !== (meta.type || 'direct')) return;
				if (!meta.validate(event.data)) meta.delete(initBpDataset(event.ownerId));
				else meta.set(initBpDataset(event.ownerId), event.data);
			});
		});

		// Get current records
		return deferred(
			storage.search(function (id, record) {
				var bpId = id.split('/', 1)[0], stepPath, stepKeyPath, meta, keyPath, multiItemValue, path;
				if (bpId === id) {
					if (record.value[0] === '7') initBpDataset(bpId)._existing = true;
					return;
				}
				if (includes.call(id, '*')) {
					keyPath = resolveKeyPath(id);
					path = bpId + '/' + keyPath;
					if (path !== id) multiItemValue = id.slice(path.length + 1);
				} else {
					keyPath = id.slice(bpId.length + 1);
				}
				meta = exports.businessProcessMetaMap[keyPath];
				if (meta) {
					if (meta.type && (meta.type !== 'direct')) return;
					if (multiItemValue && !meta.multiple) return;
					if (!meta.validate(record, multiItemValue)) return;
					meta.set(initBpDataset(bpId), record, multiItemValue);
				}
				var match = keyPath.match(re);
				if (!match) return;
				stepPath = match[1];
				if (!stepPaths.has(stepPath)) return;
				stepKeyPath = match[2];
				meta = exports.stepMetaMap[stepKeyPath];
				if (!meta) return;
				if (meta.type && (meta.type !== 'direct')) return;
				if (multiItemValue && !meta.multiple) return;
				if (!meta.validate(record, multiItemValue)) return;
				meta.set(initStepDataset(stepPath, bpId), record, multiItemValue);
			}),
			deferred.map(Object.keys(exports.businessProcessMetaMap), function (keyPath) {
				var meta = exports.businessProcessMetaMap[keyPath];
				if (meta.type !== 'computed') return;
				return storage.searchComputed({ keyPath: keyPath }, function (id, record) {
					if (!meta.validate(record)) return;
					meta.set(initBpDataset(id.split('/', 1)[0]), record);
				});
			}),
			deferred.map(aFrom(stepPaths), function (stepPath) {
				return deferred.map(Object.keys(exports.stepMetaMap), function (stepPropKeyPath) {
					var meta = exports.stepMetaMap[stepPropKeyPath];
					if (meta.type !== 'computed') return;
					var keyPath = 'processingSteps/map/' + stepPath + '/' + stepPropKeyPath;
					return storage.searchComputed({ keyPath: keyPath }, function (id, record) {
						if (!meta.validate(record)) return;
						meta.set(initStepDataset(stepPath, id.split('/', 1)[0]), record);
					});
				});
			})
		);
	})(result).aside(function () {
		debugLoad('business process db data (in %s)', humanize(Date.now() - startTime));
	});

}, { length: 0 });

// Map of all properties to be mapped to result with corresponding instructions
exports.stepMetaMap = {
	correctionTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.correctionTime = unserializeValue(record.value); },
		delete: function (data) { delete data.correctionTime; }
	},
	isReady: {
		type: 'computed',
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) {
			data.pendingDate = toDateInTz(new Date(record.stamp / 1000), timeZone);
			data.pendingDateTime = new Date(record.stamp / 1000);
		},
		delete: function (data) {
			delete data.pendingDate;
			delete data.pendingDateTime;
		}
	},
	processingHolidaysTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.processingHolidaysTime = unserializeValue(record.value); },
		delete: function (data) { delete data.processingHolidaysTime; }
	},
	processor: {
		validate: function (record) { return (record.value[0] === '7'); },
		set: function (data, record) { data.processor = record.value.slice(1); },
		delete: function (data) { delete data.processor; }
	},
	status: {
		validate: function (record) {
			return (record.value[0] === '3');
		},
		set: function (data, record) {
			if ((record.value === '3approved') || (record.value === '3rejected')) {
				data.processingDate = toDateInTz(new Date(record.stamp / 1000), timeZone);
				data.processingDateTime = new Date(record.stamp / 1000);
			} else {
				delete data.processingDate;
				delete data.processingDateTime;
			}
			data.status = record.value.slice(1);
			data.statusStamp = record.stamp;
		},
		delete: function (data) {
			delete data.processingDate;
			delete data.processingDateTime;
			delete data.status;
			delete data.statusStamp;
		}
	}
};

exports.businessProcessMetaMap = {
	correctionTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.correctionTime = unserializeValue(record.value); },
		delete: function (data) { delete data.correctionTime; }
	},
	isApproved: {
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) {
			data.approvedDateTime = new Date(record.stamp / 1000);
			data.approvedDate = toDateInTz(data.approvedDateTime, timeZone);
		},
		delete: function (data) {
			delete data.approvedDateTime;
			delete data.approvedDate;
		}
	},
	isDemo: {
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) { data.isDemo = true; },
		delete: function (data) { delete data.isDemo; }
	},
	isSubmitted: {
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) {
			data.submissionDateTime = new Date(record.stamp / 1000);
		},
		delete: function (data) { delete data.submissionDateTime; }
	},
	processingHolidaysTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.processingHolidaysTime = unserializeValue(record.value); },
		delete: function (data) { delete data.processingHolidaysTime; }
	},
	'registrations/requested': {
		type: 'computed',
		validate: function (record) { return record.value[0] === '['; },
		set: function (data, record) {
			data.registrations = new Set(resolveEventKeys(JSON.parse(record.value)).map(function (value) {
				return value.slice(value.lastIndexOf('/') + 1);
			}));
		},
		delete: function (data) { delete data.registrations; }
	},
	searchString: {
		type: 'computed',
		validate: function (record) { return record.value[0] === '3'; },
		set: function (data, record) { data.searchString = unserializeValue(record.value); },
		delete: function (data) { delete data.searchString; }
	},
	status: {
		type: 'computed',
		validate: function (record) { return (record.value[0] === '3'); },
		set: function (data, record) { data.status = record.value.slice(1); },
		delete: function (data) { delete data.status; }
	},
	submitterType: {
		validate: function (record) { return record.value[0] === '3'; },
		set: function (data, record) { data.submitterType = record.value.slice(1); },
		delete: function (data) { delete data.submitterType; }
	}
};
<commit_msg>#1653 added handling of nonProcessingTime in getData<commit_after>// Retrive data for all steps of all files
// (map db data into meta objects for our needs)

'use strict';

var aFrom                         = require('es5-ext/array/from')
  , forEach                       = require('es5-ext/object/for-each')
  , capitalize                    = require('es5-ext/string/#/capitalize')
  , includes                      = require('es5-ext/string/#/contains')
  , Set                           = require('es6-set')
  , Map                           = require('es6-map')
  , deferred                      = require('deferred')
  , memoize                       = require('memoizee')
  , unserializeValue              = require('dbjs/_setup/unserialize/value')
  , resolveKeyPath                = require('dbjs/_setup/utils/resolve-key-path')
  , resolveEventKeys              = require('dbjs-persistence/lib/resolve-event-keys')
  , debugLoad                     = require('debug-ext')('load', 6)
  , humanize                      = require('debug-ext').humanize
  , resolveProcessingStepFullPath = require('../../utils/resolve-processing-step-full-path')
  , toDateInTz                    = require('../../utils/to-date-in-time-zone')
  , timeZone                      = require('../../db').timeZone;

var re = new RegExp('^processingSteps\\/map\\/([a-zA-Z0-9]+' +
	'(?:\\/steps\\/map\\/[a-zA-Z0-9]+)*)\\/([a-z0-9A-Z\\/]+)$');

module.exports = exports = memoize(function (driver, processingStepsMeta) {
	var storageStepsMap = new Map(), stepShortPathMap = new Map()
	  , serviceFullShortNameMap = new Map()
	  , startTime = Date.now();

	var result = { steps: new Map(), businessProcesses: new Map() };

	forEach(processingStepsMeta, function (meta, stepShortPath) {
		var stepPath = resolveProcessingStepFullPath(stepShortPath);
		stepShortPathMap.set(stepPath, stepShortPath);
		meta._services.forEach(function (serviceName) {
			var serviceFullName = 'businessProcess' + capitalize.call(serviceName)
			  , storage = driver.getStorage(serviceFullName);
			serviceFullShortNameMap.set(serviceFullName, serviceName);
			if (!storageStepsMap.has(storage)) storageStepsMap.set(storage, new Set());
			storageStepsMap.get(storage).add(stepPath);
		});

		result.steps.set(stepShortPath, new Map());
	});

	return deferred.map(aFrom(storageStepsMap), function (data) {
		var storage = data[0], stepPaths = data[1]
		  , serviceName = serviceFullShortNameMap.get(storage.name);

		var initStepDataset = function (stepPath, businessProcessId) {
			var stepShortPath = stepShortPathMap.get(stepPath);
			if (!result.steps.get(stepShortPath).get(businessProcessId)) {
				result.steps.get(stepShortPath).set(businessProcessId, {
					stepShortPath: stepShortPath,
					businessProcessId: businessProcessId,
					stepFullPath: 'processingSteps/map/' + stepPath
				});
			}
			return result.steps.get(stepShortPath).get(businessProcessId);
		};
		var initBpDataset = function (businessProcessId) {
			if (!result.businessProcesses.has(businessProcessId)) {
				result.businessProcesses.set(businessProcessId,  {
					businessProcessId: businessProcessId,
					serviceName: serviceName
				});
			}
			return result.businessProcesses.get(businessProcessId);
		};

		// Listen for new records
		storage.on('key:&', function (event) {
			if (event.data.value[0] !== '7') delete initBpDataset(event.ownerId)._existing;
			else initBpDataset(event.ownerId)._existing = true;
		});
		stepPaths.forEach(function (stepPath) {
			// Status
			forEach(exports.stepMetaMap, function (meta, stepKeyPath) {
				storage.on('key:processingSteps/map/' + stepPath + '/' + stepKeyPath, function (event) {
					if (event.type !== (meta.type || 'direct')) return;
					if (!meta.validate(event.data)) meta.delete(initStepDataset(stepPath, event.ownerId));
					else meta.set(initStepDataset(stepPath, event.ownerId), event.data);
				});
			});
		});
		forEach(exports.businessProcessMetaMap, function (meta, keyPath) {
			storage.on('key:' + keyPath, function (event) {
				if (event.type !== (meta.type || 'direct')) return;
				if (!meta.validate(event.data)) meta.delete(initBpDataset(event.ownerId));
				else meta.set(initBpDataset(event.ownerId), event.data);
			});
		});

		// Get current records
		return deferred(
			storage.search(function (id, record) {
				var bpId = id.split('/', 1)[0], stepPath, stepKeyPath, meta, keyPath, multiItemValue, path;
				if (bpId === id) {
					if (record.value[0] === '7') initBpDataset(bpId)._existing = true;
					return;
				}
				if (includes.call(id, '*')) {
					keyPath = resolveKeyPath(id);
					path = bpId + '/' + keyPath;
					if (path !== id) multiItemValue = id.slice(path.length + 1);
				} else {
					keyPath = id.slice(bpId.length + 1);
				}
				meta = exports.businessProcessMetaMap[keyPath];
				if (meta) {
					if (meta.type && (meta.type !== 'direct')) return;
					if (multiItemValue && !meta.multiple) return;
					if (!meta.validate(record, multiItemValue)) return;
					meta.set(initBpDataset(bpId), record, multiItemValue);
				}
				var match = keyPath.match(re);
				if (!match) return;
				stepPath = match[1];
				if (!stepPaths.has(stepPath)) return;
				stepKeyPath = match[2];
				meta = exports.stepMetaMap[stepKeyPath];
				if (!meta) return;
				if (meta.type && (meta.type !== 'direct')) return;
				if (multiItemValue && !meta.multiple) return;
				if (!meta.validate(record, multiItemValue)) return;
				meta.set(initStepDataset(stepPath, bpId), record, multiItemValue);
			}),
			deferred.map(Object.keys(exports.businessProcessMetaMap), function (keyPath) {
				var meta = exports.businessProcessMetaMap[keyPath];
				if (meta.type !== 'computed') return;
				return storage.searchComputed({ keyPath: keyPath }, function (id, record) {
					if (!meta.validate(record)) return;
					meta.set(initBpDataset(id.split('/', 1)[0]), record);
				});
			}),
			deferred.map(aFrom(stepPaths), function (stepPath) {
				return deferred.map(Object.keys(exports.stepMetaMap), function (stepPropKeyPath) {
					var meta = exports.stepMetaMap[stepPropKeyPath];
					if (meta.type !== 'computed') return;
					var keyPath = 'processingSteps/map/' + stepPath + '/' + stepPropKeyPath;
					return storage.searchComputed({ keyPath: keyPath }, function (id, record) {
						if (!meta.validate(record)) return;
						meta.set(initStepDataset(stepPath, id.split('/', 1)[0]), record);
					});
				});
			})
		);
	})(result).aside(function () {
		debugLoad('business process db data (in %s)', humanize(Date.now() - startTime));
	});

}, { length: 0 });

// Map of all properties to be mapped to result with corresponding instructions
exports.stepMetaMap = {
	correctionTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.correctionTime = unserializeValue(record.value); },
		delete: function (data) { delete data.correctionTime; }
	},
	isReady: {
		type: 'computed',
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) {
			data.pendingDate = toDateInTz(new Date(record.stamp / 1000), timeZone);
			data.pendingDateTime = new Date(record.stamp / 1000);
		},
		delete: function (data) {
			delete data.pendingDate;
			delete data.pendingDateTime;
		}
	},
	nonProcessingTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.nonProcessingTime = unserializeValue(record.value); },
		delete: function (data) { delete data.nonProcessingTime; }
	},
	processingHolidaysTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.processingHolidaysTime = unserializeValue(record.value); },
		delete: function (data) { delete data.processingHolidaysTime; }
	},
	processor: {
		validate: function (record) { return (record.value[0] === '7'); },
		set: function (data, record) { data.processor = record.value.slice(1); },
		delete: function (data) { delete data.processor; }
	},
	status: {
		validate: function (record) {
			return (record.value[0] === '3');
		},
		set: function (data, record) {
			if ((record.value === '3approved') || (record.value === '3rejected')) {
				data.processingDate = toDateInTz(new Date(record.stamp / 1000), timeZone);
				data.processingDateTime = new Date(record.stamp / 1000);
			} else {
				delete data.processingDate;
				delete data.processingDateTime;
			}
			data.status = record.value.slice(1);
			data.statusStamp = record.stamp;
		},
		delete: function (data) {
			delete data.processingDate;
			delete data.processingDateTime;
			delete data.status;
			delete data.statusStamp;
		}
	}
};

exports.businessProcessMetaMap = {
	correctionTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.correctionTime = unserializeValue(record.value); },
		delete: function (data) { delete data.correctionTime; }
	},
	isApproved: {
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) {
			data.approvedDateTime = new Date(record.stamp / 1000);
			data.approvedDate = toDateInTz(data.approvedDateTime, timeZone);
		},
		delete: function (data) {
			delete data.approvedDateTime;
			delete data.approvedDate;
		}
	},
	isDemo: {
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) { data.isDemo = true; },
		delete: function (data) { delete data.isDemo; }
	},
	isSubmitted: {
		validate: function (record) { return (record.value === '11'); },
		set: function (data, record) {
			data.submissionDateTime = new Date(record.stamp / 1000);
		},
		delete: function (data) { delete data.submissionDateTime; }
	},
	processingHolidaysTime: {
		validate: function (record) { return (record.value[0] === '2'); },
		set: function (data, record) { data.processingHolidaysTime = unserializeValue(record.value); },
		delete: function (data) { delete data.processingHolidaysTime; }
	},
	'registrations/requested': {
		type: 'computed',
		validate: function (record) { return record.value[0] === '['; },
		set: function (data, record) {
			data.registrations = new Set(resolveEventKeys(JSON.parse(record.value)).map(function (value) {
				return value.slice(value.lastIndexOf('/') + 1);
			}));
		},
		delete: function (data) { delete data.registrations; }
	},
	searchString: {
		type: 'computed',
		validate: function (record) { return record.value[0] === '3'; },
		set: function (data, record) { data.searchString = unserializeValue(record.value); },
		delete: function (data) { delete data.searchString; }
	},
	status: {
		type: 'computed',
		validate: function (record) { return (record.value[0] === '3'); },
		set: function (data, record) { data.status = record.value.slice(1); },
		delete: function (data) { delete data.status; }
	},
	submitterType: {
		validate: function (record) { return record.value[0] === '3'; },
		set: function (data, record) { data.submitterType = record.value.slice(1); },
		delete: function (data) { delete data.submitterType; }
	}
};
<|endoftext|>


<commit_before>/*
 *  Refernce only: prefer model_keyfob.js
 *  Connect to TI Key Fob
 */

"use strict";

var iotdb = require('iotdb');
var _ = iotdb.helpers;

var BLEBridge = require('../BLEBridge').Bridge;
var TIKeyFob = require('../models/TIKeyFob');

var bridge_exemplar = new BLEBridge();
bridge_exemplar.discovered = function (bridge) {
    var meta = bridge.meta();
    console.log("+ got one\n ", meta);

    meta = _.ld.compact(meta);
    if (!_.d_contains_d(meta, TIKeyFob.binding.matchd)) {
        return;
    }

    console.log("+ use this");

    bridge.pulled = function (state) {
        console.log("+ state-change\n ", state);
    };
    bridge.connect(TIKeyFob.binding.connectd);
};
bridge_exemplar.discover();
<commit_msg>d_contains_d -> d.is.superset<commit_after>/*
 *  Refernce only: prefer model_keyfob.js
 *  Connect to TI Key Fob
 */

"use strict";

var iotdb = require('iotdb');
var _ = iotdb.helpers;

var BLEBridge = require('../BLEBridge').Bridge;
var TIKeyFob = require('../models/TIKeyFob');

var bridge_exemplar = new BLEBridge();
bridge_exemplar.discovered = function (bridge) {
    var meta = bridge.meta();
    console.log("+ got one\n ", meta);

    meta = _.ld.compact(meta);
    if (!_.d.is.superset(meta, TIKeyFob.binding.matchd)) {
        return;
    }

    console.log("+ use this");

    bridge.pulled = function (state) {
        console.log("+ state-change\n ", state);
    };
    bridge.connect(TIKeyFob.binding.connectd);
};
bridge_exemplar.discover();
<|endoftext|>


<commit_before>fdom.apis.set("core.transport", {
  'create': {type: "method", value: []},
  'accept': {type: "method", value: ["number", "object"]},
  'send': {type: "method", value: ["number", "blob"]},
  'close': {type: "method", value: ["number"]},

  'onStateChange': {type: "event", value: "object"},
  'onMessage': {type: "event", value: "blob"},
  'onSignal': {type: "event", value: "object"}
});
<commit_msg>make interface support data<commit_after>fdom.apis.set("core.transport", {
  'create': {type: "method", value: []},
  'accept': {type: "method", value: ["number", "object"]},
  'send': {type: "method", value: {
    "header": "object",
    "data": "blob"
  }},
  'close': {type: "method", value: ["number"]},

  'onStateChange': {type: "event", value: "object"},
  'onMessage': {type: "event", value: {
    "header": "object",
    "data": "blob"
  }},
  'onSignal': {type: "event", value: "object"}
});
<|endoftext|>


<commit_before>"use strict";
var parseURL = require("../basic/parseURL");
var Http2ServerResponse = require("http2").Http2ServerResponse;
var headersKeys = "Content-Type,Content-Length,User-Agent,Accept-Language,Accept-Encoding,Range,If-Range,Last-Modified".split(",");
var privateKeys = Object.create(null);
"Cookie,Connection,Referer,Host,Origin,Authorization".split(",").forEach(k => privateKeys[k] = privateKeys[k.toLowerCase()] = true);
var record = require("./record");
var options = {};
var crossmark = /[~,;\.&\*]/;
// ------------ //////////////1--------------- --/ 2 ----------------- ///// 3 ////////// 4 /////
var matchmark = new RegExp(`^(${crossmark.source}(${crossmark.source}?))${/(.*?)(?:[\,&](.*?))?$/.source}`);
-function () {
    var fs = require("fs");
    var path = require("path");
    var loadCertFile = function (keyname, reletivepath) {
        var fullpath = path.join(__dirname, "../../data/keystore", reletivepath);
        fs.readFile(fullpath, function (err, chunk) {
            if (err) console.error(err);
            else options[keyname] = chunk;
        });
    }
    loadCertFile("key", "cross-key.pem");
    loadCertFile("cert", "cross-cert.pem");
}();
function parseUrl(hostpath, real) {
    var { pathname, search, hostname, protocol, port } = parseURL(hostpath);
    if (real === undefined && /^https?\:\/\//i.test(hostpath)) {
        var headers = {};
        var realpath = pathname.slice(1);
        hostpath = `${protocol}//${hostname}${port ? ':' + port : ''}/`;
    } else {
        if (crossmark.test(pathname[1])) var slice_end = pathname.indexOf("/", 2);
        else slice_end = pathname.indexOf("@", 2);
        if (slice_end < 0) slice_end = pathname.length;
        var realpath = real ? real.slice(1) : pathname.slice(slice_end + 1) + (search || "");
        var jsonlike = pathname.slice(1, slice_end);
        // //////////////// 1 -- 2 - /// 3 ////////// 4 /////
        var matchlike = matchmark.exec(jsonlike);
        if (!matchlike) matchlike = /^(?:\{|%7b)(s?)(\/|%2f)\2(.*?)\2(.*?)(?:\}|%7d)$/i.exec(jsonlike);
        if (matchlike) {
            // {s//wx2.qq.com/k=v,k=v,k=v}
            // *wx2.qq.com,k=v,k=v,k=v    http only
            // **wx2.qq.com,k=v,k=v,k=v   https only

            var headers = {};
            let [, s, , host, header] = matchlike;
            if (crossmark.test(s)) s = s.length > 1 ? 's' : '';
            var hostpath = `http${s}://${host}/`;
            if (header) header.split(/[,&]/).forEach(function (kv) {
                var [k, v] = kv.split("=");
                if (k && v) try {
                    headers[decodeURIComponent(k)] = decodeURIComponent(v);
                } catch (e) {
                    headers[unescape(k)] = unescape(v);
                }
            });
        } else {
            var { url: hostpath, token, headers = {} } = JSON.parse(decodeURIComponent(jsonlike));
            if (!token) throw new Error("验证身份失败！");
            hostpath = escape(hostpath);
        }
    }
    return { jsonlike, realpath, hostpath, headers };
}
// https://github.com/nodejs/node/blob/02a0c74861c3107e6a9a1752e91540f8d4c49a76/lib/_http_common.js :204
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
/**
 * Verifies that the given val is a valid HTTP token
 * per the rules defined in RFC 7230
 * See https://tools.ietf.org/html/rfc7230#section-3.2.6
 */
function checkIsHttpToken(val) {
    return tokenRegExp.test(val);
}
/**
 *
 * @param {*} req
 * @param {http.ServerResponse} res
 * @param {*} referer
 */
function cross(req, res, referer) {
    try {
        if (referer) {
            var { jsonlike, realpath, hostpath, headers } = parseUrl(referer, req.url);
            req.url = "/" + unescape(jsonlike) + (crossmark.test(jsonlike[0]) ? "/" : "@") + realpath;
        }
        var { jsonlike, realpath, hostpath, headers } = parseUrl(req.url);
        if (/^&/.test(jsonlike)) hostpath = hostpath.replace(/^https?:/i, req.protocol);

        var $url = hostpath + realpath;
        // $data = $cross['data'],//不再接受数据参数，如果是get请直接写入$url，如果是post，请直接post
        var method = req.method;//$_SERVER['REQUEST_METHOD'];
        var _headers = req.headers;
        req.referer = _headers.referer;
        if (cross.referer.test(_headers.referer) && !headers.referer) {
            headers.referer = hostpath + parseUrl(_headers.referer, false).realpath;
        } else if (_headers.referer || _headers.origin === 'null') {
            headers.referer = hostpath;
        }
        for (let key in headers) {
            let k = key.toLowerCase();
            if (k !== key) {
                headers[k] = headers[key];
                delete headers[key];
            }
        }
        headersKeys.forEach(function (key) {
            key = key.toLowerCase();
            if (!headers[key] && _headers[key]) {
                headers[key] = _headers[key];
            }
        });
        for (var k in _headers) {
            if ({}.hasOwnProperty.call(privateKeys, k)) {
                continue;
            }
            if (!headers[k] && _headers[k] && checkIsHttpToken(k)) {
                headers[k] = _headers[k];
            }
        }
        var http;
        if (/^https\:/i.test(hostpath)) {
            http = require("https");
        } else {
            http = require("http");
        }

        var request = http.request(Object.assign({
            method: method,
            headers: headers,
            rejectUnauthorized: false// 放行证书不可用的网站
        }, parseURL($url), options), function (response) {
            var headers = response.headers;
            var exposeHeaders = [];
            var setCookie = headers["set-cookie"];
            if (setCookie && req.referer && referer === false) headers["efront-cookie"] = setCookie, delete headers["set-cookie"], exposeHeaders.push("efront-cookie");
            a: if (headers.location && referer === false) {
                var parsed = parseURL(headers.location);
                if (parsed.host) {
                    var mark = crossmark.test(jsonlike[0]) ? jsonlike[0] : '~';
                    if (parsed.protocol) mark = "~";
                    headers["location"] = "/" + mark + (/^https:/i.test(parsed.protocol || req.protocol) ? mark : "") + parsed.host + parsed.path;
                    break a;
                }
                headers["efront-location"] = headers.location;
                exposeHeaders.push("efront-location");
                delete headers.location;
            }
            headers["access-control-expose-headers"] = exposeHeaders.join();
            delete headers["access-control-allow-origin"];
            delete headers["access-control-allow-methods"];
            delete headers["access-control-allow-credentials"];
            delete headers["access-control-allow-headers"];
            if (res instanceof Http2ServerResponse) {
                delete headers["transfer-encoding"];
                delete headers["connection"];
            }
            if (!closed) {
                if (/get/i.test(req.method) && (record.enabled || /^[\.&~]/.test(jsonlike)) && response.statusCode === 200) {
                    record($url, request, response, req, res);
                } else {
                    res.writeHead(response.statusCode, headers);
                    response.pipe(res);
                }
            } else {
                response.destroy();
                res.end();
            }
        });
        var closed = false;
        req.on("close", function (e) {
            closed = true;
        });
        request.setTimeout(120000/*support for wechat long pull*/);
        request.on("error", function (e) {
            var code;
            switch (e.code) {
                case "ECONNRESET":
                case "ECONNREFUSED":
                    code = 502;
                    break;
                case "ETIMEDOUT":
                    code = 504;
                    break;
                default:
                    code = 500;
            }
            closed = true;
            res.writeHead(code, {});
            res.end(String(e));
        });
        req.pipe(request);
    } catch (e) {
        res.writeHead(500, {});
        res.end(String(e));
    }
}
cross.prefix = new RegExp(`${/^\//.source}(?:${/\{/.source}|%7b|${crossmark.source}+[^${crossmark.source.replace(/^\[|\]/g, '')}])`, 'i');
cross.referer = new RegExp(`${/^https?\:\/\/[^\/]*/.source}${cross.prefix.source.slice(1)}`, 'i');
module.exports = cross;<commit_msg>修复一处错误<commit_after>"use strict";
var parseURL = require("../basic/parseURL");
var Http2ServerResponse = require("http2").Http2ServerResponse;
var headersKeys = "Content-Type,Content-Length,User-Agent,Accept-Language,Accept-Encoding,Range,If-Range,Last-Modified".split(",");
var privateKeys = Object.create(null);
"Cookie,Connection,Referer,Host,Origin,Authorization".split(",").forEach(k => privateKeys[k] = privateKeys[k.toLowerCase()] = true);
var record = require("./record");
var options = {};
var crossmark = /[~,;\.&\*]/;
// ------------ //////////////1--------------- --/ 2 ----------------- ///// 3 ////////// 4 /////
var matchmark = new RegExp(`^(${crossmark.source}(${crossmark.source}?))${/(.*?)(?:[\,&](.*?))?$/.source}`);
-function () {
    var fs = require("fs");
    var path = require("path");
    var loadCertFile = function (keyname, reletivepath) {
        var fullpath = path.join(__dirname, "../../data/keystore", reletivepath);
        fs.readFile(fullpath, function (err, chunk) {
            if (err) console.error(err);
            else options[keyname] = chunk;
        });
    }
    loadCertFile("key", "cross-key.pem");
    loadCertFile("cert", "cross-cert.pem");
}();
function parseUrl(hostpath, real) {
    var { pathname, search, hostname, protocol, port } = parseURL(hostpath);
    if (real === undefined && /^https?\:\/\//i.test(hostpath)) {
        var headers = {};
        var realpath = pathname.slice(1);
        hostpath = `${protocol}//${hostname}${port ? ':' + port : ''}/`;
    } else {
        if (crossmark.test(pathname[1])) var slice_end = pathname.indexOf("/", 2);
        else slice_end = pathname.indexOf("@", 2);
        if (slice_end < 0) slice_end = pathname.length;
        var realpath = real ? real.slice(1) : pathname.slice(slice_end + 1) + (search || "");
        var jsonlike = pathname.slice(1, slice_end);
        // //////////////// 1 -- 2 - /// 3 ////////// 4 /////
        var matchlike = matchmark.exec(jsonlike);
        if (!matchlike) matchlike = /^(?:\{|%7b)(s?)(\/|%2f)\2(.*?)\2(.*?)(?:\}|%7d)$/i.exec(jsonlike);
        if (matchlike) {
            // {s//wx2.qq.com/k=v,k=v,k=v}
            // *wx2.qq.com,k=v,k=v,k=v    http only
            // **wx2.qq.com,k=v,k=v,k=v   https only

            var headers = {};
            let [, s, , host, header] = matchlike;
            if (crossmark.test(s)) s = s.length > 1 ? 's' : '';
            var hostpath = `http${s}://${host}/`;
            if (header) header.split(/[,&]/).forEach(function (kv) {
                var [k, v] = kv.split("=");
                if (k && v) try {
                    headers[decodeURIComponent(k)] = decodeURIComponent(v);
                } catch (e) {
                    headers[unescape(k)] = unescape(v);
                }
            });
        } else {
            var { url: hostpath, token, headers = {} } = JSON.parse(decodeURIComponent(jsonlike));
            if (!token) throw new Error("验证身份失败！");
            hostpath = escape(hostpath);
        }
    }
    return { jsonlike, realpath, hostpath, headers };
}
// https://github.com/nodejs/node/blob/02a0c74861c3107e6a9a1752e91540f8d4c49a76/lib/_http_common.js :204
const tokenRegExp = /^[\^_`a-zA-Z\-0-9!#$%&'*+.|~]+$/
/**
 * Verifies that the given val is a valid HTTP token
 * per the rules defined in RFC 7230
 * See https://tools.ietf.org/html/rfc7230#section-3.2.6
 */
function checkIsHttpToken(val) {
    return tokenRegExp.test(val);
}
/**
 *
 * @param {*} req
 * @param {http.ServerResponse} res
 * @param {*} referer
 */
function cross(req, res, referer) {
    try {
        if (referer) {
            var { jsonlike, realpath, hostpath, headers } = parseUrl(referer, req.url);
            req.url = "/" + unescape(jsonlike) + (crossmark.test(jsonlike[0]) ? "/" : "@") + realpath;
        }
        var { jsonlike, realpath, hostpath, headers } = parseUrl(req.url);
        if (/^&/.test(jsonlike)) hostpath = req.protocol + hostpath.replace(/^https?:/i, "");

        var $url = hostpath + realpath;
        // $data = $cross['data'],//不再接受数据参数，如果是get请直接写入$url，如果是post，请直接post
        var method = req.method;//$_SERVER['REQUEST_METHOD'];
        var _headers = req.headers;
        req.referer = _headers.referer;
        if (cross.referer.test(_headers.referer) && !headers.referer) {
            headers.referer = hostpath + parseUrl(_headers.referer, false).realpath;
        } else if (_headers.referer || _headers.origin === 'null') {
            headers.referer = hostpath;
        }
        for (let key in headers) {
            let k = key.toLowerCase();
            if (k !== key) {
                headers[k] = headers[key];
                delete headers[key];
            }
        }
        headersKeys.forEach(function (key) {
            key = key.toLowerCase();
            if (!headers[key] && _headers[key]) {
                headers[key] = _headers[key];
            }
        });
        for (var k in _headers) {
            if ({}.hasOwnProperty.call(privateKeys, k)) {
                continue;
            }
            if (!headers[k] && _headers[k] && checkIsHttpToken(k)) {
                headers[k] = _headers[k];
            }
        }
        var http;
        if (/^https\:/i.test(hostpath)) {
            http = require("https");
        } else {
            http = require("http");
        }

        var request = http.request(Object.assign({
            method: method,
            headers: headers,
            rejectUnauthorized: false// 放行证书不可用的网站
        }, parseURL($url), options), function (response) {
            var headers = response.headers;
            var exposeHeaders = [];
            var setCookie = headers["set-cookie"];
            if (setCookie && req.referer && referer === false) headers["efront-cookie"] = setCookie, delete headers["set-cookie"], exposeHeaders.push("efront-cookie");
            a: if (headers.location && referer === false) {
                var parsed = parseURL(headers.location);
                if (parsed.host) {
                    var mark = crossmark.test(jsonlike[0]) ? jsonlike[0] : '~';
                    if (parsed.protocol) mark = "~";
                    headers["location"] = "/" + mark + (/^https:/i.test(parsed.protocol || req.protocol) ? mark : "") + parsed.host + parsed.path;
                    break a;
                }
                headers["efront-location"] = headers.location;
                exposeHeaders.push("efront-location");
                delete headers.location;
            }
            headers["access-control-expose-headers"] = exposeHeaders.join();
            delete headers["access-control-allow-origin"];
            delete headers["access-control-allow-methods"];
            delete headers["access-control-allow-credentials"];
            delete headers["access-control-allow-headers"];
            if (res instanceof Http2ServerResponse) {
                delete headers["transfer-encoding"];
                delete headers["connection"];
            }
            if (!closed) {
                if (/get/i.test(req.method) && (record.enabled || /^[\.&~]/.test(jsonlike)) && response.statusCode === 200) {
                    record($url, request, response, req, res);
                } else {
                    res.writeHead(response.statusCode, headers);
                    response.pipe(res);
                }
            } else {
                response.destroy();
                res.end();
            }
        });
        var closed = false;
        req.on("close", function (e) {
            closed = true;
        });
        request.setTimeout(120000/*support for wechat long pull*/);
        request.on("error", function (e) {
            var code;
            switch (e.code) {
                case "ECONNRESET":
                case "ECONNREFUSED":
                    code = 502;
                    break;
                case "ETIMEDOUT":
                    code = 504;
                    break;
                default:
                    code = 500;
            }
            closed = true;
            res.writeHead(code, {});
            res.end(String(e));
        });
        req.pipe(request);
    } catch (e) {
        res.writeHead(500, {});
        res.end(String(e));
    }
}
cross.prefix = new RegExp(`${/^\//.source}(?:${/\{/.source}|%7b|${crossmark.source}+[^${crossmark.source.replace(/^\[|\]/g, '')}])`, 'i');
cross.referer = new RegExp(`${/^https?\:\/\/[^\/]*/.source}${cross.prefix.source.slice(1)}`, 'i');
module.exports = cross;<|endoftext|>