async function downloadAndUnpackResource(uri, destPath, callback) {
  // If HTTPS_PROXY, https_proxy, HTTP_PROXY, or http_proxy is set
  const proxy = process.env['HTTPS_PROXY'] || process.env['https_proxy'] ||
      process.env['HTTP_PROXY'] || process.env['http_proxy'] || '';

  // Using object destructuring to construct the options object for the
  // http request.  the '...url.parse(targetUri)' part fills in the host,
  // path, protocol, etc from the targetUri and then we set the agent to the
  // default agent which is overridden a few lines down if there is a proxy
  const options = {...url.parse(uri), agent: https.globalAgent};

  if (proxy !== '') {
    options.agent = new HttpsProxyAgent(proxy);
  }

  const request = https.get(uri, response => {
    const bar = new ProgressBar('[:bar] :rate/bps :percent :etas', {
      complete: '=',
      incomplete: ' ',
      width: 30,
      total: parseInt(response.headers['content-length'], 10)
    });

    if (uri.endsWith('.zip')) {
      // Save zip file to disk, extract, and delete the downloaded zip file.
      const tempFileName = path.join(__dirname, '_tmp.zip');
      const outputFile = fs.createWriteStream(tempFileName);

      response.on('data', chunk => bar.tick(chunk.length))
          .pipe(outputFile)
          .on('close', async () => {
            const zipFile = new zip(tempFileName);
            zipFile.extractAllTo(destPath, true /* overwrite */);

            await unlink(tempFileName);

            if (callback !== undefined) {
              callback();
            }
          });
    } else if (uri.endsWith('.tar.gz')) {
      response.on('data', chunk => bar.tick(chunk.length))
          .pipe(tar.x({C: destPath, strict: true}))
          .on('close', () => {
            if (callback !== undefined) {
              callback();
            }
          });
    } else {
      throw new Error(`Unsupported packed resource: ${uri}`);
    }
  });
  request.end();
}

Downloads and unpacks a given tarball or zip file at a given path.
@param {string} uri The path of the compressed file to download and extract.
@param {string} destPath The destination path for the compressed content.
@param {Function} callback Handler for when downloading and extraction is
complete.

Downloads and unpacks a given tarball or zip file at a given path .


function cssToAST(text, syntax, filename) {
  var string = JSON.stringify;
  var fileInfo = filename ? ' at ' + filename : '';
  var tree;

  try {
    tree = gonzales.parse(text, {syntax: syntax});
  } catch (e) {
    throw new Error('Parsing error' + fileInfo + ': ' + e.message);
  }

  // TODO: When can tree be undefined? <tg>
  if (typeof tree === 'undefined') {
    let message = `Undefined tree ${fileInfo}: ${string(text)} =>
                  ${string(tree)}`;
    throw new Error(format(message));
  }

  return tree;
}

Converts CSS string to AST.

@param {String} text CSS string
@param {String} [syntax] Syntax name (e.g., `scss`)
@param {String} [filename]
@returns {Array} AST


function detectInTree(tree, handlers) {
  var detectedOptions = {};
  // We walk across complete tree for each handler,
  // because we need strictly maintain order in which handlers work,
  // despite fact that handlers work on different level of the tree.
  handlers.forEach(function(handler) {
    detectedOptions[handler.name] = handler.detect(tree);
  });
  return detectedOptions;
}

Processes tree and detects options.

@param {Array} tree
@param {Array} handlers List of options that we should look for
@returns {Object} Map with detected options and all variants of possible
values


function getDetectedOptions(detected, handlers) {
  var options = {};
  Object.keys(detected).forEach(function(option) {
    // List of all the detected variants from the stylesheet
    // for the given option:
    var values = detected[option];
    var i;
    if (!values.length) {
      // If there are no values for the option, check if there is
      // a default one:
      for (i = handlers.length; i--;) {
        if (handlers[i].name === option &&
            handlers[i].detectDefault !== undefined) {
          options[option] = handlers[i].detectDefault;
          break;
        }
      }
    } else if (values.length === 1) {
      options[option] = values[0];
    } else {
      // If there are more than one value for the option, find
      // the most popular one; `variants` would be populated
      // with the popularity for different values.
      var variants = {};
      var bestGuess = null;
      var maximum = 0;
      for (i = values.length; i--;) {
        var currentValue = values[i];
        // Count the current value:
        if (variants[currentValue]) {
          variants[currentValue]++;
        } else {
          variants[currentValue] = 1;
        }
        // If the current variant is the most popular one, treat
        // it as the best guess:
        if (variants[currentValue] >= maximum) {
          maximum = variants[currentValue];
          bestGuess = currentValue;
        }
      }
      if (bestGuess !== null) {
        options[option] = bestGuess;
      }
    }
  });

  return options;
}

Gets the detected options.

@param {Object} detected
@param {Array} handlers
@returns {Object}


function getHandler(optionName) {
  var option = require('./options/' + optionName);
  if (!option.detect)
    throw new Error('Option does not have `detect()` method.');

  return {
    name: option.name,
    detect: option.detect,
    detectDefault: option.detectDefault
  };
}

Gets option's data needed for detection

@param {String} optionName
@returns {Object} Object with option's name, link to `detect()` method
and default value for the case when nothing can be detected


function isEmptyBlock(node) {
    if (!node.length) return true;

    return !node.content.some(function(node) {
      return !node.is('space');
    });
  }

function isEmptyBlock ( node ) { if ( ! node . length ) return true ; return ! node . content . some ( function ( node ) { return ! node . is ( 'space' ) ; } ) ; }

Block is considered empty when it has nothing but spaces.

Block is considered empty when it has nothing but spaces .


function getPrefixInfo(propertyName, namespace, extraSymbols) {
    var baseName = propertyName;
    var prefixLength = 0;

    namespace = namespace || '';
    extraSymbols = extraSymbols || 0;

    if (!propertyName) return;

    PREFIXES.some(function(prefix) {
      prefix = '-' + prefix + '-';
      if (propertyName.indexOf(prefix) !== 0) return;

      baseName = baseName.substr(prefix.length);
      prefixLength = prefix.length;

      return true;
    });

    return {
      id: namespace + baseName,
      baseName: baseName,
      prefixLength: prefixLength,
      extra: extraSymbols
    };
  }

function getPrefixInfo ( propertyName , namespace , extraSymbols ) { var baseName = propertyName ; var prefixLength = 0 ; namespace = namespace || '' ; extraSymbols = extraSymbols || 0 ; if ( ! propertyName ) return ; PREFIXES . some ( function ( prefix ) { prefix = '-' + prefix + '-' ; if ( propertyName . indexOf ( prefix ) !== 0 ) return ; baseName = baseName . substr ( prefix . length ) ; prefixLength = prefix . length ; return true ; } ) ; return { id : namespace + baseName , baseName : baseName , prefixLength : prefixLength , extra : extraSymbols } ; }

Creates object which contains info about vendor prefix used
in propertyName.

@param {String} propertyName property name
@param {String} [namespace=''] namespace name
@param {Number} [extraSymbols=0] extra symbols count
@returns {Object|undefined}

Creates object which contains info about vendor prefix used in propertyName .


function extraIndent(nodes) {
    if (!nodes || !nodes.length) return;

    var node;
    var crPos;
    var tabPos;
    var result = 0;

    for (var i = nodes.length; i--;) {
      node = nodes[i];

      if (!node.content) {
        crPos = -1;
      } else {
        crPos = node.content.lastIndexOf('\n');
        tabPos = node.content.lastIndexOf('\t');
        if (tabPos > crPos) crPos = tabPos;
      }

      if (crPos !== -1)
        oneline = false;

      if (node.is('space')) {
        result += node.content.length - crPos - 1;
        if (crPos !== -1)
          break;
      }
      if (node.is('multilineComment')) {
        if (crPos === -1) {
          // Comment symbols length
          let offset = 4;
          result += node.content.length + offset;
        } else {
          // Only last comment symbols length - 1 (not count \n)
          let offset = crPos - 1;
          result += node.content.length - offset;
          break;
        }
      }
    }

    return result;
  }

Returns extra indent for item in arguments

@param {Array} nodes nodes to process
@returns {Number|undefined}


function extraIndentProperty(nodes, i) {
    var subset = [];
    while (i--) {
      if (!nodes.get(i) || nodes.get(i).is('declarationDelimiter'))
        break;
      subset.unshift(nodes.get(i));
    }
    return extraIndent(subset);
  }

function extraIndentProperty ( nodes , i ) { var subset = [ ] ; while ( i -- ) { if ( ! nodes . get ( i ) || nodes . get ( i ) . is ( 'declarationDelimiter' ) ) break ; subset . unshift ( nodes . get ( i ) ) ; } return extraIndent ( subset ) ; }

Wrapper for extra indent function for `property` node.

@param {Array} nodes all nodes
@param {Number} i position in nodes array

Wrapper for extra indent function for property node .


function extraIndentVal(nodes, i) {
    var subset = [];
    var declaration = nodes.get(i);
    if (!declaration.is('declaration')) return;

    for (var x = declaration.length; x--;) {
      if (!declaration.get(x).is('value')) continue;

      x--;

      while (!declaration.get(x).is('propertyDelimiter')) {
        subset.push(declaration.get(x));
        x--;
      }

      break;
    }
    return extraIndent(subset);
  }

function extraIndentVal ( nodes , i ) { var subset = [ ] ; var declaration = nodes . get ( i ) ; if ( ! declaration . is ( 'declaration' ) ) return ; for ( var x = declaration . length ; x -- ; ) { if ( ! declaration . get ( x ) . is ( 'value' ) ) continue ; x -- ; while ( ! declaration . get ( x ) . is ( 'propertyDelimiter' ) ) { subset . push ( declaration . get ( x ) ) ; x -- ; } break ; } return extraIndent ( subset ) ; }

Wrapper for extra indent function for val-node.

@param {Array} nodes all nodes
@param {Number} i position in nodes array

Wrapper for extra indent function for val - node .


function walk(args) {
    args.node.forEach(function(item, i) {
      var name = args.selector(item);
      var namespace = args.namespaceSelector &&
                      makeNamespace(args.namespaceSelector(item));
      var extraSymbols = args.getExtraSymbols(args.node, i);

      var info = name && getPrefixInfo(name, namespace, extraSymbols);
      if (!info) return;
      args.payload(info, i);
    });
  }

function walk ( args ) { args . node . forEach ( function ( item , i ) { var name = args . selector ( item ) ; var namespace = args . namespaceSelector && makeNamespace ( args . namespaceSelector ( item ) ) ; var extraSymbols = args . getExtraSymbols ( args . node , i ) ; var info = name && getPrefixInfo ( name , namespace , extraSymbols ) ; if ( ! info ) return ; args . payload ( info , i ) ; } ) ; }

Walks across nodes, and call payload for every node that pass
selector check.

@param {Object} args arguments in form of:
{
node: {object} current node,
selector: {function} propertyName selector
payload: {function} work to do with gathered info
namespaceSelector: {function} selector for namespace
getExtraSymbols: {Number} extra symbols count
}

Walks across nodes and call payload for every node that pass selector check .


function updateDict(info, dict) {
    if (info.prefixLength === 0 && info.extra === 0) return;

    var indent = dict[info.id] || {prefixLength: 0, extra: 0};

    let indentLength = indent.prefixLength + indent.extra;
    let infoLength = info.prefixLength + info.extra;
    if (indentLength > infoLength) {
      dict[info.id] = indent;
    } else {
      dict[info.id] = {
        prefixLength: info.prefixLength,
        extra: info.extra
      };
    }
  }

function updateDict ( info , dict ) { if ( info . prefixLength === 0 && info . extra === 0 ) return ; var indent = dict [ info . id ] || { prefixLength : 0 , extra : 0 } ; let indentLength = indent . prefixLength + indent . extra ; let infoLength = info . prefixLength + info . extra ; if ( indentLength > infoLength ) { dict [ info . id ] = indent ; } else { dict [ info . id ] = { prefixLength : info . prefixLength , extra : info . extra } ; } }

Updates dict which contains info about items align.

@param {Object} info
@param {Object} dict

Updates dict which contains info about items align .


