<commit_before>/*
 @author Zakai Hamilton
 @component AppEnvision
 */

screens.app.envision = function AppEnvision(me) {
    me.launch = function (args) {
        if (me.core.property.get(me.singleton, "ui.node.parent")) {
            me.core.property.set(me.singleton, "widget.window.show", true);
            return me.singleton;
        }
        me.singleton = me.ui.element(__json__, "workspace", "self");
    };
    me.initOptions = async function (object) {
        var window = me.widget.window(object);
        me.ui.options.load(me, window, {
            editMode: false,
            autoRefresh: true,
            outputMode: false
        });
        me.ui.options.toggleSet(me, null, {
            "editMode": me.updateMode,
            "formatMode": me.updateMode,
            "autoRefresh": me.updateMode,
            "outputMode": me.updateMode
        });
        me.core.property.set(window, "app", me);
        me.updateMode(window);
        me.refresh(window);
    };
    me.importData = function (object, text) {
        var window = me.widget.window(object);
        me.core.property.set(window.var.source, "text", text);
        me.core.property.set(window.var.content, "ui.basic.html", me.convert(object, text));
        me.core.property.set(window.var.source, "ui.basic.save");
    };
    me.updateMode = function (object) {
        var window = me.widget.window(object);
        me.core.property.set(window.var.content, "ui.style.opacity", window.options.editMode ? "0" : "");
        me.core.property.set([window.var.editorContainer], "ui.basic.show", window.options.editMode);
        me.core.property.set(window.var.source, "ui.basic.show", window.options.editMode && !window.options.formatMode);
        me.core.property.set(window.var.format, "ui.basic.show", window.options.editMode && window.options.formatMode);
        if (!window.options.editMode) {
            me.refresh(object);
        }
        clearInterval(window.intervalHandle);
        if (!window.options.editMode && window.options.autoRefresh) {
            window.intervalHandle = setInterval(() => {
                me.refresh(object);
            }, 1000);
        }
    };
    me.refresh = function (object) {
        var window = me.widget.window(object);
        var text = me.core.property.get(window.var.source, "text");
        text = me.convert(object, text);
        if (window.options.outputMode) {
            text = me.formatXml(text);
        }
        me.core.property.set(window.var.content, window.options.outputMode ? "ui.basic.text" : "ui.basic.html", text);
    };
    me.clear = function (object) {
        var window = me.widget.window(object);
        me.core.property.set(window.var.source, "text", "");
        me.core.property.set(window.var.source, "ui.basic.save");
        me.core.property.set(window.var.content, "ui.basic.html", "");
        window.searchText = "";
    };
    me.exportText = function (object, target) {
        var window = me.widget.window(object);
        var text = me.core.property.get(window.var.source, "text");
        me.core.property.set(target, "importData", text);
    };
    me.serialize = function (node) {
        var serializer = new XMLSerializer();
        return serializer.serializeToString(node);
    };
    me.formatXml = function (xml) {
        var formatted = '';
        var reg = /(>)(<)(\/*)/g;
        xml = xml.replace(reg, '$1\r\n$2$3');
        var pad = 0;
        var tokens = xml.split('\r\n');
        for (var index = 0; index < tokens.length; index++) {
            node = tokens[index];
            var indent = 0;
            if (node.match(/.+<\/\w[^>]*>$/)) {
                indent = 0;
            } else if (node.match(/^<\/\w/)) {
                if (pad != 0) {
                    pad -= 1;
                }
            } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
                indent = 1;
            } else {
                indent = 0;
            }

            var padding = '';
            for (var i = 0; i < pad; i++) {
                padding += '  ';
            }

            formatted += padding + node + '\r\n';
            pad += indent;
        }
        return formatted;
    };
    me.beautifyName = function (object, name) {
        name = name.charAt(0).toUpperCase() + name.slice(1);
        name = name.replace(/_([a-z])/g, function (m, w) {
            return " " + w.toUpperCase();
        });
        return name;
    };
    me.processArrays = function (object, text, root) {
        text = text.replace(/\[[^\[\]]*\]/g, function (match) {
            console.log("match: " + match);
            match = match.substring(1, match.length - 1);
            var [path, content] = match.split("=");
            var item = me.core.json.traverse(root, path);
            var result = "";
            if (item.found && content && item.value) {
                for (var element of item.value) {
                    result += me.processVars(object, content, element);
                }
            }
            return result;
        });
        return text;
    };
    me.processVars = function (object, text, root) {
        text = text.replace(/{[^{}]*}/g, function (match) {
            console.log("match: " + match);
            var path = match.substring(1, match.length - 1);
            if (path.startsWith("@")) {
                path = path.substring(1);
                if (path === "date") {
                    return new Date().toString();
                }
                else {
                    var info = me.core.property.split(object, path);
                    var item = me.core.json.traverse(root, info.value);
                    if (item.found) {
                        return me.core.property.get(object, info.name, item.value);
                    }
                    return "";
                }
            }
            var item = me.core.json.traverse(root, path);
            if (item.found) {
                return item.value;
            }
            return "";
        });
        return text;
    };
    me.convert = function (object, text) {
        var window = me.widget.window(object);
        var format = me.core.property.get(window.var.format, "text");
        if (!format) {
            format = "

";
        }
        if (!text) {
            text = "{}";
        }
        me.core.property.set(window.var.format, "ui.basic.save");
        try {
            try {
                var source = JSON.parse(text);
            }
            catch (err) {
                throw "Cannot parse text: " + err;
            }
            try {
                format = me.processArrays(object, format, source);
            }
            catch(err) {
                throw "Cannot process arrays: " + err;
            }
            try {
                format = me.processVars(object, format, source);
            }
            catch(err) {
                throw "Cannot process vars: " + err;
            }
            parser = new DOMParser();
            try {
                format = parser.parseFromString(format, "text/xml");
            }
            catch(err) {
                throw "Cannot parse format: " + err;
            }
            try {
                format = me.serialize(format);
            }
            catch(err) {
                throw "Cannot serialise format: " + err;
            }
        }
        catch (err) {
            return `

Error

                            Error parsing content: ${err}
                            Stack: ${new Error().stack}

`
        }
        return `${format}`;
        return text;
    };
};
<commit_msg>bug fixes<commit_after>/*
 @author Zakai Hamilton
 @component AppEnvision
 */

screens.app.envision = function AppEnvision(me) {
    me.launch = function (args) {
        if (me.core.property.get(me.singleton, "ui.node.parent")) {
            me.core.property.set(me.singleton, "widget.window.show", true);
            return me.singleton;
        }
        me.singleton = me.ui.element(__json__, "workspace", "self");
    };
    me.initOptions = async function (object) {
        var window = me.widget.window(object);
        me.ui.options.load(me, window, {
            editMode: false,
            autoRefresh: true,
            outputMode: false
        });
        me.ui.options.toggleSet(me, null, {
            "editMode": me.updateMode,
            "formatMode": me.updateMode,
            "autoRefresh": me.updateMode,
            "outputMode": me.updateMode
        });
        me.core.property.set(window, "app", me);
        me.updateMode(window);
        me.refresh(window);
    };
    me.importData = function (object, text) {
        var window = me.widget.window(object);
        me.core.property.set(window.var.source, "text", text);
        me.core.property.set(window.var.content, "ui.basic.html", me.convert(object, text));
        me.core.property.set(window.var.source, "ui.basic.save");
    };
    me.updateMode = function (object) {
        var window = me.widget.window(object);
        me.core.property.set(window.var.content, "ui.style.opacity", window.options.editMode ? "0" : "");
        me.core.property.set([window.var.editorContainer], "ui.basic.show", window.options.editMode);
        me.core.property.set(window.var.source, "ui.basic.show", window.options.editMode && !window.options.formatMode);
        me.core.property.set(window.var.format, "ui.basic.show", window.options.editMode && window.options.formatMode);
        if (!window.options.editMode) {
            me.refresh(object);
        }
        clearInterval(window.intervalHandle);
        if (!window.options.editMode && window.options.autoRefresh) {
            window.intervalHandle = setInterval(() => {
                me.refresh(object);
            }, 1000);
        }
    };
    me.refresh = function (object) {
        var window = me.widget.window(object);
        var text = me.core.property.get(window.var.source, "text");
        text = me.convert(object, text);
        if (window.options.outputMode) {
            text = me.formatXml(text);
        }
        me.core.property.set(window.var.content, window.options.outputMode ? "ui.basic.text" : "ui.basic.html", text);
    };
    me.clear = function (object) {
        var window = me.widget.window(object);
        me.core.property.set(window.var.source, "text", "");
        me.core.property.set(window.var.source, "ui.basic.save");
        me.core.property.set(window.var.content, "ui.basic.html", "");
        window.searchText = "";
    };
    me.exportText = function (object, target) {
        var window = me.widget.window(object);
        var text = me.core.property.get(window.var.source, "text");
        me.core.property.set(target, "importData", text);
    };
    me.serialize = function (node) {
        var serializer = new XMLSerializer();
        return serializer.serializeToString(node);
    };
    me.formatXml = function (xml) {
        var formatted = '';
        var reg = /(>)(<)(\/*)/g;
        xml = xml.replace(reg, '$1\r\n$2$3');
        var pad = 0;
        var tokens = xml.split('\r\n');
        for (var index = 0; index < tokens.length; index++) {
            node = tokens[index];
            var indent = 0;
            if (node.match(/.+<\/\w[^>]*>$/)) {
                indent = 0;
            } else if (node.match(/^<\/\w/)) {
                if (pad != 0) {
                    pad -= 1;
                }
            } else if (node.match(/^<\w[^>]*[^\/]>.*$/)) {
                indent = 1;
            } else {
                indent = 0;
            }

            var padding = '';
            for (var i = 0; i < pad; i++) {
                padding += '  ';
            }

            formatted += padding + node + '\r\n';
            pad += indent;
        }
        return formatted;
    };
    me.beautifyName = function (object, name) {
        name = name.charAt(0).toUpperCase() + name.slice(1);
        name = name.replace(/_([a-z])/g, function (m, w) {
            return " " + w.toUpperCase();
        });
        return name;
    };
    me.processArrays = function (object, text, root) {
        text = text.replace(/\[[^\[\]]*\]/g, function (match) {
            console.log("match: " + match);
            match = match.substring(1, match.length - 1);
            var [path, content] = match.split("=");
            var item = me.core.json.traverse(root, path);
            var result = "";
            if (item.found && content && item.value) {
                for (var element of item.value) {
                    result += me.processVars(object, content, element);
                }
            }
            return result;
        });
        return text;
    };
    me.processVars = function (object, text, root) {
        text = text.replace(/{{[^{}]*}}/g, function (match) {
            console.log("match: " + match);
            var path = match.substring(2, match.length - 2);
            if (path.startsWith("@")) {
                path = path.substring(1);
                if (path === "date") {
                    return new Date().toString();
                }
                else {
                    var info = me.core.property.split(object, path);
                    var item = me.core.json.traverse(root, info.value);
                    if (item.found) {
                        return me.core.property.get(object, info.name, item.value);
                    }
                    return "";
                }
            }
            var item = me.core.json.traverse(root, path);
            if (item.found) {
                return item.value;
            }
            return "";
        });
        return text;
    };
    me.convert = function (object, text) {
        var window = me.widget.window(object);
        var format = me.core.property.get(window.var.format, "text");
        if (!format) {
            format = "

";
        }
        if (!text) {
            text = "{}";
        }
        me.core.property.set(window.var.format, "ui.basic.save");
        try {
            try {
                var source = JSON.parse(text);
            }
            catch (err) {
                throw "Cannot parse text: " + err;
            }
            try {
                format = me.processArrays(object, format, source);
            }
            catch(err) {
                throw "Cannot process arrays: " + err;
            }
            try {
                format = me.processVars(object, format, source);
            }
            catch(err) {
                throw "Cannot process vars: " + err;
            }
            parser = new DOMParser();
            try {
                format = parser.parseFromString(format, "text/xml");
            }
            catch(err) {
                throw "Cannot parse format: " + err;
            }
            try {
                format = me.serialize(format);
            }
            catch(err) {
                throw "Cannot serialise format: " + err;
            }
        }
        catch (err) {
            return `

Error

                            Error parsing content: ${err}
                            Stack: ${new Error().stack}

`
        }
        return `${format}`;
        return text;
    };
};
<|endoftext|>


<commit_before>// @flow

import { getElementSizeLocation, getDocumentScroll } from './dom';

export type Metadata = {
  sizeLocation?: {
    width: number,
    height: number,
    top: number,
    left: number,
    raw: {
      scrollTop: number,
      scrollLeft: number,
    },
  },
};

type Options = {
  useOffsetsize?: boolean,
};

export default function calculateFromSizeLocation (
  node: HTMLElement,
  metadata: Metadata,
  { useOffsetSize }: Options = {},
) {
  const initialSizeLocation = metadata.sizeLocation;
  if (!initialSizeLocation) {
    return getElementSizeLocation(node, { useOffsetSize });
  }

  const { scrollTop, scrollLeft } = getDocumentScroll();
  const scrollTopDiff = (scrollTop - initialSizeLocation.raw.scrollTop);
  const scrollLeftDiff = (scrollLeft - initialSizeLocation.raw.scrollLeft);

  return {
    ...initialSizeLocation,
    top: initialSizeLocation.top + scrollTopDiff,
    left: initialSizeLocation.left + scrollLeftDiff,
  };
}
<commit_msg>fix build<commit_after>// @flow

import { getElementSizeLocation, getDocumentScroll } from './dom';

export type Metadata = {
  sizeLocation?: {
    width: number,
    height: number,
    top: number,
    left: number,
    raw: {
      scrollTop: number,
      scrollLeft: number,
    },
  },
};

type Options = {
  useOffsetSize?: boolean,
};

export default function calculateFromSizeLocation (
  node: HTMLElement,
  metadata: Metadata,
  { useOffsetSize }: Options = {},
) {
  const initialSizeLocation = metadata.sizeLocation;
  if (!initialSizeLocation) {
    return getElementSizeLocation(node, { useOffsetSize });
  }

  const { scrollTop, scrollLeft } = getDocumentScroll();
  const scrollTopDiff = (scrollTop - initialSizeLocation.raw.scrollTop);
  const scrollLeftDiff = (scrollLeft - initialSizeLocation.raw.scrollLeft);

  return {
    ...initialSizeLocation,
    top: initialSizeLocation.top + scrollTopDiff,
    left: initialSizeLocation.left + scrollLeftDiff,
  };
}
<|endoftext|>


<commit_before>import { createApp } from './main'

// ENV Variables
require('dotenv').config()

global.fetch = require('node-fetch')

const isDev = process.env.NODE_ENV !== 'production'

// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
  /* eslint-disable-next-line no-async-promise-executor */
  return new Promise(async (resolve, reject) => {
    const s = isDev && Date.now()
    const {
      app,
      router,
      store,
    } = await createApp(undefined, context)

    // set router's location
    router.push(context.url)

    // wait until router has resolved possible async hooks
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      const errors = []
      // Call fetchData hooks on components matched by the route.
      // A preFetch hook dispatches a store action and returns a Promise,
      // which is resolved when the action is complete and store state has been
      // updated.
      Promise.all(
        matchedComponents.map(async c => {
          try {
            await c.asyncData({
              store,
              route: router.currentRoute,
            })
          } catch (e) {
            errors.push(`${c.name} - ${c.__file}`)

            return Promise.resolve(e)
          }
        })
      ).then(() => {
        if (isDev || errors.length > 0) {
          console.log(errors)
          console.log(`data pre-fetch: ${Date.now() - s}ms`)
        }
        // After all preFetch hooks are resolved, our store is now
        // filled with the state needed to render the app.
        // Expose the state on the render context, and let the request handler
        // inline the state in the HTML response. This allows the client-side
        // store to pick-up the server-side state without having to duplicate
        // the initial data fetching on the client.
        context.state = store.state

        resolve(app)
      })
    }, reject)
  })
}
<commit_msg>docs: remove knife<commit_after>import { createApp } from './main'

// ENV Variables
require('dotenv').config()

global.fetch = require('node-fetch')

const isDev = process.env.NODE_ENV !== 'production'

// This exported function will be called by `bundleRenderer`.
// This is where we perform data-prefetching to determine the
// state of our application before actually rendering it.
// Since data fetching is async, this function is expected to
// return a Promise that resolves to the app instance.
export default context => {
  /* eslint-disable-next-line no-async-promise-executor */
  return new Promise(async (resolve, reject) => {
    const s = isDev && Date.now()
    const {
      app,
      router,
      store,
    } = await createApp(undefined, context)

    // set router's location
    router.push(context.url)

    // wait until router has resolved possible async hooks
    router.onReady(() => {
      const matchedComponents = router.getMatchedComponents()
      // Call fetchData hooks on components matched by the route.
      // A preFetch hook dispatches a store action and returns a Promise,
      // which is resolved when the action is complete and store state has been
      // updated.
      Promise.all(
        matchedComponents.map(async c => {
          try {
            await c.asyncData({
              store,
              route: router.currentRoute,
            })
          } catch (e) {
            return Promise.resolve(e)
          }
        })
      ).then(() => {
        isDev && console.log(`data pre-fetch: ${Date.now() - s}ms`)
        // After all preFetch hooks are resolved, our store is now
        // filled with the state needed to render the app.
        // Expose the state on the render context, and let the request handler
        // inline the state in the HTML response. This allows the client-side
        // store to pick-up the server-side state without having to duplicate
        // the initial data fetching on the client.
        context.state = store.state

        resolve(app)
      })
    }, reject)
  })
}
<|endoftext|>


<commit_before>/*
Winthrop Annotator Plugin
- Requirements: jQuery, annotator.js 2.0+, marginalia
- Create object 'winthrop' with methods to create a rende rextension
and editor extension for Winthrop Family on the Page Project fields
- conf objects should specify the following properties: name, label, and type (if list),
along with an autocompleteUrl, fieldType ('textarea', 'input' (default))
*/

var winthrop = {
  // Factory function for creating a render extension for all fields
  // Takes an array of configuration objects, in order of display
  makeRenderExtension: function(confs) {

    // Renders the field for display on side card
    function renderField(fieldValue, conf) {
      var div = null;
      // Just in case, to catch an actual 0 if this code
      // is ever re-used
      if (typeof fieldValue != 'undefined' && !$.isEmptyObject(fieldValue)) {
        // split arrays into a comma delimited string
        if ($.isArray(fieldValue)) {
          div = $('
').addClass('annotator-' + conf.name).html(function() {
            return '' + conf.label + '' + $.map(fieldValue, function(value) {
              return value
            }).join(', ');
          });
        } else {
          // otherwise return the field value
          div = $('
').addClass('annotator-' + conf.name).html(
            fieldValue
          ).prepend($('' + conf.label + ''));
        }
      }
      return div;
    }
    // rendered function that iterates over confs array
    return function(annotation, item) {
      var previousClass = '.text'
      for (i in confs) {
        // Invoke renderField with options as passed to function
        var div = renderField(annotation[confs[i].name], confs[i]);
        var divClass = '.annotator-' + confs[i].name
        item.find(divClass).remove();
        if (div != null) {
          div.insertAfter(item.find(previousClass));
          previousClass = divClass;
        }
      }

      var annotationText = item.find('.text').children();
      if (annotationText.text() == 'No comment') {
        annotationText.text('No annotation text');
      }

      return item;
    }
  },

  // Factory function for extension fieldNames
  // Takes an array of FieldConfig objects, in order of display
  makeEditorExtension: function(confs) {

    // Generic functionality for field setter
    function makeSetField(conf, input) {

      return function(field, annotation) {
        // set the annotation object with an empty string
        annotation[conf.name] = '';
        if (input.val() != '') {
          if (conf.type == 'list') {
            values = input.val().split(',');
            for (i in values) {
              // set an array of values, trimming any stray commas
              values[i] = values[i].trim().replace(/(^,)|(,$)/g, "");
            }
            annotation[conf.name] = values
          } else {
            annotation[conf.name] = input.val();
          }
          console.log(conf.name+": "+annotation[conf.name]);
        }
      }
    }

    // Generic functionality for updating a field
    function makeUpdateField(conf, input) {
      return function(field, annotation) {
        // Give it a class to mark it as that field based on name property
        $(field).addClass(conf.name + '-lookup');
        // If the property exists on the object and isn't a falsy value
        // use the type property to determine how to set the field values
        if (annotation[conf.name] || annotation[conf.name == 0]) {
          if (conf.type == 'list') {
            input.val(annotation[conf.name].join(', '));
          } else {
            input.val(annotation[conf.name]);
          }
        }
        // otherwise, set it to an empty string
        else {
          input.val('');
        }
      }
    }
    // The input element added to the Annotator.Editor wrapped in jQuery.
    // Cached to save having to recreate it everytime the editor is displayed.
    return function editorExtension(e) {
      var field = null;
      var input = null;

      for (var i in confs) {
        // loop through all the fields and add them to e
        var type = confs[i].size ? confs[i].size : 'input'
        field = e.addField({
          // This is properly *placeholder*, so setting is as such
          // using label as fallback
          label: confs[i].placeholder ? confs[i].placeholder : confs[i].label,
          type: type
        });
        input = $(field).find(type);
        var fields = e.fields
        // Add the load and submit functions now that
        // we have an input DOM object
        for (var j in fields) {
          if (fields[j].label == confs[i].label || fields[j].label == confs[i].placeholder) {
            fields[j].load = makeUpdateField(confs[i], input);
            fields[j].submit = makeSetField(confs[i], input);
          }
        }
        // Give a label field to the input too
        input.parent().prepend(''+confs[i].label+'');

        /*
        The following code does the following:
          1) Provides utility functions for parsing a list of tags into an array
          2) Binds an autocomplete for all of the tag lists and provides
            an alternate for the author field specifically
          3)
        */
        // Function to parse the most recent tag
        function parseRecent(str) {
          var list = str.split(',');
          var latest = list[list.length - 1]
          return latest.trim()
        }

        function makeSearchFunction(conf) {
        // Configure the ajax query using GET shorthand
          return function(request, response) {
            term = parseRecent(request.term);
            // autoCompleteUrl from conf function
            $.get(conf.autocompleteUrl, {
                q: term
              },
              function(data) {
                // convert response into format jquery-ui expects
                response($.map(data.results, function(value, key) {
                  return {
                    label: value.text,
                    value: value.text,
                    id: value.id
                  };
                }));
              });
          }
      }

      // Function that triggers on select to add an item to a list
      function SelectFunc(obj, ui) {
          var val = obj.val();
          // comma list style handling
            if (val.indexOf(',') == -1) {
              val = ui.item.value + ', ';
            } else {
              val = val.replace(/,[^,]+$/, "") + ", " + ui.item.value + ', ';
            }
        // Regardless, set the object
        obj.val(val);
      }

        // Actually configure and bind the autocomplete
        if (confs[i].autocompleteUrl) {
          if (confs[i].name != 'author') {
            input.autocomplete({
              source: makeSearchFunction(confs[i]),
              minLength: 0,
              select: function(event, ui) {
                SelectFunc($(this), ui);
                event.preventDefault();
              },
              focus: function(event, ui) {
                event.preventDefault();
              },
              open: function(event, ui) {
                // annotator purposely sets the editor at a very high z-index;
                // set autocomplete still higher so it isn't obscured by annotator buttons
                $('.ui-autocomplete')
                  .css('z-index', $('.annotator-editor').css('z-index') + 1);
              },
            });
          }
          // Special handling for author as the odd field out
          // since it's not a parsed list
          else {
            input.autocomplete({
              source: makeSearchFunction(confs[i]),
              minLength: 0,
              focus: function(event, ui) {
                event.preventDefault();
              },
              open: function(event, ui) {
                // annotator purposely sets the editor at a very high z-index;
                // set autocomplete still higher so it isn't obscured by annotator buttons
                $('.ui-autocomplete')
                  .css('z-index', $('.annotator-editor').css('z-index') + 1);
              },
            });
          }
          // Bind a element to make the autocomplete window pop up on focus
          input.bind('focus', function () {
            $(this).autocomplete("search");
          })
      }

      }
    }

    // makeEditorExtension wrapper end brance
  }
  // winthrop wrapper end brace
}

/*
Winthrop Annotator Override
-A simple jQuery shim to fix the text on the 'Comment' box to something more
sensible
*/
function fixComment(field) {
  // field - any valid jQuery selector for the annotation text field
  $(field).attr('placeholder', 'Annotation text...');
}
<commit_msg>Fix anchorLanguage/language setting problem for pull request<commit_after>/*
Winthrop Annotator Plugin
- Requirements: jQuery, annotator.js 2.0+, marginalia
- Create object 'winthrop' with methods to create a rende rextension
and editor extension for Winthrop Family on the Page Project fields
- conf objects should specify the following properties: name, label, and type (if list),
along with an autocompleteUrl, fieldType ('textarea', 'input' (default))
*/

var winthrop = {
  // Factory function for creating a render extension for all fields
  // Takes an array of configuration objects, in order of display
  makeRenderExtension: function(confs) {

    // Renders the field for display on side card
    function renderField(fieldValue, conf) {
      var div = null;
      // Just in case, to catch an actual 0 if this code
      // is ever re-used
      if (typeof fieldValue != 'undefined' && !$.isEmptyObject(fieldValue)) {
        // split arrays into a comma delimited string
        if ($.isArray(fieldValue)) {
          div = $('
').addClass('annotator-' + conf.name).html(function() {
            return '' + conf.label + '' + $.map(fieldValue, function(value) {
              return value
            }).join(', ');
          });
        } else {
          // otherwise return the field value
          div = $('
').addClass('annotator-' + conf.name).html(
            fieldValue
          ).prepend($('' + conf.label + ''));
        }
      }
      return div;
    }
    // rendered function that iterates over confs array
    return function(annotation, item) {
      var previousClass = '.text'
      for (i in confs) {
        // Invoke renderField with options as passed to function
        var div = renderField(annotation[confs[i].name], confs[i]);
        var divClass = '.annotator-' + confs[i].name
        item.find(divClass).remove();
        if (div != null) {
          div.insertAfter(item.find(previousClass));
          previousClass = divClass;
        }
      }

      var annotationText = item.find('.text').children();
      if (annotationText.text() == 'No comment') {
        annotationText.text('No annotation text');
      }

      return item;
    }
  },

  // Factory function for extension fieldNames
  // Takes an array of FieldConfig objects, in order of display
  makeEditorExtension: function(confs) {

    // Generic functionality for field setter
    function makeSetField(conf, input) {
      return function(field, annotation) {
        // set the annotation object with an empty string
        annotation[conf.name] = '';
        if (input.val() != '') {
          if (conf.type == 'list') {
            values = input.val().split(',');
            for (i in values) {
              // set an array of values, trimming any stray commas
              values[i] = values[i].trim().replace(/(^,)|(,$)/g, "");
            }
            annotation[conf.name] = values
          } else {
            annotation[conf.name] = input.val();
          }
        }
      }
    }

    // Generic functionality for updating a field
    function makeUpdateField(conf, input) {
      return function(field, annotation) {
        // Give it a class to mark it as that field based on name property
        $(field).addClass(conf.name + '-lookup');
        // If the property exists on the object and isn't a falsy value
        // use the type property to determine how to set the field values
        if (annotation[conf.name] || annotation[conf.name == 0]) {
          if (conf.type == 'list') {
            input.val(annotation[conf.name].join(', '));
          } else {
            input.val(annotation[conf.name]);
          }
        }
        // otherwise, set it to an empty string
        else {
          input.val('');
        }
      }
    }
    // The input element added to the Annotator.Editor wrapped in jQuery.
    // Cached to save having to recreate it everytime the editor is displayed.
    return function editorExtension(e) {
      var field = null;
      var input = null;

      for (var i in confs) {
        // loop through all the fields and add them to e
        var type = confs[i].size ? confs[i].size : 'input'
        field = e.addField({
          // This is properly *placeholder*, so setting is as such
          // using label as fallback
          label: confs[i].placeholder ? confs[i].placeholder : confs[i].label,
          type: type
        });
        input = $(field).find(type);
        var fields = e.fields
        // Add the load and submit functions now that
        // we have an input DOM object
        // field will always be last added to array
        fields[fields.length-1].load = makeUpdateField(confs[i], input);
        fields[fields.length-1].submit = makeSetField(confs[i], input);

        // Give a label field to the input too
        input.parent().prepend(''+confs[i].label+'');

        /*
        The following code does the following:
          1) Provides utility functions for parsing a list of tags into an array
          2) Binds an autocomplete for all of the tag lists and provides
            an alternate for the author field specifically
          3)
        */
        // Function to parse the most recent tag
        function parseRecent(str) {
          var list = str.split(',');
          var latest = list[list.length - 1]
          return latest.trim()
        }

        function makeSearchFunction(conf) {
        // Configure the ajax query using GET shorthand
          return function(request, response) {
            term = parseRecent(request.term);
            // autoCompleteUrl from conf function
            $.get(conf.autocompleteUrl, {
                q: term
              },
              function(data) {
                // convert response into format jquery-ui expects
                response($.map(data.results, function(value, key) {
                  return {
                    label: value.text,
                    value: value.text,
                    id: value.id
                  };
                }));
              });
          }
      }

      // Function that triggers on select to add an item to a list
      function SelectFunc(obj, ui) {
          var val = obj.val();
          // comma list style handling
            if (val.indexOf(',') == -1) {
              val = ui.item.value + ', ';
            } else {
              val = val.replace(/,[^,]+$/, "") + ", " + ui.item.value + ', ';
            }
        // Regardless, set the object
        obj.val(val);
      }

        // Actually configure and bind the autocomplete
        if (confs[i].autocompleteUrl) {
          if (confs[i].name != 'author') {
            input.autocomplete({
              source: makeSearchFunction(confs[i]),
              minLength: 0,
              select: function(event, ui) {
                SelectFunc($(this), ui);
                event.preventDefault();
              },
              focus: function(event, ui) {
                event.preventDefault();
              },
              open: function(event, ui) {
                // annotator purposely sets the editor at a very high z-index;
                // set autocomplete still higher so it isn't obscured by annotator buttons
                $('.ui-autocomplete')
                  .css('z-index', $('.annotator-editor').css('z-index') + 1);
              },
            });
          }
          // Special handling for author as the odd field out
          // since it's not a parsed list
          else {
            input.autocomplete({
              source: makeSearchFunction(confs[i]),
              minLength: 0,
              focus: function(event, ui) {
                event.preventDefault();
              },
              open: function(event, ui) {
                // annotator purposely sets the editor at a very high z-index;
                // set autocomplete still higher so it isn't obscured by annotator buttons
                $('.ui-autocomplete')
                  .css('z-index', $('.annotator-editor').css('z-index') + 1);
              },
            });
          }
          // Bind a element to make the autocomplete window pop up on focus
          input.bind('focus', function () {
            $(this).autocomplete("search");
          })
      }

      }
    }

    // makeEditorExtension wrapper end brance
  }
  // winthrop wrapper end brace
}

/*
Winthrop Annotator Override
-A simple jQuery shim to fix the text on the 'Comment' box to something more
sensible
*/
function fixComment(field) {
  // field - any valid jQuery selector for the annotation text field
  $(field).attr('placeholder', 'Annotation text...');
}
<|endoftext|>


<commit_before>import { get } from "ember-metal/property_get";
import { set } from "ember-metal/property_set";
import EmberError from "ember-metal/error";
import { Descriptor, defineProperty } from "ember-metal/properties";
import { create } from "ember-metal/platform";
import { meta, inspect } from "ember-metal/utils";
import {
  addDependentKeys,
  removeDependentKeys
} from "ember-metal/dependent_keys";

export function alias(altKey) {
  return new AliasedProperty(altKey);
}

export function AliasedProperty(altKey) {
  this.altKey = altKey;
  this._dependentKeys = [ altKey ];
}

AliasedProperty.prototype = create(Descriptor.prototype);

AliasedProperty.prototype.get = function AliasedProperty_get(obj, keyName) {
  return get(obj, this.altKey);
};

AliasedProperty.prototype.set = function AliasedProperty_set(obj, keyName, value) {
  return set(obj, this.altKey, value);
};

AliasedProperty.prototype.willWatch = function(obj, keyName) {
  addDependentKeys(this, obj, keyName, meta(obj));
};

AliasedProperty.prototype.didUnwatch = function(obj, keyName) {
  removeDependentKeys(this, obj, keyName, meta(obj));
};

AliasedProperty.prototype.setup = function(obj, keyName) {
  var m = meta(obj);
  if (m.watching[keyName]) {
    addDependentKeys(this, obj, keyName, m);
  }
};

AliasedProperty.prototype.teardown = function(obj, keyName) {
  var m = meta(obj);
  if (m.watching[keyName]) {
    removeDependentKeys(this, obj, keyName, m);
  }
};

AliasedProperty.prototype.readOnly = function() {
  this.set = AliasedProperty_readOnlySet;
  return this;
};

function AliasedProperty_readOnlySet(obj, keyName, value) {
  throw new EmberError('Cannot set read-only property "' + keyName + '" on object: ' + inspect(obj));
}

AliasedProperty.prototype.oneWay = function() {
  this.set = AliasedProperty_oneWaySet;
  return this;
};

function AliasedProperty_oneWaySet(obj, keyName, value) {
  defineProperty(obj, keyName, null);
  return set(obj, keyName, value);
}
<commit_msg>[BUGFIX beta] Stub meta on AliasedProperty<commit_after>import { get } from "ember-metal/property_get";
import { set } from "ember-metal/property_set";
import EmberError from "ember-metal/error";
import { Descriptor, defineProperty } from "ember-metal/properties";
import { ComputedProperty } from "ember-metal/computed";
import { create } from "ember-metal/platform";
import { meta, inspect } from "ember-metal/utils";
import {
  addDependentKeys,
  removeDependentKeys
} from "ember-metal/dependent_keys";

export function alias(altKey) {
  return new AliasedProperty(altKey);
}

export function AliasedProperty(altKey) {
  this.altKey = altKey;
  this._dependentKeys = [ altKey ];
}

AliasedProperty.prototype = create(Descriptor.prototype);

AliasedProperty.prototype.get = function AliasedProperty_get(obj, keyName) {
  return get(obj, this.altKey);
};

AliasedProperty.prototype.set = function AliasedProperty_set(obj, keyName, value) {
  return set(obj, this.altKey, value);
};

AliasedProperty.prototype.willWatch = function(obj, keyName) {
  addDependentKeys(this, obj, keyName, meta(obj));
};

AliasedProperty.prototype.didUnwatch = function(obj, keyName) {
  removeDependentKeys(this, obj, keyName, meta(obj));
};

AliasedProperty.prototype.setup = function(obj, keyName) {
  var m = meta(obj);
  if (m.watching[keyName]) {
    addDependentKeys(this, obj, keyName, m);
  }
};

AliasedProperty.prototype.teardown = function(obj, keyName) {
  var m = meta(obj);
  if (m.watching[keyName]) {
    removeDependentKeys(this, obj, keyName, m);
  }
};

AliasedProperty.prototype.readOnly = function() {
  this.set = AliasedProperty_readOnlySet;
  return this;
};

function AliasedProperty_readOnlySet(obj, keyName, value) {
  throw new EmberError('Cannot set read-only property "' + keyName + '" on object: ' + inspect(obj));
}

AliasedProperty.prototype.oneWay = function() {
  this.set = AliasedProperty_oneWaySet;
  return this;
};

function AliasedProperty_oneWaySet(obj, keyName, value) {
  defineProperty(obj, keyName, null);
  return set(obj, keyName, value);
}

// Backwards compatibility with Ember Data
AliasedProperty.prototype._meta = undefined;
AliasedProperty.prototype.meta = ComputedProperty.prototype.meta;
<|endoftext|>


<commit_before>/* eslint-disable import/no-unresolved, import/extensions */
import { StyleSheet } from 'react-native'
/* @flow */
import arrayEach from 'fast-loops/lib/arrayEach'
import { processStyleWithPlugins, RULE_TYPE, CLEAR_TYPE } from 'fela-utils'
import assignStyle from 'css-in-js-utils/lib/assignStyle'

import type {
  NativeRenderer,
  NativeRendererConfig,
} from '../../../flowtypes/NativeRenderer'

export function createRenderer(
  config: NativeRendererConfig = {}
): NativeRenderer {
  let renderer: NativeRenderer = {
    listeners: [],
    cache: {},
    plugins: config.plugins || [],
    isNativeRenderer: true,

    clear(): void {
      renderer.cache = {}

      renderer._emitChange({
        type: CLEAR_TYPE,
      })
    },

    subscribe(callback: Function): { unsubscribe: Function } {
      renderer.listeners.push(callback)

      return {
        unsubscribe: () =>
          renderer.listeners.splice(renderer.listeners.indexOf(callback), 1),
      }
    },

    renderRule(rule: Function, props: Object = {}): Object {
      const style = rule(props, renderer)
      const reference = JSON.stringify(style)

      if (!renderer.cache.hasOwnProperty(reference)) {
        const processedStyle = processStyleWithPlugins(
          renderer,
          rule(props, renderer),
          RULE_TYPE,
          props
        )

        renderer.cache[reference] = StyleSheet.create({
          style: processedStyle,
        })

        renderer._emitChange({
          type: RULE_TYPE,
          style: processedStyle,
        })
      }

      return renderer.cache[reference].style
    },

    _mergeStyle: assignStyle,

    _emitChange(change: Object): void {
      arrayEach(renderer.listeners, listener => listener(change))
    },
  }

  if (config.enhancers) {
    arrayEach(config.enhancers, enhancer => {
      renderer = enhancer(renderer)
    })
  }

  return renderer
}
<commit_msg>#593 process style before caching to ensure predictable output closes #593<commit_after>/* eslint-disable import/no-unresolved, import/extensions */
import { StyleSheet } from 'react-native'
/* @flow */
import arrayEach from 'fast-loops/lib/arrayEach'
import { processStyleWithPlugins, RULE_TYPE, CLEAR_TYPE } from 'fela-utils'
import assignStyle from 'css-in-js-utils/lib/assignStyle'

import type {
  NativeRenderer,
  NativeRendererConfig,
} from '../../../flowtypes/NativeRenderer'

export function createRenderer(
  config: NativeRendererConfig = {}
): NativeRenderer {
  let renderer: NativeRenderer = {
    listeners: [],
    cache: {},
    plugins: config.plugins || [],
    isNativeRenderer: true,

    clear(): void {
      renderer.cache = {}

      renderer._emitChange({
        type: CLEAR_TYPE,
      })
    },

    subscribe(callback: Function): { unsubscribe: Function } {
      renderer.listeners.push(callback)

      return {
        unsubscribe: () =>
          renderer.listeners.splice(renderer.listeners.indexOf(callback), 1),
      }
    },

    renderRule(rule: Function, props: Object = {}): Object {
      const processedStyle = processStyleWithPlugins(
        renderer,
        rule(props, renderer),
        RULE_TYPE,
        props
      )

      const reference = JSON.stringify(processedStyle)

      if (!renderer.cache.hasOwnProperty(reference)) {
        renderer.cache[reference] = StyleSheet.create({
          style: processedStyle,
        })

        renderer._emitChange({
          type: RULE_TYPE,
          style: processedStyle,
        })
      }

      return renderer.cache[reference].style
    },

    _mergeStyle: assignStyle,

    _emitChange(change: Object): void {
      arrayEach(renderer.listeners, listener => listener(change))
    },
  }

  if (config.enhancers) {
    arrayEach(config.enhancers, enhancer => {
      renderer = enhancer(renderer)
    })
  }

  return renderer
}
<|endoftext|>


<commit_before>import PropTypes from "prop-types"
import React from "react"
import { Link } from "@reach/router"

import { parsePath } from "./parse-path"

export { parsePath }

export function withPrefix(path) {
  return normalizePath(
    [
      typeof __BASE_PATH__ !== `undefined` ? __BASE_PATH__ : __PATH_PREFIX__,
      path,
    ].join(`/`)
  )
}

export function withAssetPrefix(path) {
  return [__PATH_PREFIX__].concat([path.replace(/^\//, ``)]).join(`/`)
}

function normalizePath(path) {
  return path.replace(/\/+/g, `/`)
}

const NavLinkPropTypes = {
  activeClassName: PropTypes.string,
  activeStyle: PropTypes.object,
  partiallyActive: PropTypes.bool,
}

// Set up IntersectionObserver
const handleIntersection = (el, cb) => {
  const io = new window.IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (el === entry.target) {
        // Check if element is within viewport, remove listener, destroy observer, and run link callback.
        // MSEdge doesn't currently support isIntersecting, so also test for  an intersectionRatio > 0
        if (entry.isIntersecting || entry.intersectionRatio > 0) {
          io.unobserve(el)
          io.disconnect()
          cb()
        }
      }
    })
  })
  // Add element to the observer
  io.observe(el)
}

