<commit_before>import format from 'date-fns/format'
import addDays from 'date-fns/add_days'
import Router from 'next/router'

const changeRoute = (start, end) => {
  console.log({ start, end })
  Router.push(`/?start=${start}&end=${end}`)
    .then(() => window.scrollTo(0, 0))
    .catch(e => console.log(e))
}

export default props => {
  const {
    today,
    yesterday,
    last7thDay,
    thisWeekStartDay,
    thisWeekLastDay,
    lastWeekStartDay,
    lastWeekLastDay,
    last30thDay,
    thisMonthStartDay,
    lastMonthStartDay,
    lastMonthEndDay
  } = props.filterOptions

  const todayStr = format(today, 'dddd, MMM Do')
  const yesterdayStr = format(yesterday, 'dddd, MMM Do')
  const last7Days = `${format(last7thDay, 'MMM Do')} - ${format(today, 'MMM Do')}`
  const thisWeek = `${format(thisWeekStartDay, 'MMM Do')} - ${format(thisWeekLastDay, 'MMM Do')}`
  const lastWeek = `${format(lastWeekStartDay, 'MMM Do')} - ${format(lastWeekLastDay, 'MMM Do')}`
  const last30days = `${format(last30thDay, 'MMM Do')} - ${format(today, 'MMM Do')}`
  const thisMonth = format(today, 'MMMM')
  const lastMonth = format(last30thDay, 'MMMM')
  return (

Filter By

# Day

                <input
                  onClick={() => {
                    console.log('clicked')
                    props.toggleFilter()
                    changeRoute(
                      new Date(yesterday).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='today'
                  type='radio'
                  name='filter'
                />

Today

                  {todayStr}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      addDays(yesterday, -1).getTime(),
                      new Date(yesterday).getTime()
                    )
                  }}
                  id='yesterday'
                  type='radio'
                  name='filter'
                />

Yesterday

                  {yesterdayStr}

# Week

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(last7thDay).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='last-seven-days'
                  name='filter'
                  type='radio'
                />

Last 7 days

                  {last7Days}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(thisWeekStartDay).getTime(),
                      new Date(thisWeekLastDay).getTime()
                    )
                  }}
                  id='this-week'
                  name='filter'
                  type='radio'
                />

This week

                  {thisWeek}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(lastWeekStartDay).getTime(),
                      new Date(lastWeekLastDay).getTime()
                    )
                  }}
                  id='last-week'
                  name='filter'
                  type='radio'
                />

Last week

                  {lastWeek}

# Month

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(last30thDay).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='last-30-days'
                  name='filter'
                  type='radio'
                />

Last 30 days

                  {last30days}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(thisMonthStartDay).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='this-month'
                  name='filter'
                  type='radio'
                />

This month

                  {thisMonth}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(lastMonthStartDay).getTime(),
                      new Date(lastMonthEndDay).getTime()
                    )
                  }}
                  id='last-month'
                  name='filter'
                  type='radio'
                />

Last month

                  {lastMonth}

# Custom

                <input
                  onClick={e => {
                    props.toggleFilter()
                    props.showModal()
                  }}
                  name='filter'
                  id='custom'
                  type='radio'
                />

Select date range

  )
}
<commit_msg>fix sidebar z-index issue<commit_after>import format from 'date-fns/format'
import addDays from 'date-fns/add_days'
import Router from 'next/router'

const changeRoute = (start, end) => {
  console.log({ start, end })
  Router.push(`/?start=${start}&end=${end}`)
    .then(() => window.scrollTo(0, 0))
    .catch(e => console.log(e))
}

export default props => {
  const {
    today,
    yesterday,
    last7thDay,
    thisWeekStartDay,
    thisWeekLastDay,
    lastWeekStartDay,
    lastWeekLastDay,
    last30thDay,
    thisMonthStartDay,
    lastMonthStartDay,
    lastMonthEndDay
  } = props.filterOptions

  const todayStr = format(today, 'dddd, MMM Do')
  const yesterdayStr = format(yesterday, 'dddd, MMM Do')
  const last7Days = `${format(last7thDay, 'MMM Do')} - ${format(today, 'MMM Do')}`
  const thisWeek = `${format(thisWeekStartDay, 'MMM Do')} - ${format(thisWeekLastDay, 'MMM Do')}`
  const lastWeek = `${format(lastWeekStartDay, 'MMM Do')} - ${format(lastWeekLastDay, 'MMM Do')}`
  const last30days = `${format(last30thDay, 'MMM Do')} - ${format(today, 'MMM Do')}`
  const thisMonth = format(today, 'MMMM')
  const lastMonth = format(last30thDay, 'MMMM')
  return (

Filter By

# Day

                <input
                  onClick={() => {
                    console.log('clicked')
                    props.toggleFilter()
                    changeRoute(
                      new Date(yesterday).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='today'
                  type='radio'
                  name='filter'
                />

Today

                  {todayStr}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      addDays(yesterday, -1).getTime(),
                      new Date(yesterday).getTime()
                    )
                  }}
                  id='yesterday'
                  type='radio'
                  name='filter'
                />

Yesterday

                  {yesterdayStr}

# Week

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(last7thDay).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='last-seven-days'
                  name='filter'
                  type='radio'
                />

Last 7 days

                  {last7Days}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(thisWeekStartDay).getTime(),
                      new Date(thisWeekLastDay).getTime()
                    )
                  }}
                  id='this-week'
                  name='filter'
                  type='radio'
                />

This week

                  {thisWeek}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(lastWeekStartDay).getTime(),
                      new Date(lastWeekLastDay).getTime()
                    )
                  }}
                  id='last-week'
                  name='filter'
                  type='radio'
                />

Last week

                  {lastWeek}

# Month

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(last30thDay).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='last-30-days'
                  name='filter'
                  type='radio'
                />

Last 30 days

                  {last30days}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(thisMonthStartDay).getTime(),
                      new Date(today).getTime()
                    )
                  }}
                  id='this-month'
                  name='filter'
                  type='radio'
                />

This month

                  {thisMonth}

                <input
                  onClick={() => {
                    props.toggleFilter()
                    changeRoute(
                      new Date(lastMonthStartDay).getTime(),
                      new Date(lastMonthEndDay).getTime()
                    )
                  }}
                  id='last-month'
                  name='filter'
                  type='radio'
                />

Last month

                  {lastMonth}

# Custom

                <input
                  onClick={e => {
                    props.toggleFilter()
                    props.showModal()
                  }}
                  name='filter'
                  id='custom'
                  type='radio'
                />

Select date range

  )
}
<|endoftext|>


<commit_before>/*
 * Basic components rendering
 *
 * Author   : Mikhail Shiryaev
 * Created  : 2016
 * Modified : 2017
 *
 * Requires:
 * - jquery
 * - schemecommon.js
 * - schemerender.js
 */

/********** Led Renderer **********/

scada.scheme.LedRenderer = function () {
    scada.scheme.ComponentRenderer.call(this);
};

scada.scheme.LedRenderer.prototype = Object.create(scada.scheme.ComponentRenderer.prototype);
scada.scheme.LedRenderer.constructor = scada.scheme.LedRenderer;