function updateIndent(info, dict, whitespaceNode) {
    var item = dict[info.id];
    if (!item)
        return whitespaceNode;

    var crPos = whitespaceNode.lastIndexOf('\n');
    var tabPos = whitespaceNode.lastIndexOf('\t');
    if (tabPos > crPos) crPos = tabPos;

    var firstPart = whitespaceNode.substr(0, crPos + 1);
    var extraIndent = new Array(
        (item.prefixLength - info.prefixLength) +
        (item.extra - info.extra) +
        whitespaceNode.length - firstPart.length +
        1).join(' ');

    return firstPart.concat(extraIndent);
  }

function
updateIndent
(
info
,
dict
,
whitespaceNode
)
{
var
item
=
dict
[
info
.
id
]
;
if
(
!
item
)
return
whitespaceNode
;
var
crPos
=
whitespaceNode
.
lastIndexOf
(
'\n'
)
;
var
tabPos
=
whitespaceNode
.
lastIndexOf
(
'\t'
)
;
if
(
tabPos
>
crPos
)
crPos
=
tabPos
;
var
firstPart
=
whitespaceNode
.
substr
(
0
,
crPos
+
1
)
;
var
extraIndent
=
new
Array
(
(
item
.
prefixLength
info
.
prefixLength
)
+
(
item
.
extra
info
.
extra
)
+
whitespaceNode
.
length
firstPart
.
length
+
1
)
.
join
(
' '
)
;
return
firstPart
.
concat
(
extraIndent
)
;
}

Returns string with correct number of spaces for info.baseName property.

@param {Object} info
@param {Object} dict
@param {String} whitespaceNode
@returns {String}

Returns string with correct number of spaces for info . baseName property .


function animate() {
    controls.update();
    renderer.render(scene, camera);

    controls1.update();
    renderer1.render(scene1, camera1);

    // request new frame
    requestAnimationFrame(function() {
        animate();
    });
}

function
animate
(
)
{
controls
.
update
(
)
;
renderer
.
render
(
scene
,
camera
)
;
controls1
.
update
(
)
;
renderer1
.
render
(
scene1
,
camera1
)
;
// request new frame
requestAnimationFrame
(
function
(
)
{
animate
(
)
;
}
)
;
}


function(vertex, face) {
      vertex.face = face;

      if (face.outside === null) {
        this.assigned.append(vertex);
      } else {
        this.assigned.insertBefore(face.outside, vertex);
      }

      face.outside = vertex;

      return this;
    }

function ( vertex , face ) { vertex . face = face ; if ( face . outside === null ) { this . assigned . append ( vertex ) ; } else { this . assigned . insertBefore ( face . outside , vertex ) ; } face . outside = vertex ; return this ; }

Adds a vertex to the 'assigned' list of vertices and assigns it to the given face

Adds a vertex to the assigned list of vertices and assigns it to the given face


function(vertex, face) {
      if (vertex === face.outside) {
        // fix face.outside link

        if (vertex.next !== null && vertex.next.face === face) {
          // face has at least 2 outside vertices, move the 'outside' reference

          face.outside = vertex.next;
        } else {
          // vertex was the only outside vertex that face had

          face.outside = null;
        }
      }

      this.assigned.remove(vertex);

      return this;
    }

Removes a vertex from the 'assigned' list of vertices and from the given face

Removes a vertex from the assigned list of vertices and from the given face


function(face) {
      if (face.outside !== null) {
        // reference to the first and last vertex of this face

        var start = face.outside;
        var end = face.outside;

        while (end.next !== null && end.next.face === face) {
          end = end.next;
        }

        this.assigned.removeSubList(start, end);

        // fix references

        start.prev = end.next = null;
        face.outside = null;

        return start;
      }
    }

Removes all the visible vertices that a given face is able to see which are stored in the 'assigned' vertext list

Removes all the visible vertices that a given face is able to see which are stored in the assigned vertext list


function(face, absorbingFace) {
      var faceVertices = this.removeAllVerticesFromFace(face);

      if (faceVertices !== undefined) {
        if (absorbingFace === undefined) {
          // mark the vertices to be reassigned to some other face

          this.unassigned.appendChain(faceVertices);
        } else {
          // if there's an absorbing face try to assign as many vertices as possible to it

          var vertex = faceVertices;

          do {
            // we need to buffer the subsequent vertex at this point because the 'vertex.next' reference
            // will be changed by upcoming method calls

            var nextVertex = vertex.next;

            var distance = absorbingFace.distanceToPoint(vertex.point);

            // check if 'vertex' is able to see 'absorbingFace'

            if (distance > this.tolerance) {
              this.addVertexToFace(vertex, absorbingFace);
            } else {
              this.unassigned.append(vertex);
            }

            // now assign next vertex

            vertex = nextVertex;
          } while (vertex !== null);
        }
      }

      return this;
    }

Removes all the visible vertices that 'face' is able to see

Removes all the visible vertices that face is able to see


function(newFaces) {
      if (this.unassigned.isEmpty() === false) {
        var vertex = this.unassigned.first();

        do {
          // buffer 'next' reference, see .deleteFaceVertices()

          var nextVertex = vertex.next;

          var maxDistance = this.tolerance;

          var maxFace = null;

          for (var i = 0; i < newFaces.length; i++) {
            var face = newFaces[i];

            if (face.mark === Visible) {
              var distance = face.distanceToPoint(vertex.point);

              if (distance > maxDistance) {
                maxDistance = distance;
                maxFace = face;
              }

              if (maxDistance > 1000 * this.tolerance) break;
            }
          }

          // 'maxFace' can be null e.g. if there are identical vertices

          if (maxFace !== null) {
            this.addVertexToFace(vertex, maxFace);
          }

          vertex = nextVertex;
        } while (vertex !== null);
      }

      return this;
    }

Reassigns as many vertices as possible from the unassigned list to the new faces


function() {
      var min = new THREE.Vector3();
      var max = new THREE.Vector3();

      var minVertices = [];
      var maxVertices = [];

      var i, l, j;

      // initially assume that the first vertex is the min/max

      for (i = 0; i < 3; i++) {
        minVertices[i] = maxVertices[i] = this.vertices[0];
      }

      min.copy(this.vertices[0].point);
      max.copy(this.vertices[0].point);

      // compute the min/max vertex on all six directions

      for (i = 0, l = this.vertices.length; i < l; i++) {
        var vertex = this.vertices[i];
        var point = vertex.point;

        // update the min coordinates

        for (j = 0; j < 3; j++) {
          if (point.getComponent(j) < min.getComponent(j)) {
            min.setComponent(j, point.getComponent(j));
            minVertices[j] = vertex;
          }
        }

        // update the max coordinates

        for (j = 0; j < 3; j++) {
          if (point.getComponent(j) > max.getComponent(j)) {
            max.setComponent(j, point.getComponent(j));
            maxVertices[j] = vertex;
          }
        }
      }

      // use min/max vectors to compute an optimal epsilon

      this.tolerance =
        3 *
        Number.EPSILON *
        (Math.max(Math.abs(min.x), Math.abs(max.x)) +
          Math.max(Math.abs(min.y), Math.abs(max.y)) +
          Math.max(Math.abs(min.z), Math.abs(max.z)));

      return { min: minVertices, max: maxVertices };
    }

Computes the extremes of a simplex which will be the initial hull


function() {
      var activeFaces = [];

      for (var i = 0; i < this.faces.length; i++) {
        var face = this.faces[i];

        if (face.mark === Visible) {
          activeFaces.push(face);
        }
      }

      this.faces = activeFaces;

      return this;
    }

function ( ) { var activeFaces = [ ] ; for ( var i = 0 ; i < this . faces . length ; i ++ ) { var face = this . faces [ i ] ; if ( face . mark === Visible ) { activeFaces . push ( face ) ; } } this . faces = activeFaces ; return this ; }


function() {
      // if the 'assigned' list of vertices is empty, no vertices are left. return with 'undefined'

      if (this.assigned.isEmpty() === false) {
        var eyeVertex,
          maxDistance = 0;

        // grap the first available face and start with the first visible vertex of that face

        var eyeFace = this.assigned.first().face;
        var vertex = eyeFace.outside;

        // now calculate the farthest vertex that face can see

        do {
          var distance = eyeFace.distanceToPoint(vertex.point);

          if (distance > maxDistance) {
            maxDistance = distance;
            eyeVertex = vertex;
          }

          vertex = vertex.next;
        } while (vertex !== null && vertex.face === eyeFace);

        return eyeVertex;
      }
    }

Finds the next vertex to create faces with the current hull


function(eyePoint, crossEdge, face, horizon) {
      // moves face's vertices to the 'unassigned' vertex list

      this.deleteFaceVertices(face);

      face.mark = Deleted;

      var edge;

      if (crossEdge === null) {
        edge = crossEdge = face.getEdge(0);
      } else {
        // start from the next edge since 'crossEdge' was already analyzed
        // (actually 'crossEdge.twin' was the edge who called this method recursively)

        edge = crossEdge.next;
      }

      do {
        var twinEdge = edge.twin;
        var oppositeFace = twinEdge.face;

        if (oppositeFace.mark === Visible) {
          if (oppositeFace.distanceToPoint(eyePoint) > this.tolerance) {
            // the opposite face can see the vertex, so proceed with next edge

            this.computeHorizon(eyePoint, twinEdge, oppositeFace, horizon);
          } else {
            // the opposite face can't see the vertex, so this edge is part of the horizon

            horizon.push(edge);
          }
        }

        edge = edge.next;
      } while (edge !== crossEdge);

      return this;
    }

Computes a chain of half edges in CCW order called the 'horizon'. For an edge to be part of the horizon it must join a face that can see 'eyePoint' and a face that cannot see 'eyePoint'.

Computes a chain of half edges in CCW order called the horizon . For an edge to be part of the horizon it must join a face that can see eyePoint and a face that cannot see eyePoint .


function(eyeVertex, horizonEdge) {
      // all the half edges are created in ccw order thus the face is always pointing outside the hull

      var face = Face.create(eyeVertex, horizonEdge.tail(), horizonEdge.head());

      this.faces.push(face);

      // join face.getEdge( - 1 ) with the horizon's opposite edge face.getEdge( - 1 ) = face.getEdge( 2 )

      face.getEdge(-1).setTwin(horizonEdge.twin);

      return face.getEdge(0); // the half edge whose vertex is the eyeVertex
    }

Creates a face with the vertices 'eyeVertex.point', 'horizonEdge.tail' and 'horizonEdge.head' in CCW order

Creates a face with the vertices eyeVertex . point horizonEdge . tail and horizonEdge . head in CCW order


function(eyeVertex) {
      var horizon = [];

      this.unassigned.clear();

      // remove 'eyeVertex' from 'eyeVertex.face' so that it can't be added to the 'unassigned' vertex list

      this.removeVertexFromFace(eyeVertex, eyeVertex.face);

      this.computeHorizon(eyeVertex.point, null, eyeVertex.face, horizon);

      this.addNewFaces(eyeVertex, horizon);

      // reassign 'unassigned' vertices to the new faces

      this.resolveUnassignedPoints(this.newFaces);

      return this;
    }


function VertexNode(point) {
    this.point = point;
    this.prev = null;
    this.next = null;
    this.face = null; // the face that is able to see this vertex
  }


function(target, vertex) {
      vertex.prev = target.prev;
      vertex.next = target;

      if (vertex.prev === null) {
        this.head = vertex;
      } else {
        vertex.prev.next = vertex;
      }

      target.prev = vertex;

      return this;
    }

function ( target , vertex ) { vertex . prev = target . prev ; vertex . next = target ; if ( vertex . prev === null ) { this . head = vertex ; } else { vertex . prev . next = vertex ; } target . prev = vertex ; return this ; }

Inserts a vertex before the target vertex


function(target, vertex) {
      vertex.prev = target;
      vertex.next = target.next;

      if (vertex.next === null) {
        this.tail = vertex;
      } else {
        vertex.next.prev = vertex;
      }

      target.next = vertex;

      return this;
    }

function ( target , vertex ) { vertex . prev = target ; vertex . next = target . next ; if ( vertex . next === null ) { this . tail = vertex ; } else { vertex . next . prev = vertex ; } target . next = vertex ; return this ; }