class GatsbyLink extends React.Component {
  constructor(props) {
    super(props)
    // Default to no support for IntersectionObserver
    let IOSupported = false
    if (typeof window !== `undefined` && window.IntersectionObserver) {
      IOSupported = true
    }

    this.state = {
      IOSupported,
    }
    this.handleRef = this.handleRef.bind(this)
  }

  componentDidUpdate(prevProps, prevState) {
    // Preserve non IO functionality if no support
    if (this.props.to !== prevProps.to && !this.state.IOSupported) {
      ___loader.enqueue(parsePath(this.props.to).pathname)
    }
  }

  componentDidMount() {
    // Preserve non IO functionality if no support
    if (!this.state.IOSupported) {
      ___loader.enqueue(parsePath(this.props.to).pathname)
    }
  }

  handleRef(ref) {
    if (this.props.innerRef && this.props.innerRef.hasOwnProperty(`current`)) {
      this.props.innerRef.current = ref
    } else if (this.props.innerRef) {
      this.props.innerRef(ref)
    }

    if (this.state.IOSupported && ref) {
      // If IO supported and element reference found, setup Observer functionality
      handleIntersection(ref, () => {
        ___loader.enqueue(parsePath(this.props.to).pathname)
      })
    }
  }

  defaultGetProps = ({ isPartiallyCurrent, isCurrent }) => {
    if (this.props.partiallyActive ? isPartiallyCurrent : isCurrent) {
      return {
        className: [this.props.className, this.props.activeClassName]
          .filter(Boolean)
          .join(` `),
        style: { ...this.props.style, ...this.props.activeStyle },
      }
    }
    return null
  }

