<commit_before>import getDocument from 'get-document'

import DOMException from './dom-exception'

// https://developer.mozilla.org/en-US/docs/XPathResult
const FIRST_ORDERED_NODE_TYPE = 9

// Default namespace for XHTML documents
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'

/**
 * Compute an XPath expression for the given node.
 *
 * If the optional parameter `root` is supplied, the computed XPath expression
 * will be relative to it.
 *
 * @param {Node} node The node for which to compute an XPath expression.
 * @param {Node} [root] The root context for the XPath expression.
 * @returns {string}
 */
export function fromNode(node, root = document) {
  let path = '/';
  while (node !== root) {
    if (!node) {
      let message = 'The supplied node is not contained by the root node.'
      let name = 'InvalidNodeTypeError'
      throw new DOMException(message, name)
    }
    path = `/${nodeName(node)}[${nodePosition(node)}]${path}`
    node = node.parentNode
  }
  return path.replace(/\/$/, '')
}

/**
 * Find a node using an XPath relative to the given root node.
 *
 * If the optional parameter `root` is supplied, the XPath expressions are
 * evaluated as relative to it.
 *
 * If the optional parameter `resolver` is supplied, it will be used to resolve
 * any namespaces within the XPath.
 *
 * @param {string} path An XPath String to evaluate.
 * @param {Node} [root] The root context for the XPath expression.
 * @returns {Node|null} The first matching Node or null if none is found.
 */