Inserts a vertex after the target vertex


function(vertex) {
      if (this.head === null) {
        this.head = vertex;
      } else {
        this.tail.next = vertex;
      }

      vertex.prev = this.tail;
      vertex.next = null; // the tail has no subsequent vertex

      this.tail = vertex;

      return this;
    }

function
(
vertex
)
{
if
(
this
.
head
===
)
{
this
.
head
=
vertex
;
}
else
{
this
.
tail
.
next
=
vertex
;
}
vertex
.
prev
=
this
.
tail
;
vertex
.
next
=
;
// the tail has no subsequent vertex
this
.
tail
=
vertex
;
return
this
;
}

Appends a vertex to the end of the linked list


function(vertex) {
      if (vertex.prev === null) {
        this.head = vertex.next;
      } else {
        vertex.prev.next = vertex.next;
      }

      if (vertex.next === null) {
        this.tail = vertex.prev;
      } else {
        vertex.next.prev = vertex.prev;
      }

      return this;
    }

function ( vertex ) { if ( vertex . prev === null ) { this . head = vertex . next ; } else { vertex . prev . next = vertex . next ; } if ( vertex . next === null ) { this . tail = vertex . prev ; } else { vertex . next . prev = vertex . prev ; } return this ; }


function(a, b) {
      if (a.prev === null) {
        this.head = b.next;
      } else {
        a.prev.next = b.next;
      }

      if (b.next === null) {
        this.tail = a.prev;
      } else {
        b.next.prev = a.prev;
      }

      return this;
    }

function ( a , b ) { if ( a . prev === null ) { this . head = b . next ; } else { a . prev . next = b . next ; } if ( b . next === null ) { this . tail = a . prev ; } else { b . next . prev = a . prev ; } return this ; }

Removes a list of vertices whose 'head' is 'a' and whose 'tail' is b

Removes a list of vertices whose head is a and whose tail is b


function onWindowResize() {
    camera.aspect = container.offsetWidth / container.offsetHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(container.offsetWidth, container.offsetHeight);
}

function onWindowResize ( ) { camera . aspect = container . offsetWidth / container . offsetHeight ; camera . updateProjectionMatrix ( ) ; renderer . setSize ( container . offsetWidth , container . offsetHeight ) ; }


function info(msg) {
  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.infos) {
    console.log('Info: ' + msg);
  }
}

function
info
(
msg
)
{
if
(
PDFJS
.
verbosity
>=
PDFJS
.
VERBOSITY_LEVELS
.
infos
)
{
console
.
log
(
'Info: '
+
msg
)
;
}
}

A notice for devs. These are good for things that are helpful to devs, such as warning that Workers were disabled, which is important to devs but not end users.

A notice for devs . These are good for things that are helpful to devs such as warning that Workers were disabled which is important to devs but not end users .


function warn(msg) {
  if (PDFJS.verbosity >= PDFJS.VERBOSITY_LEVELS.warnings) {
    console.log('Warning: ' + msg);
  }
}

function
warn
(
msg
)
{
if
(
PDFJS
.
verbosity
>=
PDFJS
.
VERBOSITY_LEVELS
.
warnings
)
{
console
.
log
(
'Warning: '
+
msg
)
;
}
}


function error(msg) {
  // If multiple arguments were passed, pass them all to the log function.
  if (arguments.length > 1) {
    var logArguments = ['Error:'];
    logArguments.push.apply(logArguments, arguments);
    console.log.apply(console, logArguments);
    // Join the arguments into a single string for the lines below.
    msg = [].join.call(arguments, ' ');
  } else {
    console.log('Error: ' + msg);
  }
  console.log(backtrace());
  UnsupportedManager.notify(UNSUPPORTED_FEATURES.unknown);
  throw new Error(msg);
}

Fatal errors that should trigger the fallback UI and halt execution by throwing an exception.

Fatal errors that should trigger the fallback UI and halt execution by throwing an exception .


function combineUrl(baseUrl, url) {
  if (!url) {
    return baseUrl;
  }
  if (/^[a-z][a-z0-9+\-.]*:/i.test(url)) {
    return url;
  }
  var i;
  if (url.charAt(0) === '/') {
    // absolute path
    i = baseUrl.indexOf('://');
    if (url.charAt(1) === '/') {
      ++i;
    } else {
      i = baseUrl.indexOf('/', i + 3);
    }
    return baseUrl.substring(0, i) + url;
  } else {
    // relative path
    var pathLength = baseUrl.length;
    i = baseUrl.lastIndexOf('#');
    pathLength = i >= 0 ? i : pathLength;
    i = baseUrl.lastIndexOf('?', pathLength);
    pathLength = i >= 0 ? i : pathLength;
    var prefixLength = baseUrl.lastIndexOf('/', pathLength);
    return baseUrl.substring(0, prefixLength + 1) + url;
  }
}

function
combineUrl
(
baseUrl
,
url
)
{
if
(
!
url
)
{
return
baseUrl
;
}
if
(
/
^[a-z][a-z0-9+\-.]*:
/
i
.
test
(
url
)
)
{
return
url
;
}
var
i
;
if
(
url
.
charAt
(
0
)
===
'/'
)
{
// absolute path
i
=
baseUrl
.
indexOf
(
'://'
)
;
if
(
url
.
charAt
(
1
)
===
'/'
)
{
++
i
;
}
else
{
i
=
baseUrl
.
indexOf
(
'/'
,
i
+
3
)
;
}
return
baseUrl
.
substring
(
0
,
i
)
+
url
;
}
else
{
// relative path
var
pathLength
=
baseUrl
.
length
;
i
=
baseUrl
.
lastIndexOf
(
'#'
)
;
pathLength
=
i
>=
0
i
:
pathLength
;
i
=
baseUrl
.
lastIndexOf
(
'?'
,
pathLength
)
;
pathLength
=
i
>=
0
i
:
pathLength
;
var
prefixLength
=
baseUrl
.
lastIndexOf
(
'/'
,
pathLength
)
;
return
baseUrl
.
substring
(
0
,
prefixLength
+
1
)
+
url
;
}
}

Combines two URLs. The baseUrl shall be absolute URL. If the url is an absolute URL, it will be returned as is.

Combines two URLs . The baseUrl shall be absolute URL . If the url is an absolute URL it will be returned as is .


function PageViewPort_clone(args) {
      args = args || {};
      var scale = 'scale' in args ? args.scale : this.scale;
      var rotation = 'rotation' in args ? args.rotation : this.rotation;
      return new PageViewport(this.viewBox.slice(), scale, rotation,
                              this.offsetX, this.offsetY, args.dontFlip);
    }

function PageViewPort_clone ( args ) { args = args || { } ; var scale = 'scale' in args ? args . scale : this . scale ; var rotation = 'rotation' in args ? args . rotation : this . rotation ; return new PageViewport ( this . viewBox . slice ( ) , scale , rotation , this . offsetX , this . offsetY , args . dontFlip ) ; }

Clones viewport with additional properties.
@param args {Object} (optional) If specified, may contain the 'scale' or
'rotation' properties to override the corresponding properties in
the cloned viewport.
@returns {PDFJS.PageViewport} Cloned viewport.

Clones viewport with additional properties .


function PageViewport_convertToViewportRectangle(rect) {
      var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
      var br = Util.applyTransform([rect[2], rect[3]], this.transform);
      return [tl[0], tl[1], br[0], br[1]];
    }

function PageViewport_convertToViewportRectangle ( rect ) { var tl = Util . applyTransform ( [ rect [ 0 ] , rect [ 1 ] ] , this . transform ) ; var br = Util . applyTransform ( [ rect [ 2 ] , rect [ 3 ] ] , this . transform ) ; return [ tl [ 0 ] , tl [ 1 ] , br [ 0 ] , br [ 1 ] ] ; }

Converts PDF rectangle to the viewport coordinates.
@param rect {Array} xMin, yMin, xMax and yMax coordinates.
@returns {Array} Contains corresponding coordinates of the rectangle
in the viewport coordinate space.
@see {@link convertToViewportPoint}

Converts PDF rectangle to the viewport coordinates .


function createPromiseCapability() {
  var capability = {};
  capability.promise = new Promise(function (resolve, reject) {
    capability.resolve = resolve;
    capability.reject = reject;
  });
  return capability;
}

function createPromiseCapability ( ) { var capability = { } ; capability . promise = new Promise ( function ( resolve , reject ) { capability . resolve = resolve ; capability . reject = reject ; } ) ; return capability ; }

Promise Capability object.

@typedef {Object} PromiseCapability
@property {Promise} promise - A promise object.
@property {function} resolve - Fullfills the promise.
@property {function} reject - Rejects the promise.

Creates a promise capability object.
@alias PDFJS.createPromiseCapability

@return {PromiseCapability} A capability object contains:
- a Promise, resolve and reject methods.


function messageHandlerSend(actionName, data, transfers) {
    var message = {
      action: actionName,
      data: data
    };
    this.postMessage(message, transfers);
  }

function messageHandlerSend ( actionName , data , transfers ) { var message = { action : actionName , data : data } ; this . postMessage ( message , transfers ) ; }

Sends a message to the comObj to invoke the action with the supplied data.
@param {String} actionName Action to call.
@param {JSON} data JSON data to send.
@param {Array} [transfers] Optional list of transfers/ArrayBuffers

Sends a message to the comObj to invoke the action with the supplied data .


function messageHandlerSendWithPromise(actionName, data, transfers) {
    var callbackId = this.callbackIndex++;
    var message = {
      action: actionName,
      data: data,
      callbackId: callbackId
    };
    var capability = createPromiseCapability();
    this.callbacksCapabilities[callbackId] = capability;
    try {
      this.postMessage(message, transfers);
    } catch (e) {
      capability.reject(e);
    }
    return capability.promise;
  }

function messageHandlerSendWithPromise ( actionName , data , transfers ) { var callbackId = this . callbackIndex ++ ; var message = { action : actionName , data : data , callbackId : callbackId } ; var capability = createPromiseCapability ( ) ; this . callbacksCapabilities [ callbackId ] = capability ; try { this . postMessage ( message , transfers ) ; } catch ( e ) { capability . reject ( e ) ; } return capability . promise ; }

Sends a message to the comObj to invoke the action with the supplied data.
Expects that other side will callback with the response.
@param {String} actionName Action to call.
@param {JSON} data JSON data to send.
@param {Array} [transfers] Optional list of transfers/ArrayBuffers.
@returns {Promise} Promise to be resolved with response data.

Sends a message to the comObj to invoke the action with the supplied data . Expects that other side will callback with the response .


function (message, transfers) {
    if (transfers && this.postMessageTransfers) {
      this.comObj.postMessage(message, transfers);
    } else {
      this.comObj.postMessage(message);
    }
  }

function ( message , transfers ) { if ( transfers && this . postMessageTransfers ) { this . comObj . postMessage ( message , transfers ) ; } else { this . comObj . postMessage ( message ) ; } }

Sends raw message to the comObj.
@private
@param message {Object} Raw message.
@param transfers List of transfers/ArrayBuffers, or undefined.