  render() {
    const {
      to,
      getProps = this.defaultGetProps,
      onClick,
      onMouseEnter,
      /* eslint-disable no-unused-vars */
      activeClassName: $activeClassName,
      activeStyle: $activeStyle,
      innerRef: $innerRef,
      partiallyActive,
      state,
      replace,
      /* eslint-enable no-unused-vars */
      ...rest
    } = this.props

    const LOCAL_URL = /^\/(?!\/)/
    if (process.env.NODE_ENV !== `production` && !LOCAL_URL.test(to)) {
      console.warn(
        `External link ${to} was detected in a Link component. Use the Link component only for internal links. See: https://gatsby.dev/internal-links`
      )
    }

    const prefixedTo = withPrefix(to)

    return (
      <Link
        to={prefixedTo}
        state={state}
        getProps={getProps}
        innerRef={this.handleRef}
        onMouseEnter={e => {
          if (onMouseEnter) {
            onMouseEnter(e)
          }
          ___loader.hovering(parsePath(to).pathname)
        }}
        onClick={e => {
          if (onClick) {
            onClick(e)
          }

          if (
            e.button === 0 && // ignore right clicks
            !this.props.target && // let browser handle "target=_blank"
            !e.defaultPrevented && // onClick prevented default
            !e.metaKey && // ignore clicks with modifier keys...
            !e.altKey &&
            !e.ctrlKey &&
            !e.shiftKey
          ) {
            e.preventDefault()

            // Make sure the necessary scripts and data are
            // loaded before continuing.
            navigate(to, { state, replace })
          }

          return true
        }}
        {...rest}
      />
    )
  }
}