export function toNode(path, root = document, resolver = null) {
  // Check for resolver but no root argument.
  if (typeof(root) === 'function') {
    resolver = root;
    root = document;
  }

  // Make the path relative to the root, if not the document.
  if (root !== document) path = path.replace(/^\//, './')

  // Make a default resolver.
  if (resolver === null && document.lookupNamespaceURI) {
    let documentElement = getDocument(root).documentElement
    let defaultNS = documentElement.lookupNamespaceURI(null) || HTML_NAMESPACE
    resolver = (prefix) => {
      let ns = {'_default_': defaultNS}
      return ns[prefix] || documentElement.lookupNamespaceURI(prefix)
    }

    // Add a default prefix to each path part.
    path = path.replace(/\/(?!\.)([^\/:\(]+)(?=\/|$)/g, '/_default_:$1')
    return resolve(path, root, resolver)
  } else {
    return resolve(path, root, resolver)
  }
}

// Get the XPath node name.
function nodeName(node) {
  switch (node.nodeName) {
  case '#text': return 'text()'
  case '#comment': return 'comment()'
  case '#cdata-section': return 'cdata-section()'
  default: return node.nodeName.toLowerCase()
  }
}

// Get the ordinal position of this node among its siblings of the same name.
function nodePosition(node) {
  let name = node.nodeName
  let position = 1
  while ((node = node.previousSibling)) {
    if (node.nodeName === name) position += 1
  }
  return position
}

// Find a single node with XPath `path`
function resolve(path, root, resolver) {
  try {
    return platformResolve(path, root, resolver);
  } catch (err) {
    return fallbackResolve(path, root)
  }
}

// Find a single node with XPath `path` using the simple, built-in evaluator.
function fallbackResolve(path, root) {
  let steps = path.split("/")
  let node = root
  while (node) {
    let step = steps.shift()
    if (step === undefined) break
    if (step === '.') continue
    let [name, position] = step.split(/[\[\]]/)
    name = name.replace('_default_:', '')
    position = position ? parseInt(position) : 1
    node = findChild(node, name, position)
  }
  return node
}

// Find a single node with XPath `path` using `document.evaluate`.
function platformResolve(path, root, resolver) {
  let r = document.evaluate(path, root, resolver, FIRST_ORDERED_NODE_TYPE, null)
  return r.singleNodeValue
}

// Find the child of the given node by name and ordinal position.
function findChild(node, name, position) {
  for (node = node.firstChild ; node ; node = node.nextSibling) {
    if (nodeName(node) === name && --position === 0) return node
  }
  return null
}
<commit_msg>Only use default namespace with platform XPath<commit_after>import getDocument from 'get-document'

import DOMException from './dom-exception'

// https://developer.mozilla.org/en-US/docs/XPathResult
const FIRST_ORDERED_NODE_TYPE = 9

// Default namespace for XHTML documents
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'

/**
 * Compute an XPath expression for the given node.
 *
 * If the optional parameter `root` is supplied, the computed XPath expression
 * will be relative to it.
 *
 * @param {Node} node The node for which to compute an XPath expression.
 * @param {Node} [root] The root context for the XPath expression.
 * @returns {string}
 */
export function fromNode(node, root = document) {
  let path = '/';
  while (node !== root) {
    if (!node) {
      let message = 'The supplied node is not contained by the root node.'
      let name = 'InvalidNodeTypeError'
      throw new DOMException(message, name)
    }
    path = `/${nodeName(node)}[${nodePosition(node)}]${path}`
    node = node.parentNode
  }
  return path.replace(/\/$/, '')
}

/**
 * Find a node using an XPath relative to the given root node.
 *
 * If the optional parameter `root` is supplied, the XPath expressions are
 * evaluated as relative to it.
 *
 * If the optional parameter `resolver` is supplied, it will be used to resolve
 * any namespaces within the XPath.
 *
 * @param {string} path An XPath String to evaluate.
 * @param {Node} [root] The root context for the XPath expression.
 * @returns {Node|null} The first matching Node or null if none is found.
 */
export function toNode(path, root = document, resolver = null) {
  // Check for resolver but no root argument.
  if (typeof(root) === 'function') {
    resolver = root;
    root = document;
  }

  // Make the path relative to the root, if not the document.
  if (root !== document) path = path.replace(/^\//, './')

  // Make a default resolver.
  if (resolver === null && document.lookupNamespaceURI) {
    let documentElement = getDocument(root).documentElement
    let defaultNS = documentElement.lookupNamespaceURI(null) || HTML_NAMESPACE
    resolver = (prefix) => {
      let ns = {'_default_': defaultNS}
      return ns[prefix] || documentElement.lookupNamespaceURI(prefix)
    }
  }

  return resolve(path, root, resolver)
}

// Get the XPath node name.
function nodeName(node) {
  switch (node.nodeName) {
  case '#text': return 'text()'
  case '#comment': return 'comment()'
  case '#cdata-section': return 'cdata-section()'
  default: return node.nodeName.toLowerCase()
  }
}

// Get the ordinal position of this node among its siblings of the same name.
function nodePosition(node) {
  let name = node.nodeName
  let position = 1
  while ((node = node.previousSibling)) {
    if (node.nodeName === name) position += 1
  }
  return position
}

// Find a single node with XPath `path`
function resolve(path, root, resolver) {
  try {
    // Add a default value to each path part lacking a prefix.
    let nspath = path.replace(/\/(?!\.)([^\/:\(]+)(?=\/|$)/g, '/_default_:$1')
    return platformResolve(nspath, root, resolver)
  } catch (err) {
    return fallbackResolve(path, root)
  }
}

// Find a single node with XPath `path` using the simple, built-in evaluator.
function fallbackResolve(path, root) {
  let steps = path.split("/")
  let node = root
  while (node) {
    let step = steps.shift()
    if (step === undefined) break
    if (step === '.') continue
    let [name, position] = step.split(/[\[\]]/)
    name = name.replace('_default_:', '')
    position = position ? parseInt(position) : 1
    node = findChild(node, name, position)
  }
  return node
}

// Find a single node with XPath `path` using `document.evaluate`.
function platformResolve(path, root, resolver) {
  let r = document.evaluate(path, root, resolver, FIRST_ORDERED_NODE_TYPE, null)
  return r.singleNodeValue
}

// Find the child of the given node by name and ordinal position.
function findChild(node, name, position) {
  for (node = node.firstChild ; node ; node = node.nextSibling) {
    if (nodeName(node) === name && --position === 0) return node
  }
  return null
}
<|endoftext|>


<commit_before>jumplink.cms.controller('GalleryContentController', function($rootScope, $scope, $state, Fullscreen, $sailsSocket, $stateParams, contents_images, navs, config, $modal, $log, $location, $state, SortableService, GalleryService, ContentService, SubnavigationService, HistoryService) {
  $scope.images = contents_images.images;;
  $scope.config = config;
  $scope.contents = contents_images.contents;
  $scope.navs = navs;
  $scope.html = ContentService.getShowHtml();
  $scope.dropdown = GalleryService.getDropdown();
  $scope.goTo = HistoryService.goToHashPosition;

  var page = $state.current.name;

  GalleryService.setEditModal($scope);
  GalleryService.setUploadModal($scope, $scope.images, $scope.contents);
  GalleryService.subscribe();

  ContentService.setEditModal($scope);
  ContentService.subscribe();

  SubnavigationService.setEditModal($scope);
  SubnavigationService.subscribe();

  $scope.toogleHtml = function() {
    $scope.html = ContentService.toogleShowHtml($scope.contents);
  };

  $scope.removeContent = function(index, content) {
    if($rootScope.authenticated) {
      ContentService.remove($scope.contents, index, content, page, function (err, contents) {
        if(err) {
          $log.error("Error: On remove content!", err);
          $rootScope.pop('error', 'Inhaltsblock konnte nicht entfernt werden', "");
        }
        else{
          $rootScope.pop('success', 'Inhaltsblock wurde entfernt', "");
          // $scope.contents = contents;
          SubnavigationService.removeByTarget($scope.navs, content.name, page, function (err, navs) {
            if(err) {
              $log.error("Error: On remove subnavigation!", err);
              $rootScope.pop('error', 'Subnavigation konnte nicht entfernt werden', "");
            }
            else{
              $rootScope.pop('success', 'Subnavigation wurde entfernt', "");

            }
          });
        }
      });
    }
  };

  $scope.removeImage = function(image) {
    $log.debug("removeImage", "image", image);
    if($rootScope.authenticated) {
      GalleryService.remove($scope.images[image.content], null, image, page, function (err, images) {
        if(err) {
          $log.error("Error: On remove content!", err);
          $rootScope.pop('error', 'Bild konnte nicht entfernt werden', "");
        } else{
          $rootScope.pop('success', 'Bild wurde entfernt', "");
          $scope.images[content.name] = images;
        }
      });
    }
  };

  $scope.removeNav = function(index, nav) {
    if($rootScope.authenticated) {
      SubnavigationService.remove($scope.navs, index, nav, page, function (err, navs) {
        if(err) $log.error("Error: On remove subnavigation!", err);
        else {
          $log.debug("remove done!");
          $scope.navs = navs;
        }
      });
    }
  }

  var addImage = function() {
    if($rootScope.authenticated) {
      GalleryService.add($scope.images, $scope.contents, function(err, image) {
        if(err) $log.error("Error: On image content!", err);
      });
    }
  };

  var addContent = function() {
    var errors = [
      "Error: Konnte Inhaltsblock nicht erzeugen",
      "Error: Konnte Inhaltsblock nicht hinzufügen",
      "Error: Konnte subnavigation nicht hinzufügen",
      "Error: Konnte Bilderblock nicht hinzufügen"
    ];

    var successes = [
      "Neuen Inhaltsblock erfolgreich hinzugefügt",
      "Neue Subnavigation erfolgreich hinzugefügt",
      "Neuen Bilderblock erfolgreich hinzugefügt",
      "Neuen Block erfolgreich hinzugefügt",
    ];

    if($rootScope.authenticated) {
      ContentService.createEdit($scope.contents, page, function(err, content) {
        if(err) {
          $log.error(errors[0], err);
          $rootScope.pop('error', errors[0], err);
          $scope.$apply();
        } else {
          ContentService.append($scope.contents, content, function (err, contents) {
            if(err) {
              $log.error(errors[1], err);
              // $rootScope.pop('error', errors[1], err);
              $scope.$apply();
            } else {
              // $scope.contents = contents;
              // $rootScope.pop('success', successes[0], content.title);
              $scope.$apply();

              SubnavigationService.append($scope.navs, {page:page, target: content.name, name: content.title}, function(err, navs) {
                if(err) {
                  $log.error(errors[2], err);
                  $rootScope.pop('error', errors[2], err);
                } else {
                  // $scope.navs = navs;
                  // $rootScope.pop('success', successes[1], content.name);
                  $scope.$apply();

                  // also add an image block to content block
                  GalleryService.addBlock($scope.images, content, function(err, imageBock) {
                    if(err) {
                      $log.error(errors[3], err);
                      $rootScope.pop('error', errors[3], err);
                    } else {
                      $rootScope.pop('success', successes[3], content.name);
                      $scope.$apply();
                    }
                  });
                }
              });
            }
          });
        }
      });
    }
  };

  $scope.addNav = function() {
    if($rootScope.authenticated) {
      SubnavigationService.append($scope.navs, {page:page}, function(err, navs) {
        if(err) $log.error("Error: On add subnavigation!", err);
        // $log.debug("add done on subnavigation!", '');
      });
    }
  };

  $scope.moveForwardContent = function(index, content) {
    SortableService.moveForward(index, $scope.contents, function(err, contents) {
      if(err) $log.error("Error: On move content forward!", err);
      else $scope.contents = contents;
    });
  };

  $scope.moveForwardNav = function(index, nav) {
    SortableService.moveForward(index, $scope.navs, function(err, navs) {
      if(err) $log.error("Error: On move subnavigation forward!", err);
      else $scope.navs = navs;
    });
  };

  $scope.moveBackwardContent = function(index, content) {
    SortableService.moveBackward(index, $scope.contents, function(err, contents) {
      if(err) $log.error("Error: On move content backward!", err);
      else $scope.contents = contents;
    });
  }

  $scope.moveBackwardNav = function(index, nav) {
    SortableService.moveBackward(index, $scope.navs, function(err, navs) {
      if(err) $log.error("Error: On move content backward!", err);
      else $scope.navs = navs;
    });
  };

  $scope.editImage = GalleryService.edit;

  $scope.editContent = function(index, content) {
    if($rootScope.authenticated) {
      ContentService.edit(content, function(err, newContent) {
        if(err) $log.error("Error: On edit content!", err);
        else {
          content = newContent;
        }
      });
    }
  };

  $scope.editNavs = function(navs) {
    if($rootScope.authenticated) {
      SubnavigationService.edit(navs, function(err) {
        if(err) $log.error("Error: On edit subnavigations!", err);
      });
    }
  };

  $scope.saveImage = function(image) {
    if($rootScope.authenticated) {
      if(angular.isDefined(image)) {
        GalleryService.saveOne(image, page, image.name, function (err, image) {
          if(err) {
            $log.error (err, image);
            $rootScope.pop('error', err, image);
          } else {
            $rootScope.pop('success', 'Bild wurde aktualisiert', '');
          }
        });
      }
    }
  };

  $scope.save = function() {
    if($rootScope.authenticated) {
      GalleryService.saveAllBlocks($scope.images, page, function (err, images) {
        if(err) {
          $log.error (err, images);
          $rootScope.pop('error', err, images);
        } else {
          $log.debug ("Bilder wurden aktualisiert", images);
          $rootScope.pop('success', 'Bilder wurden auf dem Server gespeichert', '');
          ContentService.save($scope.contents, page, function(err, contents) {
            if(err) {
              $log.error("Error: On save content!", err);
              if(cb) return cb(err);
              return err;
            } else {
              $log.debug ('Contentblocks wurden aktualisiert', contents);
              $rootScope.pop('success', 'Contentblocks wurden auf dem Server gespeichert', '');
              SubnavigationService.save($scope.navs, page, function(err, navs) {
                if(err) {
                  $log.error("Error: On save subnavigations!", err);
                  if(cb) return cb(err);
                  return err;
                } else {
                  $log.debug ('Subnavigation wurde aktualisiert', contents);
                  $rootScope.pop('success', 'Subnavigation wurde auf dem Server gespeichert', '');
                }
              });
            }
          });
        }
      });
    }
  };

  // http://stackoverflow.com/questions/21702375/angularjs-ng-click-over-ng-click
  $scope.stopPropagation = function (event) {
     event.stopPropagation();
  };

  $scope.goToImage = function (image) {
    $state.go('layout.gallery-fullscreen', {id:image.id});
  }

  /* ==== Drag and Drop Stuff ==== */
  $scope.onDragOnImageComplete = function(index, image, evt, content) {
    if(image == null) {
      $log.debug("*click*", index);
      var image = $scope.images[content.name][index];
      $scope.goToImage(image);
    }
  };

  $scope.onDropOnImageComplete = function(dropimageindex, dragimage, evt, content) {

    SortableService.dropMove($scope.images[content.name], dropimageindex, dragimage, evt, function(err, images) {
      $scope.images[content.name] = images;
    }, SortableService.move);
  };

  $scope.onDropOnAreaComplete = function(image, evt, content) {
    $log.debug("onDropOnAreaComplete");
    // $log.debug("images", $scope.images);
    // $log.debug("content", content);

    // var index = $scope.images[content.name].indexOf(image);

    if(image !== null && image.content != content.name) {
      $log.debug("Move image from one content block to another:\n\t"+image.content+" => "+content.name);
      var content_from = image.content;
      var content_to = content.name;
      SortableService.moveObjectToAnotherArray(image, null, $scope.images[content_from], $scope.images[content_to], function (err, result) {
        if(err) $rootScope.pop('error', "Bild konnte nicht verschoben werden", err);
        else {
          result.array_to[result.index_to].content = content_to;
          // $rootScope.pop('success', "Bild erfolgreich verschoben", content_from+" => "+content_to);
        }
        $scope.$apply();
      });
    } else {
      $log.debug("Move image within the content block:\n\t"+content.name);
    }

  };

  $scope.addDropdownActions = {
    'addImage': addImage,
    'addContent': addContent
  }

  $scope.addDropdown = [
    {
      "text": " Bild",
      "click": "addDropdownActions.addImage()"
    },
    {
      "text": " Überschrift",
      "click": "addDropdownActions.addContent()"
    }
  ];

});<commit_msg>save image on edits<commit_after>jumplink.cms.controller('GalleryContentController', function($rootScope, $scope, $state, Fullscreen, $sailsSocket, $stateParams, contents_images, navs, config, $modal, $log, $location, $state, SortableService, GalleryService, ContentService, SubnavigationService, HistoryService) {
  $scope.images = contents_images.images;;
  $scope.config = config;
  $scope.contents = contents_images.contents;
  $scope.navs = navs;
  $scope.html = ContentService.getShowHtml();
  $scope.dropdown = GalleryService.getDropdown();
  $scope.goTo = HistoryService.goToHashPosition;

  var page = $state.current.name;

  GalleryService.setEditModal($scope);
  GalleryService.setUploadModal($scope, $scope.images, $scope.contents);
  GalleryService.subscribe();

  ContentService.setEditModal($scope);
  ContentService.subscribe();

  SubnavigationService.setEditModal($scope);
  SubnavigationService.subscribe();

  $scope.toogleHtml = function() {
    $scope.html = ContentService.toogleShowHtml($scope.contents);
  };

  $scope.removeContent = function(index, content) {
    if($rootScope.authenticated) {
      ContentService.remove($scope.contents, index, content, page, function (err, contents) {
        if(err) {
          $log.error("Error: On remove content!", err);
          $rootScope.pop('error', 'Inhaltsblock konnte nicht entfernt werden', "");
        }
        else{
          // $rootScope.pop('success', 'Inhaltsblock wurde entfernt', "");
          // $scope.contents = contents;
          SubnavigationService.removeByTarget($scope.navs, content.name, page, function (err, navs) {
            if(err) {
              $log.error("Error: On remove subnavigation!", err);
              $rootScope.pop('error', 'Subnavigation konnte nicht entfernt werden', "");
            }
            else{
              // $rootScope.pop('success', 'Subnavigation wurde entfernt', "");
              $rootScope.pop('success', 'Erfolgreich entfernt', "");
            }
          });
        }
      });
    }
  };

  $scope.removeImage = function(image) {
    $log.debug("removeImage", "image", image);
    if($rootScope.authenticated) {
      GalleryService.remove($scope.images[image.content], null, image, page, function (err, images) {
        if(err) {
          $log.error("Error: On remove content!", err);
          $rootScope.pop('error', 'Bild konnte nicht entfernt werden', "");
        } else{
          $rootScope.pop('success', 'Bild wurde entfernt', "");
          $scope.images[content.name] = images;
        }
      });
    }
  };

  $scope.removeNav = function(index, nav) {
    if($rootScope.authenticated) {
      SubnavigationService.remove($scope.navs, index, nav, page, function (err, navs) {
        if(err) $log.error("Error: On remove subnavigation!", err);
        else {
          $log.debug("remove done!");
          $scope.navs = navs;
        }
      });
    }
  }

  var addImage = function() {
    if($rootScope.authenticated) {
      GalleryService.add($scope.images, $scope.contents, function(err, image) {
        if(err) $log.error("Error: On image content!", err);
      });
    }
  };

  var addContent = function() {
    var errors = [
      "Error: Konnte Inhaltsblock nicht erzeugen",
      "Error: Konnte Inhaltsblock nicht hinzufügen",
      "Error: Konnte subnavigation nicht hinzufügen",
      "Error: Konnte Bilderblock nicht hinzufügen"
    ];

    var successes = [
      "Neuen Inhaltsblock erfolgreich hinzugefügt",
      "Neue Subnavigation erfolgreich hinzugefügt",
      "Neuen Bilderblock erfolgreich hinzugefügt",
      "Neuen Block erfolgreich hinzugefügt",
    ];

    if($rootScope.authenticated) {
      ContentService.createEdit($scope.contents, page, function(err, content) {
        if(err) {
          $log.error(errors[0], err);
          $rootScope.pop('error', errors[0], err);
          $scope.$apply();
        } else {
          ContentService.append($scope.contents, content, function (err, contents) {
            if(err) {
              $log.error(errors[1], err);
              // $rootScope.pop('error', errors[1], err);
              $scope.$apply();
            } else {
              // $scope.contents = contents;
              // $rootScope.pop('success', successes[0], content.title);
              $scope.$apply();

              SubnavigationService.append($scope.navs, {page:page, target: content.name, name: content.title}, function(err, navs) {
                if(err) {
                  $log.error(errors[2], err);
                  $rootScope.pop('error', errors[2], err);
                } else {
                  // $scope.navs = navs;
                  // $rootScope.pop('success', successes[1], content.name);
                  $scope.$apply();

                  // also add an image block to content block
                  GalleryService.addBlock($scope.images, content, function(err, imageBock) {
                    if(err) {
                      $log.error(errors[3], err);
                      $rootScope.pop('error', errors[3], err);
                    } else {
                      $rootScope.pop('success', successes[3], content.name);
                      $scope.$apply();
                    }
                  });
                }
              });
            }
          });
        }
      });
    }
  };

  $scope.addNav = function() {
    if($rootScope.authenticated) {
      SubnavigationService.append($scope.navs, {page:page}, function(err, navs) {
        if(err) $log.error("Error: On add subnavigation!", err);
        // $log.debug("add done on subnavigation!", '');
      });
    }
  };

  $scope.moveForwardContent = function(index, content) {
    SortableService.moveForward(index, $scope.contents, function(err, contents) {
      if(err) $log.error("Error: On move content forward!", err);
      else $scope.contents = contents;
    });
  };

  $scope.moveForwardNav = function(index, nav) {
    SortableService.moveForward(index, $scope.navs, function(err, navs) {
      if(err) $log.error("Error: On move subnavigation forward!", err);
      else $scope.navs = navs;
    });
  };

  $scope.moveBackwardContent = function(index, content) {
    SortableService.moveBackward(index, $scope.contents, function(err, contents) {
      if(err) $log.error("Error: On move content backward!", err);
      else $scope.contents = contents;
    });
  }

  $scope.moveBackwardNav = function(index, nav) {
    SortableService.moveBackward(index, $scope.navs, function(err, navs) {
      if(err) $log.error("Error: On move content backward!", err);
      else $scope.navs = navs;
    });
  };

  $scope.editImage = GalleryService.edit;

  $scope.editContent = function(index, content) {
    if($rootScope.authenticated) {
      ContentService.edit(content, function(err, newContent) {
        if(err) $log.error("Error: On edit content!", err);
        else {
          content = newContent;
          $scope.save();
        }
      });
    }
  };

  $scope.editNavs = function(navs) {
    if($rootScope.authenticated) {
      SubnavigationService.edit(navs, function(err) {
        if(err) $log.error("Error: On edit subnavigations!", err);
      });
    }
  };

  $scope.saveImage = function(image) {
    if($rootScope.authenticated) {
      if(angular.isDefined(image)) {
        GalleryService.saveOne(image, page, image.name, function (err, image) {
          if(err) {
            $log.error (err, image);
            $rootScope.pop('error', err, image);
          } else {
            $rootScope.pop('success', 'Bild wurde aktualisiert', '');
          }
        });
      }
    }
  };

  $scope.save = function() {
    if($rootScope.authenticated) {
      GalleryService.saveAllBlocks($scope.images, page, function (err, images) {
        if(err) {
          $log.error (err, images);
          $rootScope.pop('error', err, images);
        } else {
          $log.debug ("Bilder wurden aktualisiert", images);
          // $rootScope.pop('success', 'Bilder wurden auf dem Server gespeichert', '');
          ContentService.save($scope.contents, page, function(err, contents) {
            if(err) {
              $log.error("Error: On save content!", err);
              if(cb) return cb(err);
              return err;
            } else {
              $log.debug ('Contentblocks wurden aktualisiert', contents);
              // $rootScope.pop('success', 'Contentblocks wurden auf dem Server gespeichert', '');
              SubnavigationService.save($scope.navs, page, function(err, navs) {
                if(err) {
                  $log.error("Error: On save subnavigations!", err);
                  if(cb) return cb(err);
                  return err;
                } else {
                  $log.debug ('Subnavigation wurde aktualisiert', contents);
                  // $rootScope.pop('success', 'Subnavigation wurde auf dem Server gespeichert', '');
                  $rootScope.pop('success', 'Galerie wurde gespeichert', '');
                }
              });
            }
          });
        }
      });
    }
  };

  // http://stackoverflow.com/questions/21702375/angularjs-ng-click-over-ng-click
  $scope.stopPropagation = function (event) {
     event.stopPropagation();
  };

  $scope.goToImage = function (image) {
    $state.go('layout.gallery-fullscreen', {id:image.id});
  }

  /* ==== Drag and Drop Stuff ==== */
  $scope.onDragOnImageComplete = function(index, image, evt, content) {
    if(image == null) {
      $log.debug("*click*", index);
      var image = $scope.images[content.name][index];
      $scope.goToImage(image);
    }
  };

  $scope.onDropOnImageComplete = function(dropimageindex, dragimage, evt, content) {

    SortableService.dropMove($scope.images[content.name], dropimageindex, dragimage, evt, function(err, images) {
      $scope.images[content.name] = images;
      $scope.save();
    }, SortableService.move);
  };

  $scope.onDropOnAreaComplete = function(image, evt, content) {
    $log.debug("onDropOnAreaComplete");
    // $log.debug("images", $scope.images);
    // $log.debug("content", content);

    // var index = $scope.images[content.name].indexOf(image);

    if(image !== null && image.content != content.name) {
      $log.debug("Move image from one content block to another:\n\t"+image.content+" => "+content.name);
      var content_from = image.content;
      var content_to = content.name;
      SortableService.moveObjectToAnotherArray(image, null, $scope.images[content_from], $scope.images[content_to], function (err, result) {
        if(err) $rootScope.pop('error', "Bild konnte nicht verschoben werden", err);
        else {
          result.array_to[result.index_to].content = content_to;
          // $rootScope.pop('success', "Bild erfolgreich verschoben", content_from+" => "+content_to);
        }
        $scope.save();
        $scope.$apply();
      });
    } else {
      $log.debug("Move image within the content block:\n\t"+content.name);
    }

  };

  $scope.addDropdownActions = {
    'addImage': addImage,
    'addContent': addContent
  }

  $scope.addDropdown = [
    {
      "text": " Bild",
      "click": "addDropdownActions.addImage()"
    },
    {
      "text": " Überschrift",
      "click": "addDropdownActions.addContent()"
    }
  ];

});<|endoftext|>


<commit_before>'use strict';

/*
* The original C++ code and copyright notice is from the [Boost library]{@link http://www.boost.org/doc/libs/1_64_0/boost/math/special_functions/factorials.hpp}.
*
* The implementation has been modified for JavaScript.
*/

/*
* (C) Copyright John Maddock 2006, 2010.
* Use, modification and distribution are subject to the
* Boost Software License, Version 1.0. (See accompanying file
* LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
*/

// MODULES //

var isNonNegativeInteger = require( '@stdlib/math/base/assert/is-nonnegative-integer' );
var isInteger = require( '@stdlib/math/base/assert/is-integer' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var gammaDeltaRatio = require( '@stdlib/math/base/special/gamma-delta-ratio' );
var floor = require( '@stdlib/math/base/special/floor' );
var abs = require( '@stdlib/math/base/special/abs' );
var FLOAT64_MAX = require( '@stdlib/math/constants/float64-max' );
var PINF = require( '@stdlib/math/constants/float64-pinf' );

// VARIABLES //

var MAX_FACTORIAL = 170;

// FUNCTIONS //

/**
* Computes the rising factorial of x and n.
*
* ## Notes
*
* * The rising factorial is defined as
*
*   ``` tex
*   \operatorname{risingFactorial}(x, n) = x (x-1) (x-2) (x-3) \ldots (x-n+1)
*   ```
*
*   or equivalently
*
*   ``` tex
*   \operatorname{risingFactorial}(x, n) = \frac{ \Gamma(x + n) }{ \Gamma(x) };
*   ```
*
* @private
* @param {number} x - first function parameter
* @param {integer} n - second function parameter
* @returns {number} function value
*/
function risingFactorial( x, n ) {
	var result;
	var inv;
	var val;

	if ( isnan( x ) || !isInteger( n ) ) {
		return NaN;
	}
	if ( x < 0.0 ) {
		// For x less than zero, we really have a falling factorial, modulo a possible change of sign. Note that the falling factorial isn't defined for negative n, so we'll get rid of that case first:
		inv = false;
		if ( n < 0.0 ) {
			x += n;
			n = -n;
			inv = true;
		}
		result = ( (n&1) ? -1 : 1 ) * fallingFactorial( -x, n );
		if ( inv ) {
			result = 1.0 / result;
		}
		return result;
	}
	if ( n === 0.0 ) {
		return 1.0;
	}
	if ( x === 0.0 ) {
		if ( n < 0 ) {
			return -gammaDeltaRatio( x + 1.0, -n );
		}
		return 0.0;
	}
	if ( x < 1.0 && x + n < 0.0 ) {
		val = gammaDeltaRatio( 1.0 - x, -n );
		return ( n & 1 ) ? -val : val;
	}
	// We don't optimize this for small n, because gammaDeltaRatio is already optimized for that use case:
	return 1.0 / gammaDeltaRatio( x, n );
} // end FUNCTION risingFactorial()

// MAIN //

/**
* Computes the falling factorial of x and n.
*
* ## Notes
*
* * The falling factorial is defined as
*
*   ``` tex
*   \operatorname{fallingFactorial}(x, n) = x (x-1) (x-2) (x-3) \ldots (x-n+1)
*   ```
*
* @param {number} x - first function parameter
* @param {NonNegativeInteger} n - second function parameter
* @returns {number} function value
*
* @example
* var v = fallingFactorial( 0.9, 5 );
* // returns ~0.644
*
* @example
* var v = fallingFactorial( -9.0, 3 );
* // returns -990
*
* @example
* var v = fallingFactorial( 0.0, 2 );
* // returns 0.0
*
* @example
* var v = fallingFactorial( 3.0, -2 );
* // returns NaN
*/
function fallingFactorial( x, n ) {
	var result;
	var xp1;
	var n2;
	var t1;
	var t2;

	if ( isnan( x ) || !isNonNegativeInteger( n ) ) {
		return NaN;
	}
	if ( x === 0.0 ) {
		return 0.0;
	}
	if ( x < 0 ) {
		// For x < 0 we really have a rising factorial modulo a possible change of sign:
		return ( n&1 ? -1 : 1 ) * risingFactorial( -x, n );
	}
	if ( n === 0 ) {
		return 1.0;
	}
	if ( x < 0.5 ) {
		// 1 + x below will throw away digits, so split up calculation:
		if ( n > MAX_FACTORIAL - 2 ) {
			// If the two end of the range are far apart we have a ratio of two very large numbers, split the calculation up into two blocks:
			t1 = x * fallingFactorial( x - 1, MAX_FACTORIAL - 2 );
			t2 = fallingFactorial( x-MAX_FACTORIAL+1, n-MAX_FACTORIAL+1 );
			if ( FLOAT64_MAX / abs(t1) < abs(t2) ) {
				return PINF;
			}
			return t1 * t2;
		}
		return x * fallingFactorial( x - 1.0, n - 1.0 );
	}
	if ( x <= n - 1.0 ) {
		// `x+1-n` will be negative and gammaDeltaRatio won't handle it, split the product up into three parts:
		xp1 = x + 1.0;
		n2 = abs( floor( xp1 ) );
		if ( n2 === xp1 ) {
			return 0.0;
		}
		result = gammaDeltaRatio( xp1, -n2 );
		x -= n2;
		result *= x;
		n2 += 1.0;
		if ( n2 < n ) {
			result *= fallingFactorial( x - 1.0, n - n2 );
		}
		return result;
	}
	// Simple case: just the ratio of two (positive argument) gamma functions. Note that we don't optimize this for small n, because gammaDeltaRatio is already optimized for that use case:
	return gammaDeltaRatio( x + 1.0, -n );
} // end FUNCTION fallingFactorial()

// EXPORTS //

module.exports = fallingFactorial;
<commit_msg>Lazy-load risingFactorial dependency<commit_after>'use strict';

/*
* The original C++ code and copyright notice is from the [Boost library]{@link http://www.boost.org/doc/libs/1_64_0/boost/math/special_functions/factorials.hpp}.
*
* The implementation has been modified for JavaScript.
*/

/*
* (C) Copyright John Maddock 2006, 2010.
* Use, modification and distribution are subject to the
* Boost Software License, Version 1.0. (See accompanying file
* LICENSE or copy at http://www.boost.org/LICENSE_1_0.txt)
*/

// MODULES //

var isNonNegativeInteger = require( '@stdlib/math/base/assert/is-nonnegative-integer' );
var isnan = require( '@stdlib/math/base/assert/is-nan' );
var gammaDeltaRatio = require( '@stdlib/math/base/special/gamma-delta-ratio' );
var floor = require( '@stdlib/math/base/special/floor' );
var abs = require( '@stdlib/math/base/special/abs' );
var FLOAT64_MAX = require( '@stdlib/math/constants/float64-max' );
var PINF = require( '@stdlib/math/constants/float64-pinf' );

// VARIABLES //

var MAX_FACTORIAL = 170;
var risingFactorial;

// MAIN //

/**
* Computes the falling factorial of x and n.
*
* ## Notes
*
* * The falling factorial is defined as
*
*   ``` tex
*   \operatorname{fallingFactorial}(x, n) = x (x-1) (x-2) (x-3) \ldots (x-n+1)
*   ```
*
* @param {number} x - first function parameter
* @param {NonNegativeInteger} n - second function parameter
* @returns {number} function value
*
* @example
* var v = fallingFactorial( 0.9, 5 );
* // returns ~0.644
*
* @example
* var v = fallingFactorial( -9.0, 3 );
* // returns -990
*
* @example
* var v = fallingFactorial( 0.0, 2 );
* // returns 0.0
*
* @example
* var v = fallingFactorial( 3.0, -2 );
* // returns NaN
*/
function fallingFactorial( x, n ) {
	var result;
	var xp1;
	var n2;
	var t1;
	var t2;

	// Lazy-load to avoid undefined reference due to circular dependency...
	if ( !risingFactorial ) {
		risingFactorial = require( '@stdlib/math/base/special/rising-factorial' );
	}
	if ( isnan( x ) || !isNonNegativeInteger( n ) ) {
		return NaN;
	}
	if ( x === 0.0 ) {
		return 0.0;
	}
	if ( x < 0 ) {
		// For x < 0 we really have a rising factorial modulo a possible change of sign:
		return ( n&1 ? -1 : 1 ) * risingFactorial( -x, n );
	}
	if ( n === 0 ) {
		return 1.0;
	}
	if ( x < 0.5 ) {
		// 1 + x below will throw away digits, so split up calculation:
		if ( n > MAX_FACTORIAL - 2 ) {
			// If the two end of the range are far apart we have a ratio of two very large numbers, split the calculation up into two blocks:
			t1 = x * fallingFactorial( x - 1, MAX_FACTORIAL - 2 );
			t2 = fallingFactorial( x-MAX_FACTORIAL+1, n-MAX_FACTORIAL+1 );
			if ( FLOAT64_MAX / abs(t1) < abs(t2) ) {
				return PINF;
			}
			return t1 * t2;
		}
		return x * fallingFactorial( x - 1.0, n - 1.0 );
	}
	if ( x <= n - 1.0 ) {
		// `x+1-n` will be negative and gammaDeltaRatio won't handle it, split the product up into three parts:
		xp1 = x + 1.0;
		n2 = abs( floor( xp1 ) );
		if ( n2 === xp1 ) {
			return 0.0;
		}
		result = gammaDeltaRatio( xp1, -n2 );
		x -= n2;
		result *= x;
		n2 += 1.0;
		if ( n2 < n ) {
			result *= fallingFactorial( x - 1.0, n - n2 );
		}
		return result;
	}
	// Simple case: just the ratio of two (positive argument) gamma functions. Note that we don't optimize this for small n, because gammaDeltaRatio is already optimized for that use case:
	return gammaDeltaRatio( x + 1.0, -n );
} // end FUNCTION fallingFactorial()

// EXPORTS //

module.exports = fallingFactorial;
<|endoftext|>


<commit_before>/**
 * modules/analytics data store: settings.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import {
	isValidAccountID,
	isValidInternalWebPropertyID,
	isValidPropertySelection,
	isValidProfileSelection,
	isValidPropertyID,
} from '../util';
import { STORE_NAME, PROPERTY_CREATE, PROFILE_CREATE } from './constants';

const { createRegistrySelector, createRegistryControl } = Data;

// Actions
const SUBMIT_CHANGES_START = 'SUBMIT_CHANGES_START';
const SUBMIT_CHANGES_COMPLETED = 'SUBMIT_CHANGES_COMPLETED';
const SUBMIT_PROPERTY_CREATE = 'SUBMIT_PROPERTY_CREATE';
const SUBMIT_PROFILE_CREATE = 'SUBMIT_PROFILE_CREATE';
const SUBMIT_SAVE_SETTINGS = 'SUBMIT_SAVE_SETTINGS';
const SUBMIT_CHANGES_FAILED = 'SUBMIT_CHANGES_FAILED';

export const INITIAL_STATE = {
	isDoingSubmitChanges: false,
};

export const actions = {
	*submitChanges() {
		yield actions.startSubmitChanges();

		const registry = yield Data.commonActions.getRegistry();
		let propertyID = registry.select( STORE_NAME ).getPropertyID();

		if ( propertyID === PROPERTY_CREATE ) {
			const accountID = registry.select( STORE_NAME ).getAccountID();

			const { payload } = yield actions.submitPropertyCreate( accountID );
			const { property, error } = payload;

			if ( property ) {
				propertyID = property.id;
			} else if ( error ) {
				return actions.submitChangesFailed( { error } );
			}
		}

		const profileID = registry.select( STORE_NAME ).getProfileID();
		if ( profileID === PROFILE_CREATE ) {
			const accountID = registry.select( STORE_NAME ).getAccountID();

			const { payload } = yield actions.submitProfileCreate( accountID, propertyID );
			const { error } = payload;

			if ( error ) {
				return actions.submitChangesFailed( { error } );
			}
		}

		const { error } = yield actions.submitSaveSettings();
		if ( error ) {
			return actions.submitChangesFailed( { error } );
		}

		return actions.finishSubmitChanges();
	},
	submitPropertyCreate( accountID ) {
		return {
			payload: { accountID },
			type: SUBMIT_PROPERTY_CREATE,
		};
	},
	submitProfileCreate( accountID, propertyID ) {
		return {
			payload: { accountID, propertyID },
			type: SUBMIT_PROFILE_CREATE,
		};
	},
	submitSaveSettings() {
		return {
			payload: {},
			type: SUBMIT_SAVE_SETTINGS,
		};
	},
	startSubmitChanges() {
		return {
			payload: {},
			type: SUBMIT_CHANGES_START,
		};
	},
	finishSubmitChanges() {
		return {
			payload: {},
			type: SUBMIT_CHANGES_COMPLETED,
		};
	},
	submitChangesFailed( { error } ) {
		return {
			payload: { error },
			type: SUBMIT_CHANGES_FAILED,
		};
	},
};

export const controls = {
	[ SUBMIT_PROPERTY_CREATE ]: createRegistryControl( ( registry ) => ( { payload } ) => {
		return registry.dispatch( STORE_NAME ).createProperty( payload.accountID );
	} ),
	[ SUBMIT_PROFILE_CREATE ]: createRegistryControl( ( registry ) => ( { payload } ) => {
		const { accountID, propertyID } = payload;
		return registry.dispatch( STORE_NAME ).createProfile( accountID, propertyID );
	} ),
	[ SUBMIT_SAVE_SETTINGS ]: createRegistryControl( ( registry ) => () => {
		return registry.dispatch( STORE_NAME ).saveSettings();
	} ),
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case SUBMIT_CHANGES_START: {
			return {
				...state,
				isDoingSubmitChanges: true,
			};
		}

		case SUBMIT_CHANGES_COMPLETED: {
			return {
				...state,
				isDoingSubmitChanges: false,
			};
		}

		case SUBMIT_CHANGES_FAILED: {
			return {
				...state,
				error: payload.error,
				isDoingSubmitChanges: false,
			};
		}

		default: return state;
	}
};

export const resolvers = {};

export const selectors = {
	/**
	 * Checks if changes can be submitted.
	 */
	canSubmitChanges: createRegistrySelector( ( select ) => () => {
		const {
			getAccountID,
			getInternalWebPropertyID,
			getProfileID,
			getPropertyID,
			hasExistingTagPermission,
			haveSettingsChanged,
			isDoingSubmitChanges,
		} = select( STORE_NAME );

		if ( isDoingSubmitChanges() ) {
			return false;
		}
		if ( ! haveSettingsChanged() ) {
			return false;
		}
		if ( ! isValidAccountID( getAccountID() ) ) {
			return false;
		}
		if ( ! isValidPropertySelection( getPropertyID() ) ) {
			return false;
		}
		if ( ! isValidProfileSelection( getProfileID() ) ) {
			return false;
		}
		// If the property ID is valid (non-create) the internal ID must be valid as well.
		if ( isValidPropertyID( getPropertyID() ) && ! isValidInternalWebPropertyID( getInternalWebPropertyID() ) ) {
			return false;
		}
		// Do existing tag check last.
		if ( hasExistingTagPermission() === false ) {
			return false;
		}

		return true;
	} ),

	isDoingSubmitChanges( state ) {
		return !! state.isDoingSubmitChanges;
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};

<commit_msg>Update consuming code of createProperty.<commit_after>/**
 * modules/analytics data store: settings.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import {
	isValidAccountID,
	isValidInternalWebPropertyID,
	isValidPropertySelection,
	isValidProfileSelection,
	isValidPropertyID,
} from '../util';
import { STORE_NAME, PROPERTY_CREATE, PROFILE_CREATE } from './constants';

const { createRegistrySelector, createRegistryControl } = Data;

// Actions
const SUBMIT_CHANGES_START = 'SUBMIT_CHANGES_START';
const SUBMIT_CHANGES_COMPLETED = 'SUBMIT_CHANGES_COMPLETED';
const SUBMIT_PROPERTY_CREATE = 'SUBMIT_PROPERTY_CREATE';
const SUBMIT_PROFILE_CREATE = 'SUBMIT_PROFILE_CREATE';
const SUBMIT_SAVE_SETTINGS = 'SUBMIT_SAVE_SETTINGS';
const SUBMIT_CHANGES_FAILED = 'SUBMIT_CHANGES_FAILED';

export const INITIAL_STATE = {
	isDoingSubmitChanges: false,
};

export const actions = {
	*submitChanges() {
		yield actions.startSubmitChanges();

		const registry = yield Data.commonActions.getRegistry();
		let propertyID = registry.select( STORE_NAME ).getPropertyID();

		if ( propertyID === PROPERTY_CREATE ) {
			const accountID = registry.select( STORE_NAME ).getAccountID();

			const { response: property, error } = yield actions.submitPropertyCreate( accountID );

			if ( property ) {
				propertyID = property.id;
			} else if ( error ) {
				return actions.submitChangesFailed( { error } );
			}
		}

		const profileID = registry.select( STORE_NAME ).getProfileID();
		if ( profileID === PROFILE_CREATE ) {
			const accountID = registry.select( STORE_NAME ).getAccountID();

			const { payload } = yield actions.submitProfileCreate( accountID, propertyID );
			const { error } = payload;

			if ( error ) {
				return actions.submitChangesFailed( { error } );
			}
		}

		const { error } = yield actions.submitSaveSettings();
		if ( error ) {
			return actions.submitChangesFailed( { error } );
		}

		return actions.finishSubmitChanges();
	},
	submitPropertyCreate( accountID ) {
		return {
			payload: { accountID },
			type: SUBMIT_PROPERTY_CREATE,
		};
	},
	submitProfileCreate( accountID, propertyID ) {
		return {
			payload: { accountID, propertyID },
			type: SUBMIT_PROFILE_CREATE,
		};
	},
	submitSaveSettings() {
		return {
			payload: {},
			type: SUBMIT_SAVE_SETTINGS,
		};
	},
	startSubmitChanges() {
		return {
			payload: {},
			type: SUBMIT_CHANGES_START,
		};
	},
	finishSubmitChanges() {
		return {
			payload: {},
			type: SUBMIT_CHANGES_COMPLETED,
		};
	},
	submitChangesFailed( { error } ) {
		return {
			payload: { error },
			type: SUBMIT_CHANGES_FAILED,
		};
	},
};

export const controls = {
	[ SUBMIT_PROPERTY_CREATE ]: createRegistryControl( ( registry ) => ( { payload } ) => {
		return registry.dispatch( STORE_NAME ).createProperty( payload.accountID );
	} ),
	[ SUBMIT_PROFILE_CREATE ]: createRegistryControl( ( registry ) => ( { payload } ) => {
		const { accountID, propertyID } = payload;
		return registry.dispatch( STORE_NAME ).createProfile( accountID, propertyID );
	} ),
	[ SUBMIT_SAVE_SETTINGS ]: createRegistryControl( ( registry ) => () => {
		return registry.dispatch( STORE_NAME ).saveSettings();
	} ),
};

export const reducer = ( state, { type, payload } ) => {
	switch ( type ) {
		case SUBMIT_CHANGES_START: {
			return {
				...state,
				isDoingSubmitChanges: true,
			};
		}

		case SUBMIT_CHANGES_COMPLETED: {
			return {
				...state,
				isDoingSubmitChanges: false,
			};
		}

		case SUBMIT_CHANGES_FAILED: {
			return {
				...state,
				error: payload.error,
				isDoingSubmitChanges: false,
			};
		}

		default: return state;
	}
};

export const resolvers = {};

export const selectors = {
	/**
	 * Checks if changes can be submitted.
	 */
	canSubmitChanges: createRegistrySelector( ( select ) => () => {
		const {
			getAccountID,
			getInternalWebPropertyID,
			getProfileID,
			getPropertyID,
			hasExistingTagPermission,
			haveSettingsChanged,
			isDoingSubmitChanges,
		} = select( STORE_NAME );

		if ( isDoingSubmitChanges() ) {
			return false;
		}
		if ( ! haveSettingsChanged() ) {
			return false;
		}
		if ( ! isValidAccountID( getAccountID() ) ) {
			return false;
		}
		if ( ! isValidPropertySelection( getPropertyID() ) ) {
			return false;
		}
		if ( ! isValidProfileSelection( getProfileID() ) ) {
			return false;
		}
		// If the property ID is valid (non-create) the internal ID must be valid as well.
		if ( isValidPropertyID( getPropertyID() ) && ! isValidInternalWebPropertyID( getInternalWebPropertyID() ) ) {
			return false;
		}
		// Do existing tag check last.
		if ( hasExistingTagPermission() === false ) {
			return false;
		}

		return true;
	} ),

	isDoingSubmitChanges( state ) {
		return !! state.isDoingSubmitChanges;
	},
};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};

<|endoftext|>


<commit_before>/**
 * External dependencies
 */
import { get } from 'lodash';
import { ampSlug, errorMessages, isStandardMode } from 'amp-block-editor-data';
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { Component, createRef, renderToString } from '@wordpress/element';
import { Icon, IconButton } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { withSelect, withDispatch } from '@wordpress/data';
import { DotTip } from '@wordpress/nux';
import { ifCondition, compose } from '@wordpress/compose';
import { addQueryArgs } from '@wordpress/url';

/**
 * Internal dependencies
 */
import ampBlackIcon from '../../../images/amp-black-icon.svg';
import ampFilledIcon from '../../../images/amp-icon.svg';
import { isAMPEnabled } from '../helpers';
import { POST_PREVIEW_CLASS } from '../constants';

/**
 * Writes the message and graphic in the new preview window that was opened.
 *
 * Forked from the Core component <PostPreviewButton>.
 * The errorMessages are imported via wp_localize_script().
 *
 * @see https://github.com/WordPress/gutenberg/blob/95e769df1f82f6b0ef587d81af65dd2f48cd1c38/packages/editor/src/components/post-preview-button/index.js#L17
 * @param {Object} targetDocument The target document.
 */
function writeInterstitialMessage( targetDocument ) {
	let markup = renderToString(

			<Icon
				icon={ ampBlackIcon }
				viewBox="0 0 98 98"
			/>

{ __( 'Generating AMP preview…', 'amp' ) }

	);

	markup += `

	`;

	targetDocument.write( markup );
	targetDocument.title = __( 'Generating AMP preview…', 'amp' );
	targetDocument.close();
}

/**
 * A 'Preview AMP' button, forked from the Core 'Preview' button: <PostPreviewButton>.
 *
 * Rendered into the DOM with renderPreviewButton() in helpers/index.js.
 * This also moves the (non-AMP) 'Preview' button to before this, if it's not already there.
 *
 * @see https://github.com/WordPress/gutenberg/blob/95e769df1f82f6b0ef587d81af65dd2f48cd1c38/packages/editor/src/components/post-preview-button/index.js
 */
class AMPPreview extends Component {
	/**
	 * Constructs the class.
	 *
	 * @param {*} args Constructor arguments.
	 */
	constructor( ...args ) {
		super( ...args );
		this.openPreviewWindow = this.openPreviewWindow.bind( this );
		this.moveButton = this.moveButton.bind( this );
		this.buttonRef = createRef();
	}

	/**
	 * Called after the component updated.
	 *
	 * @param {Object} prevProps The previous props.
	 */
	componentDidUpdate( prevProps ) {
		const { previewLink } = this.props;

		// This relies on the window being responsible to unset itself when
		// navigation occurs or a new preview window is opened, to avoid
		// unintentional forceful redirects.
		if ( previewLink && ! prevProps.previewLink ) {
			this.setPreviewWindowLink( previewLink );
		}

		this.moveButton();
	}

	/**
	 * Moves the (non-AMP) 'Preview' button to before this 'Preview AMP' button, if it's not there already.
	 */
	moveButton() {
		const buttonWrapper = get( this, [ 'buttonRef', 'current', 'parentNode' ], false );
		if ( ! buttonWrapper ) {
			return;
		}

		if ( ! buttonWrapper.previousSibling || ! buttonWrapper.previousSibling.classList.contains( POST_PREVIEW_CLASS ) ) {
			const postPreviewButton = document.querySelector( `.${ POST_PREVIEW_CLASS }` );
			buttonWrapper.parentNode.insertBefore( postPreviewButton, buttonWrapper );
		}
	}

	/**
	 * Sets the preview window's location to the given URL, if a preview window
	 * exists and is not closed.
	 *
	 * @param {string} url URL to assign as preview window location.
	 */
	setPreviewWindowLink( url ) {
		const { previewWindow } = this;

		if ( previewWindow && ! previewWindow.closed ) {
			previewWindow.location = url;
		}
	}

	/**
	 * Gets the window target.
	 */
	getWindowTarget() {
		const { postId } = this.props;
		return `wp-preview-${ postId }`;
	}

	/**
	 * Opens the preview window.
	 *
	 * @param {Object} event The DOM event.
	 */
	openPreviewWindow( event ) {
		// Our Preview button has its 'href' and 'target' set correctly for a11y
		// purposes. Unfortunately, though, we can't rely on the default 'click'
		// handler since sometimes it incorrectly opens a new tab instead of reusing
		// the existing one.
		// https://github.com/WordPress/gutenberg/pull/8330
		event.preventDefault();

		// Open up a Preview tab if needed. This is where we'll show the preview.
		if ( ! this.previewWindow || this.previewWindow.closed ) {
			this.previewWindow = window.open( '', this.getWindowTarget() );
		}

		// Focus the Preview tab. This might not do anything, depending on the browser's
		// and user's preferences.
		// https://html.spec.whatwg.org/multipage/interaction.html#dom-window-focus
		this.previewWindow.focus();

		// If we don't need to autosave the post before previewing, then we simply
		// load the Preview URL in the Preview tab.
		if ( ! this.props.isAutosaveable ) {
			this.setPreviewWindowLink( event.target.href );
			return;
		}

		// Request an autosave. This happens asynchronously and causes the component
		// to update when finished.
		if ( this.props.isDraft ) {
			this.props.savePost( { isPreview: true } );
		} else {
			this.props.autosave( { isPreview: true } );
		}

		// Display a 'Generating preview' message in the Preview tab while we wait for the
		// autosave to finish.
		writeInterstitialMessage( this.previewWindow.document );
	}

	/**
	 * Renders the component.
	 */
	render() {
		const { previewLink, currentPostLink, isEnabled, isSaveable } = this.props;

		// Link to the `?preview=true` URL if we have it, since this lets us see
		// changes that were autosaved since the post was last published. Otherwise,
		// just link to the post's URL.
		const href = previewLink || currentPostLink;
		const buttonIcon = <Icon viewBox="0 0 62 62" icon={ ampFilledIcon } />;

		return (
			isEnabled && ! errorMessages.length && ! isStandardMode && (
				<IconButton
					icon={ buttonIcon }
					isLarge
					className="amp-editor-post-preview"
					href={ href }
					label={ __( 'Preview AMP', 'amp' ) }
					target={ this.getWindowTarget() }
					disabled={ ! isSaveable }
					onClick={ this.openPreviewWindow }
					ref={ this.buttonRef }
				>

						{
							/* translators: accessibility text */
							__( '(opens in a new tab)', 'amp' )
						}

					<DotTip tipId="core/editor.preview">
						{ __( 'Click “Preview” to load a preview of this page in AMP, so you can make sure you’re happy with your blocks.', 'amp' ) }
					</DotTip>
				</IconButton>
			)
		);
	}
}

AMPPreview.propTypes = {
	autosave: PropTypes.func.isRequired,
	currentPostLink: PropTypes.string.isRequired,
	postId: PropTypes.number.isRequired,
	previewLink: PropTypes.string,
	isAutosaveable: PropTypes.bool.isRequired,
	isDraft: PropTypes.bool.isRequired,
	isEnabled: PropTypes.bool.isRequired,
	isSaveable: PropTypes.bool.isRequired,
	isViewable: PropTypes.bool.isRequired,
	savePost: PropTypes.func.isRequired,
};

export default compose( [
	withSelect( ( select, { forcePreviewLink, forceIsAutosaveable } ) => {
		const {
			getCurrentPostId,
			getCurrentPostAttribute,
			getEditedPostAttribute,
			isEditedPostSaveable,
			isEditedPostAutosaveable,
			getEditedPostPreviewLink,
		} = select( 'core/editor' );
		const {
			getPostType,
		} = select( 'core' );

		const queryArgs = {};
		queryArgs[ ampSlug ] = 1;
		const initialPreviewLink = getEditedPostPreviewLink();
		const previewLink = initialPreviewLink ? addQueryArgs( initialPreviewLink, queryArgs ) : undefined;
		const postType = getPostType( getEditedPostAttribute( 'type' ) );

		return {
			postId: getCurrentPostId(),
			currentPostLink: addQueryArgs( getCurrentPostAttribute( 'link' ), queryArgs ),
			previewLink: forcePreviewLink !== undefined ? forcePreviewLink : previewLink,
			isSaveable: isEditedPostSaveable(),
			isAutosaveable: forceIsAutosaveable || isEditedPostAutosaveable(),
			isViewable: get( postType, [ 'viewable' ], false ),
			isDraft: [ 'draft', 'auto-draft' ].indexOf( getEditedPostAttribute( 'status' ) ) !== -1,
			isEnabled: isAMPEnabled(),
		};
	} ),
	withDispatch( ( dispatch ) => ( {
		autosave: dispatch( 'core/editor' ).autosave,
		savePost: dispatch( 'core/editor' ).savePost,
	} ) ),
	ifCondition( ( { isViewable } ) => isViewable ),
] )( AMPPreview );
<commit_msg>Fix an issue where the 'Preview AMP' button was out of place on a new post<commit_after>/**
 * External dependencies
 */
import { get } from 'lodash';
import { ampSlug, errorMessages, isStandardMode } from 'amp-block-editor-data';
import PropTypes from 'prop-types';

/**
 * WordPress dependencies
 */
import { Component, createRef, renderToString } from '@wordpress/element';
import { Icon, IconButton } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { withSelect, withDispatch } from '@wordpress/data';
import { DotTip } from '@wordpress/nux';
import { ifCondition, compose } from '@wordpress/compose';
import { addQueryArgs } from '@wordpress/url';

/**
 * Internal dependencies
 */
import ampBlackIcon from '../../../images/amp-black-icon.svg';
import ampFilledIcon from '../../../images/amp-icon.svg';
import { isAMPEnabled } from '../helpers';
import { POST_PREVIEW_CLASS } from '../constants';

/**
 * Writes the message and graphic in the new preview window that was opened.
 *
 * Forked from the Core component <PostPreviewButton>.
 * The errorMessages are imported via wp_localize_script().
 *
 * @see https://github.com/WordPress/gutenberg/blob/95e769df1f82f6b0ef587d81af65dd2f48cd1c38/packages/editor/src/components/post-preview-button/index.js#L17
 * @param {Object} targetDocument The target document.
 */
function writeInterstitialMessage( targetDocument ) {
	let markup = renderToString(

			<Icon
				icon={ ampBlackIcon }
				viewBox="0 0 98 98"
			/>

{ __( 'Generating AMP preview…', 'amp' ) }

	);

	markup += `

	`;

	targetDocument.write( markup );
	targetDocument.title = __( 'Generating AMP preview…', 'amp' );
	targetDocument.close();
}

/**
 * A 'Preview AMP' button, forked from the Core 'Preview' button: <PostPreviewButton>.
 *
 * Rendered into the DOM with renderPreviewButton() in helpers/index.js.
 * This also moves the (non-AMP) 'Preview' button to before this, if it's not already there.
 *
 * @see https://github.com/WordPress/gutenberg/blob/95e769df1f82f6b0ef587d81af65dd2f48cd1c38/packages/editor/src/components/post-preview-button/index.js
 */
class AMPPreview extends Component {
	/**
	 * Constructs the class.
	 *
	 * @param {*} args Constructor arguments.
	 */
	constructor( ...args ) {
		super( ...args );
		this.openPreviewWindow = this.openPreviewWindow.bind( this );
		this.moveButton = this.moveButton.bind( this );
		this.buttonRef = createRef();
	}

	/**
	 * Called after the component updated.
	 *
	 * @param {Object} prevProps The previous props.
	 */
	componentDidUpdate( prevProps ) {
		const { previewLink } = this.props;

		// This relies on the window being responsible to unset itself when
		// navigation occurs or a new preview window is opened, to avoid
		// unintentional forceful redirects.
		if ( previewLink && ! prevProps.previewLink ) {
			this.setPreviewWindowLink( previewLink );
		}

		this.moveButton();
	}

	/**
	 * Moves the (non-AMP) 'Preview' button to before this 'Preview AMP' button, if it's not there already.
	 */
	moveButton() {
		const buttonWrapper = get( this, [ 'buttonRef', 'current', 'parentNode' ], false );
		if ( ! buttonWrapper ) {
			return;
		}

		if ( ! buttonWrapper.previousSibling || ! buttonWrapper.previousSibling.classList.contains( POST_PREVIEW_CLASS ) ) {
			const postPreviewButton = document.querySelector( `.${ POST_PREVIEW_CLASS }` );
			if ( get( postPreviewButton, 'nextSibling' ) ) {
				buttonWrapper.parentNode.insertBefore( buttonWrapper, postPreviewButton.nextSibling );
			}
		}
	}

	/**
	 * Sets the preview window's location to the given URL, if a preview window
	 * exists and is not closed.
	 *
	 * @param {string} url URL to assign as preview window location.
	 */
	setPreviewWindowLink( url ) {
		const { previewWindow } = this;

		if ( previewWindow && ! previewWindow.closed ) {
			previewWindow.location = url;
		}
	}

	/**
	 * Gets the window target.
	 */
	getWindowTarget() {
		const { postId } = this.props;
		return `wp-preview-${ postId }`;
	}

	/**
	 * Opens the preview window.
	 *
	 * @param {Object} event The DOM event.
	 */
	openPreviewWindow( event ) {
		// Our Preview button has its 'href' and 'target' set correctly for a11y
		// purposes. Unfortunately, though, we can't rely on the default 'click'
		// handler since sometimes it incorrectly opens a new tab instead of reusing
		// the existing one.
		// https://github.com/WordPress/gutenberg/pull/8330
		event.preventDefault();

		// Open up a Preview tab if needed. This is where we'll show the preview.
		if ( ! this.previewWindow || this.previewWindow.closed ) {
			this.previewWindow = window.open( '', this.getWindowTarget() );
		}

		// Focus the Preview tab. This might not do anything, depending on the browser's
		// and user's preferences.
		// https://html.spec.whatwg.org/multipage/interaction.html#dom-window-focus
		this.previewWindow.focus();

		// If we don't need to autosave the post before previewing, then we simply
		// load the Preview URL in the Preview tab.
		if ( ! this.props.isAutosaveable ) {
			this.setPreviewWindowLink( event.target.href );
			return;
		}

		// Request an autosave. This happens asynchronously and causes the component
		// to update when finished.
		if ( this.props.isDraft ) {
			this.props.savePost( { isPreview: true } );
		} else {
			this.props.autosave( { isPreview: true } );
		}

		// Display a 'Generating preview' message in the Preview tab while we wait for the
		// autosave to finish.
		writeInterstitialMessage( this.previewWindow.document );
	}

	/**
	 * Renders the component.
	 */
	render() {
		const { previewLink, currentPostLink, isEnabled, isSaveable } = this.props;

		// Link to the `?preview=true` URL if we have it, since this lets us see
		// changes that were autosaved since the post was last published. Otherwise,
		// just link to the post's URL.
		const href = previewLink || currentPostLink;
		const buttonIcon = <Icon viewBox="0 0 62 62" icon={ ampFilledIcon } />;

		return (
			isEnabled && ! errorMessages.length && ! isStandardMode && (
				<IconButton
					icon={ buttonIcon }
					isLarge
					className="amp-editor-post-preview"
					href={ href }
					label={ __( 'Preview AMP', 'amp' ) }
					target={ this.getWindowTarget() }
					disabled={ ! isSaveable }
					onClick={ this.openPreviewWindow }
					ref={ this.buttonRef }
				>

						{
							/* translators: accessibility text */
							__( '(opens in a new tab)', 'amp' )
						}

					<DotTip tipId="core/editor.preview">
						{ __( 'Click “Preview” to load a preview of this page in AMP, so you can make sure you’re happy with your blocks.', 'amp' ) }
					</DotTip>
				</IconButton>
			)
		);
	}
}

AMPPreview.propTypes = {
	autosave: PropTypes.func.isRequired,
	currentPostLink: PropTypes.string.isRequired,
	postId: PropTypes.number.isRequired,
	previewLink: PropTypes.string,
	isAutosaveable: PropTypes.bool.isRequired,
	isDraft: PropTypes.bool.isRequired,
	isEnabled: PropTypes.bool.isRequired,
	isSaveable: PropTypes.bool.isRequired,
	isViewable: PropTypes.bool.isRequired,
	savePost: PropTypes.func.isRequired,
};

export default compose( [
	withSelect( ( select, { forcePreviewLink, forceIsAutosaveable } ) => {
		const {
			getCurrentPostId,
			getCurrentPostAttribute,
			getEditedPostAttribute,
			isEditedPostSaveable,
			isEditedPostAutosaveable,
			getEditedPostPreviewLink,
		} = select( 'core/editor' );
		const {
			getPostType,
		} = select( 'core' );

		const queryArgs = {};
		queryArgs[ ampSlug ] = 1;
		const initialPreviewLink = getEditedPostPreviewLink();
		const previewLink = initialPreviewLink ? addQueryArgs( initialPreviewLink, queryArgs ) : undefined;
		const postType = getPostType( getEditedPostAttribute( 'type' ) );

		return {
			postId: getCurrentPostId(),
			currentPostLink: addQueryArgs( getCurrentPostAttribute( 'link' ), queryArgs ),
			previewLink: forcePreviewLink !== undefined ? forcePreviewLink : previewLink,
			isSaveable: isEditedPostSaveable(),
			isAutosaveable: forceIsAutosaveable || isEditedPostAutosaveable(),
			isViewable: get( postType, [ 'viewable' ], false ),
			isDraft: [ 'draft', 'auto-draft' ].indexOf( getEditedPostAttribute( 'status' ) ) !== -1,
			isEnabled: isAMPEnabled(),
		};
	} ),
	withDispatch( ( dispatch ) => ( {
		autosave: dispatch( 'core/editor' ).autosave,
		savePost: dispatch( 'core/editor' ).savePost,
	} ) ),
	ifCondition( ( { isViewable } ) => isViewable ),
] )( AMPPreview );
<|endoftext|>


<commit_before>// Article represents the metadata for a single article.
// ArticleList is a collection of articles for use in the list view
//
$( function() {
	mw.pageTriage = {
		Article: Backbone.Model.extend( {
			defaults: {
				title: 'Empty Article',
				pageid: ''
			},

			initialize: function() {
				this.bind( 'change', this.formatMetadata, this );
			},

			formatMetadata: function ( article ) {
				var creation_date_parsed = Date.parseExact( article.get( 'creation_date' ), 'yyyyMMddHHmmss' );
				article.set('creation_date_pretty', creation_date_parsed.toString( gM( 'pagetriage-creation-dateformat' ) ) );

				// sometimes user info isn't set, so check that first.
				if( article.get( 'user_creation_date' ) ) {
					var user_creation_date_parsed = Date.parseExact( article.get( 'user_creation_date' ), 'yyyyMMddHHmmss' );
					article.set( 'user_creation_date_pretty', user_creation_date_parsed.toString( gM( 'pagetriage-user-creation-dateformat' ) ) );
				} else {
					article.set( 'user_creation_date_pretty', '');
				}

				var userName = article.get( 'user_name' );
				if( userName ) {
					article.set( 'user_title', new mw.Title( userName, mw.config.get('wgNamespaceIds')['user'] ) );
					article.set( 'user_talk_title', new mw.Title( userName, mw.config.get('wgNamespaceIds')['user_talk'] ) );
					article.set( 'user_contribs_title', new mw.Title( gM( 'pagetriage-special-contributions' ) + '/' + userName ) );
				}
				article.set( 'title_url', mw.util.wikiUrlencode( article.get( 'title' ) ) );
			}

		} ),
	};

	// can't include this in the declaration above because it references the
	// object created therein.
	mw.pageTriage.ArticleList = Backbone.Collection.extend( {
		model: mw.pageTriage.Article,

		apiParams: {
			namespace: 0,
			limit: 50,
			/*
			showbots: null,
			showredirs: null,
			showtriaged: null,
			limit: 10,
			namespace: 0,
			no_category: 1,
			no_inbound_links: 1,
			non_autoconfirmed_users: 1,
			blocked_users: 1,
			*/
		},

		initialize: function( options ) {
			this.eventBus = options.eventBus;
			this.eventBus.bind( "filterSet", this.setParams );
			//this.eventBus.bind( "paramsSet", this.fetch );
		},

		url: function() {
			var paramString = '';
			for ( var key in this.apiParams ) {
				console.debug(this.apiParams);
				paramString += '&' + key + '=' + this.apiParams[key];
			}
			var url = mw.util.wikiScript( 'api' ) + '?action=pagetriagelist&format=json' + paramString;
			console.log('fetching ' + url);
			return url;
		},

		parse: function( response ) {
			// extract the useful bits of json.
			return response.pagetriagelist.pages;
		},

		setParams: function( apiParams ) {
			this.apiParams = apiParams;
			this.eventBus.trigger( "paramsSet", this.apiParams );
		},

		getParam: function( key ) {
			return this.apiParams[key];
		}

	} );

} );
<commit_msg>follow-up to r114689 - reverting accidental commit of debugging code<commit_after>// Article represents the metadata for a single article.
// ArticleList is a collection of articles for use in the list view
//
$( function() {
	mw.pageTriage = {
		Article: Backbone.Model.extend( {
			defaults: {
				title: 'Empty Article',
				pageid: ''
			},

			initialize: function() {
				this.bind( 'change', this.formatMetadata, this );
			},

			formatMetadata: function ( article ) {
				var creation_date_parsed = Date.parseExact( article.get( 'creation_date' ), 'yyyyMMddHHmmss' );
				article.set('creation_date_pretty', creation_date_parsed.toString( gM( 'pagetriage-creation-dateformat' ) ) );

				// sometimes user info isn't set, so check that first.
				if( article.get( 'user_creation_date' ) ) {
					var user_creation_date_parsed = Date.parseExact( article.get( 'user_creation_date' ), 'yyyyMMddHHmmss' );
					article.set( 'user_creation_date_pretty', user_creation_date_parsed.toString( gM( 'pagetriage-user-creation-dateformat' ) ) );
				} else {
					article.set( 'user_creation_date_pretty', '');
				}

				var userName = article.get( 'user_name' );
				if( userName ) {
					article.set( 'user_title', new mw.Title( userName, mw.config.get('wgNamespaceIds')['user'] ) );
					article.set( 'user_talk_title', new mw.Title( userName, mw.config.get('wgNamespaceIds')['user_talk'] ) );
					article.set( 'user_contribs_title', new mw.Title( gM( 'pagetriage-special-contributions' ) + '/' + userName ) );
				}
				article.set( 'title_url', mw.util.wikiUrlencode( article.get( 'title' ) ) );
			}

		} ),
	};

	// can't include this in the declaration above because it references the
	// object created therein.
	mw.pageTriage.ArticleList = Backbone.Collection.extend( {
		model: mw.pageTriage.Article,

		apiParams: {
			namespace: 0,
			limit: 50,
			/*
			showbots: null,
			showredirs: null,
			showtriaged: null,
			limit: 10,
			namespace: 0,
			no_category: 1,
			no_inbound_links: 1,
			non_autoconfirmed_users: 1,
			blocked_users: 1,
			*/
		},

		initialize: function( options ) {
			this.eventBus = options.eventBus;
			this.eventBus.bind( "filterSet", this.setParams );
			//this.eventBus.bind( "paramsSet", this.fetch );
		},

		url: function() {
			var paramString = '';
			for ( var key in this.apiParams ) {
				paramString += '&' + key + '=' + this.apiParams[key];
			}
			var url = mw.util.wikiScript( 'api' ) + '?action=pagetriagelist&format=json' + paramString;
			console.log('fetching ' + url);
			return url;
		},

		parse: function( response ) {
			// extract the useful bits of json.
			return response.pagetriagelist.pages;
		},

		setParams: function( apiParams ) {
			this.apiParams = apiParams;
			this.eventBus.trigger( "paramsSet", this.apiParams );
		},

		getParam: function( key ) {
			return this.apiParams[key];
		}

	} );

} );
<|endoftext|>


<commit_before>import React from 'react';
import Avatar from '@/modules/users/client/components/Avatar.component.js';
import PropTypes from 'prop-types';
import { userType } from '@/modules/users/client/users.prop-types';
import { useTranslation } from 'react-i18next';

export default function Navigation({ user, onSignout, isNativeMobileApp }) {
  const { t } = useTranslation('pages');

  return (
    <>

        <a
          className="list-group-item page-navigation-profile"
          href={`/profile/${user.username}`}
        >
          <Avatar user={user} link={false} size={64} />

{user.displayName}

            {t('View your profile')}

          {t('Edit profile')}

          {t('Host')}

          {t('Meet')}

        <a
          href={`/profile/${user.username}/contacts`}
          className="list-group-item"
        >
          {t('Contacts')}

          {t('Circles')}

          {t('Account')}

         onSignout(event)}
          href="/api/auth/signout"
          target="_top"
          className="list-group-item"
        >
           {t('Sign out')}

{t('Info & support')}

          {t('Contact & Support')}

          {t('FAQ')}

          {t('About')}

          {t('Blog')}

          {t('Shop')}

          {t('Media')}

          {t('Foundation')}

          {t('Privacy')}

          {t('Rules')}

        {!isNativeMobileApp && (

              <svg viewBox="0 0 2700 800" xmlns="http://www.w3.org/2000/svg">
                <path
                  d="M499 464L235 664c-8-3-14-12-14-25V290c0-14 6-23 15-26l263 200z"
                  fill="url(#goo1)"
                />
                <path
                  d="M499 530L258 661c-9 5-17 5-23 3l211-213 53 79z"
                  fill="url(#goo2)"
                />
                <path
                  d="M236 264c6-2 14-1 22 4l240 131-53 77-209-212z"
                  fill="url(#goo3)"
                />
                <path
                  d="M498 399l85 46c20 11 20 28 0 39l-84 46-66-66 65-65z"
                  fill="url(#goo4)"
                />
                <path d="M1292 230c35 0 61-27 61-61 0-35-27-62-61-62s-61 28-61 62 27 61 61 61zm0-14c-26 0-45-21-45-47s19-48 45-48 46 22 46 48-20 47-46 47zm-14 373c50 0 89-38 89-88s-39-89-89-89-89 39-89 89 39 88 89 88zm0-35c-28 0-50-24-50-53 0-30 21-54 50-54 30 0 50 23 50 54 0 29-22 53-50 53zm240 13v15c0 32-16 51-47 51-34 0-44-31-44-31l-34 14s19 52 78 52c54 0 84-35 84-92V418h-37v15c-7-10-26-21-47-21-47 0-85 41-85 89 0 47 38 88 85 88 21 0 39-9 47-22zm1030-156c0-1-46-1-46-1l-47 118-51-118-44 1 74 167-41 87h44zm-244 150h2v24h39V480c0-54-38-80-84-80-56 0-73 41-73 41l34 16s10-21 39-21c21 0 44 13 44 39-32-18-123-13-123 53 0 49 49 63 68 63 25 0 41-10 54-30zm-570 28c35 0 60-19 74-39l-31-20s-12 24-43 24c-19 0-34-9-43-27l118-49c-11-33-36-66-79-66-49 0-84 39-84 89s38 88 88 88zm-650 0c50 0 89-38 89-88s-39-89-89-89-89 39-89 89 39 88 89 88zm-233 0c80 0 140-60 126-150H851v38h89c-5 47-40 74-89 74-56 0-99-44-99-99 0-57 44-100 99-100 26 0 48 9 67 27l27-27c-27-24-58-38-94-38-75 0-140 62-140 138s64 137 140 137zm1299-4V327h-39v258h39zm-156-258h-90v258h39V485h51c48 0 83-32 83-79s-35-79-83-79zm-370 257V324h-39v260h39zm-150-30c-29 0-49-24-49-53 0-30 20-54 49-54s47 24 47 54c0 29-19 53-47 53zm-390 0c-28 0-50-24-50-53 0-30 21-54 50-54 30 0 50 23 50 54 0 29-22 53-50 53zm1171-2c-14 0-32-6-32-23 0-36 65-36 83-19 0 10-19 42-51 42zm-571-54c0-35 28-52 47-52 12 0 27 6 33 19zm311-133c27 0 42 23 42 41s-13 41-42 41h-52v-82h52zM842 165h-58v14h43c-1 23-20 37-43 37-25 0-46-20-46-47 0-30 23-48 46-48s33 14 33 14l10-10s-13-18-43-18c-34 0-61 27-61 62 0 33 26 61 61 61 34 0 63-25 58-65zm544 63v-97l60 97h16V110h-15v69l1 23h-1l-57-92h-19v118h15zm-198-104v-14h-81v14h33v104h15V124h33zm-97 104V110h-15v118h15zm-67-104v-14h-81v14h33v104h15V124h33zm-94 0v-14h-69v118h69v-15h-54v-37h49v-14h-49v-38h54z" />
                <defs>
                  <linearGradient id="goo1" gradientTransform="rotate(90)">
                    <stop offset="0%" stopColor="#3588b7" />
                    <stop offset="100%" stopColor="#66ffd4" />
                  </linearGradient>
                  <linearGradient id="goo2">
                    <stop offset="0%" stopColor="#9635c2" />
                    <stop offset="100%" stopColor="#e3424f" />
                  </linearGradient>
                  <linearGradient id="goo3">
                    <stop offset="0%" stopColor="#60cab6" />
                    <stop offset="100%" stopColor="#d6ffa2" />
                  </linearGradient>
                  <linearGradient id="goo4" gradientTransform="rotate(45)">
                    <stop offset="0%" stopColor="#ff9877" />
                    <stop offset="100%" stopColor="#ffc466" />
                  </linearGradient>
                </defs>
              </svg>

        )}

                {t('Facebook (Page)')}

                {t('Facebook (Group)')}

                Twitter

                Instagram

                GitHub

    </>
  );
}

Navigation.propTypes = {
  user: userType,
  isNativeMobileApp: PropTypes.bool,
  onSignout: PropTypes.func,
};
<commit_msg>update copy for consictency: facebook (Group) -> facebook group (#2047)<commit_after>import React from 'react';
import Avatar from '@/modules/users/client/components/Avatar.component.js';
import PropTypes from 'prop-types';
import { userType } from '@/modules/users/client/users.prop-types';
import { useTranslation } from 'react-i18next';

export default function Navigation({ user, onSignout, isNativeMobileApp }) {
  const { t } = useTranslation('pages');

  return (
    <>

        <a
          className="list-group-item page-navigation-profile"
          href={`/profile/${user.username}`}
        >
          <Avatar user={user} link={false} size={64} />

{user.displayName}

            {t('View your profile')}

          {t('Edit profile')}

          {t('Host')}

          {t('Meet')}

        <a
          href={`/profile/${user.username}/contacts`}
          className="list-group-item"
        >
          {t('Contacts')}

          {t('Circles')}

          {t('Account')}

         onSignout(event)}
          href="/api/auth/signout"
          target="_top"
          className="list-group-item"
        >
           {t('Sign out')}

{t('Info & support')}

          {t('Contact & Support')}

          {t('FAQ')}

          {t('About')}

          {t('Blog')}

          {t('Shop')}

          {t('Media')}

          {t('Foundation')}

          {t('Privacy')}

          {t('Rules')}

        {!isNativeMobileApp && (

              <svg viewBox="0 0 2700 800" xmlns="http://www.w3.org/2000/svg">
                <path
                  d="M499 464L235 664c-8-3-14-12-14-25V290c0-14 6-23 15-26l263 200z"
                  fill="url(#goo1)"
                />
                <path
                  d="M499 530L258 661c-9 5-17 5-23 3l211-213 53 79z"
                  fill="url(#goo2)"
                />
                <path
                  d="M236 264c6-2 14-1 22 4l240 131-53 77-209-212z"
                  fill="url(#goo3)"
                />
                <path
                  d="M498 399l85 46c20 11 20 28 0 39l-84 46-66-66 65-65z"
                  fill="url(#goo4)"
                />
                <path d="M1292 230c35 0 61-27 61-61 0-35-27-62-61-62s-61 28-61 62 27 61 61 61zm0-14c-26 0-45-21-45-47s19-48 45-48 46 22 46 48-20 47-46 47zm-14 373c50 0 89-38 89-88s-39-89-89-89-89 39-89 89 39 88 89 88zm0-35c-28 0-50-24-50-53 0-30 21-54 50-54 30 0 50 23 50 54 0 29-22 53-50 53zm240 13v15c0 32-16 51-47 51-34 0-44-31-44-31l-34 14s19 52 78 52c54 0 84-35 84-92V418h-37v15c-7-10-26-21-47-21-47 0-85 41-85 89 0 47 38 88 85 88 21 0 39-9 47-22zm1030-156c0-1-46-1-46-1l-47 118-51-118-44 1 74 167-41 87h44zm-244 150h2v24h39V480c0-54-38-80-84-80-56 0-73 41-73 41l34 16s10-21 39-21c21 0 44 13 44 39-32-18-123-13-123 53 0 49 49 63 68 63 25 0 41-10 54-30zm-570 28c35 0 60-19 74-39l-31-20s-12 24-43 24c-19 0-34-9-43-27l118-49c-11-33-36-66-79-66-49 0-84 39-84 89s38 88 88 88zm-650 0c50 0 89-38 89-88s-39-89-89-89-89 39-89 89 39 88 89 88zm-233 0c80 0 140-60 126-150H851v38h89c-5 47-40 74-89 74-56 0-99-44-99-99 0-57 44-100 99-100 26 0 48 9 67 27l27-27c-27-24-58-38-94-38-75 0-140 62-140 138s64 137 140 137zm1299-4V327h-39v258h39zm-156-258h-90v258h39V485h51c48 0 83-32 83-79s-35-79-83-79zm-370 257V324h-39v260h39zm-150-30c-29 0-49-24-49-53 0-30 20-54 49-54s47 24 47 54c0 29-19 53-47 53zm-390 0c-28 0-50-24-50-53 0-30 21-54 50-54 30 0 50 23 50 54 0 29-22 53-50 53zm1171-2c-14 0-32-6-32-23 0-36 65-36 83-19 0 10-19 42-51 42zm-571-54c0-35 28-52 47-52 12 0 27 6 33 19zm311-133c27 0 42 23 42 41s-13 41-42 41h-52v-82h52zM842 165h-58v14h43c-1 23-20 37-43 37-25 0-46-20-46-47 0-30 23-48 46-48s33 14 33 14l10-10s-13-18-43-18c-34 0-61 27-61 62 0 33 26 61 61 61 34 0 63-25 58-65zm544 63v-97l60 97h16V110h-15v69l1 23h-1l-57-92h-19v118h15zm-198-104v-14h-81v14h33v104h15V124h33zm-97 104V110h-15v118h15zm-67-104v-14h-81v14h33v104h15V124h33zm-94 0v-14h-69v118h69v-15h-54v-37h49v-14h-49v-38h54z" />
                <defs>
                  <linearGradient id="goo1" gradientTransform="rotate(90)">
                    <stop offset="0%" stopColor="#3588b7" />
                    <stop offset="100%" stopColor="#66ffd4" />
                  </linearGradient>
                  <linearGradient id="goo2">
                    <stop offset="0%" stopColor="#9635c2" />
                    <stop offset="100%" stopColor="#e3424f" />
                  </linearGradient>
                  <linearGradient id="goo3">
                    <stop offset="0%" stopColor="#60cab6" />
                    <stop offset="100%" stopColor="#d6ffa2" />
                  </linearGradient>
                  <linearGradient id="goo4" gradientTransform="rotate(45)">
                    <stop offset="0%" stopColor="#ff9877" />
                    <stop offset="100%" stopColor="#ffc466" />
                  </linearGradient>
                </defs>
              </svg>

        )}

                {t('Facebook page')}

                {t('Facebook group')}

                Twitter

                Instagram

                GitHub

    </>
  );
}

Navigation.propTypes = {
  user: userType,
  isNativeMobileApp: PropTypes.bool,
  onSignout: PropTypes.func,
};
<|endoftext|>