function hookCallbacks(stackHelper) {
    let stack = stackHelper._stack;
    // hook up callbacks
    controls.addEventListener('OnScroll', function(e) {
      if (e.delta > 0) {
        if (stackHelper.index >= stackHelper.orientationMaxIndex - 1) {
          return false;
        }
        stackHelper.index += 1;
      } else {
        if (stackHelper.index <= 0) {
          return false;
        }
        stackHelper.index -= 1;
      }
    });

    /**
     * On window resize callback
     */
    function onWindowResize() {
      let threeD = document.getElementById('r3d');
      camera.canvas = {
        width: threeD.clientWidth,
        height: threeD.clientHeight,
      };
      camera.fitBox(2);

      renderer.setSize(threeD.clientWidth, threeD.clientHeight);

      // update info to draw borders properly
      stackHelper.slice.canvasWidth = threeD.clientWidth;
      stackHelper.slice.canvasHeight = threeD.clientHeight;
    }

    window.addEventListener('resize', onWindowResize, false);
    onWindowResize();

    /**
     * On key pressed callback
     */
    function onWindowKeyPressed(event) {
      ctrlDown = event.ctrlKey;
      if (!ctrlDown) {
        drag.start.x = null;
        drag.start.y = null;
      }
    }
    document.addEventListener('keydown', onWindowKeyPressed, false);
    document.addEventListener('keyup', onWindowKeyPressed, false);

    /**
     * On mouse move callback
     */
    function onMouseMove(event) {
      if (ctrlDown) {
        if (drag.start.x === null) {
          drag.start.x = event.clientX;
          drag.start.y = event.clientY;
        }
        let threshold = 15;

        stackHelper.slice.intensityAuto = false;

        let dynamicRange = stack.minMax[1] - stack.minMax[0];
        dynamicRange /= threeD.clientWidth;

        if (Math.abs(event.clientX - drag.start.x) > threshold) {
          // window width
          stackHelper.slice.windowWidth += dynamicRange * (event.clientX - drag.start.x);
          drag.start.x = event.clientX;
        }

        if (Math.abs(event.clientY - drag.start.y) > threshold) {
          // window center
          stackHelper.slice.windowCenter -= dynamicRange * (event.clientY - drag.start.y);
          drag.start.y = event.clientY;
        }
      }
    }
    document.addEventListener('mousemove', onMouseMove);
  }

/**
     * On window resize callback
     */

/**
     * On key pressed callback
     */

/**
     * On mouse move callback
     */


function onWindowResize() {
      let threeD = document.getElementById('r3d');
      camera.canvas = {
        width: threeD.clientWidth,
        height: threeD.clientHeight,
      };
      camera.fitBox(2);

      renderer.setSize(threeD.clientWidth, threeD.clientHeight);

      // update info to draw borders properly
      stackHelper.slice.canvasWidth = threeD.clientWidth;
      stackHelper.slice.canvasHeight = threeD.clientHeight;
    }

function
onWindowResize
(
)
{
let
threeD
=
document
.
getElementById
(
'r3d'
)
;
camera
.
canvas
=
{
width
:
threeD
.
clientWidth
,
height
:
threeD
.
clientHeight
,
}
;
camera
.
fitBox
(
2
)
;
renderer
.
setSize
(
threeD
.
clientWidth
,
threeD
.
clientHeight
)
;
// update info to draw borders properly
stackHelper
.
slice
.
canvasWidth
=
threeD
.
clientWidth
;
stackHelper
.
slice
.
canvasHeight
=
threeD
.
clientHeight
;
}


function onWindowKeyPressed(event) {
      ctrlDown = event.ctrlKey;
      if (!ctrlDown) {
        drag.start.x = null;
        drag.start.y = null;
      }
    }

function onWindowKeyPressed ( event ) { ctrlDown = event . ctrlKey ; if ( ! ctrlDown ) { drag . start . x = null ; drag . start . y = null ; } }


function onMouseMove(event) {
      if (ctrlDown) {
        if (drag.start.x === null) {
          drag.start.x = event.clientX;
          drag.start.y = event.clientY;
        }
        let threshold = 15;

        stackHelper.slice.intensityAuto = false;

        let dynamicRange = stack.minMax[1] - stack.minMax[0];
        dynamicRange /= threeD.clientWidth;

        if (Math.abs(event.clientX - drag.start.x) > threshold) {
          // window width
          stackHelper.slice.windowWidth += dynamicRange * (event.clientX - drag.start.x);
          drag.start.x = event.clientX;
        }

        if (Math.abs(event.clientY - drag.start.y) > threshold) {
          // window center
          stackHelper.slice.windowCenter -= dynamicRange * (event.clientY - drag.start.y);
          drag.start.y = event.clientY;
        }
      }
    }

function
onMouseMove
(
event
)
{
if
(
ctrlDown
)
{
if
(
drag
.
start
.
x
===
)
{
drag
.
start
.
x
=
event
.
clientX
;
drag
.
start
.
y
=
event
.
clientY
;
}
let
threshold
=
15
;
stackHelper
.
slice
.
intensityAuto
=
false
;
let
dynamicRange
=
stack
.
minMax
[
1
]
stack
.
minMax
[
0
]
;
dynamicRange
/=
threeD
.
clientWidth
;
if
(
Math
.
abs
(
event
.
clientX
drag
.
start
.
x
)
>
threshold
)
{
// window width
stackHelper
.
slice
.
windowWidth
+=
dynamicRange
*
(
event
.
clientX
drag
.
start
.
x
)
;
drag
.
start
.
x
=
event
.
clientX
;
}
if
(
Math
.
abs
(
event
.
clientY
drag
.
start
.
y
)
>
threshold
)
{
// window center
stackHelper
.
slice
.
windowCenter
-=
dynamicRange
*
(
event
.
clientY
drag
.
start
.
y
)
;
drag
.
start
.
y
=
event
.
clientY
;
}
}
}


function handleSeries(seriesContainer) {
    // cleanup the loader and its progress bar
    loader.free();
    loader = null;
    // prepare for slice visualization
    // first stack of first series
    let stack = seriesContainer[0].mergeSeries(seriesContainer)[0].stack[0];

    let stackHelper = new HelpersStack(stack);
    stackHelper.bbox.visible = false;
    stackHelper.borderColor = '#2196F3';
    stackHelper.border.visible = false;
    scene.add(stackHelper);

    // set camera
    let worldbb = stack.worldBoundingBox();
    let lpsDims = new THREE.Vector3(
      (worldbb[1] - worldbb[0]) / 2,
      (worldbb[3] - worldbb[2]) / 2,
      (worldbb[5] - worldbb[4]) / 2
    );

    // box: {halfDimensions, center}
    let box = {
      center: stack.worldCenter().clone(),
      halfDimensions: new THREE.Vector3(lpsDims.x + 10, lpsDims.y + 10, lpsDims.z + 10),
    };

    // init and zoom
    let canvas = {
      width: threeD.clientWidth,
      height: threeD.clientHeight,
    };

    camera.directions = [stack.xCosine, stack.yCosine, stack.zCosine];
    camera.box = box;
    camera.canvas = canvas;
    camera.update();
    camera.fitBox(2);

    updateLabels(camera.directionsLabel, stack.modality);
    buildGUI(stackHelper);
    hookCallbacks(stackHelper);
  }


function loadSequenceGroup(files) {
      const fetchSequence = [];

      for (let i = 0; i < files.length; i++) {
        fetchSequence.push(
          new Promise((resolve, reject) => {
            const myReader = new FileReader();
            // should handle errors too...
            myReader.addEventListener('load', function(e) {
              resolve(e.target.result);
            });
            myReader.readAsArrayBuffer(files[i].file);
          }).then(function(buffer) {
            return { url: files[i].file.name, buffer };
          })
        );
      }

      return Promise.all(fetchSequence)
        .then(rawdata => {
          return loader.parse(rawdata);
        })
        .then(function(series) {
          seriesContainer.push(series);
        })
        .catch(function(error) {
          window.console.log('oops... something went wrong...');
          window.console.log(error);
        });
    }

function
loadSequenceGroup
(
files
)
{
const
fetchSequence
=
[
]
;
for
(
let
i
=
0
;
i
<
files
.
length
;
i
++
)
{
fetchSequence
.
push
(
new
Promise
(
(
resolve
,
reject
)
=>
{
const
myReader
=
new
FileReader
(
)
;
// should handle errors too...
myReader
.
addEventListener
(
'load'
,
function
(
e
)
{
resolve
(
e
.
target
.
result
)
;
}
)
;
myReader
.
readAsArrayBuffer
(
files
[
i
]
.
file
)
;
}
)
.
then
(
function
(
buffer
)
{
return
{
url
:
files
[
i
]
.
file
.
name
,
buffer
}
;
}
)
)
;
}
return
Promise
.
all
(
fetchSequence
)
.
then
(
rawdata
=>
{
return
loader
.
parse
(
rawdata
)
;
}
)
.
then
(
function
(
series
)
{
seriesContainer
.
push
(
series
)
;
}
)
.
catch
(
function
(
error
)
{
window
.
console
.
log
(
'oops... something went wrong...'
)
;
window
.
console
.
log
(
error
)
;
}
)
;
}


function init() {
  /**
   * Animation loop
   */
  function animate() {
    updateGeometries();
    render();

    // request new frame
    requestAnimationFrame(function() {
      animate();
    });
  }

  // renderer
  threeD = document.getElementById('r3d');
  renderer = new THREE.WebGLRenderer({
    antialias: true,
  });
  renderer.setSize(threeD.offsetWidth, threeD.offsetHeight);
  renderer.setClearColor(0x353535, 1);
  renderer.setPixelRatio(window.devicePixelRatio);
  threeD.appendChild(renderer.domElement);

  // stats
  stats = new Stats();
  threeD.appendChild(stats.domElement);

  // scene
  scene = new THREE.Scene();

  // camera
  camera = new THREE.PerspectiveCamera(
    45,
    threeD.offsetWidth / threeD.offsetHeight,
    0.01,
    10000000
  );
  camera.position.x = 150;
  camera.position.y = 150;
  camera.position.z = 100;

  // controls
  controls = new ControlsTrackball(camera, threeD);
  controls.rotateSpeed = 1.4;
  controls.zoomSpeed = 1.2;
  controls.panSpeed = 0.8;
  controls.noZoom = false;
  controls.noPan = false;
  controls.dynamicDampingFactor = 0.3;

  particleLight = new THREE.Mesh(
    new THREE.SphereGeometry(2, 8, 8),
    new THREE.MeshBasicMaterial({ color: 0xfff336 })
  );
  scene.add(particleLight);

  animate();
}


function init() {
  /**
   * Called on each animation frame
   */
  function animate() {
    render();

    // request new frame
    requestAnimationFrame(function() {
      animate();
    });
  }

  // renderers
  initRenderer3D(r0);
  initRenderer2D(r1);
  initRenderer2D(r2);
  initRenderer2D(r3);

  // start rendering loop
  animate();
}

/**
   * Called on each animation frame
   */


function updateLocalizer(refObj, targetLocalizersHelpers) {
        let refHelper = refObj.stackHelper;
        let localizerHelper = refObj.localizerHelper;
        let plane = refHelper.slice.cartesianEquation();
        localizerHelper.referencePlane = plane;

        // bit of a hack... works fine for this application
        for (let i = 0; i < targetLocalizersHelpers.length; i++) {
          for (let j = 0; j < 3; j++) {
            let targetPlane = targetLocalizersHelpers[i]['plane' + (j + 1)];
            if (
              targetPlane &&
              plane.x.toFixed(6) === targetPlane.x.toFixed(6) &&
              plane.y.toFixed(6) === targetPlane.y.toFixed(6) &&
              plane.z.toFixed(6) === targetPlane.z.toFixed(6)
            ) {
              targetLocalizersHelpers[i]['plane' + (j + 1)] = plane;
            }
          }
        }

        // update the geometry will create a new mesh
        localizerHelper.geometry = refHelper.slice.geometry;
      }


function mapCanvasToData() {
    for (let i = ijkBBox[0]; i < ijkBBox[1] + 1; i++) {
      for (let j = ijkBBox[2]; j < ijkBBox[3] + 1; j++) {
        for (let k = ijkBBox[4]; k < ijkBBox[5] + 1; k++) {
          // ijk to world
          // center of voxel
          let worldCoordinate = new THREE.Vector3(i, j, k).applyMatrix4(stack2._ijk2LPS);
          // world to screen coordinate
          let screenCoordinates = worldCoordinate.clone();
          screenCoordinates.project(camera);

          screenCoordinates.x = Math.round(((screenCoordinates.x + 1) * canvas.offsetWidth) / 2);
          screenCoordinates.y = Math.round(((-screenCoordinates.y + 1) * canvas.offsetHeight) / 2);
          screenCoordinates.z = 0;

          let pixel = context.getImageData(screenCoordinates.x, screenCoordinates.y, 1, 1).data;
          if (pixel[3] > 0 && i >= 0 && j >= 0 && k >= 0) {
            // find index and texture
            let voxelIndex = i + j * stack2._columns + k * stack2._rows * stack2._columns;

            let textureSize = 4096;
            let textureDimension = textureSize * textureSize;

            let rawDataIndex = ~~(voxelIndex / textureDimension);
            let inRawDataIndex = voxelIndex % textureDimension;

            // update value...
            let oldValue = stack2.rawData[rawDataIndex][inRawDataIndex];
            let newValue = cursor.value;

            if (oldValue != newValue) {
              // update raw data
              stack2.rawData[rawDataIndex][inRawDataIndex] = newValue;

              // update texture that is passed to shader
              textures2[rawDataIndex].image.data = stack2.rawData[rawDataIndex]; // tex;
              textures2[rawDataIndex].needsUpdate = true;

              // update stats
              editorStats[oldValue] -= 1;
              editorStats[newValue] += 1;
            }
          }
        }
      }
    }
  }