GatsbyLink.propTypes = {
  ...NavLinkPropTypes,
  onClick: PropTypes.func,
  to: PropTypes.string.isRequired,
  replace: PropTypes.bool,
}

export default React.forwardRef((props, ref) => (
  <GatsbyLink innerRef={ref} {...props} />
))

export const navigate = (to, options) => {
  window.___navigate(withPrefix(to), options)
}

export const push = to => {
  console.warn(
    `The "push" method is now deprecated and will be removed in Gatsby v3. Please use "navigate" instead.`
  )
  window.___push(withPrefix(to))
}

export const replace = to => {
  console.warn(
    `The "replace" method is now deprecated and will be removed in Gatsby v3. Please use "navigate" instead.`
  )
  window.___replace(withPrefix(to))
}

// TODO: Remove navigateTo for Gatsby v3
export const navigateTo = to => {
  console.warn(
    `The "navigateTo" method is now deprecated and will be removed in Gatsby v3. Please use "navigate" instead.`
  )
  return push(to)
}
<commit_msg>chore(gatsby-link): Minor refactor (#16571)<commit_after>import PropTypes from "prop-types"
import React from "react"
import { Link } from "@reach/router"

import { parsePath } from "./parse-path"

export { parsePath }

export function withPrefix(path) {
  return normalizePath(
    [
      typeof __BASE_PATH__ !== `undefined` ? __BASE_PATH__ : __PATH_PREFIX__,
      path,
    ].join(`/`)
  )
}

export function withAssetPrefix(path) {
  return [__PATH_PREFIX__].concat([path.replace(/^\//, ``)]).join(`/`)
}

function normalizePath(path) {
  return path.replace(/\/+/g, `/`)
}

const NavLinkPropTypes = {
  activeClassName: PropTypes.string,
  activeStyle: PropTypes.object,
  partiallyActive: PropTypes.bool,
}

// Set up IntersectionObserver
const handleIntersection = (el, cb) => {
  const io = new window.IntersectionObserver(entries => {
    entries.forEach(entry => {
      if (el === entry.target) {
        // Check if element is within viewport, remove listener, destroy observer, and run link callback.
        // MSEdge doesn't currently support isIntersecting, so also test for  an intersectionRatio > 0
        if (entry.isIntersecting || entry.intersectionRatio > 0) {
          io.unobserve(el)
          io.disconnect()
          cb()
        }
      }
    })
  })
  // Add element to the observer
  io.observe(el)
}

class GatsbyLink extends React.Component {
  constructor(props) {
    super(props)
    // Default to no support for IntersectionObserver
    let IOSupported = false
    if (typeof window !== `undefined` && window.IntersectionObserver) {
      IOSupported = true
    }

    this.state = {
      IOSupported,
    }
    this.handleRef = this.handleRef.bind(this)
  }

  componentDidUpdate(prevProps, prevState) {
    // Preserve non IO functionality if no support
    if (this.props.to !== prevProps.to && !this.state.IOSupported) {
      ___loader.enqueue(parsePath(this.props.to).pathname)
    }
  }

  componentDidMount() {
    // Preserve non IO functionality if no support
    if (!this.state.IOSupported) {
      ___loader.enqueue(parsePath(this.props.to).pathname)
    }
  }

  handleRef(ref) {
    if (this.props.innerRef && this.props.innerRef.hasOwnProperty(`current`)) {
      this.props.innerRef.current = ref
    } else if (this.props.innerRef) {
      this.props.innerRef(ref)
    }

    if (this.state.IOSupported && ref) {
      // If IO supported and element reference found, setup Observer functionality
      handleIntersection(ref, () => {
        ___loader.enqueue(parsePath(this.props.to).pathname)
      })
    }
  }

  defaultGetProps = ({ isPartiallyCurrent, isCurrent }) => {
    if (this.props.partiallyActive ? isPartiallyCurrent : isCurrent) {
      return {
        className: [this.props.className, this.props.activeClassName]
          .filter(Boolean)
          .join(` `),
        style: { ...this.props.style, ...this.props.activeStyle },
      }
    }
    return null
  }

  render() {
    const {
      to,
      getProps = this.defaultGetProps,
      onClick,
      onMouseEnter,
      /* eslint-disable no-unused-vars */
      activeClassName: $activeClassName,
      activeStyle: $activeStyle,
      innerRef: $innerRef,
      partiallyActive,
      state,
      replace,
      /* eslint-enable no-unused-vars */
      ...rest
    } = this.props

    const LOCAL_URL = /^\/(?!\/)/
    if (process.env.NODE_ENV !== `production` && !LOCAL_URL.test(to)) {
      console.warn(
        `External link ${to} was detected in a Link component. Use the Link component only for internal links. See: https://gatsby.dev/internal-links`
      )
    }

    const prefixedTo = withPrefix(to)

    return (
      <Link
        to={prefixedTo}
        state={state}
        getProps={getProps}
        innerRef={this.handleRef}
        onMouseEnter={e => {
          if (onMouseEnter) {
            onMouseEnter(e)
          }
          ___loader.hovering(parsePath(to).pathname)
        }}
        onClick={e => {
          if (onClick) {
            onClick(e)
          }

          if (
            e.button === 0 && // ignore right clicks
            !this.props.target && // let browser handle "target=_blank"
            !e.defaultPrevented && // onClick prevented default
            !e.metaKey && // ignore clicks with modifier keys...
            !e.altKey &&
            !e.ctrlKey &&
            !e.shiftKey
          ) {
            e.preventDefault()

            // Make sure the necessary scripts and data are
            // loaded before continuing.
            navigate(to, { state, replace })
          }

          return true
        }}
        {...rest}
      />
    )
  }
}

GatsbyLink.propTypes = {
  ...NavLinkPropTypes,
  onClick: PropTypes.func,
  to: PropTypes.string.isRequired,
  replace: PropTypes.bool,
}

const showDeprecationWarning = functionName =>
  console.warn(
    `The "${functionName}" method is now deprecated and will be removed in Gatsby v3. Please use "navigate" instead.`
  )

export default React.forwardRef((props, ref) => (
  <GatsbyLink innerRef={ref} {...props} />
))

export const navigate = (to, options) => {
  window.___navigate(withPrefix(to), options)
}

export const push = to => {
  showDeprecationWarning(`push`)
  window.___push(withPrefix(to))
}

export const replace = to => {
  showDeprecationWarning(`replace`)
  window.___replace(withPrefix(to))
}

// TODO: Remove navigateTo for Gatsby v3
export const navigateTo = to => {
  showDeprecationWarning(`navigateTo`)
  return push(to)
}
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

import type {
  AsyncFn,
  BlockMode,
  BlockName,
  DescribeBlock,
  Exception,
  Hook,
  TestEntry,
  TestContext,
  TestFn,
  TestMode,
  TestName,
  TestResults,
} from 'types/Circus';

const makeDescribe = (
  name: BlockName,
  parent: ?DescribeBlock,
  mode?: BlockMode,
): DescribeBlock => {
  if (parent && !mode) {
    // If not set explicitly, inherit from the parent describe.
    mode = parent.mode;
  }

  return {
    children: [],
    hooks: [],
    mode,
    name,
    parent,
    tests: [],
  };
};

const makeTest = (
  fn: ?TestFn,
  mode: TestMode,
  name: TestName,
  parent: DescribeBlock,
): TestEntry => {
  if (!fn) {
    mode = 'skip'; // skip test if no fn passed
  } else if (!mode) {
    // if not set explicitly, inherit from its parent describe
    mode = parent.mode;
  }

  return {
    duration: null,
    errors: [],
    fn,
    mode,
    name,
    parent,
    startedAt: null,
    status: null,
  };
};

const getAllHooksForDescribe = (
  describe: DescribeBlock,
): {[key: 'beforeAll' | 'afterAll']: Array<Hook>} => {
  const result = {afterAll: [], beforeAll: []};

  for (const hook of describe.hooks) {
    switch (hook.type) {
      case 'beforeAll':
        result.beforeAll.push(hook);
        break;
      case 'afterAll':
        result.afterAll.push(hook);
        break;
    }
  }

  return result;
};

const getEachHooksForTest = (
  test: TestEntry,
): {[key: 'beforeEach' | 'afterEach']: Array<Hook>} => {
  const result = {afterEach: [], beforeEach: []};
  let {parent: block} = test;

  do {
    for (const hook of block.hooks) {
      switch (hook.type) {
        case 'beforeEach':
          // Before hooks are executed from top to bottom, the opposite of the
          // way we traversed it.
          result.beforeEach.unshift(hook);
          break;
        case 'afterEach':
          result.afterEach.push(hook);
          break;
      }
    }
  } while ((block = block.parent));
  return result;
};

const _makeTimeoutMessage = (timeout, isHook) =>
  new Error(
    `Exceeded timeout of ${timeout}ms for a ${isHook
      ? 'hook'
      : 'test'}.\nUse jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test.`,
  );

// Global values can be overwritten by mocks or tests. We'll capture
// the original values in the variables before we require any files.
const {setTimeout, clearTimeout} = global;

const callAsyncFn = (
  fn: AsyncFn,
  testContext: ?TestContext,
  {
    isHook,
    test,
    timeout,
  }: {isHook?: ?boolean, test?: TestEntry, timeout: number},
): Promise<any> => {
  let timeoutID;

  return new Promise((resolve, reject) => {
    timeoutID = setTimeout(
      () => reject(_makeTimeoutMessage(timeout, isHook)),
      timeout,
    );

    // If this fn accepts `done` callback we return a promise that fullfills as
    // soon as `done` called.
    if (fn.length) {
      const done = (reason?: Error | string): void =>
        reason ? reject(reason) : resolve();

      return fn.call(testContext, done);
    }

    let returnedValue;
    try {
      returnedValue = fn.call(testContext);
    } catch (error) {
      return reject(error);
    }

    // If it's a Promise, return it.
    if (returnedValue instanceof Promise) {
      return returnedValue.then(resolve, reject);
    }

    if (!isHook && returnedValue !== void 0) {
      return reject(
        new Error(
          `
      test functions can only return Promise or undefined.
      Returned value: ${String(returnedValue)}
      `,
        ),
      );
    }

    // Otherwise this test is synchronous, and if it didn't throw it means
    // it passed.
    return resolve();
  })
    .then(() => {
      // If timeout is not cleared/unrefed the node process won't exit until
      // it's resolved.
      timeoutID.unref && timeoutID.unref();
      clearTimeout(timeoutID);
    })
    .catch(error => {
      timeoutID.unref && timeoutID.unref();
      clearTimeout(timeoutID);
      throw error;
    });
};

const getTestDuration = (test: TestEntry): ?number => {
  const {startedAt} = test;
  return startedAt ? Date.now() - startedAt : null;
};

const makeTestResults = (describeBlock: DescribeBlock): TestResults => {
  let testResults = [];
  for (const test of describeBlock.tests) {
    const testPath = [];
    let parent = test;
    do {
      testPath.unshift(parent.name);
    } while ((parent = parent.parent));

    const {status} = test;

    if (!status) {
      throw new Error('Status should be present after tests are run.');
    }
    testResults.push({
      duration: test.duration,
      errors: test.errors.map(_formatError),
      status,
      testPath,
    });
  }

  for (const child of describeBlock.children) {
    testResults = testResults.concat(makeTestResults(child));
  }

  return testResults;
};

// Return a string that identifies the test (concat of parent describe block
// names + test title)
const getTestID = (test: TestEntry) => {
  const titles = [];
  let parent = test;
  do {
    titles.unshift(parent.name);
  } while ((parent = parent.parent));

  titles.shift(); // remove TOP_DESCRIBE_BLOCK_NAME
  return titles.join(' ');
};

const _formatError = (error: ?Exception): string => {
  if (!error) {
    return 'NO ERROR MESSAGE OR STACK TRACE SPECIFIED';
  } else if (error.stack) {
    return error.stack;
  } else if (error.message) {
    return error.message;
  } else {
    return `${String(error)} thrown`;
  }
};

module.exports = {
  callAsyncFn,
  getAllHooksForDescribe,
  getEachHooksForTest,
  getTestDuration,
  getTestID,
  makeDescribe,
  makeTest,
  makeTestResults,
};
<commit_msg>Change Promise detection code in jest-circus to support non-global Promise implementations (#4375)<commit_after>/**
 * Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @flow
 */

import type {
  AsyncFn,
  BlockMode,
  BlockName,
  DescribeBlock,
  Exception,
  Hook,
  TestEntry,
  TestContext,
  TestFn,
  TestMode,
  TestName,
  TestResults,
} from 'types/Circus';

const makeDescribe = (
  name: BlockName,
  parent: ?DescribeBlock,
  mode?: BlockMode,
): DescribeBlock => {
  if (parent && !mode) {
    // If not set explicitly, inherit from the parent describe.
    mode = parent.mode;
  }

  return {
    children: [],
    hooks: [],
    mode,
    name,
    parent,
    tests: [],
  };
};

const makeTest = (
  fn: ?TestFn,
  mode: TestMode,
  name: TestName,
  parent: DescribeBlock,
): TestEntry => {
  if (!fn) {
    mode = 'skip'; // skip test if no fn passed
  } else if (!mode) {
    // if not set explicitly, inherit from its parent describe
    mode = parent.mode;
  }

  return {
    duration: null,
    errors: [],
    fn,
    mode,
    name,
    parent,
    startedAt: null,
    status: null,
  };
};

const getAllHooksForDescribe = (
  describe: DescribeBlock,
): {[key: 'beforeAll' | 'afterAll']: Array<Hook>} => {
  const result = {afterAll: [], beforeAll: []};

  for (const hook of describe.hooks) {
    switch (hook.type) {
      case 'beforeAll':
        result.beforeAll.push(hook);
        break;
      case 'afterAll':
        result.afterAll.push(hook);
        break;
    }
  }

  return result;
};

const getEachHooksForTest = (
  test: TestEntry,
): {[key: 'beforeEach' | 'afterEach']: Array<Hook>} => {
  const result = {afterEach: [], beforeEach: []};
  let {parent: block} = test;

  do {
    for (const hook of block.hooks) {
      switch (hook.type) {
        case 'beforeEach':
          // Before hooks are executed from top to bottom, the opposite of the
          // way we traversed it.
          result.beforeEach.unshift(hook);
          break;
        case 'afterEach':
          result.afterEach.push(hook);
          break;
      }
    }
  } while ((block = block.parent));
  return result;
};

const _makeTimeoutMessage = (timeout, isHook) =>
  new Error(
    `Exceeded timeout of ${timeout}ms for a ${isHook
      ? 'hook'
      : 'test'}.\nUse jest.setTimeout(newTimeout) to increase the timeout value, if this is a long-running test.`,
  );

// Global values can be overwritten by mocks or tests. We'll capture
// the original values in the variables before we require any files.
const {setTimeout, clearTimeout} = global;

const callAsyncFn = (
  fn: AsyncFn,
  testContext: ?TestContext,
  {
    isHook,
    test,
    timeout,
  }: {isHook?: ?boolean, test?: TestEntry, timeout: number},
): Promise<any> => {
  let timeoutID;

  return new Promise((resolve, reject) => {
    timeoutID = setTimeout(
      () => reject(_makeTimeoutMessage(timeout, isHook)),
      timeout,
    );

    // If this fn accepts `done` callback we return a promise that fullfills as
    // soon as `done` called.
    if (fn.length) {
      const done = (reason?: Error | string): void =>
        reason ? reject(reason) : resolve();

      return fn.call(testContext, done);
    }

    let returnedValue;
    try {
      returnedValue = fn.call(testContext);
    } catch (error) {
      return reject(error);
    }

    // If it's a Promise, return it. Test for an object with a `then` function
    // to support custom Promise implementations.
    if (
      typeof returnedValue === 'object' &&
      returnedValue !== null &&
      typeof returnedValue.then === 'function'
    ) {
      return returnedValue.then(resolve, reject);
    }

    if (!isHook && returnedValue !== void 0) {
      return reject(
        new Error(
          `
      test functions can only return Promise or undefined.
      Returned value: ${String(returnedValue)}
      `,
        ),
      );
    }

    // Otherwise this test is synchronous, and if it didn't throw it means
    // it passed.
    return resolve();
  })
    .then(() => {
      // If timeout is not cleared/unrefed the node process won't exit until
      // it's resolved.
      timeoutID.unref && timeoutID.unref();
      clearTimeout(timeoutID);
    })
    .catch(error => {
      timeoutID.unref && timeoutID.unref();
      clearTimeout(timeoutID);
      throw error;
    });
};

const getTestDuration = (test: TestEntry): ?number => {
  const {startedAt} = test;
  return startedAt ? Date.now() - startedAt : null;
};

const makeTestResults = (describeBlock: DescribeBlock): TestResults => {
  let testResults = [];
  for (const test of describeBlock.tests) {
    const testPath = [];
    let parent = test;
    do {
      testPath.unshift(parent.name);
    } while ((parent = parent.parent));

    const {status} = test;

    if (!status) {
      throw new Error('Status should be present after tests are run.');
    }
    testResults.push({
      duration: test.duration,
      errors: test.errors.map(_formatError),
      status,
      testPath,
    });
  }

  for (const child of describeBlock.children) {
    testResults = testResults.concat(makeTestResults(child));
  }

  return testResults;
};

// Return a string that identifies the test (concat of parent describe block
// names + test title)
const getTestID = (test: TestEntry) => {
  const titles = [];
  let parent = test;
  do {
    titles.unshift(parent.name);
  } while ((parent = parent.parent));

  titles.shift(); // remove TOP_DESCRIBE_BLOCK_NAME
  return titles.join(' ');
};

const _formatError = (error: ?Exception): string => {
  if (!error) {
    return 'NO ERROR MESSAGE OR STACK TRACE SPECIFIED';
  } else if (error.stack) {
    return error.stack;
  } else if (error.message) {
    return error.message;
  } else {
    return `${String(error)} thrown`;
  }
};

module.exports = {
  callAsyncFn,
  getAllHooksForDescribe,
  getEachHooksForTest,
  getTestDuration,
  getTestID,
  makeDescribe,
  makeTest,
  makeTestResults,
};
<|endoftext|>


<commit_before>/*! [replace-name] v[replace-version] */
/***************************************************************************************************************************************************************
 *
 * mainNav function
 *
 * Horizontal list of links to key areas on the website. Usually located in the header.
 *
 **************************************************************************************************************************************************************/

import React from 'react';
import PropTypes from 'prop-types';

import AU from '@gov.au/animate'; // interdependency with our animate lib

// ES5 dependency: import AUlinkList from '@gov.au/link-list';
// ES6 dependency: import AUlinkList from './link-list';

// The following line will be replaced automatically with generic imports for the ES5 pipeline.
// You can safely ignore this bit if you use this module with pancake
//
// [replace-imports]

/**
 * A menu inside the AUmainNav
 *
 * @param  {array}  items            - The links in an array containing text, location and active status
 * @param  {string} linkComponent    - The component used for the link
 */
const AUmainNavMenu = ({ items, linkComponent }) => {
	// Generate the menu
	const GenerateMenu = ( items ) => {
		const menu = items.map( item => {
			const link =  item.active ? {
				link: item.link,
				text: item.text,
				'aria-current': 'page',
				li: {
					className: 'active',
				},
			} :
			{
				link: item.link,
				text: item.text,
			};

			// return the link, maybe with children
			return link;
		});

		return menu;
	}

	// Create the menu with children
	return (
		<AUlinkList items={ GenerateMenu( items ) } linkComponent={ linkComponent } />
	);
}

AUmainNavMenu.propTypes = {
	items: PropTypes.arrayOf(
		PropTypes.shape({
			link: PropTypes.string,
			text: PropTypes.node.isRequired,
			children: PropTypes.array,
		})
	).isRequired,
	linkComponent: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]),
};

AUmainNavMenu.defaultProps = {
	linkComponent: 'a',
};

// This value gets increased for each navigation component
let IDvalue = 0;

export class AUmainNavContent extends React.PureComponent {
	constructor( props ) {
		super( props );

		const {
			items,
			className = '',
			children,
			linkComponent,
			onOpen,
			afterOpen,
			onClose,
			afterClose,
			...attributeOptions
		} = props;

		// Functions
		this.toggleClasses        = this.toggleClasses.bind( this );
		this.removeClass          = this.removeClass.bind( this );
		this.addClass             = this.addClass.bind( this );
		this.mainNavToggle        = this.mainNavToggle.bind( this );
		this.toggle               = this.toggle.bind( this );
		this.toggleAnimationState = function() {
			this.setState({ animating: !this.state.animating } )
		}.bind( this );

		// Event listeners
		this.auFocusTrapListenerTop    = this.auFocusTrapListenerTop.bind( this );
		this.auFocusTrapListenerBottom = this.auFocusTrapListenerBottom.bind( this );
		this.auKeyListener             = this.auKeyListener.bind( this );

		// Variables
		this.state = {
			closed: true,
			animating: false,
		};

		// Increase the ID so it's unique for each instance
		IDvalue += 1;
		this.id = `au-main-nav-${ IDvalue }`;
	}

	/**
	 * Toggle an main navigation on click
	 *
	 * @param  {event object} event - The event object of the click
	 */
	toggle( event ) {
		event.preventDefault();

		// If the main menu is animating don't change toggle the menu
		if( this.state.animating ){
			return;
		}

		// Toggle the menu's current state
		this.setState({
			closed: !this.state.closed,
			animating: true,
		});
	}

	/**
	 * componentDidUpdate - Update the component when it changes
	 *
	 * @param {*} prevProps - The previous property values
	 * @param {*} prevState - The previous state values
	 */
	componentDidUpdate( prevProps, prevState ){
		if( prevState !== undefined && this.state !== undefined && this.state.closed !== prevState.closed ) {
			this.mainNavToggle( this.mainNavContent, this.props.speed, {
				onOpen: this.props.onOpen,
				afterOpen: this.props.afterOpen,
				onClose: this.props.onClose,
				afterClose: this.props.afterClose,
			});
		}
	}

	/**
	 * IE8 compatible function for replacing classes on a DOM node
	 *
	 * @param  {object} element       - The DOM element we want to toggle classes on
	 * @param  {string} openingClass  - The firstClass you want to toggle on the DOM node
	 * @param  {string} closingClass  - The secondClass you want to toggle on the DOM node
	 */
	toggleClasses( element, state, openingClass, closingClass ) {
		if( state === 'opening' || state === 'open' ) {
			var oldClass = openingClass || 'au-main-nav__content--closed';
			var newClass = closingClass || 'au-main-nav__content--open';
		}
		else {
			var oldClass = closingClass || 'au-main-nav__content--open';
			var newClass = openingClass || 'au-main-nav__content--closed';
		}

		this.removeClass( element, oldClass );
		this.addClass( element, newClass );
	}

	/**
	 * IE8 compatible function for removing a class
	 *
	 * @param  {object} element   - The DOM element we want to manipulate
	 * @param  {object} className - The name of the class to be removed
	 */
	removeClass( element, className ) {
		if( element.classList ) {
			element.classList.remove( className );
		}
		else {
			element.className = element.className.replace( new RegExp("(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", "gi"), " " );
		}
	}

	/**
	 * IE8 compatible function for adding a class
	 *
	 * @param  {object} element   - The DOM element we want to manipulate
	 * @param  {object} className - The name of the class to be added
	 */
	addClass( element, className ) {
		if( element.classList ) {
			element.classList.add( className );
		}
		else {
			element.className = element.className + " " + className;
		}
	}

	/**
	 * auFocusTrapListenerTop - What happens when the focus trap top gets focus
	 *
	 * @param {event} event - the event
	 */
	auFocusTrapListenerTop( event ) {
		var focusContent = this.mainNavMenu.querySelectorAll( 'a, .au-main-nav__toggle' );
		focusContent[ focusContent.length - 1 ].focus();
	}

	/**
	 * auFocusTrapListenerBottom - What happens when the focus trap bottom gets focus
	 *
	 * @param {event} event - the event
	 */
	auFocusTrapListenerBottom( event ) {
		var focusContent = this.mainNavMenu.querySelectorAll( 'a, .au-main-nav__toggle' );
		focusContent[ 0 ].focus();
	}

	/**
	 * auKeyListener - What happens when a key gets pressed and the menu is open
	 *
	 * @param {event} event - the event
	 */
	auKeyListener( event ) {
		// This should only close the menu when the menu is visibly open
		var overlayOpen = window.getComputedStyle( this.mainNavOverlay ).getPropertyValue( 'display' );

		// Check the menu is open and visible and the escape key is pressed
		if( event.keyCode === 27 && overlayOpen === 'block' ) {
			this.toggle( event );
		}
	}

	/**
	 * Toggle a mainNav element
	 *
	 * @param  {string}  element  - The toggle for the main nav
	 * @param  {integer} speed    - The speed in ms for the animation
	 *
	 */
	mainNavToggle( element, speed, callbacks ) {
		// stop event propagation
		try {
			window.event.cancelBubble = true;
			event.stopPropagation();
		}
		catch( error ) {}

		// check this once
		if( typeof callbacks != 'object' ) {
			callbacks = {};
		}

		// Elements we modify
		var menu            = this.mainNavMenu;
		var overlay         = this.mainNavOverlay;
		var closeButton     = element.querySelector( '.au-main-nav__toggle--close' );
		var openButton      = element.querySelector( '.au-main-nav__toggle--open' );
		var focustrapTop    = element.querySelector( '.au-main-nav__focus-trap-top' );
		var focustrapBottom = element.querySelector( '.au-main-nav__focus-trap-bottom' );

		var closed          = !this.state.closed;
		var state           = closed ? 'opening' : '';
		var menuWidth       = menu.offsetWidth;

		// Functions
		var ToggleClasses             = this.toggleClasses;
		var AUkeyListener             = this.auKeyListener;
		var AUfocusTrapListenerTop    = this.auFocusTrapListenerTop;
		var AUfocusTrapListenerBottom = this.auFocusTrapListenerBottom;
		var ToggleAnimationState      = this.toggleAnimationState;

		overlay.style.display = 'block';

		(function( menu ) {
			AU.animate.Toggle({
				element: menu,
				property: 'left',
				openSize: 0,
				closeSize: -1 * menuWidth,
				speed: speed || 250,
				prefunction: function( ) {
					// Set these value immediately for transitions
					if( state === 'opening' ) {
						menu.style.display    = 'block';
						overlay.style.left    = 0;
						overlay.style.opacity = 0.5;

						// run when opening
						if( typeof callbacks.onOpen === 'function' ) {
							callbacks.onOpen();
						}
					}
					else {
						overlay.style.opacity = '0';

						// run when closing
						if( typeof callbacks.onClose === 'function' ) {
							callbacks.onClose();
						}
					}
				},
				postfunction: function(){
					if ( state === 'opening' ){

						// Move the focus to the close button
						closeButton.focus();

						// Focus trap enabled
						focustrapTop.setAttribute( "tabindex", 0 );
						focustrapBottom.setAttribute( "tabindex", 0 );

						focustrapTop.addEventListener( 'focus', AUfocusTrapListenerTop );
						focustrapBottom.addEventListener( 'focus', AUfocusTrapListenerBottom );

						// Add key listener
						document.addEventListener( 'keyup', AUkeyListener );

						if( typeof callbacks.afterOpen === 'function' ) {
							callbacks.afterOpen();
						}
					}
					else {
						// Move the focus back to the menu button
						openButton.focus();

						// Remove the focus trap
						focustrapTop.removeAttribute( "tabindex" );
						focustrapBottom.removeAttribute( "tabindex" );

						// Remove the event listeners
						focustrapTop.removeEventListener( 'focus', AUfocusTrapListenerTop );
						focustrapBottom.removeEventListener( 'focus', AUfocusTrapListenerBottom );

						// Remove the event listener for the keypress
						document.removeEventListener( 'keyup', AUkeyListener );

						if( typeof callbacks.afterClose === 'function' ) {
							callbacks.afterClose();
						}
					}

					// Toggle classes
					ToggleClasses( element, state );
					ToggleClasses(
						document.body,
						state,
						'au-main-nav__scroll--unlocked',
						'au-main-nav__scroll--locked',
					);

					// Reset inline styles
					menu.style.display    = '';
					menu.style.left       = '';
					overlay.style.display = '';
					overlay.style.left    = '';
					overlay.style.opacity = '';

					// Toggle the animating state to false so that we can allow toggling again
					ToggleAnimationState();
				},
			});
		})( menu );
	}

	render(){
		return (

 { this.mainNavContent = mainNavContent }}>
				<button
					aria-controls={ this.id }
					onClick={ ( event ) => this.toggle( event ) }
					className="au-main-nav__toggle au-main-nav__toggle--open">
					Menu
				</button>

 { this.mainNavMenu = mainNavMenu }}
					>

						<button
							aria-controls={ this.id }
							onClick={ ( event ) => this.toggle( event ) }
							className="au-main-nav__toggle au-main-nav__toggle--close">
							Close
						</button>
						<AUmainNavMenu
							linkComponent={ this.props.linkComponent }
							items={ this.props.items }
						/>
						{ this.props.children }

 { this.mainNavOverlay = mainNavOverlay }}
					onClick={ ( event ) => this.toggle( event ) }
					className="au-main-nav__overlay">

		);
	}
};