scada.scheme.LedRenderer.prototype.createDom = function (component, renderContext) {
    var props = component.props;

    var divComp = $("

");
    this.prepareComponent(divComp, component, true);
    this.setBackColor(divComp, props.FillColor);
    this.setToolTip(divComp, props.ToolTip);
    this.bindAction(divComp, component, renderContext);

    if (props.BorderWidth > 0) {
        var divBorder = $("

").appendTo(divComp);
        this.setBorderColor(divBorder, props.BorderColor);
        this.setBorderWidth(divBorder, props.BorderWidth);

        var opacity = props.BorderOpacity / 100;
        if (opacity < 0) {
            opacity = 0;
        } else if (opacity > 1) {
            opacity = 1;
        }

        divBorder.css("opacity", opacity);
        divComp.append(divBorder);
    }

    component.dom = divComp;
};

scada.scheme.LedRenderer.prototype.updateData = function (component, renderContext) {
    var props = component.props;
    var divComp = component.dom;
    var curCnlDataExt = renderContext.curCnlDataMap.get(props.InCnlNum);

    if (divComp && curCnlDataExt) {
        // set fill color
        var fillColor = props.FillColor;

        // define fill color according to the channel status
        if (fillColor == this.STATUS_COLOR) {
            fillColor = curCnlDataExt.Color;
        }

        // define fill color according to the led conditions and channel value
        if (curCnlDataExt.Stat > 0 && props.Conditions) {
            var cnlVal = curCnlDataExt.Val;

            for (var cond of props.Conditions) {
                if (scada.scheme.calc.conditionSatisfied(cond, cnlVal)) {
                    fillColor = cond.Color;
                    break;
                }
            }
        }

        // apply fill color
        divComp.css("background-color", fillColor);

        // set border color
        if (props.BorderColor == this.STATUS_COLOR) {
            var divBorder = divComp.children(".basic-led-border");
            divBorder.css("border-color", curCnlDataExt.Color);
        }
    }
};

/********** Toggle Renderer **********/

scada.scheme.ToggleRenderer = function () {
    scada.scheme.ComponentRenderer.call(this);
};

scada.scheme.ToggleRenderer.prototype = Object.create(scada.scheme.ComponentRenderer.prototype);
scada.scheme.ToggleRenderer.constructor = scada.scheme.ToggleRenderer;

scada.scheme.ToggleRenderer.prototype.createDom = function (component, renderContext) {
    var props = component.props;

    var divComp = $("

");
    var divLever = $("

");

    this.prepareComponent(divComp, component, true);
    this.setBackColor(divComp, props.BackColor);
    this.setBorderColor(divComp, props.BorderColor);
    this.setBorderWidth(divComp, props.BorderWidth);
    this.setToolTip(divComp, props.ToolTip);
    this.bindAction(divComp, component, renderContext);

    var minSize = Math.min(props.Size.Width, props.Size.Height);
    divComp.css({
        "border-radius": minSize / 2,
        "padding": props.Padding
    });

    // lever
    this.setBackColor(divLever, props.LeverColor);
    var leverSize = minSize - props.Padding * 2;
    divLever.css({
        "width": leverSize,
        "height": leverSize
    });

    divComp.append(divLever);
    component.dom = divComp;
}

/********** Renderer Map **********/

// Add components to the renderer map
scada.scheme.rendererMap.set("Scada.Web.Plugins.SchBasicComp.Led", new scada.scheme.LedRenderer());
scada.scheme.rendererMap.set("Scada.Web.Plugins.SchBasicComp.Toggle", new scada.scheme.ToggleRenderer());<commit_msg>ScadaScheme: dev toggle<commit_after>/*
 * Basic components rendering
 *
 * Author   : Mikhail Shiryaev
 * Created  : 2016
 * Modified : 2017
 *
 * Requires:
 * - jquery
 * - schemecommon.js
 * - schemerender.js
 */

/********** Led Renderer **********/

scada.scheme.LedRenderer = function () {
    scada.scheme.ComponentRenderer.call(this);
};

scada.scheme.LedRenderer.prototype = Object.create(scada.scheme.ComponentRenderer.prototype);
scada.scheme.LedRenderer.constructor = scada.scheme.LedRenderer;

scada.scheme.LedRenderer.prototype.createDom = function (component, renderContext) {
    var props = component.props;

    var divComp = $("

");
    this.prepareComponent(divComp, component, true);
    this.setBackColor(divComp, props.FillColor);
    this.setToolTip(divComp, props.ToolTip);
    this.bindAction(divComp, component, renderContext);

    if (props.BorderWidth > 0) {
        var divBorder = $("

").appendTo(divComp);
        this.setBorderColor(divBorder, props.BorderColor);
        this.setBorderWidth(divBorder, props.BorderWidth);

        var opacity = props.BorderOpacity / 100;
        if (opacity < 0) {
            opacity = 0;
        } else if (opacity > 1) {
            opacity = 1;
        }

        divBorder.css("opacity", opacity);
        divComp.append(divBorder);
    }

    component.dom = divComp;
};

scada.scheme.LedRenderer.prototype.updateData = function (component, renderContext) {
    var props = component.props;
    var divComp = component.dom;
    var curCnlDataExt = renderContext.curCnlDataMap.get(props.InCnlNum);

    if (divComp && curCnlDataExt) {
        // set fill color
        var fillColor = props.FillColor;

        // define fill color according to the channel status
        if (fillColor == this.STATUS_COLOR) {
            fillColor = curCnlDataExt.Color;
        }

        // define fill color according to the led conditions and channel value
        if (curCnlDataExt.Stat > 0 && props.Conditions) {
            var cnlVal = curCnlDataExt.Val;

            for (var cond of props.Conditions) {
                if (scada.scheme.calc.conditionSatisfied(cond, cnlVal)) {
                    fillColor = cond.Color;
                    break;
                }
            }
        }

        // apply fill color
        divComp.css("background-color", fillColor);

        // set border color
        if (props.BorderColor == this.STATUS_COLOR) {
            var divBorder = divComp.children(".basic-led-border");
            divBorder.css("border-color", curCnlDataExt.Color);
        }
    }
};

/********** Toggle Renderer **********/

scada.scheme.ToggleRenderer = function () {
    scada.scheme.ComponentRenderer.call(this);
};

scada.scheme.ToggleRenderer.prototype = Object.create(scada.scheme.ComponentRenderer.prototype);
scada.scheme.ToggleRenderer.constructor = scada.scheme.ToggleRenderer;

scada.scheme.ToggleRenderer.prototype.createDom = function (component, renderContext) {
    var props = component.props;

    var divComp = $("

");
    var divLever = $("

");

    this.prepareComponent(divComp, component, true);
    this.setBackColor(divComp, props.BackColor);
    this.setBorderColor(divComp, props.BorderColor);
    this.setBorderWidth(divComp, props.BorderWidth);
    this.setToolTip(divComp, props.ToolTip);
    this.bindAction(divComp, component, renderContext);

    var minSize = Math.min(props.Size.Width, props.Size.Height);
    divComp.css({
        "border-radius": minSize / 2,
        "padding": props.Padding
    });

    // lever
    this.setBackColor(divLever, props.LeverColor);
    var leverSize = minSize - (props.BorderWidth + props.Padding) * 2;
    divLever.css({
        "width": leverSize,
        "height": leverSize
    });

    divComp.append(divLever);
    component.dom = divComp;
}

/********** Renderer Map **********/

// Add components to the renderer map
scada.scheme.rendererMap.set("Scada.Web.Plugins.SchBasicComp.Led", new scada.scheme.LedRenderer());
scada.scheme.rendererMap.set("Scada.Web.Plugins.SchBasicComp.Toggle", new scada.scheme.ToggleRenderer());<|endoftext|>


<commit_before>function Diary () {
    this._url = "http://www.tamurayukari.com/diary/index.html";
}
Diary.prototype.Fetch = function (callback, args) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", this._url, true);
    xhr.onreadystatechange = function () {
        if (xhr.readyState != 4)
            return;
        var dom = document.implementation.createHTMLDocument("");
        var range = dom.createRange();
        range.selectNodeContents(dom.documentElement);
        range.deleteContents();
        var node = range.createContextualFragment(xhr.responseText);
        dom.documentElement.appendChild(node);
        if (callback)
            callback(dom, args);
    };
    xhr.send();
};
Diary.prototype.Notify = function (entry) {
    var title = entry.getElementsByClassName("title")[0].textContent;
    var date = entry.getElementsByClassName("date")[0].textContent;
    var text = entry.getElementsByClassName("text-box")[0].textContent;
    var notification = webkitNotifications.createNotification(
            "icon.png",
            title + " (" + date + ") from Diary",
            text);
    notification.show();
};
Diary.prototype.Check = function (callback, args) {
    this.Fetch(function (dom, self) {
        if (!dom)
            return false;
        var diary = dom.getElementById("diary-box");
        if (!diary)
            return false;
        var oldId = localStorage.diaryId;
        var diary = dom.getElementById("diary-box");
        if (!diary)
            return false;
        var entries = diary.getElementsByClassName("entry-box");
        if (!entries)
            return false;
        for (var i = 0, entry, newId = false; entry = entries[i]; i++) {
            var title = entry.getElementsByClassName("title")[0].textContent;
            var date = entry.getElementsByClassName("date")[0].textContent;
            if (!title || !date)
                continue;
            var id = title + date;
            if (!newId) {
                localStorage.diaryId = id;
                newId = true;
            }
            if (id === oldId)
                break;
            self.Notify(entry);
        }
        if (callback)
            callback(args);
        return true;
    }, this);
};
<commit_msg>fix meaningless dup.<commit_after>function Diary () {
    this._url = "http://www.tamurayukari.com/diary/index.html";
}
Diary.prototype.Fetch = function (callback, args) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", this._url, true);
    xhr.onreadystatechange = function () {
        if (xhr.readyState != 4)
            return;
        var dom = document.implementation.createHTMLDocument("");
        var range = dom.createRange();
        range.selectNodeContents(dom.documentElement);
        range.deleteContents();
        var node = range.createContextualFragment(xhr.responseText);
        dom.documentElement.appendChild(node);
        if (callback)
            callback(dom, args);
    };
    xhr.send();
};
Diary.prototype.Notify = function (entry) {
    var title = entry.getElementsByClassName("title")[0].textContent;
    var date = entry.getElementsByClassName("date")[0].textContent;
    var text = entry.getElementsByClassName("text-box")[0].textContent;
    var notification = webkitNotifications.createNotification(
            "icon.png",
            title + " (" + date + ") from Diary",
            text);
    notification.show();
};
Diary.prototype.Check = function (callback, args) {
    this.Fetch(function (dom, self) {
        if (!dom)
            return false;
        var oldId = localStorage.diaryId;
        var diary = dom.getElementById("diary-box");
        if (!diary)
            return false;
        var entries = diary.getElementsByClassName("entry-box");
        if (!entries)
            return false;
        for (var i = 0, entry, newId = false; entry = entries[i]; i++) {
            var title = entry.getElementsByClassName("title")[0].textContent;
            var date = entry.getElementsByClassName("date")[0].textContent;
            var id = title + date;
            if (!title || !date)
                continue;
            if (!newId) {
                localStorage.diaryId = id;
                newId = true;
            }
            if (id === oldId)
                break;
            self.Notify(entry);
        }
        if (callback)
            callback(args);
        return true;
    }, this);
};
<|endoftext|>


<commit_before>/**
* Copyright 2012-2015, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Plotly = require('../../plotly');

var createModebar = require('./');
var modebarButtons = require('./buttons');

/**
 * Modebar wrapper around 'create' and 'update',
 * chooses buttons to pass to Modebar constructor based on
 * plot type and plot config.
 *
 * @param {object} gd main plot object
 *
 */
module.exports = function manageModebar(gd) {
    var fullLayout = gd._fullLayout,
        context = gd._context,
        modebar = fullLayout._modebar;

    if(!context.displayModeBar && modebar) {
        modebar.destroy();
        delete fullLayout._modebar;
        return;
    }

    if(!Array.isArray(context.modebarButtonsToRemove)) {
        throw new Error([
            '*modebarButtonsToRemove* configuration options',
            'must be an array.'
        ].join(' '));
    }

    var buttons = chooseButtons(fullLayout, context.modebarButtonsToRemove);

    if(modebar) modebar.update(gd, buttons);
    else fullLayout._modebar = createModebar(gd, buttons);
};

function chooseButtons(fullLayout, buttonsToRemove) {
    var buttons = findButtons({category: 'all'}),
        buttons2d = findButtons({category: '2d'});

    // TODO how to plots of multiple types?

    if(fullLayout._hasGL3D) {
        buttons = buttons.concat(findButtons({category: 'gl3d'}));
    }

    if(fullLayout._hasGeo) {
        buttons = buttons.concat(findButtons({category: 'geo'}));
    }

    if(fullLayout._hasCartesian) {
        if(areAllAxesFixed(fullLayout)) {
            buttons = buttons.concat(findButtons({
                category: 'cartesian',
                group: 'hover'
            }));
        }
        else {
            buttons = buttons.concat(buttons2d);
            buttons = buttons.concat(findButtons({category: 'cartesian'}));
        }
    }

    if(fullLayout._hasGL2D) {
        buttons = buttons.concat(buttons2d);
        buttons = buttons.concat(findButtons({category: 'gl2d'}));
    }

    if(fullLayout._hasPie) {
        buttons = buttons.concat(findButtons({category: 'pie'}));
    }

    buttons = filterButtons(buttons, buttonsToRemove);
    buttons = groupButtons(buttons);

    return buttons;
}

// Find buttons in buttonsConfig by category or group
function findButtons(opts) {
    var buttonNames = Object.keys(buttonsConfig),
        category = opts.category,
        group = opts.group;

    var out = [];

    for(var i = 0; i < buttonNames.length; i++) {
        var buttonName = buttonNames[i];

        if(category && buttonsConfig[buttonName].category !== category) continue;
        if(group && buttonsConfig[buttonName].group !== group) continue;

        out.push(buttonName);
    }

    return out;
}

function areAllAxesFixed(fullLayout) {
    var axList = Plotly.Axes.list({_fullLayout: fullLayout}, null, true);
    var allFixed = true;

    for(var i = 0; i < axList.length; i++) {
        if(!axList[i].fixedrange) {
            allFixed = false;
            break;
        }
    }

    return allFixed;
}

// Remove buttons according to modebarButtonsToRemove plot config options
function filterButtons(buttons, buttonsToRemove) {
    var out = [];

    for(var i = 0; i < buttons.length; i++) {
        var button = buttons[i];

        if(buttonsToRemove.indexOf(button) !== -1) continue;

        out.push(button);
    }

    return out;
}

function groupButtons(buttons) {
    var hashObj = {};
    var i;

    for(i = 0; i < buttons.length; i++) {
        var button = buttons[i],
            group = buttonsConfig[button].group;

        if(hashObj[group] === undefined) hashObj[group] = [button];
        else hashObj[group].push(button);
    }

    var groups = Object.keys(hashObj);
    var out = [];

    for(i = 0; i < groups.length; i++) {
        out.push(hashObj[groups[i]]);
    }

    return out;
}
<commit_msg>update modebar manager:<commit_after>/**
* Copyright 2012-2015, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

var Plotly = require('../../plotly');

var createModebar = require('./');
var modebarButtons = require('./buttons');

/**
 * Modebar wrapper around 'create' and 'update',
 * chooses buttons to pass to Modebar constructor based on
 * plot type and plot config.
 *
 * @param {object} gd main plot object
 *
 */
module.exports = function manageModebar(gd) {
    var fullLayout = gd._fullLayout,
        context = gd._context,
        modebar = fullLayout._modebar;

    if(!context.displayModeBar && modebar) {
        modebar.destroy();
        delete fullLayout._modebar;
        return;
    }

    if(!Array.isArray(context.modebarButtonsToRemove)) {
        throw new Error([
            '*modebarButtonsToRemove* configuration options',
            'must be an array.'
        ].join(' '));
    }

    var buttonGroups = getButtonGroups(fullLayout, context.modebarButtonsToRemove);

    if(modebar) modebar.update(gd, buttonGroups);
    else fullLayout._modebar = createModebar(gd, buttonGroups);
};

// logic behind which buttons are displayed by default
function getButtonGroups(fullLayout, buttonsToRemove) {
    var groups = [];

    function addGroup(newGroup) {
        var out = [];

        for(var i = 0; i < newGroup.length; i++) {
            var button = newGroup[i];
            if(buttonsToRemove.indexOf(button) !== -1) continue;
            out.push(modebarButtons[button]);
        }

        groups.push(out);
    }

    // buttons common to all plot types
    addGroup(['toImage', 'sendDataToCloud']);

    if(fullLayout._hasGL3D) {
        addGroup(['zoom3d', 'pan3d', 'orbitRotation', 'tableRotation']);
        addGroup(['resetCameraDefault3d', 'resetCameraLastSave3d']);
        addGroup(['hoverClosest3d']);
    }

    if(fullLayout._hasGeo) {
        addGroup(['zoomInGeo', 'zoomOutGeo', 'resetGeo']);
        addGroup(['hoverClosestGeo']);
    }

    var hasCartesian = fullLayout._hasCartesian,
        hasGL2D = fullLayout._hasGL2D,
        allAxesFixed = areAllAxesFixed(fullLayout);

    if((hasCartesian || hasGL2D) && !allAxesFixed) {
        addGroup(['zoom2d', 'pan2d']);
        addGroup(['zoomIn2d', 'zoomOut2d', 'autoScale2d', 'resetScale2d']);
    }

    if(hasCartesian) {
        addGroup(['hoverClosestCartesian', 'hoverCompareCartesian']);
    }
    if(hasGL2D) {
        addGroup(['hoverClosestGl2d']);
    }
    if(fullLayout._hasPie) {
        addGroup(['hoverClosestPie']);
    }

    return groups;
}

function areAllAxesFixed(fullLayout) {
    var axList = Plotly.Axes.list({_fullLayout: fullLayout}, null, true);
    var allFixed = true;

    for(var i = 0; i < axList.length; i++) {
        if(!axList[i].fixedrange) {
            allFixed = false;
            break;
        }
    }

    return allFixed;
}
<|endoftext|>


<commit_before>// @flow

import {
  FormType,
  FormResponseType,
  SectionType,
  ChoiceType,
  QuestionType,
  QuestionDependencyType,
  QuestionDependencyChoiceType,
  QuestionSubmissionType,
  FormSubmissionResponseType
} from "./types";
import { List, Map } from "immutable";

type ApiApplication = {
  id: number,
  created_at: string,
  updated_at: string
};

type ApiSection = {
  id: number,
  form_id: number,
  name: string,
  order: number,
  content: string,
  created_at: string,
  updated_at: string
};

type ApiChoice = {
  id: string,
  question_id: number,
  question_dependency_id: number,
  metadata: string,
  maximum_chosen: number,
  label: string
};

type ApiQuestionDependencyChoice = {
  id: string,
  question_dependency_id: number,
  choice_id: number
};

type ApiQuestionDependency = {
  id: string,
  display: boolean,
  question_dependency_choices: Array<ApiQuestionDependencyChoice>,
  and: boolean
};

type ApiQuestion = {
  id: number,
  key: string,
  label: string,
  content: string,
  order: number,
  placeholder: string,
  required: boolean,
  hidden: boolean,
  question_type: string,
  validate_as: string | null,
  created_at: string,
  choices: Array<ApiChoice>,
  question_dependency: ApiQuestionDependency,
  updated_at: string,
  section_id: number
};

type ApiForm = {
  id: number,
  application: ApiApplication,
  completion_content: string,
  sections: Array<ApiSection>,
  questions: Array<ApiQuestion>
};

type ApiFormResponse = {
  id: number,
  application_id: number
};

type ApiQuestionSubmissionResponse = {
  id: number,
  value: string | Object,
  question_type: string
};

type ApiFormSubmissionResponse = {
  id: number,
  form: ApiFormResponse,
  question_submissions: Array<ApiQuestionSubmissionResponse>
};

function decodeSection(
  section: ApiSection,
  questions: List<QuestionType>
): SectionType {
  return new SectionType({
    id: section.id,
    name: section.name,
    content: section.content,
    order: section.order,
    questions: List(
      questions.filter(question => {
        return question.get("sectionId") === section.id;
      })
    ),
    persisted: true
  });
}

function decodeQuestion(apiQuestion: ApiQuestion): QuestionType {
  const questionDependency = decodeQuestionDependency(
    apiQuestion.question_dependency
  );
  let question = new QuestionType({
    id: apiQuestion.id,
    key: apiQuestion.key,
    label: apiQuestion.label,
    content: apiQuestion.content,
    type: apiQuestion.question_type,
    validateAs: apiQuestion.validate_as,
    order: apiQuestion.order,
    required: apiQuestion.required,
    placeholder: apiQuestion.placeholder,
    sectionId: apiQuestion.section_id,
    choices: decodeChoices(apiQuestion.choices),
    questionDependency: questionDependency,
    persisted: true
  });
  // Add metadata fields by looking at all choice metadata
  let metadataFields = question.choices
    .flatMap(choice => choice.metadata.keys())
    .toSet()
    .toList();
  return question.set("metadataFields", metadataFields);
}

function decodeQuestionDependency(
  questionDependency: ApiQuestionDependency
): ?QuestionDependencyType {
  if (questionDependency === null) {
    return null;
  }
  return new QuestionDependencyType({
    id: questionDependency.id,
    display: questionDependency.display,
    questionDependencyChoices: decodeQuestionDependencyChoices(
      questionDependency.question_dependency_choices
    ),
    and: questionDependency.and,
    persisted: true
  });
}

function decodeQuestionDependencyChoices(
  questionDependencyChoices: Array<ApiQuestionDependencyChoice>
): List<QuestionDependencyChoiceType> {
  return List(
    questionDependencyChoices.map(choice => {
      return new QuestionDependencyChoiceType({
        id: choice.id,
        questionDependencyId: choice.question_dependency_id,
        choiceId: choice.choice_id,
        persisted: true
      });
    })
  );
}

function decodeChoices(choices: Array<ApiChoice>): List<ChoiceType> {
  return List(
    choices.map(choice => {
      return new ChoiceType({
        id: choice.id,
        question_id: choice.question_id,
        question_dependency_id: choice.question_dependency_id,
        metadata: new Map(choice.metadata),
        maximum_chosen: choice.maximum_chosen,
        label: choice.label,
        persisted: true
      });
    })
  );
}

function decodeForm(data: ApiForm): FormType {
  // Decode the questions on their own
  const questions = List(data.questions.map(decodeQuestion));
  return new FormType({
    id: data.id,
    applicationId: data.application.id,
    completionContent: data.completion_content,
    sections: List(
      data.sections.map(section => {
        return decodeSection(section, questions);
      })
    ),
    persisted: true
  });
}

function decodeFormResponse(data: ApiFormResponse): FormResponseType {
  return new FormResponseType({
    id: data.id,
    applicationId: data.application_id,
    persisted: true
  });
}

function decodeQuestionSubmissions(data: Array<ApiQuestionSubmissionResponse>) {
  return data.map(
    qs =>
      new QuestionSubmissionType({
        id: qs.id,
        value: qs.value,
        questionType: qs.question_type,
        persisted: true
      })
  );
}

function decodeFormSubmissionResponse(
  data: ApiFormSubmissionResponse
): FormSubmissionResponseType {
  return new FormSubmissionResponseType({
    id: data.id,
    formResponse: decodeFormResponse(data.form),
    questionSubmissions: decodeQuestionSubmissions(data.question_submissions),
    persisted: true
  });
}

export { decodeForm, decodeSection, decodeFormSubmissionResponse };
<commit_msg>Fix problem on decoders<commit_after>// @flow

import {
  FormType,
  FormResponseType,
  SectionType,
  ChoiceType,
  QuestionType,
  QuestionDependencyType,
  QuestionDependencyChoiceType,
  QuestionSubmissionType,
  FormSubmissionResponseType
} from "./types";
import { List, Map } from "immutable";

type ApiApplication = {
  id: number,
  created_at: string,
  updated_at: string
};

type ApiSection = {
  id: number,
  form_id: number,
  name: string,
  order: number,
  content: string,
  created_at: string,
  updated_at: string
};

type ApiChoice = {
  id: string,
  question_id: number,
  question_dependency_id: number,
  metadata: string,
  maximum_chosen: number,
  label: string
};

type ApiQuestionDependencyChoice = {
  id: string,
  question_dependency_id: number,
  choice_id: number
};

type ApiQuestionDependency = {
  id: string,
  display: boolean,
  question_dependency_choices: Array<ApiQuestionDependencyChoice>,
  and: boolean
};

type ApiQuestion = {
  id: number,
  key: string,
  label: string,
  content: string,
  order: number,
  placeholder: string,
  required: boolean,
  hidden: boolean,
  question_type: string,
  validate_as: string | null,
  created_at: string,
  choices: Array<ApiChoice>,
  question_dependency: ApiQuestionDependency,
  updated_at: string,
  section_id: number
};

type ApiForm = {
  id: number,
  application: ApiApplication,
  completion_content: string,
  sections: Array<ApiSection>,
  questions: Array<ApiQuestion>
};

type ApiFormResponse = {
  id: number,
  application_id: number
};

type ApiQuestionSubmissionResponse = {
  id: number,
  value: string | Object,
  question_type: string
};

type ApiFormSubmissionResponse = {
  id: number,
  form: ApiFormResponse,
  question_submissions: Array<ApiQuestionSubmissionResponse>
};

function decodeSection(
  section: ApiSection,
  questions: List<QuestionType>
): SectionType {
  return new SectionType({
    id: section.id,
    name: section.name,
    content: section.content,
    order: section.order,
    questions: List(
      questions.filter(question => {
        return question.get("sectionId") === section.id;
      })
    ),
    persisted: true
  });
}

function decodeQuestion(apiQuestion: ApiQuestion): QuestionType {
  const questionDependency = decodeQuestionDependency(
    apiQuestion.question_dependency
  );
  let question = new QuestionType({
    id: apiQuestion.id,
    key: apiQuestion.key,
    label: apiQuestion.label,
    content: apiQuestion.content,
    type: apiQuestion.question_type,
    validateAs: apiQuestion.validate_as,
    order: apiQuestion.order,
    required: apiQuestion.required,
    placeholder: apiQuestion.placeholder,
    sectionId: apiQuestion.section_id,
    choices: decodeChoices(apiQuestion.choices),
    questionDependency: questionDependency,
    persisted: true
  });
  // Add metadata fields by looking at all choice metadata
  let metadataFields = question.choices
    .flatMap(choice => choice.metadata.keys())
    .toSet()
    .toList();
  return question.set("metadataFields", metadataFields);
}

function decodeQuestionDependency(
  questionDependency: ApiQuestionDependency
): ?QuestionDependencyType {
  if (questionDependency === null) {
    return null;
  }
  return new QuestionDependencyType({
    id: questionDependency.id,
    display: questionDependency.display,
    questionDependencyChoices: decodeQuestionDependencyChoices(
      questionDependency.question_dependency_choices
    ),
    and: questionDependency.and,
    persisted: true
  });
}

function decodeQuestionDependencyChoices(
  questionDependencyChoices: Array<ApiQuestionDependencyChoice>
): List<QuestionDependencyChoiceType> {
  return List(
    questionDependencyChoices.map(choice => {
      return new QuestionDependencyChoiceType({
        id: choice.id,
        questionDependencyId: choice.question_dependency_id,
        choiceId: choice.choice_id,
        persisted: true
      });
    })
  );
}

function decodeChoices(choices: Array<ApiChoice>): List<ChoiceType> {
  return List(
    choices.map(choice => {
      return new ChoiceType({
        id: choice.id,
        question_id: choice.question_id,
        question_dependency_id: choice.question_dependency_id,
        metadata: new Map(JSON.parse(choice.metadata)),
        maximum_chosen: choice.maximum_chosen,
        label: choice.label,
        persisted: true
      });
    })
  );
}

function decodeForm(data: ApiForm): FormType {
  // Decode the questions on their own
  const questions = List(data.questions.map(decodeQuestion));
  return new FormType({
    id: data.id,
    applicationId: data.application.id,
    completionContent: data.completion_content,
    sections: List(
      data.sections.map(section => {
        return decodeSection(section, questions);
      })
    ),
    persisted: true
  });
}

function decodeFormResponse(data: ApiFormResponse): FormResponseType {
  return new FormResponseType({
    id: data.id,
    applicationId: data.application_id,
    persisted: true
  });
}

function decodeQuestionSubmissions(data: Array<ApiQuestionSubmissionResponse>) {
  return data.map(
    qs =>
      new QuestionSubmissionType({
        id: qs.id,
        value: qs.value,
        questionType: qs.question_type,
        persisted: true
      })
  );
}

function decodeFormSubmissionResponse(
  data: ApiFormSubmissionResponse
): FormSubmissionResponseType {
  return new FormSubmissionResponseType({
    id: data.id,
    formResponse: decodeFormResponse(data.form),
    questionSubmissions: decodeQuestionSubmissions(data.question_submissions),
    persisted: true
  });
}

export { decodeForm, decodeSection, decodeFormSubmissionResponse };
<|endoftext|>


<commit_before>// Template.post_edit.preserve(['#title', '#url', '#editor', '#sticky']);

// Template.post_edit.preserve({
//   // 'input[id]': function (node) { return node.id; }
//    '[name]': function(node) { return node.getAttribute('name');}
// });

Template.post_edit.helpers({
  post: function(){
    return Posts.findOne(Session.get('selectedPostId'));
  },
  created: function(){
    var post= Posts.findOne(Session.get('selectedPostId'));
    return moment(post.createdAt).format("MMMM Do, h:mm:ss a");
  },
  categories: function(){
    var post = this;
    return Categories.find().map(function(category) {
      category.checked = _.contains(post.categories, category.name) ? 'checked' : '';
      return category;
    });
  },
  isApproved: function(){
    return this.status == STATUS_APPROVED;
  },
  isSticky: function(){
    return this.sticky ? 'checked' : '';
  },
  isSelected: function(){
    // console.log('isSelected?')
    var post= Posts.findOne(Session.get('selectedPostId'));
    return post && this._id == post.userId ? 'selected' : '';
  },
  submittedDate: function(){
    return moment(this.submitted).format("MM/DD/YYYY");
  },
  submittedTime: function(){
    return moment(this.submitted).format("HH:mm");
  },
  users: function(){
    return Meteor.users.find().fetch();
  },
  userName: function(){
    return getDisplayName(this);
  },
  hasStatusPending: function(){
    return this.status == STATUS_PENDING ? 'checked' : '';
  },
  hasStatusApproved: function(){
    return this.status == STATUS_APPROVED ? 'checked' : '';
  },
  hasStatusRejected: function(){
    return this.status == STATUS_REJECTED ? 'checked' : '';
  },
});

Template.post_edit.rendered = function(){
  var post= Posts.findOne(Session.get('selectedPostId'));
  if(post && !this.editor){

    this.editor= new EpicEditor(EpicEditorOptions).load();
    this.editor.importFile('editor',post.body);

    $('#submitted_date').datepicker();

  }
}

Template.post_edit.events = {
  'click input[type=submit]': function(e, instance){
    e.preventDefault();
    if(!Meteor.user()){
      throwError('You must be logged in.');
      return false;
    }

    var selectedPostId=Session.get('selectedPostId');
    var post = Posts.findOne(selectedPostId);
    var categories = [];
    var url = $('#url').val();
    var status = parseInt($('input[name=status]:checked').val());

    $('input[name=category]:checked').each(function() {
       categories.push($(this).val());
    });

    var properties = {
      headline:         $('#title').val(),
      body:             instance.editor.exportFile(),
      categories:       categories,
    };

    if(url){
      properties.url = (url.substring(0, 7) == "http://" || url.substring(0, 8) == "https://") ? url : "http://"+url;
    }

    if(isAdmin(Meteor.user())){
      if(status == STATUS_APPROVED){
        if(post.submitted == ''){
          // this is the first time we are approving the post
          properties.submitted = new Date().getTime();
        }else if($('#submitted_date').exists()){
          properties.submitted = parseInt(moment($('#submitted_date').val()+$('#submitted_time').val(), "MM/DD/YYYY HH:mm").valueOf());
        }
      }
      adminProperties = {
        sticky:     !!$('#sticky').attr('checked'),
        userId:     $('#postUser').val(),
        status:     status,
      };
      properties = _.extend(properties, adminProperties);
    }

    Posts.update(selectedPostId,
    {
        $set: properties
      }
    ,function(error){
      if(error){
        throwError(error.reason);
      }else{
        trackEvent("edit post", {'postId': selectedPostId});
        Meteor.Router.to("/posts/"+selectedPostId);
      }
    }
    );
  }

  , 'click .delete-link': function(e){
    e.preventDefault();
    if(confirm("Are you sure?")){
      var selectedPostId=Session.get('selectedPostId');
      Posts.remove(selectedPostId);
      Meteor.Router.to("/posts/deleted");
    }
  }
};<commit_msg>fixed pending post approved bug<commit_after>// Template.post_edit.preserve(['#title', '#url', '#editor', '#sticky']);

// Template.post_edit.preserve({
//   // 'input[id]': function (node) { return node.id; }
//    '[name]': function(node) { return node.getAttribute('name');}
// });

Template.post_edit.helpers({
  post: function(){
    return Posts.findOne(Session.get('selectedPostId'));
  },
  created: function(){
    var post= Posts.findOne(Session.get('selectedPostId'));
    return moment(post.createdAt).format("MMMM Do, h:mm:ss a");
  },
  categories: function(){
    var post = this;
    return Categories.find().map(function(category) {
      category.checked = _.contains(post.categories, category.name) ? 'checked' : '';
      return category;
    });
  },
  isApproved: function(){
    return this.status == STATUS_APPROVED;
  },
  isSticky: function(){
    return this.sticky ? 'checked' : '';
  },
  isSelected: function(){
    // console.log('isSelected?')
    var post= Posts.findOne(Session.get('selectedPostId'));
    return post && this._id == post.userId ? 'selected' : '';
  },
  submittedDate: function(){
    return moment(this.submitted).format("MM/DD/YYYY");
  },
  submittedTime: function(){
    return moment(this.submitted).format("HH:mm");
  },
  users: function(){
    return Meteor.users.find().fetch();
  },
  userName: function(){
    return getDisplayName(this);
  },
  hasStatusPending: function(){
    return this.status == STATUS_PENDING ? 'checked' : '';
  },
  hasStatusApproved: function(){
    return this.status == STATUS_APPROVED ? 'checked' : '';
  },
  hasStatusRejected: function(){
    return this.status == STATUS_REJECTED ? 'checked' : '';
  },
});

Template.post_edit.rendered = function(){
  var post= Posts.findOne(Session.get('selectedPostId'));
  if(post && !this.editor){

    this.editor= new EpicEditor(EpicEditorOptions).load();
    this.editor.importFile('editor',post.body);

    $('#submitted_date').datepicker();

  }
}

Template.post_edit.events = {
  'click input[type=submit]': function(e, instance){
    e.preventDefault();
    if(!Meteor.user()){
      throwError('You must be logged in.');
      return false;
    }

    var selectedPostId=Session.get('selectedPostId');
    var post = Posts.findOne(selectedPostId);
    var categories = [];
    var url = $('#url').val();
    var status = parseInt($('input[name=status]:checked').val());

    $('input[name=category]:checked').each(function() {
       categories.push($(this).val());
    });

    var properties = {
      headline:         $('#title').val(),
      body:             instance.editor.exportFile(),
      categories:       categories,
    };

    if(url){
      properties.url = (url.substring(0, 7) == "http://" || url.substring(0, 8) == "https://") ? url : "http://"+url;
    }

    if(isAdmin(Meteor.user())){
      console.log('status: '+status)
      console.log(post)
      if(status == STATUS_APPROVED){
        if(!post.submitted){
          // this is the first time we are approving the post
          properties.submitted = new Date().getTime();
        }else if($('#submitted_date').exists()){
          properties.submitted = parseInt(moment($('#submitted_date').val()+$('#submitted_time').val(), "MM/DD/YYYY HH:mm").valueOf());
        }
      }
      adminProperties = {
        sticky:     !!$('#sticky').attr('checked'),
        userId:     $('#postUser').val(),
        status:     status,
      };
      properties = _.extend(properties, adminProperties);
      console.log(properties)
    }

    Posts.update(selectedPostId,
    {
        $set: properties
      }
    ,function(error){
      if(error){
        throwError(error.reason);
      }else{
        trackEvent("edit post", {'postId': selectedPostId});
        Meteor.Router.to("/posts/"+selectedPostId);
      }
    }
    );
  }

  , 'click .delete-link': function(e){
    e.preventDefault();
    if(confirm("Are you sure?")){
      var selectedPostId=Session.get('selectedPostId');
      Posts.remove(selectedPostId);
      Meteor.Router.to("/posts/deleted");
    }
  }
};<|endoftext|>


<commit_before>/**
 * Copyright 2017 Trim-marks Inc.
 *
 * Vivliostyle.js is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Vivliostyle.js is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Vivliostyle.js.  If not, see <http://www.gnu.org/licenses/>.
 */

import * as adapt_epub from "../../../src/vivliostyle/epub";

describe("epub", function() {
  describe("OPFDoc", function() {
    describe("OPFDocumentURLTransformer", function() {
      var opfDoc = new adapt_epub.OPFDoc(null, null);
      opfDoc.items = [
        { src: "http://example.com:8000/foo/bar1.html" },
        { src: "http://example.com:8000/foo/bar2.html" },
      ];
      var transformer = opfDoc.createDocumentURLTransformer();

      var illegalCharRegexp = /[^-a-zA-Z0-9_:]/;

      describe("transformFragment / restoreURL", function() {
        var baseURL = "http://base.org:9000/baz.html";
        var fragment = "some-fragment";
        var transformed = transformer.transformFragment(fragment, baseURL);

        it("transforms a pair of a fragment and a base URL into an XML ID string", function() {
          expect(transformed).not.toMatch(illegalCharRegexp);
          expect(transformed.indexOf(adapt_epub.transformedIdPrefix)).toBe(0);
        });

        it("restores a pair of the original base URL and the original fragment", function() {
          var restored = transformer.restoreURL(transformed);
          expect(restored).toEqual([baseURL, fragment]);

          restored = transformer.restoreURL("#" + transformed);
          expect(restored).toEqual([baseURL, fragment]);
        });
      });

      describe("transformURL", function() {
        var fragment = "some-fragment";

        it("transforms a URL internal to the document into an XML ID string", function() {
          var baseURL = opfDoc.items[1].src;

          var transformed = transformer.transformURL("#" + fragment, baseURL);
          expect(transformed.charAt(0)).toBe("#");
          expect(transformed.substring(1)).not.toMatch(illegalCharRegexp);

          transformed = transformer.transformURL(baseURL + "#" + fragment);
          expect(transformed.charAt(0)).toBe("#");
          expect(transformed.substring(1)).not.toMatch(illegalCharRegexp);

          var restored = transformer.restoreURL(transformed);
          expect(restored).toEqual([baseURL, fragment]);
        });

        it("does not transform an external URL", function() {
          var baseURL = "http://base.org:9000/baz.html";

          var transformed = transformer.transformURL("#" + fragment, baseURL);
          expect(transformed).toBe("#" + fragment);

          transformed = transformer.transformURL(baseURL + "#" + fragment);
          expect(transformed).toBe(baseURL + "#" + fragment);
        });
      });
    });
  });
});
<commit_msg>chore: add tests for epub metadata<commit_after>/**
 * Copyright 2017 Trim-marks Inc.
 *
 * Vivliostyle.js is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Vivliostyle.js is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Vivliostyle.js.  If not, see <http://www.gnu.org/licenses/>.
 */

import * as adapt_epub from "../../../src/vivliostyle/epub";
import * as adapt_xmldoc from "../../../src/vivliostyle/xml-doc";

describe("epub", function () {
  describe("OPFDoc", function () {
    describe("OPFDocumentURLTransformer", function () {
      var opfDoc = new adapt_epub.OPFDoc(null, null);
      opfDoc.items = [
        { src: "http://example.com:8000/foo/bar1.html" },
        { src: "http://example.com:8000/foo/bar2.html" },
      ];
      var transformer = opfDoc.createDocumentURLTransformer();

      var illegalCharRegexp = /[^-a-zA-Z0-9_:]/;

      describe("transformFragment / restoreURL", function () {
        var baseURL = "http://base.org:9000/baz.html";
        var fragment = "some-fragment";
        var transformed = transformer.transformFragment(fragment, baseURL);

        it("transforms a pair of a fragment and a base URL into an XML ID string", function () {
          expect(transformed).not.toMatch(illegalCharRegexp);
          expect(transformed.indexOf(adapt_epub.transformedIdPrefix)).toBe(0);
        });

        it("restores a pair of the original base URL and the original fragment", function () {
          var restored = transformer.restoreURL(transformed);
          expect(restored).toEqual([baseURL, fragment]);

          restored = transformer.restoreURL("#" + transformed);
          expect(restored).toEqual([baseURL, fragment]);
        });
      });

      describe("transformURL", function () {
        var fragment = "some-fragment";

        it("transforms a URL internal to the document into an XML ID string", function () {
          var baseURL = opfDoc.items[1].src;

          var transformed = transformer.transformURL("#" + fragment, baseURL);
          expect(transformed.charAt(0)).toBe("#");
          expect(transformed.substring(1)).not.toMatch(illegalCharRegexp);

          transformed = transformer.transformURL(baseURL + "#" + fragment);
          expect(transformed.charAt(0)).toBe("#");
          expect(transformed.substring(1)).not.toMatch(illegalCharRegexp);

          var restored = transformer.restoreURL(transformed);
          expect(restored).toEqual([baseURL, fragment]);
        });

        it("does not transform an external URL", function () {
          var baseURL = "http://base.org:9000/baz.html";

          var transformed = transformer.transformURL("#" + fragment, baseURL);
          expect(transformed).toBe("#" + fragment);

          transformed = transformer.transformURL(baseURL + "#" + fragment);
          expect(transformed).toBe(baseURL + "#" + fragment);
        });
      });
    });
  });

  describe("readMetadata", function () {
    var url = "foobar";

    it("parses DC11 terms in order", function () {
      var doc = new DOMParser().parseFromString(
        `
      <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
        <dc:identifier id="pub-id">urn:uuid:A1B0D67E-2E81-4DF5-9E67-A64CBE366809</dc:identifier>
        <dc:title>Norwegian Wood</dc:title>
        <dc:language>en</dc:language>
      </metadata>`,
        "text/xml",
      );
      var holder = new adapt_xmldoc.XMLDocHolder(null, url, doc);
      var items = holder.doc().childElements();
      var metadata = adapt_epub.readMetadata(items);

      expect(metadata["http://purl.org/dc/terms/identifier"]).toEqual([
        { v: "urn:uuid:A1B0D67E-2E81-4DF5-9E67-A64CBE366809", o: 1 },
      ]);

      expect(metadata["http://purl.org/dc/terms/title"]).toEqual([
        { v: "Norwegian Wood", o: 2 },
      ]);

      expect(metadata["http://purl.org/dc/terms/language"]).toEqual([
        { v: "en", o: 3 },
      ]);
    });

    it("parses DCTERMS properties in order", function () {
      var doc = new DOMParser().parseFromString(
        `
      <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
        <meta property="dcterms:modified">2011-01-01T12:00:00Z</meta>
      </metadata>`,
        "text/xml",
      );
      var holder = new adapt_xmldoc.XMLDocHolder(null, url, doc);
      var items = holder.doc().childElements();
      var metadata = adapt_epub.readMetadata(items);

      expect(metadata["http://purl.org/dc/terms/modified"]).toEqual([
        { v: "2011-01-01T12:00:00Z", o: 1 },
      ]);
    });

    it("parses refinement properties", function () {
      var doc = new DOMParser().parseFromString(
        `
      <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
        <dc:creator id="creator">Haruki Murakami</dc:creator>
        <meta refines="#creator" property="role" scheme="marc:relators" id="role">aut</meta>
        <meta refines="#creator" property="alternate-script" xml:lang="ja">村上 春樹</meta>
        <meta refines="#creator" property="file-as">Murakami, Haruki</meta>
      </metadata>`,
        "text/xml",
      );
      var holder = new adapt_xmldoc.XMLDocHolder(null, url, doc);
      var items = holder.doc().childElements();
      var metadata = adapt_epub.readMetadata(items);

      expect(metadata["http://purl.org/dc/terms/creator"]).toEqual([
        {
          v: "Haruki Murakami",
          o: 1,
          r: {
            "http://idpf.org/epub/vocab/package/meta/#role": [
              { v: "aut", o: 2, s: "http://id.loc.gov/vocabulary/relators" },
            ],
            "http://idpf.org/epub/vocab/package/meta/#alternate-script": [
              { v: "村上 春樹", o: 3 },
            ],
            "http://idpf.org/epub/vocab/package/meta/#file-as": [
              { v: "Murakami, Haruki", o: 4 },
            ],
          },
        },
      ]);
    });

    it("parses role properties", function () {
      var doc = new DOMParser().parseFromString(
        `
      <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
        <dc:creator opf:role="aut">Harkaitz Cano</dc:creator>
        <dc:creator opf:role="trl">Roberta Gozzi</dc:creator>
        <dc:contributor opf:role="bkp">calibre (3.12.0) [https://calibre-ebook.com]</dc:contributor>
      </metadata>`,
        "text/xml",
      );
      var holder = new adapt_xmldoc.XMLDocHolder(null, url, doc);
      var items = holder.doc().childElements();
      var metadata = adapt_epub.readMetadata(items);

      expect(metadata["http://purl.org/dc/terms/creator"]).toEqual([
        {
          v: "Harkaitz Cano",
          o: 1,
          r: {
            "http://idpf.org/epub/vocab/package/meta/#role": [
              { v: "aut", o: 1 },
            ],
          },
        },
        {
          v: "Roberta Gozzi",
          o: 2,
          r: {
            "http://idpf.org/epub/vocab/package/meta/#role": [
              { v: "trl", o: 2 },
            ],
          },
        },
      ]);

      expect(metadata["http://purl.org/dc/terms/contributor"]).toEqual([
        {
          v: "calibre (3.12.0) [https://calibre-ebook.com]",
          o: 3,
          r: {
            "http://idpf.org/epub/vocab/package/meta/#role": [
              { v: "bkp", o: 3 },
            ],
          },
        },
      ]);
    });
  });
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';

const cached = {};
function registerModel(app, model) {
  model = model.default || model;
  if (!cached[model.namespace]) {
    app.model(model);
    cached[model.namespace] = 1;
  }
}

let defaultLoadingComponent = () => null;

function asyncComponent(config) {
  const { resolve } = config;

  return class DynamicComponent extends Component {
    constructor(...args) {
      super(...args);
      this.LoadingComponent =
        config.LoadingComponent || defaultLoadingComponent;
      this.state = {
        AsyncComponent: null,
      };
      this.load();
    }

    componentDidMount() {
      this.mounted = true;
    }

    load() {
      resolve().then((m) => {
        const AsyncComponent = m.default || m;
        if (this.mounted) {
          this.setState({ AsyncComponent });
        } else {
          this.state.AsyncComponent = AsyncComponent; // eslint-disable-line
        }
      });
    }

    render() {
      const { AsyncComponent } = this.state;
      const { LoadingComponent } = this;
      if (AsyncComponent) return <AsyncComponent {...this.props} />;

      return <LoadingComponent {...this.props} />;
    }
  };
}

export default function dynamic(config) {
  const { app, models: resolveModels, component: resolveComponent } = config;
  return asyncComponent({
    resolve: config.resolve || function () {
      const models = typeof resolveModels === 'function' ? resolveModels() : [];
      const component = resolveComponent();
      return new Promise((resolve) => {
        Promise.all([...models, component]).then((ret) => {
          if (!models || !models.length) {
            return resolve(ret[0]);
          } else {
            const len = models.length;
            ret.slice(0, len).forEach((m) => {
              registerModel(app, m);
            });
            resolve(ret[len]);
          }
        });
      });
    },
    ...config,
  });
}

dynamic.setDefaultLoadingComponent = (LoadingComponent) => {
  defaultLoadingComponent = LoadingComponent;
};
<commit_msg>fix: dva/dynamic report setState warning when component is unmount before load is complete, Close #1391<commit_after>import React, { Component } from 'react';

const cached = {};
function registerModel(app, model) {
  model = model.default || model;
  if (!cached[model.namespace]) {
    app.model(model);
    cached[model.namespace] = 1;
  }
}

let defaultLoadingComponent = () => null;

function asyncComponent(config) {
  const { resolve } = config;

  return class DynamicComponent extends Component {
    constructor(...args) {
      super(...args);
      this.LoadingComponent =
        config.LoadingComponent || defaultLoadingComponent;
      this.state = {
        AsyncComponent: null,
      };
      this.load();
    }

    componentDidMount() {
      this.mounted = true;
    }

    componentWillUnmount() {
      this.mounted = false;
    }

    load() {
      resolve().then((m) => {
        const AsyncComponent = m.default || m;
        if (this.mounted) {
          this.setState({ AsyncComponent });
        } else {
          this.state.AsyncComponent = AsyncComponent; // eslint-disable-line
        }
      });
    }

    render() {
      const { AsyncComponent } = this.state;
      const { LoadingComponent } = this;
      if (AsyncComponent) return <AsyncComponent {...this.props} />;

      return <LoadingComponent {...this.props} />;
    }
  };
}

export default function dynamic(config) {
  const { app, models: resolveModels, component: resolveComponent } = config;
  return asyncComponent({
    resolve: config.resolve || function () {
      const models = typeof resolveModels === 'function' ? resolveModels() : [];
      const component = resolveComponent();
      return new Promise((resolve) => {
        Promise.all([...models, component]).then((ret) => {
          if (!models || !models.length) {
            return resolve(ret[0]);
          } else {
            const len = models.length;
            ret.slice(0, len).forEach((m) => {
              registerModel(app, m);
            });
            resolve(ret[len]);
          }
        });
      });
    },
    ...config,
  });
}

dynamic.setDefaultLoadingComponent = (LoadingComponent) => {
  defaultLoadingComponent = LoadingComponent;
};
<|endoftext|>


<commit_before>/**
 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const {createElement, firstChildByTag, insertAfter, insertBefore} = require('../NodeUtils');
const {AMP_CACHE_HOST} = require('../AmpConstants.js');
const {findMetaViewport} = require('../HtmlDomHelper');
const {calculateHost} = require('../RuntimeHostHelper');

/**
 * RewriteAmpUrls - rewrites AMP runtime URLs.
 *
 * This transformer supports two parameters:
 *
 * * `ampRuntimeVersion`: specifies a
 *   [specific version](https://github.com/ampproject/amp-toolbox/tree/master/runtime-version)
 *   version of the AMP runtime. For example: `ampRuntimeVersion:
 *   "001515617716922"` will result in AMP runtime URLs being re-written
 *   from `https://cdn.ampproject.org/v0.js` to
 *   `https://cdn.ampproject.org/rtv/001515617716922/v0.js`.
 *
 * * `ampUrlPrefix`: specifies an URL prefix for AMP runtime
 *   URLs. For example: `ampUrlPrefix: "/amp"` will result in AMP runtime
 *   URLs being re-written from `https://cdn.ampproject.org/v0.js` to
 *   `/amp/v0.js`. This option is experimental and not recommended.
 *
 * * `geoApiUrl`: specifies amp-geo API URL to use as a fallback when
 *   amp-geo-0.1.js is served unpatched, i.e. when
 *   {{AMP_ISO_COUNTRY_HOTPATCH}} is not replaced dynamically.
 *
 * * `lts`: Use long-term stable URLs. This option is not compatible with
 *   `ampRuntimeVersion` or `ampUrlPrefix`; an error will be thrown if
 *   these options are included together. Similarly, the `geoApiUrl`
 *   option is ineffective with the lts flag, but will simply be ignored
 *   rather than throwing an error.
 *
 * All parameters are optional. If no option is provided, runtime URLs won't be
 * re-written. You can combine `ampRuntimeVersion` and  `ampUrlPrefix` to
 * rewrite AMP runtime URLs to versioned URLs on a different origin.
 *
 * This transformer also adds a preload header for the AMP runtime (v0.js) to trigger HTTP/2
 * push for CDNs (see https://www.w3.org/TR/preload/#server-push-(http/2)).
 */
class RewriteAmpUrls {
  constructor(config) {
    this.esmModulesEnabled = config.experimentEsm;
  }
  transform(root, params) {
    const html = firstChildByTag(root, 'html');
    const head = firstChildByTag(html, 'head');
    if (!head) return;

    const host = calculateHost(params);

    let node = head.firstChild;
    let referenceNode = findMetaViewport(head);

    while (node) {
      if (node.tagName === 'script' && this._usesAmpCacheUrl(node.attribs.src)) {
        node.attribs.src = this._replaceUrl(node.attribs.src, host);
        if (this.esmModulesEnabled) {
          this._addEsm(node, node.attribs.src.endsWith('v0.js'));
        }
        referenceNode = this._addPreload(head, referenceNode, node.attribs.src, 'script');
      } else if (
        node.tagName === 'link' &&
        node.attribs.rel === 'stylesheet' &&
        this._usesAmpCacheUrl(node.attribs.href)
      ) {
        node.attribs.href = this._replaceUrl(node.attribs.href, host);
        referenceNode = this._addPreload(head, referenceNode, node.attribs.href, 'style');
      }
      node = node.nextSibling;
    }

    // runtime-host and amp-geo-api meta tags should appear before the first script
    if (!this._usesAmpCacheUrl(host) && !params.lts) {
      const versionlessHost = calculateHost({ampUrlPrefix: params.ampUrlPrefix});
      this._addMeta(head, 'runtime-host', versionlessHost);
    }
    if (params.geoApiUrl && !params.lts) {
      this._addMeta(head, 'amp-geo-api', params.geoApiUrl);
    }
  }

  _usesAmpCacheUrl(url) {
    if (!url) {
      return;
    }
    return url.startsWith(AMP_CACHE_HOST);
  }

  _replaceUrl(url, ampUrlPrefix) {
    return ampUrlPrefix + url.substring(AMP_CACHE_HOST.length);
  }

  _addEsm(scriptNode, preload) {
    const esmScriptUrl = scriptNode.attribs.src.replace(/\.js$/, '.mjs');
    if (preload) {
      const preload = createElement('link', {
        as: 'script',
        crossorigin: 'anonymous',
        href: esmScriptUrl,
        rel: 'preload',
      });
      insertBefore(scriptNode.parent, preload, scriptNode);
    }
    const nomoduleNode = createElement('script', {
      async: '',
      nomodule: '',
      src: scriptNode.attribs.src,
    });
    insertBefore(scriptNode.parent, nomoduleNode, scriptNode);

    scriptNode.attribs.type = 'module';
    // Without crossorigin=anonymous browser loads the script twice because
    // of preload.
    scriptNode.attribs.crossorigin = 'anonymous';
    scriptNode.attribs.src = esmScriptUrl;
  }

  _addPreload(parent, node, href, type) {
    if (!href.endsWith('v0.js') && !href.endsWith('v0.css')) {
      return node;
    }
    const preload = createElement('link', {
      rel: 'preload',
      href: href,
      as: type,
    });
    insertAfter(parent, preload, node);
    return preload;
  }

  _addMeta(head, name, content) {
    const meta = createElement('meta', {name, content});
    insertBefore(head, meta, firstChildByTag(head, 'script'));
  }
}

module.exports = RewriteAmpUrls;
<commit_msg>enable esm experiment via param<commit_after>/**
 * Copyright 2017 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const {createElement, firstChildByTag, insertAfter, insertBefore} = require('../NodeUtils');
const {AMP_CACHE_HOST} = require('../AmpConstants.js');
const {findMetaViewport} = require('../HtmlDomHelper');
const {calculateHost} = require('../RuntimeHostHelper');

/**
 * RewriteAmpUrls - rewrites AMP runtime URLs.
 *
 * This transformer supports two parameters:
 *
 * * `ampRuntimeVersion`: specifies a
 *   [specific version](https://github.com/ampproject/amp-toolbox/tree/master/runtime-version)
 *   version of the AMP runtime. For example: `ampRuntimeVersion:
 *   "001515617716922"` will result in AMP runtime URLs being re-written
 *   from `https://cdn.ampproject.org/v0.js` to
 *   `https://cdn.ampproject.org/rtv/001515617716922/v0.js`.
 *
 * * `ampUrlPrefix`: specifies an URL prefix for AMP runtime
 *   URLs. For example: `ampUrlPrefix: "/amp"` will result in AMP runtime
 *   URLs being re-written from `https://cdn.ampproject.org/v0.js` to
 *   `/amp/v0.js`. This option is experimental and not recommended.
 *
 * * `geoApiUrl`: specifies amp-geo API URL to use as a fallback when
 *   amp-geo-0.1.js is served unpatched, i.e. when
 *   {{AMP_ISO_COUNTRY_HOTPATCH}} is not replaced dynamically.
 *
 * * `lts`: Use long-term stable URLs. This option is not compatible with
 *   `ampRuntimeVersion` or `ampUrlPrefix`; an error will be thrown if
 *   these options are included together. Similarly, the `geoApiUrl`
 *   option is ineffective with the lts flag, but will simply be ignored
 *   rather than throwing an error.
 *
 * All parameters are optional. If no option is provided, runtime URLs won't be
 * re-written. You can combine `ampRuntimeVersion` and  `ampUrlPrefix` to
 * rewrite AMP runtime URLs to versioned URLs on a different origin.
 *
 * This transformer also adds a preload header for the AMP runtime (v0.js) to trigger HTTP/2
 * push for CDNs (see https://www.w3.org/TR/preload/#server-push-(http/2)).
 */
class RewriteAmpUrls {
  constructor(config) {
    this.esmModulesEnabled = config.experimentEsm;
  }
  transform(root, params) {
    const html = firstChildByTag(root, 'html');
    const head = firstChildByTag(html, 'head');
    if (!head) return;

    const host = calculateHost(params);

    let node = head.firstChild;
    let referenceNode = findMetaViewport(head);

    while (node) {
      if (node.tagName === 'script' && this._usesAmpCacheUrl(node.attribs.src)) {
        node.attribs.src = this._replaceUrl(node.attribs.src, host);
        if (this.esmModulesEnabled || params.experimentEsm) {
          this._addEsm(node, node.attribs.src.endsWith('v0.js'));
        }
        referenceNode = this._addPreload(head, referenceNode, node.attribs.src, 'script');
      } else if (
        node.tagName === 'link' &&
        node.attribs.rel === 'stylesheet' &&
        this._usesAmpCacheUrl(node.attribs.href)
      ) {
        node.attribs.href = this._replaceUrl(node.attribs.href, host);
        referenceNode = this._addPreload(head, referenceNode, node.attribs.href, 'style');
      }
      node = node.nextSibling;
    }

    // runtime-host and amp-geo-api meta tags should appear before the first script
    if (!this._usesAmpCacheUrl(host) && !params.lts) {
      const versionlessHost = calculateHost({ampUrlPrefix: params.ampUrlPrefix});
      this._addMeta(head, 'runtime-host', versionlessHost);
    }
    if (params.geoApiUrl && !params.lts) {
      this._addMeta(head, 'amp-geo-api', params.geoApiUrl);
    }
  }

  _usesAmpCacheUrl(url) {
    if (!url) {
      return;
    }
    return url.startsWith(AMP_CACHE_HOST);
  }

  _replaceUrl(url, ampUrlPrefix) {
    return ampUrlPrefix + url.substring(AMP_CACHE_HOST.length);
  }

  _addEsm(scriptNode, preload) {
    const esmScriptUrl = scriptNode.attribs.src.replace(/\.js$/, '.mjs');
    if (preload) {
      const preload = createElement('link', {
        as: 'script',
        crossorigin: 'anonymous',
        href: esmScriptUrl,
        rel: 'preload',
      });
      insertBefore(scriptNode.parent, preload, scriptNode);
    }
    const nomoduleNode = createElement('script', {
      async: '',
      nomodule: '',
      src: scriptNode.attribs.src,
    });
    insertBefore(scriptNode.parent, nomoduleNode, scriptNode);

    scriptNode.attribs.type = 'module';
    // Without crossorigin=anonymous browser loads the script twice because
    // of preload.
    scriptNode.attribs.crossorigin = 'anonymous';
    scriptNode.attribs.src = esmScriptUrl;
  }

  _addPreload(parent, node, href, type) {
    if (!href.endsWith('v0.js') && !href.endsWith('v0.css')) {
      return node;
    }
    const preload = createElement('link', {
      rel: 'preload',
      href: href,
      as: type,
    });
    insertAfter(parent, preload, node);
    return preload;
  }

  _addMeta(head, name, content) {
    const meta = createElement('meta', {name, content});
    insertBefore(head, meta, firstChildByTag(head, 'script'));
  }
}

module.exports = RewriteAmpUrls;
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 * @emails oncall+draft_js
 *
 * This file is a fork of DraftEditorBlock.react.js and DraftEditorContents.react.js
 *
 * This is unstable and not part of the public API and should not be used by
 * production systems. This file may be update/removed without notice.
 */

'use strict';

import type {BlockNodeRecord} from 'BlockNodeRecord';
import type ContentState from 'ContentState';
import type {DraftBlockRenderMap} from 'DraftBlockRenderMap';
import type {DraftDecoratorType} from 'DraftDecoratorType';
import type {DraftInlineStyle} from 'DraftInlineStyle';
import type EditorState from 'EditorState';
import type SelectionState from 'SelectionState';
import type {BidiDirection} from 'UnicodeBidiDirection';

const DraftEditorNode = require('DraftEditorNode.react');
const DraftOffsetKey = require('DraftOffsetKey');
const React = require('React');
const ReactDOM = require('ReactDOM');
const Scroll = require('Scroll');
const Style = require('Style');

const getElementPosition = require('getElementPosition');
const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const Immutable = require('immutable');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');

const SCROLL_BUFFER = 10;

const {List} = Immutable;

// we should harden up the bellow flow types to make them more strict
type CustomRenderConfig = Object;
type DraftRenderConfig = Object;
type BlockRenderFn = (block: BlockNodeRecord) => ?Object;
type BlockStyleFn = (block: BlockNodeRecord) => string;

type Props = {
  block: BlockNodeRecord,
  blockProps?: Object,
  blockRenderMap: DraftBlockRenderMap,
  blockRendererFn: BlockRenderFn,
  blockStyleFn: BlockStyleFn,
  contentState: ContentState,
  customStyleFn: (style: DraftInlineStyle, block: BlockNodeRecord) => ?Object,
  customStyleMap: Object,
  decorator: ?DraftDecoratorType,
  direction: BidiDirection,
  editorKey: string,
  editorState: EditorState,
  forceSelection: boolean,
  selection: SelectionState,
  startIndent?: boolean,
  tree: List<any>,
  ...
};

/**
 * Return whether a block overlaps with either edge of the `SelectionState`.
 */
const isBlockOnSelectionEdge = (
  selection: SelectionState,
  key: string,
): boolean => {
  return selection.getAnchorKey() === key || selection.getFocusKey() === key;
};

/**
 * We will use this helper to identify blocks that need to be wrapped but have siblings that
 * also share the same wrapper element, this way we can do the wrapping once the last sibling
 * is added.
 */
const shouldNotAddWrapperElement = (
  block: BlockNodeRecord,
  contentState: ContentState,
): boolean => {
  const nextSiblingKey = block.getNextSiblingKey();

  return nextSiblingKey
    ? contentState.getBlockForKey(nextSiblingKey).getType() === block.getType()
    : false;
};

const applyWrapperElementToSiblings = (
  wrapperTemplate: *,
  Element: string,
  nodes: Array<React.Node>,
): Array<React.Node> => {
  const wrappedSiblings = [];

  // we check back until we find a sibbling that does not have same wrapper
  for (const sibling: any of nodes.reverse()) {
    if (sibling.type !== Element) {
      break;
    }
    wrappedSiblings.push(sibling);
  }

  // we now should remove from acc the wrappedSiblings and add them back under same wrap
  nodes.splice(nodes.indexOf(wrappedSiblings[0]), wrappedSiblings.length + 1);

  const childrenIs = wrappedSiblings.reverse();

  const key = childrenIs[0].key;

  nodes.push(
    React.cloneElement(
      wrapperTemplate,
      {
        key: `${key}-wrap`,
        'data-offset-key': DraftOffsetKey.encode(key, 0, 0),
      },
      childrenIs,
    ),
  );

  return nodes;
};

const getDraftRenderConfig = (
  block: BlockNodeRecord,
  blockRenderMap: DraftBlockRenderMap,
): DraftRenderConfig => {
  const configForType =
    blockRenderMap.get(block.getType()) || blockRenderMap.get('unstyled');

  const wrapperTemplate = configForType.wrapper;
  const Element =
    configForType.element || blockRenderMap.get('unstyled').element;

  return {
    Element,
    wrapperTemplate,
  };
};

const getCustomRenderConfig = (
  block: BlockNodeRecord,
  blockRendererFn: BlockRenderFn,
): CustomRenderConfig => {
  const customRenderer = blockRendererFn(block);

  if (!customRenderer) {
    return {};
  }

  const {
    component: CustomComponent,
    props: customProps,
    editable: customEditable,
  } = customRenderer;

  return {
    CustomComponent,
    customProps,
    customEditable,
  };
};

const getElementPropsConfig = (
  block: BlockNodeRecord,
  editorKey: string,
  offsetKey: string,
  blockStyleFn: BlockStyleFn,
  customConfig: *,
): Object => {
  let elementProps: Object = {
    'data-block': true,
    'data-editor': editorKey,
    'data-offset-key': offsetKey,
    key: block.getKey(),
  };
  const customClass = blockStyleFn(block);

  if (customClass) {
    elementProps.className = customClass;
  }

  if (customConfig.customEditable !== undefined) {
    elementProps = {
      ...elementProps,
      contentEditable: customConfig.customEditable,
      suppressContentEditableWarning: true,
    };
  }

  return elementProps;
};

class DraftEditorBlockNode extends React.Component<Props> {
  shouldComponentUpdate(nextProps: Props): boolean {
    const {block, direction, tree} = this.props;
    const isContainerNode = !block.getChildKeys().isEmpty();
    const blockHasChanged =
      block !== nextProps.block ||
      tree !== nextProps.tree ||
      direction !== nextProps.direction ||
      (isBlockOnSelectionEdge(nextProps.selection, nextProps.block.getKey()) &&
        nextProps.forceSelection);

    // if we have children at this stage we always re-render container nodes
    // else if its a root node we avoid re-rendering by checking for block updates
    return isContainerNode || blockHasChanged;
  }

  /**
   * When a block is mounted and overlaps the selection state, we need to make
   * sure that the cursor is visible to match native behavior. This may not
   * be the case if the user has pressed `RETURN` or pasted some content, since
   * programatically creating these new blocks and setting the DOM selection
   * will miss out on the browser natively scrolling to that position.
   *
   * To replicate native behavior, if the block overlaps the selection state
   * on mount, force the scroll position. Check the scroll state of the scroll
   * parent, and adjust it to align the entire block to the bottom of the
   * scroll parent.
   */
  componentDidMount(): void {
    const selection = this.props.selection;
    const endKey = selection.getEndKey();
    if (!selection.getHasFocus() || endKey !== this.props.block.getKey()) {
      return;
    }

    const blockNode = ReactDOM.findDOMNode(this);
    const scrollParent = Style.getScrollParent(blockNode);
    const scrollPosition = getScrollPosition(scrollParent);
    let scrollDelta;

    if (scrollParent === window) {
      const nodePosition = getElementPosition(blockNode);
      const nodeBottom = nodePosition.y + nodePosition.height;
      const viewportHeight = getViewportDimensions().height;
      scrollDelta = nodeBottom - viewportHeight;
      if (scrollDelta > 0) {
        window.scrollTo(
          scrollPosition.x,
          scrollPosition.y + scrollDelta + SCROLL_BUFFER,
        );
      }
    } else {
      invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
      const htmlBlockNode: HTMLElement = (blockNode: any);
      const blockBottom = htmlBlockNode.offsetHeight + htmlBlockNode.offsetTop;
      const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
      scrollDelta = blockBottom - scrollBottom;
      if (scrollDelta > 0) {
        Scroll.setTop(
          scrollParent,
          Scroll.getTop(scrollParent) + scrollDelta + SCROLL_BUFFER,
        );
      }
    }
  }

  render(): React.Node {
    const {
      block,
      blockRenderMap,
      blockRendererFn,
      blockStyleFn,
      contentState,
      decorator,
      editorKey,
      editorState,
      customStyleFn,
      customStyleMap,
      direction,
      forceSelection,
      selection,
      tree,
    } = this.props;

    let children = null;

    if (block.children.size) {
      children = block.children.reduce((acc, key) => {
        const offsetKey = DraftOffsetKey.encode(key, 0, 0);
        const child = contentState.getBlockForKey(key);
        const customConfig = getCustomRenderConfig(child, blockRendererFn);
        const Component = customConfig.CustomComponent || DraftEditorBlockNode;
        const {Element, wrapperTemplate} = getDraftRenderConfig(
          child,
          blockRenderMap,
        );
        const elementProps = getElementPropsConfig(
          child,
          editorKey,
          offsetKey,
          blockStyleFn,
          customConfig,
        );
        const childProps = {
          ...this.props,
          tree: editorState.getBlockTree(key),
          blockProps: customConfig.customProps,
          offsetKey,
          block: child,
        };

        acc.push(
          React.createElement(
            Element,
            elementProps,
            <Component {...childProps} />,
          ),
        );

        if (
          !wrapperTemplate ||
          shouldNotAddWrapperElement(child, contentState)
        ) {
          return acc;
        }

        // if we are here it means we are the last block
        // that has a wrapperTemplate so we should wrap itself
        // and all other previous siblings that share the same wrapper
        applyWrapperElementToSiblings(wrapperTemplate, Element, acc);

        return acc;
      }, []);
    }

    const blockKey = block.getKey();
    const offsetKey = DraftOffsetKey.encode(blockKey, 0, 0);

    const customConfig = getCustomRenderConfig(block, blockRendererFn);
    const Component = customConfig.CustomComponent;
    const blockNode =
      Component != null ? (
        <Component
          {...this.props}
          tree={editorState.getBlockTree(blockKey)}
          blockProps={customConfig.customProps}
          offsetKey={offsetKey}
          block={block}
        />
      ) : (
        <DraftEditorNode
          block={block}
          children={children}
          contentState={contentState}
          customStyleFn={customStyleFn}
          customStyleMap={customStyleMap}
          decorator={decorator}
          direction={direction}
          forceSelection={forceSelection}
          hasSelection={isBlockOnSelectionEdge(selection, blockKey)}
          selection={selection}
          tree={tree}
        />
      );

    if (block.getParentKey()) {
      return blockNode;
    }

    const {Element} = getDraftRenderConfig(block, blockRenderMap);
    const elementProps = getElementPropsConfig(
      block,
      editorKey,
      offsetKey,
      blockStyleFn,
      customConfig,
    );

    // root block nodes needs to be wrapped
    return React.createElement(Element, elementProps, blockNode);
  }
}

module.exports = DraftEditorBlockNode;
<commit_msg>Remove findDOMNode from DraftEditorBlockNode<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 * @emails oncall+draft_js
 *
 * This file is a fork of DraftEditorBlock.react.js and DraftEditorContents.react.js
 *
 * This is unstable and not part of the public API and should not be used by
 * production systems. This file may be update/removed without notice.
 */

'use strict';

import type {BlockNodeRecord} from 'BlockNodeRecord';
import type ContentState from 'ContentState';
import type {DraftBlockRenderMap} from 'DraftBlockRenderMap';
import type {DraftDecoratorType} from 'DraftDecoratorType';
import type {DraftInlineStyle} from 'DraftInlineStyle';
import type EditorState from 'EditorState';
import type SelectionState from 'SelectionState';
import type {BidiDirection} from 'UnicodeBidiDirection';

const DraftEditorNode = require('DraftEditorNode.react');
const DraftOffsetKey = require('DraftOffsetKey');
const React = require('React');
const Scroll = require('Scroll');
const Style = require('Style');

const getElementPosition = require('getElementPosition');
const getScrollPosition = require('getScrollPosition');
const getViewportDimensions = require('getViewportDimensions');
const Immutable = require('immutable');
const invariant = require('invariant');
const isHTMLElement = require('isHTMLElement');

const SCROLL_BUFFER = 10;

const {List} = Immutable;

// we should harden up the bellow flow types to make them more strict
type CustomRenderConfig = Object;
type DraftRenderConfig = Object;
type BlockRenderFn = (block: BlockNodeRecord) => ?Object;
type BlockStyleFn = (block: BlockNodeRecord) => string;

type Props = {
  block: BlockNodeRecord,
  blockProps?: Object,
  blockRenderMap: DraftBlockRenderMap,
  blockRendererFn: BlockRenderFn,
  blockStyleFn: BlockStyleFn,
  contentState: ContentState,
  customStyleFn: (style: DraftInlineStyle, block: BlockNodeRecord) => ?Object,
  customStyleMap: Object,
  decorator: ?DraftDecoratorType,
  direction: BidiDirection,
  editorKey: string,
  editorState: EditorState,
  forceSelection: boolean,
  selection: SelectionState,
  startIndent?: boolean,
  tree: List<any>,
  ...
};

/**
 * Return whether a block overlaps with either edge of the `SelectionState`.
 */
const isBlockOnSelectionEdge = (
  selection: SelectionState,
  key: string,
): boolean => {
  return selection.getAnchorKey() === key || selection.getFocusKey() === key;
};

/**
 * We will use this helper to identify blocks that need to be wrapped but have siblings that
 * also share the same wrapper element, this way we can do the wrapping once the last sibling
 * is added.
 */
const shouldNotAddWrapperElement = (
  block: BlockNodeRecord,
  contentState: ContentState,
): boolean => {
  const nextSiblingKey = block.getNextSiblingKey();

  return nextSiblingKey
    ? contentState.getBlockForKey(nextSiblingKey).getType() === block.getType()
    : false;
};

const applyWrapperElementToSiblings = (
  wrapperTemplate: *,
  Element: string,
  nodes: Array<React.Node>,
): Array<React.Node> => {
  const wrappedSiblings = [];

  // we check back until we find a sibbling that does not have same wrapper
  for (const sibling: any of nodes.reverse()) {
    if (sibling.type !== Element) {
      break;
    }
    wrappedSiblings.push(sibling);
  }

  // we now should remove from acc the wrappedSiblings and add them back under same wrap
  nodes.splice(nodes.indexOf(wrappedSiblings[0]), wrappedSiblings.length + 1);

  const childrenIs = wrappedSiblings.reverse();

  const key = childrenIs[0].key;

  nodes.push(
    React.cloneElement(
      wrapperTemplate,
      {
        key: `${key}-wrap`,
        'data-offset-key': DraftOffsetKey.encode(key, 0, 0),
      },
      childrenIs,
    ),
  );

  return nodes;
};

const getDraftRenderConfig = (
  block: BlockNodeRecord,
  blockRenderMap: DraftBlockRenderMap,
): DraftRenderConfig => {
  const configForType =
    blockRenderMap.get(block.getType()) || blockRenderMap.get('unstyled');

  const wrapperTemplate = configForType.wrapper;
  const Element =
    configForType.element || blockRenderMap.get('unstyled').element;

  return {
    Element,
    wrapperTemplate,
  };
};

const getCustomRenderConfig = (
  block: BlockNodeRecord,
  blockRendererFn: BlockRenderFn,
): CustomRenderConfig => {
  const customRenderer = blockRendererFn(block);

  if (!customRenderer) {
    return {};
  }

  const {
    component: CustomComponent,
    props: customProps,
    editable: customEditable,
  } = customRenderer;

  return {
    CustomComponent,
    customProps,
    customEditable,
  };
};

const getElementPropsConfig = (
  block: BlockNodeRecord,
  editorKey: string,
  offsetKey: string,
  blockStyleFn: BlockStyleFn,
  customConfig: *,
  ref: null | {|current: null | Element|},
): Object => {
  let elementProps: Object = {
    'data-block': true,
    'data-editor': editorKey,
    'data-offset-key': offsetKey,
    key: block.getKey(),
    ref,
  };
  const customClass = blockStyleFn(block);

  if (customClass) {
    elementProps.className = customClass;
  }

  if (customConfig.customEditable !== undefined) {
    elementProps = {
      ...elementProps,
      contentEditable: customConfig.customEditable,
      suppressContentEditableWarning: true,
    };
  }

  return elementProps;
};

class DraftEditorBlockNode extends React.Component<Props> {
  wrapperRef: {|current: null | Element|} = React.createRef<Element>();

  shouldComponentUpdate(nextProps: Props): boolean {
    const {block, direction, tree} = this.props;
    const isContainerNode = !block.getChildKeys().isEmpty();
    const blockHasChanged =
      block !== nextProps.block ||
      tree !== nextProps.tree ||
      direction !== nextProps.direction ||
      (isBlockOnSelectionEdge(nextProps.selection, nextProps.block.getKey()) &&
        nextProps.forceSelection);

    // if we have children at this stage we always re-render container nodes
    // else if its a root node we avoid re-rendering by checking for block updates
    return isContainerNode || blockHasChanged;
  }

  /**
   * When a block is mounted and overlaps the selection state, we need to make
   * sure that the cursor is visible to match native behavior. This may not
   * be the case if the user has pressed `RETURN` or pasted some content, since
   * programatically creating these new blocks and setting the DOM selection
   * will miss out on the browser natively scrolling to that position.
   *
   * To replicate native behavior, if the block overlaps the selection state
   * on mount, force the scroll position. Check the scroll state of the scroll
   * parent, and adjust it to align the entire block to the bottom of the
   * scroll parent.
   */
  componentDidMount(): void {
    const selection = this.props.selection;
    const endKey = selection.getEndKey();
    if (!selection.getHasFocus() || endKey !== this.props.block.getKey()) {
      return;
    }

    const blockNode = this.wrapperRef.current;
    if (!blockNode) {
      // This Block Node was rendered without a wrapper element.
      return;
    }
    const scrollParent = Style.getScrollParent(blockNode);
    const scrollPosition = getScrollPosition(scrollParent);
    let scrollDelta;

    if (scrollParent === window) {
      const nodePosition = getElementPosition(blockNode);
      const nodeBottom = nodePosition.y + nodePosition.height;
      const viewportHeight = getViewportDimensions().height;
      scrollDelta = nodeBottom - viewportHeight;
      if (scrollDelta > 0) {
        window.scrollTo(
          scrollPosition.x,
          scrollPosition.y + scrollDelta + SCROLL_BUFFER,
        );
      }
    } else {
      invariant(isHTMLElement(blockNode), 'blockNode is not an HTMLElement');
      const htmlBlockNode: HTMLElement = (blockNode: any);
      const blockBottom = htmlBlockNode.offsetHeight + htmlBlockNode.offsetTop;
      const scrollBottom = scrollParent.offsetHeight + scrollPosition.y;
      scrollDelta = blockBottom - scrollBottom;
      if (scrollDelta > 0) {
        Scroll.setTop(
          scrollParent,
          Scroll.getTop(scrollParent) + scrollDelta + SCROLL_BUFFER,
        );
      }
    }
  }

  render(): React.Node {
    const {
      block,
      blockRenderMap,
      blockRendererFn,
      blockStyleFn,
      contentState,
      decorator,
      editorKey,
      editorState,
      customStyleFn,
      customStyleMap,
      direction,
      forceSelection,
      selection,
      tree,
    } = this.props;

    let children = null;

    if (block.children.size) {
      children = block.children.reduce((acc, key) => {
        const offsetKey = DraftOffsetKey.encode(key, 0, 0);
        const child = contentState.getBlockForKey(key);
        const customConfig = getCustomRenderConfig(child, blockRendererFn);
        const Component = customConfig.CustomComponent || DraftEditorBlockNode;
        const {Element, wrapperTemplate} = getDraftRenderConfig(
          child,
          blockRenderMap,
        );
        const elementProps = getElementPropsConfig(
          child,
          editorKey,
          offsetKey,
          blockStyleFn,
          customConfig,
          null,
        );
        const childProps = {
          ...this.props,
          tree: editorState.getBlockTree(key),
          blockProps: customConfig.customProps,
          offsetKey,
          block: child,
        };

        acc.push(
          React.createElement(
            Element,
            elementProps,
            <Component {...childProps} />,
          ),
        );

        if (
          !wrapperTemplate ||
          shouldNotAddWrapperElement(child, contentState)
        ) {
          return acc;
        }

        // if we are here it means we are the last block
        // that has a wrapperTemplate so we should wrap itself
        // and all other previous siblings that share the same wrapper
        applyWrapperElementToSiblings(wrapperTemplate, Element, acc);

        return acc;
      }, []);
    }

    const blockKey = block.getKey();
    const offsetKey = DraftOffsetKey.encode(blockKey, 0, 0);

    const customConfig = getCustomRenderConfig(block, blockRendererFn);
    const Component = customConfig.CustomComponent;
    const blockNode =
      Component != null ? (
        <Component
          {...this.props}
          tree={editorState.getBlockTree(blockKey)}
          blockProps={customConfig.customProps}
          offsetKey={offsetKey}
          block={block}
        />
      ) : (
        <DraftEditorNode
          block={block}
          children={children}
          contentState={contentState}
          customStyleFn={customStyleFn}
          customStyleMap={customStyleMap}
          decorator={decorator}
          direction={direction}
          forceSelection={forceSelection}
          hasSelection={isBlockOnSelectionEdge(selection, blockKey)}
          selection={selection}
          tree={tree}
        />
      );

    if (block.getParentKey()) {
      return blockNode;
    }

    const {Element} = getDraftRenderConfig(block, blockRenderMap);
    const elementProps = getElementPropsConfig(
      block,
      editorKey,
      offsetKey,
      blockStyleFn,
      customConfig,
      this.wrapperRef,
    );

    // root block nodes needs to be wrapped
    return React.createElement(Element, elementProps, blockNode);
  }
}

module.exports = DraftEditorBlockNode;
<|endoftext|>