Loop through IJK BBox and see if voxel can be mapped to screen


function getShortlinker() {
	if ( globalScope.yoast.shortlinker === null ) {
		globalScope.yoast.shortlinker = new Shortlinker();
	}
	return globalScope.yoast.shortlinker;
}

function getShortlinker ( ) { if ( globalScope . yoast . shortlinker === null ) { globalScope . yoast . shortlinker = new Shortlinker ( ) ; } return globalScope . yoast . shortlinker ; }

Retrieves the Shortlinker instance.

@returns {Shortlinker} The Shortlinker.


function( sentencePartText, auxiliaries, locale ) {
	this._sentencePartText = sentencePartText;
	this._auxiliaries = auxiliaries;
	this._locale = locale;
	this._isPassive = false;
}

function ( sentencePartText , auxiliaries , locale ) { this . _sentencePartText = sentencePartText ; this . _auxiliaries = auxiliaries ; this . _locale = locale ; this . _isPassive = false ; }

Constructs a sentence part object.

@param {string} sentencePartText The text in the sentence part.
@param {Array} auxiliaries The list of auxiliaries from the sentence part.
@param {string} locale The locale used for this sentence part.

@constructor


function( i18n, options = {} ) {
	Assessor.call( this, i18n, options );
	this.type = "ContentAssessor";
	const locale = ( options.hasOwnProperty( "locale" ) ) ? options.locale : "en_US";

	this._assessments = [

		new FleschReadingEase( contentConfiguration( locale ).fleschReading ),
		new SubheadingDistributionTooLong(),
		paragraphTooLong,
		new SentenceLengthInText( contentConfiguration( locale ).sentenceLength ),
		transitionWords,
		passiveVoice,
		textPresence,
		sentenceBeginnings,
		// Temporarily disabled: wordComplexity,
	];
}

Creates the Assessor

@param {object} i18n The i18n object used for translations.
@param {Object} options The options for this assessor.
@param {Object} options.marker The marker to pass the list of marks to.
@param {string} options.locale The locale.

@constructor


function DeviationFragment( options ) {
	this._location = options.location;
	this._fragment = options.word;
	this._syllables = options.syllables;
	this._regex = null;

	this._options = pick( options, [ "notFollowedBy", "alsoFollowedBy" ] );
}

function DeviationFragment ( options ) { this . _location = options . location ; this . _fragment = options . word ; this . _syllables = options . syllables ; this . _regex = null ; this . _options = pick ( options , [ "notFollowedBy" , "alsoFollowedBy" ] ) ; }

Represents a partial deviation when counting syllables

@param {Object} options Extra options about how to match this fragment.
@param {string} options.location The location in the word where this deviation can occur.
@param {string} options.word The actual string that should be counted differently.
@param {number} options.syllables The amount of syllables this fragment has.
@param {string[]} [options.notFollowedBy] A list of characters that this fragment shouldn't be followed with.
@param {string[]} [options.alsoFollowedBy] A list of characters that this fragment could be followed with.

@constructor


function sortResults( assessmentResults ) {
	return assessmentResults.sort( ( a, b ) => {
		// First compare the score.
		if ( a.score < b.score ) {
			return -1;
		}
		if ( a.score > b.score ) {
			return 1;
		}

		// If there is no difference then compare the id.
		return a.id.localeCompare( b.id );
	} );
}

Sorts analysis results by score and then alphabetically by their identifier.

@param {Array} assessmentResults The assessment results to be sorted.

@returns {Array} The sorted results.

Sorts analysis results by score and then alphabetically by their identifier .


function mapResult( result ) {
	result.id = result.getIdentifier();
	result.rating = scoreToRating( result.score );
	result.hasMarks = result.hasMarks();
	result.marker = result.getMarker();

	// Because of inconsistency between YoastSEO and yoast-components.
	if ( result.rating === "ok" ) {
		result.rating = "OK";
	}

	return result;
}

Maps a single results to a result that can be interpreted by the AnalysisList component.

@param {object} result Result provided by the worker.

@returns {Object} The mapped result.

Maps a single results to a result that can be interpreted by the AnalysisList component .


function mapResults( results ) {
	const mappedResults = [];

	for ( let i = 0; i < results.length; i++ ) {
		if ( ! results[ i ].text ) {
			continue;
		}
		mappedResults.push( mapResult( results[ i ] ) );
	}

	return sortResults( mappedResults );
}

function mapResults ( results ) { const mappedResults = [ ] ; for ( let i = 0 ; i < results . length ; i ++ ) { if ( ! results [ i ] . text ) { continue ; } mappedResults . push ( mapResult ( results [ i ] ) ) ; } return sortResults ( mappedResults ) ; }

Maps results to a results that can be interpreted by the AnalysisList component.

@param {object} results Results provided by the worker.

@returns {Array} The mapped results.

Maps results to a results that can be interpreted by the AnalysisList component .


function(  participle, sentencePart, attributes ) {
	Participle.call( this, participle, sentencePart, attributes );
	this.setSentencePartPassiveness( this.isPassive() );
}

function ( participle , sentencePart , attributes ) { Participle . call ( this , participle , sentencePart , attributes ) ; this . setSentencePartPassiveness ( this . isPassive ( ) ) ; }

Creates an Participle object for the German language.

@param {string} participle The participle.
@param {string} sentencePart The sentence part that contains the participle.
@param {object} attributes  The attributes object.

@constructor

Creates an Participle object for the German language .


function( args ) {
	this.keyword = args.keyword;
	this.assessor = args.assessor;
	this.i18n = args.i18n;
	this.output = args.targets.output;
	this.overall = args.targets.overall || "overallScore";
	this.presenterConfig = createConfig( args.i18n );

	this._disableMarkerButtons = false;

	this._activeMarker = false;
}

function ( args ) { this . keyword = args . keyword ; this . assessor = args . assessor ; this . i18n = args . i18n ; this . output = args . targets . output ; this . overall = args . targets . overall || "overallScore" ; this . presenterConfig = createConfig ( args . i18n ) ; this . _disableMarkerButtons = false ; this . _activeMarker = false ; }

Constructs the AssessorPresenter.

@param {Object} args A list of arguments to use in the presenter.
@param {object} args.targets The HTML elements to render the output to.
@param {string} args.targets.output The HTML element to render the individual ratings out to.
@param {string} args.targets.overall The HTML element to render the overall rating out to.
@param {string} args.keyword The keyword to use for checking, when calculating the overall rating.
@param {SEOAssessor} args.assessor The Assessor object to retrieve assessment results from.
@param {Jed} args.i18n The translation object.

@constructor


function getBlocks( text ) {
	var blocks = [], depth = 0,
		blockStartTag = "",
		currentBlock = "",
		blockEndTag = "";

	// Remove all comments because it is very hard to tokenize them.
	text = text.replace( commentRegex, "" );

	createTokenizer();
	htmlBlockTokenizer.onText( text );
	htmlBlockTokenizer.end();

	forEach( tokens, function( token, i ) {
		var nextToken = tokens[ i + 1 ];

		switch ( token.type ) {
			case "content":
			case "greater-than-sign-content":
			case "inline-start":
			case "inline-end":
			case "other-tag":
			case "other-element-start":
			case "other-element-end":
			case "greater than sign":
				if ( ! nextToken || ( depth === 0 && ( nextToken.type === "block-start" || nextToken.type === "block-end" ) ) ) {
					currentBlock += token.src;

					blocks.push( currentBlock );
					blockStartTag = "";
					currentBlock = "";
					blockEndTag = "";
				} else {
					currentBlock += token.src;
				}
				break;

			case "block-start":
				if ( depth !== 0 ) {
					if ( currentBlock.trim() !== "" ) {
						blocks.push( currentBlock );
					}
					currentBlock = "";
					blockEndTag = "";
				}

				depth++;
				blockStartTag = token.src;
				break;

			case "block-end":
				depth--;
				blockEndTag = token.src;

				/*
				 * We try to match the most deep blocks so discard any other blocks that have been started but not
				 * finished.
				 */
				if ( "" !== blockStartTag && "" !== blockEndTag ) {
					blocks.push( blockStartTag + currentBlock + blockEndTag );
				} else if ( "" !== currentBlock.trim() ) {
					blocks.push( currentBlock );
				}
				blockStartTag = "";
				currentBlock = "";
				blockEndTag = "";
				break;
		}

		// Handles HTML with too many closing tags.
		if ( depth < 0 ) {
			depth = 0;
		}
	} );

	return blocks;
}

Splits a text into blocks based on HTML block elements.

@param {string} text The text to split.
@returns {Array} A list of blocks based on HTML block elements.

Splits a text into blocks based on HTML block elements .


function formatNumber( number ) {
	if ( Math.round( number ) === number ) {
		return number;
	}

	return Math.round( number * 10000 ) / 10000;
}

function formatNumber ( number ) { if ( Math . round ( number ) === number ) { return number ; } return Math . round ( number * 10000 ) / 10000 ; }

Rounds number to four decimals.

@param {number} number The number to be rounded.

@returns {number} The rounded number.


function( participle, sentencePart, attributes ) {
	Participle.call( this, participle, sentencePart, attributes );
	checkException.call( this );
}

function ( participle , sentencePart , attributes ) { Participle . call ( this , participle , sentencePart , attributes ) ; checkException . call ( this ) ; }

Creates an Participle object for the French language.

@param {string} participle The participle.
@param {string} sentencePart The sentence part that contains the participle.
@param {Object} attributes  The attributes object.

@constructor

Creates an Participle object for the French language .


function( participleExceptionRegexes ) {
	var participle = this.getParticiple();
	var match = [];

	forEach( participleExceptionRegexes, function( participleExceptionRegex ) {
		var exceptionMatch = participle.match( participleExceptionRegex );
		if ( exceptionMatch ) {
			match.push( exceptionMatch[ 0 ] );
		}
	} );

	if ( match.length > 0 ) {
		return true;
	}

	return false;
}

function ( participleExceptionRegexes ) { var participle = this . getParticiple ( ) ; var match = [ ] ; forEach ( participleExceptionRegexes , function ( participleExceptionRegex ) { var exceptionMatch = participle . match ( participleExceptionRegex ) ; if ( exceptionMatch ) { match . push ( exceptionMatch [ 0 ] ) ; } } ) ; if ( match . length > 0 ) { return true ; } return false ; }

Checks whether a given participle matches a list of regex exceptions.

@param {Array} participleExceptionRegexes The array of regexes to check.
@returns {boolean} Returns true if the participle matches a regex.

Checks whether a given participle matches a list of regex exceptions .


function getSentenceBeginning( sentence, firstWordExceptions ) {
	const words = getWords( stripTags( stripSpaces( sentence ) ) );

	if ( words.length === 0 ) {
		return "";
	}

	let firstWord = words[ 0 ].toLocaleLowerCase();

	if ( firstWordExceptions.indexOf( firstWord ) > -1 && words.length > 1 ) {
		firstWord += " " + words[ 1 ];
	}

	return firstWord;
}

function
getSentenceBeginning
(
sentence
,
firstWordExceptions
)
{
const
words
=
getWords
(
stripTags
(
stripSpaces
(
sentence
)
)
)
;
if
(
words
.
length
===
0
)
{
return
""
;
}
let
firstWord
=
words
[
0
]
.
toLocaleLowerCase
(
)
;
if
(
firstWordExceptions
.
indexOf
(
firstWord
)
>
1
&&
words
.
length
>
1
)
{
firstWord
+=
" "
+
words
[
1
]
;
}
return
firstWord
;
}