AUmainNavContent.propTypes = {
	dark: PropTypes.bool,
	alt: PropTypes.bool,
	children: PropTypes.node,
	speed: PropTypes.number,
	onOpen: PropTypes.func,
	afterOpen: PropTypes.func,
	onClose: PropTypes.func,
	afterClose: PropTypes.func,
	className: PropTypes.string,
};

const AUmainNav = ({ dark, alt, className, ariaLabel, children, ...attributeOptions }) => (
	<nav
		className={
			'au-main-nav ' +
			`${ dark ? ' au-main-nav--dark' : '' }` +
			`${ alt ? ' au-main-nav--alt' : '' }` +
			`${ className ? ' ' + className : '' }`
		}
		aria-label = { ariaLabel }
		{ ...attributeOptions }
	>
		{ children }

);

AUmainNav.propTypes = {
	dark: PropTypes.bool,
	alt: PropTypes.bool,
	ariaLabel: PropTypes.string,
	children: PropTypes.node.isRequired,
};

AUmainNav.defaultProps = {
	ariaLabel: 'main',
};

export default AUmainNav;
<commit_msg>add aria expanded, remove aria controls react<commit_after>/*! [replace-name] v[replace-version] */
/***************************************************************************************************************************************************************
 *
 * mainNav function
 *
 * Horizontal list of links to key areas on the website. Usually located in the header.
 *
 **************************************************************************************************************************************************************/