Retrieves the first word from the sentence.

@param {string} sentence The sentence to retrieve the first word from.
@param {Array} firstWordExceptions Exceptions to match against.
@returns {string} The first word of the sentence.

Retrieves the first word from the sentence .


function textPresenceAssessment( paper, researcher, i18n ) {
	const text = stripHTMLTags( paper.getText() );
	const urlTitle = createAnchorOpeningTag( "https://yoa.st/35h" );
	const urlCallToAction = createAnchorOpeningTag( "https://yoa.st/35i" );

	if ( text.length < 50 ) {
		const result = new AssessmentResult();

		result.setText( i18n.sprintf(
			/* Translators: %1$s and %3$s expand to links to articles on Yoast.com,
			%2$s expands to the anchor end tag*/
			i18n.dgettext( "js-text-analysis",
				"%1$sNot enough content%2$s: %3$sPlease add some content to enable a good analysis%2$s." ),
			urlTitle,
			"</a>",
			urlCallToAction ) );

		result.setScore( 3 );
		return result;
	}

	return new AssessmentResult();
}

Assesses that the paper has at least a little bit of content.

@param {Paper} paper The paper to assess.
@param {Researcher} researcher The researcher.
@param {Jed} i18n The translations object.
@returns {AssessmentResult} The result of this assessment.

Assesses that the paper has at least a little bit of content .


function splitOnWords( sentence, stopwords ) {
	const splitSentences = [];

	// Split the sentence on each found stopword and push this part in an array.
	forEach( stopwords, function( stopword ) {
		const sentenceSplit = sentence.split( stopword );
		if ( ! isEmpty( sentenceSplit[ 0 ] ) ) {
			splitSentences.push( sentenceSplit[ 0 ] );
		}
		const startIndex = sentence.indexOf( stopword );
		const endIndex = sentence.length;
		sentence = stripSpaces( sentence.substr( startIndex, endIndex ) );
	} );

	// Push the remainder of the sentence in the sentence parts array.
	splitSentences.push( sentence );
	return splitSentences;
}

Splits sentences into sentence parts based on stopwords.

@param {string} sentence The sentence to split.
@param {Array} stopwords The array with matched stopwords.
@returns {Array} The array with sentence parts.

Splits sentences into sentence parts based on stopwords .


function createSentenceParts( sentences, language ) {
	const auxiliaryRegex = languageVariables[ language ].auxiliaryRegex;
	const SentencePart = languageVariables[ language ].SentencePart;
	const sentenceParts = [];
	forEach( sentences, function( part ) {
		const foundAuxiliaries = sanitizeMatches( part.match( auxiliaryRegex || [] ) );
		sentenceParts.push( new SentencePart( part, foundAuxiliaries, languageVariables[ language ].locale ) );
	} );
	return sentenceParts;
}

function createSentenceParts ( sentences , language ) { const auxiliaryRegex = languageVariables [ language ] . auxiliaryRegex ; const SentencePart = languageVariables [ language ] . SentencePart ; const sentenceParts = [ ] ; forEach ( sentences , function ( part ) { const foundAuxiliaries = sanitizeMatches ( part . match ( auxiliaryRegex || [ ] ) ) ; sentenceParts . push ( new SentencePart ( part , foundAuxiliaries , languageVariables [ language ] . locale ) ) ; } ) ; return sentenceParts ; }

Creates sentence parts based on split sentences.

@param {Array}   sentences   The array with split sentences.
@param {string}  language    The language for which to create sentence parts.

@returns {Array} The array with sentence parts.

Creates sentence parts based on split sentences .


function splitSentence( sentence, language ) {
	const stopwordRegex = languageVariables[ language ].stopwordRegex;
	const stopwords = sentence.match( stopwordRegex ) || [];
	const splitSentences = splitOnWords( sentence, stopwords );
	return createSentenceParts( splitSentences, language );
}

function splitSentence ( sentence , language ) { const stopwordRegex = languageVariables [ language ] . stopwordRegex ; const stopwords = sentence . match ( stopwordRegex ) || [ ] ; const splitSentences = splitOnWords ( sentence , stopwords ) ; return createSentenceParts ( splitSentences , language ) ; }

Splits the sentence into sentence parts based on stopwords.

@param {string} sentence The text to split into sentence parts.
@param {string} language The language for which to split sentences.

@returns {Array} The array with sentence parts.

Splits the sentence into sentence parts based on stopwords .


function createDefaultSnippetPreview() {
	var targetElement = document.getElementById( this.config.targets.snippet );

	return new SnippetPreview( {
		analyzerApp: this,
		targetElement: targetElement,
		callbacks: {
			saveSnippetData: this.config.callbacks.saveSnippetData,
		},
	} );
}

function createDefaultSnippetPreview ( ) { var targetElement = document . getElementById ( this . config . targets . snippet ) ; return new SnippetPreview ( { analyzerApp : this , targetElement : targetElement , callbacks : { saveSnippetData : this . config . callbacks . saveSnippetData , } , } ) ; }

Creates a default snippet preview, this can be used if no snippet preview has been passed.

@private
@this App

@returns {SnippetPreview} The SnippetPreview object.

Creates a default snippet preview this can be used if no snippet preview has been passed .


function verifyArguments( args ) {
	if ( ! isObject( args.callbacks.getData ) ) {
		throw new MissingArgument( "The app requires an object with a getdata callback." );
	}

	if ( ! isObject( args.targets ) ) {
		throw new MissingArgument( "`targets` is a required App argument, `targets` is not an object." );
	}

	// The args.targets.snippet argument is only required if not SnippetPreview object has been passed.
	if (
		args.hasSnippetPreview &&
		! isValidSnippetPreview( args.snippetPreview ) &&
		! isString( args.targets.snippet ) ) {
		throw new MissingArgument( "A snippet preview is required. When no SnippetPreview object isn't passed to " +
			"the App, the `targets.snippet` is a required App argument. `targets.snippet` is not a string." );
	}
}

Check arguments passed to the App to check if all necessary arguments are set.

@private
@param {Object}      args            The arguments object passed to the App.
@returns {void}

Check arguments passed to the App to check if all necessary arguments are set .


function getWordCombinations( text, combinationSize, functionWords ) {
	const sentences = getSentences( text );

	let words, combination;

	return flatMap( sentences, function( sentence ) {
		sentence = sentence.toLocaleLowerCase();
		sentence = normalizeQuotes( sentence );
		words = getWords( sentence );

		return filter( map( words, function( word, i ) {
			// If there are still enough words in the sentence to slice of.
			if ( i + combinationSize - 1 < words.length ) {
				combination = words.slice( i, i + combinationSize );
				return new WordCombination( combination, 0, functionWords );
			}

			return false;
		} ) );
	} );
}

Returns the word combinations for the given text based on the combination size.

@param {string} text The text to retrieve combinations for.
@param {number} combinationSize The size of the combinations to retrieve.
@param {Function} functionWords The function containing the lists of function words.
@returns {WordCombination[]} All word combinations for the given text.

Returns the word combinations for the given text based on the combination size .


function calculateOccurrences( wordCombinations ) {
	const occurrences = {};

	forEach( wordCombinations, function( wordCombination ) {
		const combination = wordCombination.getCombination();

		if ( ! has( occurrences, combination ) ) {
			occurrences[ combination ] = wordCombination;
		}

		occurrences[ combination ].incrementOccurrences();
	} );

	return values( occurrences );
}

function calculateOccurrences ( wordCombinations ) { const occurrences = { } ; forEach ( wordCombinations , function ( wordCombination ) { const combination = wordCombination . getCombination ( ) ; if ( ! has ( occurrences , combination ) ) { occurrences [ combination ] = wordCombination ; } occurrences [ combination ] . incrementOccurrences ( ) ; } ) ; return values ( occurrences ) ; }

Calculates occurrences for a list of word combinations.

@param {WordCombination[]} wordCombinations The word combinations to calculate occurrences for.
@returns {WordCombination[]} Word combinations with their respective occurrences.

Calculates occurrences for a list of word combinations .


function getRelevantCombinations( wordCombinations ) {
	wordCombinations = wordCombinations.filter( function( combination ) {
		return combination.getOccurrences() !== 1 &&
			combination.getRelevance() !== 0;
	} );
	return wordCombinations;
}

function getRelevantCombinations ( wordCombinations ) { wordCombinations = wordCombinations . filter ( function ( combination ) { return combination . getOccurrences ( ) !== 1 && combination . getRelevance ( ) !== 0 ; } ) ; return wordCombinations ; }

Returns only the relevant combinations from a list of word combinations. Assumes
occurrences have already been calculated.

@param {WordCombination[]} wordCombinations A list of word combinations.
@returns {WordCombination[]} Only relevant word combinations.

Returns only the relevant combinations from a list of word combinations . Assumes occurrences have already been calculated .


function sortCombinations( wordCombinations ) {
	wordCombinations.sort( function( combinationA, combinationB ) {
		const difference = combinationB.getRelevance() - combinationA.getRelevance();
		// The combination with the highest relevance comes first.
		if ( difference !== 0 ) {
			return difference;
		}
		// In case of a tie on relevance, the longest combination comes first.
		return combinationB.getLength() - combinationA.getLength();
	} );
}

Sorts combinations based on their relevance and length.

@param {WordCombination[]} wordCombinations The combinations to sort.
@returns {void}

Sorts combinations based on their relevance and length .


function filterOneCharacterWordCombinations( wordCombinations ) {
	return wordCombinations.filter( function( combination ) {
		return ! ( combination.getLength() === 1 && combination.getWords()[ 0 ].length <= 1 );
	} );
}

function filterOneCharacterWordCombinations ( wordCombinations ) { return wordCombinations . filter ( function ( combination ) { return ! ( combination . getLength ( ) === 1 && combination . getWords ( ) [ 0 ] . length <= 1 ) ; } ) ; }

Filters word combinations that consist of a single one-character word.

@param {WordCombination[]} wordCombinations The word combinations to filter.
@returns {WordCombination[]} Filtered word combinations.

Filters word combinations that consist of a single one - character word .


function filterFunctionWordsAnywhere( wordCombinations, functionWords ) {
	return wordCombinations.filter( function( combination ) {
		return isEmpty(
			intersection( functionWords, combination.getWords() )
		);
	} );
}

function filterFunctionWordsAnywhere ( wordCombinations , functionWords ) { return wordCombinations . filter ( function ( combination ) { return isEmpty ( intersection ( functionWords , combination . getWords ( ) ) ) ; } ) ; }

Filters word combinations containing certain function words at any position.

@param {WordCombination[]} wordCombinations The word combinations to filter.
@param {array} functionWords The list of function words.
@returns {WordCombination[]} Filtered word combinations.

Filters word combinations containing certain function words at any position .


function filterFunctionWordsAtBeginning( wordCombinations, functionWords ) {
	return wordCombinations.filter( function( combination ) {
		return ! includes( functionWords, combination.getWords()[ 0 ] );
	} );
}

function filterFunctionWordsAtBeginning ( wordCombinations , functionWords ) { return wordCombinations . filter ( function ( combination ) { return ! includes ( functionWords , combination . getWords ( ) [ 0 ] ) ; } ) ; }

Filters word combinations beginning with certain function words.

@param {WordCombination[]} wordCombinations The word combinations to filter.
@param {array} functionWords The list of function words.
@returns {WordCombination[]} Filtered word combinations.

Filters word combinations beginning with certain function words .


function filterFunctionWordsAtEnding( wordCombinations, functionWords ) {
	return wordCombinations.filter( function( combination ) {
		const words = combination.getWords();
		const lastWordIndex = words.length - 1;
		return ! includes( functionWords, words[ lastWordIndex ] );
	} );
}

function filterFunctionWordsAtEnding ( wordCombinations , functionWords ) { return wordCombinations . filter ( function ( combination ) { const words = combination . getWords ( ) ; const lastWordIndex = words . length - 1 ; return ! includes ( functionWords , words [ lastWordIndex ] ) ; } ) ; }

Filters word combinations ending with certain function words.