import React from 'react';
import PropTypes from 'prop-types';

import AU from '@gov.au/animate'; // interdependency with our animate lib

// ES5 dependency: import AUlinkList from '@gov.au/link-list';
// ES6 dependency: import AUlinkList from './link-list';

// The following line will be replaced automatically with generic imports for the ES5 pipeline.
// You can safely ignore this bit if you use this module with pancake
//
// [replace-imports]

/**
 * A menu inside the AUmainNav
 *
 * @param  {array}  items            - The links in an array containing text, location and active status
 * @param  {string} linkComponent    - The component used for the link
 */
const AUmainNavMenu = ({ items, linkComponent }) => {
	// Generate the menu
	const GenerateMenu = ( items ) => {
		const menu = items.map( item => {
			const link =  item.active ? {
				link: item.link,
				text: item.text,
				'aria-current': 'page',
				li: {
					className: 'active',
				},
			} :
			{
				link: item.link,
				text: item.text,
			};

			// return the link, maybe with children
			return link;
		});

		return menu;
	}

	// Create the menu with children
	return (
		<AUlinkList items={ GenerateMenu( items ) } linkComponent={ linkComponent } />
	);
}

AUmainNavMenu.propTypes = {
	items: PropTypes.arrayOf(
		PropTypes.shape({
			link: PropTypes.string,
			text: PropTypes.node.isRequired,
			children: PropTypes.array,
		})
	).isRequired,
	linkComponent: PropTypes.oneOfType([ PropTypes.string, PropTypes.func ]),
};