@param {WordCombination[]} wordCombinations The word combinations to filter.
@param {array} functionWords The list of function words.
@returns {WordCombination[]} Filtered word combinations.

Filters word combinations ending with certain function words .


function filterFunctionWordsAtBeginningAndEnding( wordCombinations, functionWords ) {
	wordCombinations = filterFunctionWordsAtBeginning( wordCombinations, functionWords );
	wordCombinations = filterFunctionWordsAtEnding( wordCombinations, functionWords );
	return wordCombinations;
}

function filterFunctionWordsAtBeginningAndEnding ( wordCombinations , functionWords ) { wordCombinations = filterFunctionWordsAtBeginning ( wordCombinations , functionWords ) ; wordCombinations = filterFunctionWordsAtEnding ( wordCombinations , functionWords ) ; return wordCombinations ; }

Filters word combinations beginning and ending with certain function words.

@param {WordCombination[]} wordCombinations The word combinations to filter.
@param {Array} functionWords The list of function words.
@returns {WordCombination[]} Filtered word combinations.

Filters word combinations beginning and ending with certain function words .


function filterOnDensity( wordCombinations, wordCount, lowerLimit, upperLimit ) {
	return wordCombinations.filter( function( combination ) {
		return ( combination.getDensity( wordCount ) >= lowerLimit && combination.getDensity( wordCount ) < upperLimit
		);
	} );
}

function filterOnDensity ( wordCombinations , wordCount , lowerLimit , upperLimit ) { return wordCombinations . filter ( function ( combination ) { return ( combination . getDensity ( wordCount ) >= lowerLimit && combination . getDensity ( wordCount ) < upperLimit ) ; } ) ; }

Filters word combinations based on keyword density if the word count is 200 or over.

@param {WordCombination[]} wordCombinations The word combinations to filter.
@param {number} wordCount The number of words in the total text.
@param {number} lowerLimit The lower limit of keyword density.
@param {number} upperLimit The upper limit of keyword density.
@returns {WordCombination[]} Filtered word combinations.

Filters word combinations based on keyword density if the word count is 200 or over .


function filterEndingWith( wordCombinations, str, exceptions ) {
	wordCombinations = wordCombinations.filter( function( combination ) {
		const combinationstr = combination.getCombination();
		for ( let i = 0; i < exceptions.length; i++ ) {
			if ( combinationstr.endsWith( exceptions[ i ] ) ) {
				return true;
			}
		}
		return ! combinationstr.endsWith( str );
	} );
	return wordCombinations;
}

function filterEndingWith ( wordCombinations , str , exceptions ) { wordCombinations = wordCombinations . filter ( function ( combination ) { const combinationstr = combination . getCombination ( ) ; for ( let i = 0 ; i < exceptions . length ; i ++ ) { if ( combinationstr . endsWith ( exceptions [ i ] ) ) { return true ; } } return ! combinationstr . endsWith ( str ) ; } ) ; return wordCombinations ; }

Filters combinations based on whether they end with a specified string or not.

@param {WordCombination[]} wordCombinations The array of WordCombinations to filter.
@param {string} str The string the WordCombinations that need to be filtered out end with.
@param {string[]} exceptions The array of strings containing exceptions to not filter.
@returns {WordCombination[]} The filtered array of WordCombinations.

Filters combinations based on whether they end with a specified string or not .


function filterFunctionWords( combinations, functionWords ) {
	combinations = filterFunctionWordsAnywhere( combinations, functionWords.filteredAnywhere );
	combinations = filterFunctionWordsAtBeginningAndEnding( combinations, functionWords.filteredAtBeginningAndEnding );
	combinations = filterFunctionWordsAtEnding( combinations, functionWords.filteredAtEnding );
	combinations = filterFunctionWordsAtBeginning( combinations, functionWords.filteredAtBeginning );
	return combinations;
}

function filterFunctionWords ( combinations , functionWords ) { combinations = filterFunctionWordsAnywhere ( combinations , functionWords . filteredAnywhere ) ; combinations = filterFunctionWordsAtBeginningAndEnding ( combinations , functionWords . filteredAtBeginningAndEnding ) ; combinations = filterFunctionWordsAtEnding ( combinations , functionWords . filteredAtEnding ) ; combinations = filterFunctionWordsAtBeginning ( combinations , functionWords . filteredAtBeginning ) ; return combinations ; }

Filters the list of word combination objects based on the language-specific function word filters.
Word combinations with specific parts of speech are removed.

@param {Array} combinations The list of word combination objects.
@param {Function} functionWords The function containing the lists of function words.
@returns {Array} The filtered list of word combination objects.

Filters the list of word combination objects based on the language - specific function word filters . Word combinations with specific parts of speech are removed .


function filterCombinations( combinations, functionWords, language ) {
	combinations = filterFunctionWordsAnywhere( combinations, specialCharacters );
	combinations = filterOneCharacterWordCombinations( combinations );
	combinations = filterFunctionWords( combinations, functionWords );
	if ( language === "en" ) {
		combinations = filterEndingWith( combinations, "'s", [] );
	}
	return combinations;
}

function filterCombinations ( combinations , functionWords , language ) { combinations = filterFunctionWordsAnywhere ( combinations , specialCharacters ) ; combinations = filterOneCharacterWordCombinations ( combinations ) ; combinations = filterFunctionWords ( combinations , functionWords ) ; if ( language === "en" ) { combinations = filterEndingWith ( combinations , "'s" , [ ] ) ; } return combinations ; }

Filters the list of word combination objects based on function word filters, a special character filter and
a one-character filter.

@param {Array} combinations The list of word combination objects.
@param {Function} functionWords The function containing the lists of function words.
@param {string} language The language for which specific filters should be applied.
@returns {Array} The filtered list of word combination objects.

Filters the list of word combination objects based on function word filters a special character filter and a one - character filter .


function getRelevantWords( text, locale ) {
	let language = getLanguage( locale );
	if ( ! functionWordLists.hasOwnProperty( language ) ) {
		language = "en";
	}

	const functionWords = functionWordLists[ language ];

	const words = getWordCombinations( text, 1, functionWords.all );
	const wordCount = words.length;

	let oneWordCombinations = getRelevantCombinations(
		calculateOccurrences( words )
	);

	sortCombinations( oneWordCombinations );
	oneWordCombinations = take( oneWordCombinations, 100 );

	const oneWordRelevanceMap = {};

	forEach( oneWordCombinations, function( combination ) {
		oneWordRelevanceMap[ combination.getCombination() ] = combination.getRelevance();
	} );

	const twoWordCombinations = calculateOccurrences( getWordCombinations( text, 2, functionWords.all ) );
	const threeWordCombinations = calculateOccurrences( getWordCombinations( text, 3, functionWords.all ) );
	const fourWordCombinations = calculateOccurrences( getWordCombinations( text, 4, functionWords.all ) );
	const fiveWordCombinations = calculateOccurrences( getWordCombinations( text, 5, functionWords.all ) );

	let combinations = oneWordCombinations.concat(
		twoWordCombinations,
		threeWordCombinations,
		fourWordCombinations,
		fiveWordCombinations
	);

	combinations = filterCombinations( combinations, functionWords, language );

	forEach( combinations, function( combination ) {
		combination.setRelevantWords( oneWordRelevanceMap );
	} );

	combinations = getRelevantCombinations( combinations );
	sortCombinations( combinations );

	if ( wordCount >= wordCountLowerLimit ) {
		combinations = filterOnDensity( combinations, wordCount, densityLowerLimit, densityUpperLimit );
	}

	return take( combinations, relevantWordLimit );
}

function getRelevantWords ( text , locale ) { let language = getLanguage ( locale ) ; if ( ! functionWordLists . hasOwnProperty ( language ) ) { language = "en" ; } const functionWords = functionWordLists [ language ] ; const words = getWordCombinations ( text , 1 , functionWords . all ) ; const wordCount = words . length ; let oneWordCombinations = getRelevantCombinations ( calculateOccurrences ( words ) ) ; sortCombinations ( oneWordCombinations ) ; oneWordCombinations = take ( oneWordCombinations , 100 ) ; const oneWordRelevanceMap = { } ; forEach ( oneWordCombinations , function ( combination ) { oneWordRelevanceMap [ combination . getCombination ( ) ] = combination . getRelevance ( ) ; } ) ; const twoWordCombinations = calculateOccurrences ( getWordCombinations ( text , 2 , functionWords . all ) ) ; const threeWordCombinations = calculateOccurrences ( getWordCombinations ( text , 3 , functionWords . all ) ) ; const fourWordCombinations = calculateOccurrences ( getWordCombinations ( text , 4 , functionWords . all ) ) ; const fiveWordCombinations = calculateOccurrences ( getWordCombinations ( text , 5 , functionWords . all ) ) ; let combinations = oneWordCombinations . concat ( twoWordCombinations , threeWordCombinations , fourWordCombinations , fiveWordCombinations ) ; combinations = filterCombinations ( combinations , functionWords , language ) ; forEach ( combinations , function ( combination ) { combination . setRelevantWords ( oneWordRelevanceMap ) ; } ) ; combinations = getRelevantCombinations ( combinations ) ; sortCombinations ( combinations ) ; if ( wordCount >= wordCountLowerLimit ) { combinations = filterOnDensity ( combinations , wordCount , densityLowerLimit , densityUpperLimit ) ; } return take ( combinations , relevantWordLimit ) ; }

Returns the relevant words in a given text.

@param {string} text The text to retrieve the relevant words of.
@param {string} locale The paper's locale.
@returns {WordCombination[]} All relevant words sorted and filtered for this text.

Returns the relevant words in a given text .


function WordCombination( words, occurrences, functionWords ) {
	this._words = words;
	this._length = words.length;
	this._occurrences = occurrences || 0;
	this._functionWords = functionWords;
}

function WordCombination ( words , occurrences , functionWords ) { this . _words = words ; this . _length = words . length ; this . _occurrences = occurrences || 0 ; this . _functionWords = functionWords ; }

Represents a word combination in the context of relevant words.

@constructor

@param {string[]} words The list of words that this combination consists of.
@param {number} [occurrences] The number of occurrences, defaults to 0.
@param {Function} functionWords The function containing the lists of function words.

Represents a word combination in the context of relevant words .


function createAssessment( name, score = 9, text = "Excellent" ) {
	const assessment = new Assessment();
	assessment.identifier = name;
	assessment.getResult = () => {
		const result = new AssessmentResult();
		result.setScore( score );
		result.setText( text );
		return result;
	};
	return assessment;
}

function createAssessment ( name , score = 9 , text = "Excellent" ) { const assessment = new Assessment ( ) ; assessment . identifier = name ; assessment . getResult = ( ) => { const result = new AssessmentResult ( ) ; result . setScore ( score ) ; result . setText ( text ) ; return result ; } ; return assessment ; }

Creates an assessment.

@param {string} name  The assessment identifier.
@param {number} score The result score.
@param {string} text  The result text.

@returns {Assessment} The assessment.


function MissingArgumentError( message ) {
	Error.captureStackTrace( this, this.constructor );
	this.name = this.constructor.name;
	this.message = message;
}

function MissingArgumentError ( message ) { Error . captureStackTrace ( this , this . constructor ) ; this . name = this . constructor . name ; this . message = message ; }

Error that means that an argument should be passed that wasn't passed.

@constructor

@param {string} message The message for this error.

@returns {void}

Error that means that an argument should be passed that wasn t passed .


function( participle, sentencePart, attributes ) {
	this.setParticiple( participle );
	this.setSentencePart( sentencePart );
	this._determinesSentencePartIsPassive = false;

	attributes = attributes || {};

	defaults( attributes, defaultAttributes );

	validateAttributes( attributes );

	this._attributes = attributes;
}

function ( participle , sentencePart , attributes ) { this . setParticiple ( participle ) ; this . setSentencePart ( sentencePart ) ; this . _determinesSentencePartIsPassive = false ; attributes = attributes || { } ; defaults ( attributes , defaultAttributes ) ; validateAttributes ( attributes ) ; this . _attributes = attributes ; }

Construct the Participle object and set the participle, sentence part, auxiliary and type.