AUmainNavMenu.defaultProps = {
	linkComponent: 'a',
};

// This value gets increased for each navigation component
let IDvalue = 0;

export class AUmainNavContent extends React.PureComponent {
	constructor( props ) {
		super( props );

		const {
			items,
			className = '',
			children,
			linkComponent,
			onOpen,
			afterOpen,
			onClose,
			afterClose,
			...attributeOptions
		} = props;

		// Functions
		this.toggleClasses        = this.toggleClasses.bind( this );
		this.removeClass          = this.removeClass.bind( this );
		this.addClass             = this.addClass.bind( this );
		this.mainNavToggle        = this.mainNavToggle.bind( this );
		this.toggle               = this.toggle.bind( this );
		this.toggleAnimationState = function() {
			this.setState({ animating: !this.state.animating } )
		}.bind( this );

		// Event listeners
		this.auFocusTrapListenerTop    = this.auFocusTrapListenerTop.bind( this );
		this.auFocusTrapListenerBottom = this.auFocusTrapListenerBottom.bind( this );
		this.auKeyListener             = this.auKeyListener.bind( this );

		// Variables
		this.state = {
			closed: true,
			animating: false,
			expanded: false
		};

		// Increase the ID so it's unique for each instance
		IDvalue += 1;
		this.id = `au-main-nav-${ IDvalue }`;
	}