@param {string} participle The participle.
@param {string} sentencePart The sentence part where the participle is from.
@param {object} attributes The object containing all attributes.
@constructor

Construct the Participle object and set the participle sentence part auxiliary and type .


function getSubheadingsTopLevel( text ) {
	const subheadings = [];
	const regex = /<h([2-3])(?:[^>]+)?>(.*?)<\/h\1>/ig;
	let match;

	while ( ( match = regex.exec( text ) ) !== null ) {
		subheadings.push( match );
	}

	return subheadings;
}

function getSubheadingsTopLevel ( text ) { const subheadings = [ ] ; const regex = / <h([2-3])(?:[^>]+)?>(.*?)<\/h\1> / ig ; let match ; while ( ( match = regex . exec ( text ) ) !== null ) { subheadings . push ( match ) ; } return subheadings ; }

Gets all the level 2 and 3 subheadings from the text and returns these in an array.

@param {string} text The text to return the headings from.

@returns {Array<string[]>} Matches of subheadings in the text, first key is everything including tags,
second is the heading level, third is the content of the subheading.

Gets all the level 2 and 3 subheadings from the text and returns these in an array .


function getSentencesFromBlock( block ) {
	const sentenceTokenizer = new SentenceTokenizer();
	const { tokenizer, tokens } = sentenceTokenizer.createTokenizer();
	sentenceTokenizer.tokenize( tokenizer, block );

	return tokens.length === 0 ? [] : sentenceTokenizer.getSentencesFromTokens( tokens );
}

function getSentencesFromBlock ( block ) { const sentenceTokenizer = new SentenceTokenizer ( ) ; const { tokenizer , tokens } = sentenceTokenizer . createTokenizer ( ) ; sentenceTokenizer . tokenize ( tokenizer , block ) ; return tokens . length === 0 ? [ ] : sentenceTokenizer . getSentencesFromTokens ( tokens ) ; }

Returns the sentences from a certain block.

@param {string} block The HTML inside a HTML block.
@returns {Array<string>} The list of sentences in the block.

Returns the sentences from a certain block .


function createWorker( url ) {
	let worker = null;
	try {
		worker = new Worker( url );
	} catch ( e ) {
		try {
			const blobUrl = createBlobURL( url );
			worker = new Worker( blobUrl );
		} catch ( e2 ) {
			throw e2;
		}
	}
	return worker;
}

function createWorker ( url ) { let worker = null ; try { worker = new Worker ( url ) ; } catch ( e ) { try { const blobUrl = createBlobURL ( url ) ; worker = new Worker ( blobUrl ) ; } catch ( e2 ) { throw e2 ; } } return worker ; }

Creates a WebWorker using the given url.

@param {string} url The url of the worker.

@returns {Worker} The worker.

Creates a WebWorker using the given url .


function( i18n, options ) {
	this.type = "Assessor";
	this.setI18n( i18n );
	this._assessments = [];

	this._options = options || {};

	if ( ! isUndefined( this._options.researcher ) ) {
		this._researcher = this._options.researcher;
	}
}

function ( i18n , options ) { this . type = "Assessor" ; this . setI18n ( i18n ) ; this . _assessments = [ ] ; this . _options = options || { } ; if ( ! isUndefined ( this . _options . researcher ) ) { this . _researcher = this . _options . researcher ; } }

Creates the Assessor.

@param {Object} i18n The i18n object used for translations.
@param {Object} options The options for this assessor.
@param {Object} options.marker The marker to pass the list of marks to.
@param {Object} options.researcher The researcher to use in the assessor.

@constructor


function configureStore( preloadedState = {}, extraMiddleware = [] ) {
	const enhancers = configureEnhancers( extraMiddleware );

	return createStore( rootReducer, preloadedState, enhancers );
}

function configureStore ( preloadedState = { } , extraMiddleware = [ ] ) { const enhancers = configureEnhancers ( extraMiddleware ) ; return createStore ( rootReducer , preloadedState , enhancers ) ; }

Creates a Redux store.

@param {Object} [preloadedState]  The initial state.
@param {Array}  [extraMiddleware] Any extra middleware to apply.

@returns {Object} The Redux store.


function removeLinksFromText( text ) {
	const anchors = getAnchorsFromText( text );
	if ( anchors.length > 0 ) {
		anchors.forEach( function( anchor ) {
			text = text.replace( anchor, "" );
		} );
	}

	return text;
}

function removeLinksFromText ( text ) { const anchors = getAnchorsFromText ( text ) ; if ( anchors . length > 0 ) { anchors . forEach ( function ( anchor ) { text = text . replace ( anchor , "" ) ; } ) ; } return text ; }

Removes links from text.

@param {string} text The text string to analyze.

@returns {string} The text with links stripped away.


function removeImagesFromText( text ) {
	const images = imageInText( text );
	const imageTags = matchStringWithRegex( text, "</img>" );

	if ( images.length > 0 ) {
		images.forEach( function( image ) {
			text = text.replace( image, "" );
		} );

		imageTags.forEach( function( imageTag ) {
			text = text.replace( imageTag, "" );
		} );
	}

	return text;
}

function removeImagesFromText ( text ) { const images = imageInText ( text ) ; const imageTags = matchStringWithRegex ( text , "</img>" ) ; if ( images . length > 0 ) { images . forEach ( function ( image ) { text = text . replace ( image , "" ) ; } ) ; imageTags . forEach ( function ( imageTag ) { text = text . replace ( imageTag , "" ) ; } ) ; } return text ; }

Removes images from text.

@param {string} text The text string to analyze.

@returns {string} The text with images stripped away.


function paragraphHasNoText( text ) {
	// Strip links and check if paragraph consists of links only
	text = removeLinksFromText( text );
	if ( text === "" ) {
		return true;
	}

	text = removeImagesFromText( text );
	if ( text === "" ) {
		return true;
	}

	// Remove empty divisions from the text
	const emptyDivisions = findEmptyDivisions( text );
	if ( emptyDivisions.length < 1 ) {
		return false;
	}

	emptyDivisions.forEach( function( emptyDivision ) {
		text = text.replace( emptyDivision, "" );
	} );

	return text === "";
}

Checks if the paragraph has no text.

@param {string} text The text string to analyze.

@returns {boolean} True if the paragraph has no text, false otherwise.


function getRelevantWords( text, locale ) {
	if ( ! isEqual( text, previousRelevantWords.text ) || ! isEqual( locale, previousRelevantWords.locale ) ) {
		previousRelevantWords = {
			text,
			locale,
			data: calculateRelevantWords( text, locale ),
		};
	}
	return previousRelevantWords.data;
}

function getRelevantWords ( text , locale ) { if ( ! isEqual ( text , previousRelevantWords . text ) || ! isEqual ( locale , previousRelevantWords . locale ) ) { previousRelevantWords = { text , locale , data : calculateRelevantWords ( text , locale ) , } ; } return previousRelevantWords . data ; }

Retrieve the relevant words. Uses cached version when possible.

@param {string} text   The text.
@param {string} locale The locale.

@returns {Object} The relevant words.

Retrieve the relevant words . Uses cached version when possible .


function() {
	var baseURL = this.opts.baseURL;

	/*
	 * For backwards compatibility, if no URL was passed to the snippet editor we try to retrieve the base URL from the
	 * rawData in the App. This is because the scrapers used to be responsible for retrieving the baseURL, but the base
	 * URL is static so we can just pass it to the snippet editor.
	 */
	if ( this.hasApp() && ! isEmpty( this.refObj.rawData.baseUrl ) && this.opts.baseURL === defaults.baseURL ) {
		baseURL = this.refObj.rawData.baseUrl;
	}

	return baseURL;
}

Get's the base URL for this instance of the Snippet Preview.

@private
@this SnippetPreview

@returns {string} The base URL.

Get s the base URL for this instance of the Snippet Preview .


function updateUnformattedText( key, value ) {
	this.element.input[ key ].value = value;

	this.data[ key ] = value;
}

function updateUnformattedText ( key , value ) { this . element . input [ key ] . value = value ; this . data [ key ] = value ; }

Update data and DOM objects when the unformatted text is updated, here for backwards compatibility

@private
@this SnippetPreview

@param {string} key The data key to update.
@param {string} value The value to update.

@returns {void}

Update data and DOM objects when the unformatted text is updated here for backwards compatibility


function rateTitleLength( titleLength ) {
	var rating;

	switch ( true ) {
		case titleLength > 0 && titleLength <= 399:
		case titleLength > 600:
			rating = "ok";
			break;

		case titleLength >= 400 && titleLength <= 600:
			rating = "good";
			break;

		default:
			rating = "bad";
			break;
	}

	return rating;
}

function rateTitleLength ( titleLength ) { var rating ; switch ( true ) { case titleLength > 0 && titleLength <= 399 : case titleLength > 600 : rating = "ok" ; break ; case titleLength >= 400 && titleLength <= 600 : rating = "good" ; break ; default : rating = "bad" ; break ; } return rating ; }

Returns a rating based on the length of the title

@param {number} titleLength the length of the title.

@returns {string} The rating given based on the title length.


function rateMetaDescLength( metaDescLength ) {
	var rating;

	switch ( true ) {
		case metaDescLength > 0 && metaDescLength < 120:
		case metaDescLength > maximumMetaDescriptionLength:
			rating = "ok";
			break;

		case metaDescLength >= 120 && metaDescLength <= maximumMetaDescriptionLength:
			rating = "good";
			break;

		default:
			rating = "bad";
			break;
	}

	return rating;
}

function rateMetaDescLength ( metaDescLength ) { var rating ; switch ( true ) { case metaDescLength > 0 && metaDescLength < 120 : case metaDescLength > maximumMetaDescriptionLength : rating = "ok" ; break ; case metaDescLength >= 120 && metaDescLength <= maximumMetaDescriptionLength : rating = "good" ; break ; default : rating = "bad" ; break ; } return rating ; }

Returns a rating based on the length of the meta description

@param {number} metaDescLength the length of the meta description.

@returns {string} The rating given based on the description length.


function updateProgressBar( element, value, maximum, rating ) {
	var barElement, progress,
		allClasses = [
			"snippet-editor__progress--bad",
			"snippet-editor__progress--ok",
			"snippet-editor__progress--good",
		];

	element.value = value;
	domManipulation.removeClasses( element, allClasses );
	domManipulation.addClass( element, "snippet-editor__progress--" + rating );

	if ( ! this.hasProgressSupport ) {
		barElement = element.getElementsByClassName( "snippet-editor__progress-bar" )[ 0 ];
		progress = ( value / maximum ) * 100;

		barElement.style.width = progress + "%";
	}
}

function updateProgressBar ( element , value , maximum , rating ) { var barElement , progress , allClasses = [ "snippet-editor__progress--bad" , "snippet-editor__progress--ok" , "snippet-editor__progress--good" , ] ; element . value = value ; domManipulation . removeClasses ( element , allClasses ) ; domManipulation . addClass ( element , "snippet-editor__progress--" + rating ) ; if ( ! this . hasProgressSupport ) { barElement = element . getElementsByClassName ( "snippet-editor__progress-bar" ) [ 0 ] ; progress = ( value / maximum ) * 100 ; barElement . style . width = progress + "%" ; } }

Updates a progress bar

@private
@this SnippetPreview

@param {HTMLElement} element The progress element that's rendered.
@param {number} value The current value.
@param {number} maximum The maximum allowed value.
@param {string} rating The SEO score rating for this value.

@returns {void}


function getAnalyzerTitle() {
	var title = this.data.title;

	if ( isEmpty( title ) ) {
		title = this.opts.defaultValue.title;
	}

	if ( this.hasPluggable() ) {
		title = this.refObj.pluggable._applyModifications( "data_page_title", title );
	}

	return stripSpaces( title );
}

function getAnalyzerTitle ( ) { var title = this . data . title ; if ( isEmpty ( title ) ) { title = this . opts . defaultValue . title ; } if ( this . hasPluggable ( ) ) { title = this . refObj . pluggable . _applyModifications ( "data_page_title" , title ) ; } return stripSpaces ( title ) ; }

Returns the title as meant for the analyzer

@private
@this SnippetPreview

@returns {string} The title that is meant for the analyzer.