	/**
	 * Toggle an main navigation on click
	 *
	 * @param  {event object} event - The event object of the click
	 */
	toggle( event ) {
		event.preventDefault();

		// If the main menu is animating don't change toggle the menu
		if( this.state.animating ){
			return;
		}

		// Toggle the menu's current state
		this.setState({
			closed: !this.state.closed,
			expanded: !this.state.expanded,
			animating: true,
		});
	}

	/**
	 * componentDidUpdate - Update the component when it changes
	 *
	 * @param {*} prevProps - The previous property values
	 * @param {*} prevState - The previous state values
	 */
	componentDidUpdate( prevProps, prevState ){
		if( prevState !== undefined && this.state !== undefined && this.state.closed !== prevState.closed ) {
			this.mainNavToggle( this.mainNavContent, this.props.speed, {
				onOpen: this.props.onOpen,
				afterOpen: this.props.afterOpen,
				onClose: this.props.onClose,
				afterClose: this.props.afterClose,
			});
		}
	}

	/**
	 * IE8 compatible function for replacing classes on a DOM node
	 *
	 * @param  {object} element       - The DOM element we want to toggle classes on
	 * @param  {string} openingClass  - The firstClass you want to toggle on the DOM node
	 * @param  {string} closingClass  - The secondClass you want to toggle on the DOM node
	 */
	toggleClasses( element, state, openingClass, closingClass ) {
		if( state === 'opening' || state === 'open' ) {
			var oldClass = openingClass || 'au-main-nav__content--closed';
			var newClass = closingClass || 'au-main-nav__content--open';
		}
		else {
			var oldClass = closingClass || 'au-main-nav__content--open';
			var newClass = openingClass || 'au-main-nav__content--closed';
		}

		this.removeClass( element, oldClass );
		this.addClass( element, newClass );
	}

	/**
	 * IE8 compatible function for removing a class
	 *
	 * @param  {object} element   - The DOM element we want to manipulate
	 * @param  {object} className - The name of the class to be removed
	 */
	removeClass( element, className ) {
		if( element.classList ) {
			element.classList.remove( className );
		}
		else {
			element.className = element.className.replace( new RegExp("(^|\\b)" + className.split(" ").join("|") + "(\\b|$)", "gi"), " " );
		}
	}

	/**
	 * IE8 compatible function for adding a class
	 *
	 * @param  {object} element   - The DOM element we want to manipulate
	 * @param  {object} className - The name of the class to be added
	 */
	addClass( element, className ) {
		if( element.classList ) {
			element.classList.add( className );
		}
		else {
			element.className = element.className + " " + className;
		}
	}

	/**
	 * auFocusTrapListenerTop - What happens when the focus trap top gets focus
	 *
	 * @param {event} event - the event
	 */
	auFocusTrapListenerTop( event ) {
		var focusContent = this.mainNavMenu.querySelectorAll( 'a, .au-main-nav__toggle' );
		focusContent[ focusContent.length - 1 ].focus();
	}

	/**
	 * auFocusTrapListenerBottom - What happens when the focus trap bottom gets focus
	 *
	 * @param {event} event - the event
	 */
	auFocusTrapListenerBottom( event ) {
		var focusContent = this.mainNavMenu.querySelectorAll( 'a, .au-main-nav__toggle' );
		focusContent[ 0 ].focus();
	}

	/**
	 * auKeyListener - What happens when a key gets pressed and the menu is open
	 *
	 * @param {event} event - the event
	 */
	auKeyListener( event ) {
		// This should only close the menu when the menu is visibly open
		var overlayOpen = window.getComputedStyle( this.mainNavOverlay ).getPropertyValue( 'display' );

		// Check the menu is open and visible and the escape key is pressed
		if( event.keyCode === 27 && overlayOpen === 'block' ) {
			this.toggle( event );
		}
	}

	/**
	 * Toggle a mainNav element
	 *
	 * @param  {string}  element  - The toggle for the main nav
	 * @param  {integer} speed    - The speed in ms for the animation
	 *
	 */
	mainNavToggle( element, speed, callbacks ) {
		// stop event propagation
		try {
			window.event.cancelBubble = true;
			event.stopPropagation();
		}
		catch( error ) {}

		// check this once
		if( typeof callbacks != 'object' ) {
			callbacks = {};
		}

		// Elements we modify
		var menu            = this.mainNavMenu;
		var overlay         = this.mainNavOverlay;
		var closeButton     = element.querySelector( '.au-main-nav__toggle--close' );
		var openButton      = element.querySelector( '.au-main-nav__toggle--open' );
		var focustrapTop    = element.querySelector( '.au-main-nav__focus-trap-top' );
		var focustrapBottom = element.querySelector( '.au-main-nav__focus-trap-bottom' );

		var closed          = !this.state.closed;
		var state           = closed ? 'opening' : '';
		var menuWidth       = menu.offsetWidth;

		// Functions
		var ToggleClasses             = this.toggleClasses;
		var AUkeyListener             = this.auKeyListener;
		var AUfocusTrapListenerTop    = this.auFocusTrapListenerTop;
		var AUfocusTrapListenerBottom = this.auFocusTrapListenerBottom;
		var ToggleAnimationState      = this.toggleAnimationState;

		overlay.style.display = 'block';

		(function( menu ) {
			AU.animate.Toggle({
				element: menu,
				property: 'left',
				openSize: 0,
				closeSize: -1 * menuWidth,
				speed: speed || 250,
				prefunction: function( ) {
					// Set these value immediately for transitions
					if( state === 'opening' ) {
						menu.style.display    = 'block';
						overlay.style.left    = 0;
						overlay.style.opacity = 0.5;

						// run when opening
						if( typeof callbacks.onOpen === 'function' ) {
							callbacks.onOpen();
						}
					}
					else {
						overlay.style.opacity = '0';

						// run when closing
						if( typeof callbacks.onClose === 'function' ) {
							callbacks.onClose();
						}
					}
				},
				postfunction: function(){
					if ( state === 'opening' ){

						// Move the focus to the close button
						closeButton.focus();

						// Focus trap enabled
						focustrapTop.setAttribute( "tabindex", 0 );
						focustrapBottom.setAttribute( "tabindex", 0 );

						focustrapTop.addEventListener( 'focus', AUfocusTrapListenerTop );
						focustrapBottom.addEventListener( 'focus', AUfocusTrapListenerBottom );

						// Add key listener
						document.addEventListener( 'keyup', AUkeyListener );

						if( typeof callbacks.afterOpen === 'function' ) {
							callbacks.afterOpen();
						}
					}
					else {
						// Move the focus back to the menu button
						openButton.focus();

						// Remove the focus trap
						focustrapTop.removeAttribute( "tabindex" );
						focustrapBottom.removeAttribute( "tabindex" );

						// Remove the event listeners
						focustrapTop.removeEventListener( 'focus', AUfocusTrapListenerTop );
						focustrapBottom.removeEventListener( 'focus', AUfocusTrapListenerBottom );

						// Remove the event listener for the keypress
						document.removeEventListener( 'keyup', AUkeyListener );

						if( typeof callbacks.afterClose === 'function' ) {
							callbacks.afterClose();
						}
					}

					// Toggle classes
					ToggleClasses( element, state );
					ToggleClasses(
						document.body,
						state,
						'au-main-nav__scroll--unlocked',
						'au-main-nav__scroll--locked',
					);

					// Reset inline styles
					menu.style.display    = '';
					menu.style.left       = '';
					overlay.style.display = '';
					overlay.style.left    = '';
					overlay.style.opacity = '';

					// Toggle the animating state to false so that we can allow toggling again
					ToggleAnimationState();
				},
			});
		})( menu );
	}

	render(){
		return (

 { this.mainNavContent = mainNavContent }}>

					<button
						aria-controls={ this.id }
						aria-expanded={this.state.expanded}
						onClick={ ( event ) => this.toggle( event ) }
						className="au-main-nav__toggle au-main-nav__toggle--open">
						Menu
					</button>

 { this.mainNavMenu = mainNavMenu }}
					>

						<button
							aria-controls={ this.id }
							onClick={ ( event ) => this.toggle( event ) }
							className="au-main-nav__toggle au-main-nav__toggle--close">
							Close
						</button>
						<AUmainNavMenu
							linkComponent={ this.props.linkComponent }
							items={ this.props.items }
						/>
						{ this.props.children }

 { this.mainNavOverlay = mainNavOverlay }}
					onClick={ ( event ) => this.toggle( event ) }
					className="au-main-nav__overlay">

		);
	}
};

AUmainNavContent.propTypes = {
	dark: PropTypes.bool,
	alt: PropTypes.bool,
	children: PropTypes.node,
	speed: PropTypes.number,
	onOpen: PropTypes.func,
	afterOpen: PropTypes.func,
	onClose: PropTypes.func,
	afterClose: PropTypes.func,
	className: PropTypes.string,
};

const AUmainNav = ({ dark, alt, className, ariaLabel, children, ...attributeOptions }) => (
	<nav
		className={
			'au-main-nav ' +
			`${ dark ? ' au-main-nav--dark' : '' }` +
			`${ alt ? ' au-main-nav--alt' : '' }` +
			`${ className ? ' ' + className : '' }`
		}
		aria-label = { ariaLabel }
		{ ...attributeOptions }
	>
		{ children }

);

AUmainNav.propTypes = {
	dark: PropTypes.bool,
	alt: PropTypes.bool,
	ariaLabel: PropTypes.string,
	children: PropTypes.node.isRequired,
};

AUmainNav.defaultProps = {
	ariaLabel: 'main',
};

export default AUmainNav;
<|endoftext|>