<commit_before>/*
 * This implements link hinting. Typing "F" will enter link-hinting mode, where all clickable items on
 * the page have a hint marker displayed containing a sequence of letters. Typing those letters will select
 * a link.
 *
 * The characters we use to show link hints are a user-configurable option. By default they're the home row.
 * The CSS which is used on the link hints is also a configurable option.
 */

var hintMarkers = [];
// The characters that were typed in while in "link hints" mode.
var hintKeystrokeQueue = [];
var linkHintsModeActivated = false;
var shouldOpenLinkHintInNewTab = false;
// Whether we have added to the page the CSS needed to display link hints.
var linkHintsCssAdded = false;

// An XPath describing what a clickable element is. We could also look for images with an onclick
// attribute, but let's wait to see if that really is necessary.
var clickableElementsXPath = "//a | //textarea | //button | //select | //input[not(@type='hidden')]";

// We need this as a top-level function because our command system doesn't yet support arguments.
function activateLinkHintsModeToOpenInNewTab() { activateLinkHintsMode(true); }

function activateLinkHintsMode(openInNewTab) {
  if (!linkHintsCssAdded)
    addCssToPage(linkHintCss); // linkHintCss is declared by vimiumFrontend.js
  linkHintCssAdded = true;
  linkHintsModeActivated = true;
  shouldOpenLinkHintInNewTab = openInNewTab
  buildLinkHints();
  document.addEventListener("keydown", onKeyDownInLinkHintsMode, true);
}

/*
 * Builds and displays link hints for every visible clickable item on the page.
 */
function buildLinkHints() {
  var visibleElements = getVisibleClickableElements();

  // Initialize the number used to generate the character hints to be as many digits as we need to
  // highlight all the links on the page; we don't want some link hints to have more chars than others.
  var digitsNeeded = Math.ceil(logXOfBase(visibleElements.length, settings.linkHintCharacters.length));
  var linkHintNumber = 0;
  for (var i = 0; i < visibleElements.length; i++) {
    hintMarkers.push(addMarkerFor(visibleElements[i], linkHintNumber, digitsNeeded));
    linkHintNumber++;
  }
}

function logXOfBase(x, base) { return Math.log(x) / Math.log(base); }

/*
 * Returns all clickable elements that are not hidden and are in the current viewport.
 * We prune invisible elements partly for performance reasons, but moreso it's to decrease the number
 * of digits needed to enumerate all of the links on screen.
 */
function getVisibleClickableElements() {
  var resultSet = document.evaluate(clickableElementsXPath, document.body, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  var visibleElements = [];

  // Prune all invisible clickable elements.
  for (var i = 0; i < resultSet.snapshotLength; i++) {
    var element = resultSet.snapshotItem(i);

    // Note that getBoundingClientRect() is relative to the viewport
    var boundingRect = element.getBoundingClientRect();
    if (boundingRect.bottom < 0 || boundingRect.top > window.innerHeight)
      continue;

    // Using getElementFromPoint will omit elements which have visibility=hidden or display=none, and
    // elements inside of containers that are also hidden. We're checking for whether the element occupies
    // the upper left corner and if that fails, we also check whether the element occupies the center of the
    // box. We use the center of the box because it's more accurate when inline links have vertical padding,
    // like in the links ("Source", "Commits") at the top of github.com.
    // This will not exclude links with "opacity=0", like the links on Google's homepage (see bug #16).
    if (!elementOccupiesPoint(element, boundingRect.left, boundingRect.top)) {
      var elementOccupiesCenter = elementOccupiesPoint(element, boundingRect.left + boundingRect.width / 2,
          boundingRect.top + boundingRect.height / 2);
      if (!elementOccupiesCenter)
        continue;
    }

    visibleElements.push(element);
  }
  return visibleElements;
}

/*
 * Checks whether the clickable element or one of its descendents is at the given point. We must check
 * descendents because some clickable elements like "<a>" can have many nested children.
 */
function elementOccupiesPoint(clickableElement, x, y) {
  var elementAtPoint = getElementFromPoint(x, y);
  // Recurse up to 5 parents.
  for (var i = 0; i < 5 && elementAtPoint; i++) {
    if (elementAtPoint == clickableElement)
      return true;
    elementAtPoint = elementAtPoint.parentNode;
  }
  return false;
}
/*
 * Returns the element at the given point and factors in the page's CSS zoom level, which Webkit neglects
 * to do. This should become unnecessary when webkit fixes their bug.
 */
function getElementFromPoint(x, y) {
  var zoomFactor = currentZoomLevel / 100.0;
  return document.elementFromPoint(Math.ceil(x * zoomFactor), Math.ceil(y * zoomFactor));
}

function onKeyDownInLinkHintsMode(event) {
  var keyChar = String.fromCharCode(event.keyCode).toLowerCase();
  if (!keyChar)
    return;

  // TODO(philc): Ignore keys that have modifiers.
  if (event.keyCode == keyCodes.ESC) {
    deactivateLinkHintsMode();
  } else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) {
    if (hintKeystrokeQueue.length == 0) {
      deactivateLinkHintsMode();
    } else {
      hintKeystrokeQueue.pop();
      updateLinkHints();
    }
  } else if (settings.linkHintCharacters.indexOf(keyChar) >= 0) {
    hintKeystrokeQueue.push(keyChar);
    updateLinkHints();
  } else {
    return;
  }

  event.stopPropagation();
  event.preventDefault();
}

/*
 * Updates the visibility of link hints on screen based on the keystrokes typed thus far. If only one
 * link hint remains, click on that link and exit link hints mode.
 */
function updateLinkHints() {
  var matchString = hintKeystrokeQueue.join("");
  var linksMatched = highlightLinkMatches(matchString);
  if (linksMatched.length == 0)
    deactivateLinkHintsMode();
  else if (linksMatched.length == 1) {
    var matchedLink = linksMatched[0];
    if (isSelectable(matchedLink)) {
      matchedLink.focus();
      // When focusing a textbox, put the selection caret at the end of the textbox's contents.
      matchedLink.setSelectionRange(matchedLink.value.length, matchedLink.value.length);
    } else {
      // When we're opening the link in the current tab, don't navigate to the selected link immediately;
      // we want to give the user some feedback depicting which link they've selected by focusing it.
      if (!shouldOpenLinkHintInNewTab)
        setTimeout(function() { simulateClick(matchedLink); }, 400);
      else
        simulateClick(matchedLink);
      matchedLink.focus();
    }
    deactivateLinkHintsMode();
  }
}

/*
 * Selectable means the element has a text caret; this is not the same as "focusable".
 */
function isSelectable(element) {
  var selectableTypes = ["search", "text", "password"];
  return (element.tagName == "INPUT" && selectableTypes.indexOf(element.type) >= 0) ||
      element.tagName == "TEXTAREA";
}

/*
 * Hides link hints which do not match the given search string. To allow the backspace key to work, this
 * will also show link hints which do match but were previously hidden.
 */
function highlightLinkMatches(searchString) {
  var linksMatched = [];
  for (var i = 0; i < hintMarkers.length; i++) {
    var linkMarker = hintMarkers[i];
    if (linkMarker.getAttribute("hintString").indexOf(searchString) == 0) {
      if (linkMarker.style.display == "none")
        linkMarker.style.display = "";
      for (var j = 0; j < linkMarker.childNodes.length; j++)
        linkMarker.childNodes[j].className = (j >= searchString.length) ? "" : "matchingCharacter";
      linksMatched.push(linkMarker.clickableItem);
    } else {
      linkMarker.style.display = "none";
    }
  }
  return linksMatched;
}

/*
 * Converts a number like "8" into a hint string like "JK". This is used to sequentially generate all of
 * the hint text. The hint string will be "padded with zeroes" to ensure its length is equal to numHintDigits.
 */
function numberToHintString(number, numHintDigits) {
  var base = settings.linkHintCharacters.length;
  var hintString = [];
  var remainder = 0;
  do {
    remainder = number % base;
    hintString.unshift(settings.linkHintCharacters[remainder]);
    number -= remainder;
    number /= Math.floor(base);
  } while (number > 0);

  // Pad the hint string we're returning so that it matches numHintDigits.
  var hintStringLength = hintString.length;
  for (var i = 0; i < numHintDigits - hintStringLength; i++)
    hintString.unshift(settings.linkHintCharacters[0]);
  return hintString.join("");
}

function simulateClick(link) {
  var event = document.createEvent("MouseEvents");
  // When "clicking" on a link, dispatch the event with the appropriate meta key (CMD on Mac, CTRL on windows)
  // to open it in a new tab if necessary.
  var metaKey = (platform == "Mac" && shouldOpenLinkHintInNewTab);
  var ctrlKey = (platform != "Mac" && shouldOpenLinkHintInNewTab);
  event.initMouseEvent("click", true, true, window, 1, 0, 0, 0, 0, ctrlKey, false, false, metaKey, 0, null);

  // Debugging note: Firefox will not execute the link's default action if we dispatch this click event,
  // but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately
  link.dispatchEvent(event);
}

function deactivateLinkHintsMode() {
  for (var i = 0; i < hintMarkers.length; i++)
    hintMarkers[i].parentNode.removeChild(hintMarkers[i]);
  hintMarkers = [];
  hintKeystrokeQueue = [];
  document.removeEventListener("keydown", onKeyDownInLinkHintsMode, true);
  linkHintsModeActivated = false;
}

/*
 * Adds a link marker for the given link by adding a new element to <body> and positioning it on top of
 * the link.
 */
function addMarkerFor(link, linkHintNumber, linkHintDigits) {
  var hintString = numberToHintString(linkHintNumber, linkHintDigits);
  var marker = document.createElement("div");
  marker.className = "internalVimiumHintMarker vimiumHintMarker";
  var innerHTML = [];
  // Make each hint character a span, so that we can highlight the typed characters as you type them.
  for (var i = 0; i < hintString.length; i++)
    innerHTML.push("<span>" + hintString[i].toUpperCase() + "</span>");
  marker.innerHTML = innerHTML.join("");
  marker.setAttribute("hintString", hintString);
  marker.style.position = "absolute";

  var boundingRect = link.getBoundingClientRect();
  // The coordinates given by the window do not have the zoom factor included since the zoom is set only on
  // the document node.
  var zoomFactor = currentZoomLevel / 100.0;
  marker.style.left = boundingRect.left + window.scrollX / zoomFactor + "px";
  marker.style.top = boundingRect.top  + window.scrollY / zoomFactor + "px";

  marker.clickableItem = link;
  // Note(philc): Append these markers to document.body instead of as child nodes to the link itself,
  // because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat
  // that if you scroll the page and the link has position=fixed, the marker will not stay fixed.
  document.body.appendChild(marker);
  return marker;
}
<commit_msg>Avoid calling document.elementFromPoint for very small links. This helps link hinting performance.<commit_after>/*
 * This implements link hinting. Typing "F" will enter link-hinting mode, where all clickable items on
 * the page have a hint marker displayed containing a sequence of letters. Typing those letters will select
 * a link.
 *
 * The characters we use to show link hints are a user-configurable option. By default they're the home row.
 * The CSS which is used on the link hints is also a configurable option.
 */

var hintMarkers = [];
// The characters that were typed in while in "link hints" mode.
var hintKeystrokeQueue = [];
var linkHintsModeActivated = false;
var shouldOpenLinkHintInNewTab = false;
// Whether we have added to the page the CSS needed to display link hints.
var linkHintsCssAdded = false;

// An XPath describing what a clickable element is. We could also look for images with an onclick
// attribute, but let's wait to see if that really is necessary.
var clickableElementsXPath = "//a | //textarea | //button | //select | //input[not(@type='hidden')]";

// We need this as a top-level function because our command system doesn't yet support arguments.
function activateLinkHintsModeToOpenInNewTab() { activateLinkHintsMode(true); }

function activateLinkHintsMode(openInNewTab) {
  if (!linkHintsCssAdded)
    addCssToPage(linkHintCss); // linkHintCss is declared by vimiumFrontend.js
  linkHintCssAdded = true;
  linkHintsModeActivated = true;
  shouldOpenLinkHintInNewTab = openInNewTab
  buildLinkHints();
  document.addEventListener("keydown", onKeyDownInLinkHintsMode, true);
}

/*
 * Builds and displays link hints for every visible clickable item on the page.
 */
function buildLinkHints() {
  var visibleElements = getVisibleClickableElements();

  // Initialize the number used to generate the character hints to be as many digits as we need to
  // highlight all the links on the page; we don't want some link hints to have more chars than others.
  var digitsNeeded = Math.ceil(logXOfBase(visibleElements.length, settings.linkHintCharacters.length));
  var linkHintNumber = 0;
  for (var i = 0; i < visibleElements.length; i++) {
    hintMarkers.push(addMarkerFor(visibleElements[i], linkHintNumber, digitsNeeded));
    linkHintNumber++;
  }
}

function logXOfBase(x, base) { return Math.log(x) / Math.log(base); }

/*
 * Returns all clickable elements that are not hidden and are in the current viewport.
 * We prune invisible elements partly for performance reasons, but moreso it's to decrease the number
 * of digits needed to enumerate all of the links on screen.
 */
function getVisibleClickableElements() {
  var resultSet = document.evaluate(clickableElementsXPath, document.body, null,
    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
  var visibleElements = [];

  // Prune all invisible clickable elements.
  for (var i = 0; i < resultSet.snapshotLength; i++) {
    var element = resultSet.snapshotItem(i);

    // Note that getBoundingClientRect() is relative to the viewport
    var boundingRect = element.getBoundingClientRect();
    if (boundingRect.bottom < 0 || boundingRect.top > window.innerHeight)
      continue;

    if (boundingRect.width < 3 || boundingRect.height < 3)
      continue;

    // Using getElementFromPoint will omit elements which have visibility=hidden or display=none, and
    // elements inside of containers that are also hidden. We're checking for whether the element occupies
    // the upper left corner and if that fails, we also check whether the element occupies the center of the
    // box. We use the center of the box because it's more accurate when inline links have vertical padding,
    // like in the links ("Source", "Commits") at the top of github.com.
    // This will not exclude links with "opacity=0", like the links on Google's homepage (see bug #16).
    // Note(philc): this is the most expensive part our link hinting process, so we should try hard to filter
    // out elements by whatever means possible prior to getting to this check.
    if (!elementOccupiesPoint(element, boundingRect.left, boundingRect.top)) {
      var elementOccupiesCenter = elementOccupiesPoint(element, boundingRect.left + boundingRect.width / 2,
          boundingRect.top + boundingRect.height / 2);
      if (!elementOccupiesCenter)
        continue;
    }

    visibleElements.push(element);
  }
  return visibleElements;
}

/*
 * Checks whether the clickable element or one of its descendents is at the given point. We must check
 * descendents because some clickable elements like "<a>" can have many nested children.
 */
function elementOccupiesPoint(clickableElement, x, y) {
  var elementAtPoint = getElementFromPoint(x, y);
  // Recurse up to 5 parents.
  for (var i = 0; i < 5 && elementAtPoint; i++) {
    if (elementAtPoint == clickableElement)
      return true;
    elementAtPoint = elementAtPoint.parentNode;
  }
  return false;
}
/*
 * Returns the element at the given point and factors in the page's CSS zoom level, which Webkit neglects
 * to do. This should become unnecessary when webkit fixes their bug.
 */
function getElementFromPoint(x, y) {
  var zoomFactor = currentZoomLevel / 100.0;
  return document.elementFromPoint(Math.ceil(x * zoomFactor), Math.ceil(y * zoomFactor));
}

function onKeyDownInLinkHintsMode(event) {
  var keyChar = String.fromCharCode(event.keyCode).toLowerCase();
  if (!keyChar)
    return;

  // TODO(philc): Ignore keys that have modifiers.
  if (event.keyCode == keyCodes.ESC) {
    deactivateLinkHintsMode();
  } else if (event.keyCode == keyCodes.backspace || event.keyCode == keyCodes.deleteKey) {
    if (hintKeystrokeQueue.length == 0) {
      deactivateLinkHintsMode();
    } else {
      hintKeystrokeQueue.pop();
      updateLinkHints();
    }
  } else if (settings.linkHintCharacters.indexOf(keyChar) >= 0) {
    hintKeystrokeQueue.push(keyChar);
    updateLinkHints();
  } else {
    return;
  }

  event.stopPropagation();
  event.preventDefault();
}

/*
 * Updates the visibility of link hints on screen based on the keystrokes typed thus far. If only one
 * link hint remains, click on that link and exit link hints mode.
 */
function updateLinkHints() {
  var matchString = hintKeystrokeQueue.join("");
  var linksMatched = highlightLinkMatches(matchString);
  if (linksMatched.length == 0)
    deactivateLinkHintsMode();
  else if (linksMatched.length == 1) {
    var matchedLink = linksMatched[0];
    if (isSelectable(matchedLink)) {
      matchedLink.focus();
      // When focusing a textbox, put the selection caret at the end of the textbox's contents.
      matchedLink.setSelectionRange(matchedLink.value.length, matchedLink.value.length);
    } else {
      // When we're opening the link in the current tab, don't navigate to the selected link immediately;
      // we want to give the user some feedback depicting which link they've selected by focusing it.
      if (!shouldOpenLinkHintInNewTab)
        setTimeout(function() { simulateClick(matchedLink); }, 400);
      else
        simulateClick(matchedLink);
      matchedLink.focus();
    }
    deactivateLinkHintsMode();
  }
}

/*
 * Selectable means the element has a text caret; this is not the same as "focusable".
 */
function isSelectable(element) {
  var selectableTypes = ["search", "text", "password"];
  return (element.tagName == "INPUT" && selectableTypes.indexOf(element.type) >= 0) ||
      element.tagName == "TEXTAREA";
}

/*
 * Hides link hints which do not match the given search string. To allow the backspace key to work, this
 * will also show link hints which do match but were previously hidden.
 */
function highlightLinkMatches(searchString) {
  var linksMatched = [];
  for (var i = 0; i < hintMarkers.length; i++) {
    var linkMarker = hintMarkers[i];
    if (linkMarker.getAttribute("hintString").indexOf(searchString) == 0) {
      if (linkMarker.style.display == "none")
        linkMarker.style.display = "";
      for (var j = 0; j < linkMarker.childNodes.length; j++)
        linkMarker.childNodes[j].className = (j >= searchString.length) ? "" : "matchingCharacter";
      linksMatched.push(linkMarker.clickableItem);
    } else {
      linkMarker.style.display = "none";
    }
  }
  return linksMatched;
}

/*
 * Converts a number like "8" into a hint string like "JK". This is used to sequentially generate all of
 * the hint text. The hint string will be "padded with zeroes" to ensure its length is equal to numHintDigits.
 */
function numberToHintString(number, numHintDigits) {
  var base = settings.linkHintCharacters.length;
  var hintString = [];
  var remainder = 0;
  do {
    remainder = number % base;
    hintString.unshift(settings.linkHintCharacters[remainder]);
    number -= remainder;
    number /= Math.floor(base);
  } while (number > 0);

  // Pad the hint string we're returning so that it matches numHintDigits.
  var hintStringLength = hintString.length;
  for (var i = 0; i < numHintDigits - hintStringLength; i++)
    hintString.unshift(settings.linkHintCharacters[0]);
  return hintString.join("");
}

function simulateClick(link) {
  var event = document.createEvent("MouseEvents");
  // When "clicking" on a link, dispatch the event with the appropriate meta key (CMD on Mac, CTRL on windows)
  // to open it in a new tab if necessary.
  var metaKey = (platform == "Mac" && shouldOpenLinkHintInNewTab);
  var ctrlKey = (platform != "Mac" && shouldOpenLinkHintInNewTab);
  event.initMouseEvent("click", true, true, window, 1, 0, 0, 0, 0, ctrlKey, false, false, metaKey, 0, null);

  // Debugging note: Firefox will not execute the link's default action if we dispatch this click event,
  // but Webkit will. Dispatching a click on an input box does not seem to focus it; we do that separately
  link.dispatchEvent(event);
}

function deactivateLinkHintsMode() {
  for (var i = 0; i < hintMarkers.length; i++)
    hintMarkers[i].parentNode.removeChild(hintMarkers[i]);
  hintMarkers = [];
  hintKeystrokeQueue = [];
  document.removeEventListener("keydown", onKeyDownInLinkHintsMode, true);
  linkHintsModeActivated = false;
}

/*
 * Adds a link marker for the given link by adding a new element to <body> and positioning it on top of
 * the link.
 */
function addMarkerFor(link, linkHintNumber, linkHintDigits) {
  var hintString = numberToHintString(linkHintNumber, linkHintDigits);
  var marker = document.createElement("div");
  marker.className = "internalVimiumHintMarker vimiumHintMarker";
  var innerHTML = [];
  // Make each hint character a span, so that we can highlight the typed characters as you type them.
  for (var i = 0; i < hintString.length; i++)
    innerHTML.push("<span>" + hintString[i].toUpperCase() + "</span>");
  marker.innerHTML = innerHTML.join("");
  marker.setAttribute("hintString", hintString);
  marker.style.position = "absolute";

  var boundingRect = link.getBoundingClientRect();
  // The coordinates given by the window do not have the zoom factor included since the zoom is set only on
  // the document node.
  var zoomFactor = currentZoomLevel / 100.0;
  marker.style.left = boundingRect.left + window.scrollX / zoomFactor + "px";
  marker.style.top = boundingRect.top  + window.scrollY / zoomFactor + "px";

  marker.clickableItem = link;
  // Note(philc): Append these markers to document.body instead of as child nodes to the link itself,
  // because some clickable elements cannot contain children, e.g. submit buttons. This has the caveat
  // that if you scroll the page and the link has position=fixed, the marker will not stay fixed.
  document.body.appendChild(marker);
  return marker;
}
<|endoftext|>


<commit_before>locale.de = {
    modes: {
        add_area: {
            title: "Fläche",
            description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.",
            tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten."
        },
        add_line: {
            title: "Linie",
            description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.",
            tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten."
        },
        add_point: {
            title: "Punkt",
            description: "Restaurants, Denkmäler und Briefkästen sind Punkte",
            tail: "Klicke in die Karte, um einen Punkt hinzuzufügen."
        },
        browse: {
            title: "Navigation",
            description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts."
        },
        draw_area: {
            tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke auf den ersten Punkt, um die Fläche abzuschließen."
        },
        draw_line: {
            tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie um die Linien zu verbinden und klicke doppelt, um die Linie zu beenden."
        }
    },

    operations: {
        add: {
            annotation: {
                point: "Punkt hinzugefügt.",
                vertex: "Stützpunkt einem Weg hinzugefügt."
            }
        },
        start: {
            annotation: {
                line: "Linie begonnen.",
                area: "Fläche begonnen."
            }
        },
        'continue': {
            annotation: {
                line: "Linie fortgesetzt.",
                area: "Fläche fortgesetzt."
            }
        },
        cancel_draw: {
            annotation: "Zeichnen abgebrochen."
        },
        change_tags: {
            annotation: "Tags verändert."
        },
        circularize: {
            title: "Abrunden",
            description: "Runde dies ab.",
            key: "O",
            annotation: {
                line: "Runde eine Linie ab.",
                area: "Runde eine Fläche ab."
            }
        },
        orthogonalize: {
            title: "Rechtwinkligkeit herstellen",
            description: "Diese Ecken rechtwinklig ausrichten.",
            key: "Q",
            annotation: {
                line: "Die Ecken einer Linie rechtwinklig ausgerichtet.",
                area: "Die Ecken einer Fläche rechtwinklig ausgerichtet."
            }
        },
        'delete': {
            title: "Löschen",
            description: "Lösche dies aus der Karte.",
            key: "⌫",
            annotation: {
                point: "Punkt gelöscht.",
                vertex: "Stützpunkt aus einem Weg gelöscht.",
                line: "Linie gelöscht.",
                area: "Fläche gelöscht.",
                relation: "Verbindung gelöscht.",
                multiple: "{n} Objekte gelöscht."
            }
        },
        connect: {
            annotation: {
                point: "Weg mit einem Punkt verbunden.",
                vertex: "Weg mit einem anderem Weg verbunden.",
                line: "Weg mit einer Linie verbunden.",
                area: "Weg mit einer Fläche verbunden."
            }
        },
        disconnect: {
            title: "Trennen",
            description: "Trenne diese Wege voneinander.",
            key: "D",
            annotation: "Wege getrennt."
        },
        merge: {
            title: "Vereinigen",
            description: "Vereinige diese Linien.",
            key: "C",
            annotation: "{n} Linien vereinigt."
        },
        move: {
            title: "Verschieben",
            description: "Verschiebe dieses Objekt an einen anderen Ort.",
            key: "M",
            annotation: {
                point: "Punkt verschoben.",
                vertex: "Stützpunkt in einen Weg veschoben.",
                line: "Linie verschoben.",
                area: "Fläche verschoben."
            }
        },
        reverse: {
            title: "Umkehren",
            description: "Ändere die Richtung dieser Linie.",
            key: "V",
            annotation: "Linienrichtung umgekehrt."
        },
        split: {
            title: "Teilen",
            description: "Teile dies in zwei Wege an diesem Punkt.",
            key: "X",
            annotation: "Weg geteilt."
        }
    },

    nothing_to_undo: "Nichts zum Rückgängigmachen.",
    nothing_to_redo: "Nichts zum Wiederherstellen.",

    just_edited: "Sie haben gerade OpenStreetMap editiert!",
    browser_notice: "Dieser Editor wird von Firefox, Chrome, Safari, Opera, und Internet Explorer (Version 9 und höher) unterstzützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
    view_on_osm: "Auf OSM anschauen",
    zoom_in_edit: "Hineinzoomen, um die Karte zu bearbeiten",
    logout: "Abmelden",
    report_a_bug: "Programmfehler melden",

    commit: {
        title: "Änderungen speichern",
        description_placeholder: "Eine kurze Beschreibung deiner Beiträge",
        upload_explanation: "Änderungen, die du als {user} hochlädst werden sichtbar auf allen Karte, die OpenStreetMap nutzen.",
        save: "Speichern",
        cancel: "Abbrechen",
        warnings: "Warnungen",
        modified: "Verändert",
        deleted: "Gelöscht",
        created: "Erstellt"
    },

    contributors: {
        list: "Diese Kartenansicht enthält Beiträge von:",
        truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} Anderen"
    },

    geocoder: {
        title: "Suche einen Ort",
        placeholder: "suche einen Ort",
        no_results: "Der Ort '{name}' konnte nicht gefunden werden"
    },

    geolocate: {
        title: "Zeige meine Position"
    },

    inspector: {
        no_documentation_combination:  "Für dieses Attribut ist keine Dokumentation verfügbar.",
        no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar",
        new_tag: "Neues Attribut",
        edit_tags: "Attribute bearbeiten",
        okay: "OK",
        view_on_osm: "auf OSM ansehen"
    },

    layerswitcher: {
        title: "Hintergrund",
        description: "Hintergrundeinstellungen",
        percent_brightness: "{opacity}% Helligkeit",
        fix_misalignment: "Fehlerhafte Ausrichtung reparieren",
        reset: "Zurücksetzen"
    },

    restore: {
        description: "Es gibt ungespeicherte Änderungen aus einer vorherigen Sitzung. Möchtest du diese Änderungen wiederherstellen?",
        restore: "Wiederherstellen",
        reset: "Zurücksetzen"
    },

    save: {
        title: "Speichern",
        help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen",
        error: "Beim Speichern ist ein Fehler aufgetreten",
        uploading: "Änderungen werden zu OpenStreetMap hochgeladen.",
        unsaved_changes: "Ungespeicherte Änderugen vorhanden",
    },

    splash: {
        welcome: "Willkommen beim iD OpenStreetMap editor",
        text: "Dies ist eine Entwicklungsversion {version}. Für weitere Informationen besuche {website} und melde Fehler unter {github}."
    },

    source_switch: {
        live: "live",
        dev: "dev"
    },

    tag_reference: {
        description: "Beschreibung",
        on_wiki: "{tag} auf wiki.osm.org",
        used_with: "benutzt mit {type}"
    },

    validations: {
        untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist",
        untagged_line: "Linie ohne Attribute",
        untagged_area: "Fläche ohne Attribute",
        tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche",
        deprecated_tags: "Veralterte Attribute: {tags}"
    },

    zoom: {
        in: "Hineinzoomen",
        out: "Herauszoomen"
    }
};
<commit_msg>fixed typos in german translation<commit_after>locale.de = {
    modes: {
        add_area: {
            title: "Fläche",
            description: "Füge Parks, Gebäude, Seen oder andere Flächen zur Karte hinzu.",
            tail: "Klicke in die Karte, um das Zeichnen einer Fläche wie einen Park, einen See oder Gebäude zu starten."
        },
        add_line: {
            title: "Linie",
            description: "Linien können Autobahnen, Straßen, Fußwege oder sogar Kanäle sein.",
            tail: "Klicke in die Karte, um das Zeichnen einer Straße eines Pfades oder einer Route zu starten."
        },
        add_point: {
            title: "Punkt",
            description: "Restaurants, Denkmäler und Briefkästen sind Punkte",
            tail: "Klicke in die Karte, um einen Punkt hinzuzufügen."
        },
        browse: {
            title: "Navigation",
            description: "Verschieben und Vergrößern/Verkleinern des Kartenausschnitts."
        },
        draw_area: {
            tail: "Klicke, um Punkte zur Fläche hinzuzufügen. Klicke auf den ersten Punkt, um die Fläche abzuschließen."
        },
        draw_line: {
            tail: "Klicke, um mehr Punkte zur Linie hizuzufügen. Klicke auf eine andere Linie, um die Linien zu verbinden und klicke doppelt, um die Linie zu beenden."
        }
    },

    operations: {
        add: {
            annotation: {
                point: "Punkt hinzugefügt.",
                vertex: "Stützpunkt einem Weg hinzugefügt."
            }
        },
        start: {
            annotation: {
                line: "Linie begonnen.",
                area: "Fläche begonnen."
            }
        },
        'continue': {
            annotation: {
                line: "Linie fortgesetzt.",
                area: "Fläche fortgesetzt."
            }
        },
        cancel_draw: {
            annotation: "Zeichnen abgebrochen."
        },
        change_tags: {
            annotation: "Tags verändert."
        },
        circularize: {
            title: "Abrunden",
            description: "Runde dies ab.",
            key: "O",
            annotation: {
                line: "Runde eine Linie ab.",
                area: "Runde eine Fläche ab."
            }
        },
        orthogonalize: {
            title: "Rechtwinkligkeit herstellen",
            description: "Diese Ecken rechtwinklig ausrichten.",
            key: "Q",
            annotation: {
                line: "Die Ecken einer Linie rechtwinklig ausgerichtet.",
                area: "Die Ecken einer Fläche rechtwinklig ausgerichtet."
            }
        },
        'delete': {
            title: "Löschen",
            description: "Lösche dies aus der Karte.",
            key: "⌫",
            annotation: {
                point: "Punkt gelöscht.",
                vertex: "Stützpunkt aus einem Weg gelöscht.",
                line: "Linie gelöscht.",
                area: "Fläche gelöscht.",
                relation: "Verbindung gelöscht.",
                multiple: "{n} Objekte gelöscht."
            }
        },
        connect: {
            annotation: {
                point: "Weg mit einem Punkt verbunden.",
                vertex: "Weg mit einem anderem Weg verbunden.",
                line: "Weg mit einer Linie verbunden.",
                area: "Weg mit einer Fläche verbunden."
            }
        },
        disconnect: {
            title: "Trennen",
            description: "Trenne diese Wege voneinander.",
            key: "D",
            annotation: "Wege getrennt."
        },
        merge: {
            title: "Vereinigen",
            description: "Vereinige diese Linien.",
            key: "C",
            annotation: "{n} Linien vereinigt."
        },
        move: {
            title: "Verschieben",
            description: "Verschiebe dieses Objekt an einen anderen Ort.",
            key: "M",
            annotation: {
                point: "Punkt verschoben.",
                vertex: "Stützpunkt in einen Weg veschoben.",
                line: "Linie verschoben.",
                area: "Fläche verschoben."
            }
        },
        reverse: {
            title: "Umkehren",
            description: "Ändere die Richtung dieser Linie.",
            key: "V",
            annotation: "Linienrichtung umgekehrt."
        },
        split: {
            title: "Teilen",
            description: "Teile dies in zwei Wege an diesem Punkt.",
            key: "X",
            annotation: "Weg geteilt."
        }
    },

    nothing_to_undo: "Nichts zum Rückgängigmachen.",
    nothing_to_redo: "Nichts zum Wiederherstellen.",

    just_edited: "Sie haben gerade OpenStreetMap editiert!",
    browser_notice: "Dieser Editor wird von Firefox, Chrome, Safari, Opera, und Internet Explorer (Version 9 und höher) unterstützt. Bitte aktualisieren Sie Ihren Browser oder nutzen Sie Potlatch 2, um die Karte zu modifizieren.",
    view_on_osm: "Auf OSM anschauen",
    zoom_in_edit: "Hineinzoomen, um die Karte zu bearbeiten",
    logout: "Abmelden",
    report_a_bug: "Programmfehler melden",

    commit: {
        title: "Änderungen speichern",
        description_placeholder: "Eine kurze Beschreibung deiner Beiträge",
        upload_explanation: "Änderungen, die du als {user} hochlädst werden sichtbar auf allen Karte, die OpenStreetMap nutzen.",
        save: "Speichern",
        cancel: "Abbrechen",
        warnings: "Warnungen",
        modified: "Verändert",
        deleted: "Gelöscht",
        created: "Erstellt"
    },

    contributors: {
        list: "Diese Kartenansicht enthält Beiträge von:",
        truncated_list: "Diese Kartenansicht enthält Beiträge von: {users} und {count} anderen"
    },

    geocoder: {
        title: "Suche einen Ort",
        placeholder: "suche einen Ort",
        no_results: "Der Ort '{name}' konnte nicht gefunden werden"
    },

    geolocate: {
        title: "Zeige meine Position"
    },

    inspector: {
        no_documentation_combination:  "Für dieses Attribut ist keine Dokumentation verfügbar.",
        no_documentation_key: "Für dises Schlüsselwort ist keine Dokumentation verfügbar",
        new_tag: "Neues Attribut",
        edit_tags: "Attribute bearbeiten",
        okay: "OK",
        view_on_osm: "auf OSM ansehen"
    },

    layerswitcher: {
        title: "Hintergrund",
        description: "Hintergrundeinstellungen",
        percent_brightness: "{opacity}% Helligkeit",
        fix_misalignment: "Fehlerhafte Ausrichtung reparieren",
        reset: "Zurücksetzen"
    },

    restore: {
        description: "Es gibt ungespeicherte Änderungen aus einer vorherigen Sitzung. Möchtest du diese Änderungen wiederherstellen?",
        restore: "Wiederherstellen",
        reset: "Zurücksetzen"
    },

    save: {
        title: "Speichern",
        help: "Speichere Änderungen auf OpenStreetMap, um diese für andere Nutzer sichtbar zu machen",
        error: "Beim Speichern ist ein Fehler aufgetreten",
        uploading: "Änderungen werden zu OpenStreetMap hochgeladen.",
        unsaved_changes: "Ungespeicherte Änderungen vorhanden"
    },

    splash: {
        welcome: "Willkommen beim iD OpenStreetMap-Editor",
        text: "Dies ist eine Entwicklungsversion {version}. Für weitere Informationen besuche {website} und melde Fehler unter {github}."
    },

    source_switch: {
        live: "live",
        dev: "dev"
    },

    tag_reference: {
        description: "Beschreibung",
        on_wiki: "{tag} auf wiki.osm.org",
        used_with: "benutzt mit {type}"
    },

    validations: {
        untagged_point: "Punkt ohne Attribute, der kein Teil einer Linie oder Fläche ist",
        untagged_line: "Linie ohne Attribute",
        untagged_area: "Fläche ohne Attribute",
        tag_suggests_area: "Das Attribut {tag} suggeriert eine Fläche, ist aber keine Fläche",
        deprecated_tags: "Veraltete Attribute: {tags}"
    },

    zoom: {
        in: "Hineinzoomen",
        out: "Herauszoomen"
    }
};
<|endoftext|>


<commit_before>(function() {
  var get = Ember.get;

  window.P8UI = Ember.Namespace.create();
  var P8UI = Ember.Namespace.create();
  if(window) {
    window.P8UI = P8UI;
  }

  P8UI.HiddenInput = Ember.View.extend({
    tagName: 'input',
    attributeBindings: ['name', 'value', 'type'],
    value: "",
    name:"",
    type:"hidden"
  });

  P8UI.TableView = Ember.ContainerView.extend({
    tagName: 'table',
    content: null,
    columns: null,
    columnsMeta: null,
    classNames: ['p8ui-tableView'],
    childViews: ['headerView', 'bodyView'],
    clickItem: null
  });

  P8UI.TableView.reopen({
    headerView: Ember.computed(function() {
      return Ember.View.extend({
        tagName: 'thead',
        columns: get(this, 'columns'),
        columnsMeta: get(this, 'columnsMeta'),
        template: Ember.computed(function() {
          var bodyTpl = "
";
          for(var i=0; i<this.get('columns').length; i++) {
            var col = this.get('columns')[i].replace(".",'');
            bodyTpl += '';

            if(get(this, 'columnsMeta.'+col+'.headerValue') !== undefined && get(this, 'columnsMeta.'+col+'.headerValue') !== null) {
              bodyTpl += get(this, 'columnsMeta.'+col+'.headerValue');
            } else {
              bodyTpl += col;
            }

            bodyTpl += '	';
          }
          bodyTpl += "
";
          return Ember.Handlebars.compile(bodyTpl);
        })
      }).create();
    }).property(),

    bodyView:  Ember.computed(function() {
      return P8UI.CollectionView.extend({
        clickItem: this.get('clickItem'),
        columnsMeta: this.get('columnsMeta'),
        content: Ember.computed(function() {
          return get(this.get('tableView'), 'content');
        }).property('tableView.content').cacheable(),
        tableView: this,
        columns: get(this, 'columns'),
        emptyView: Ember.View.extend({
          template: Ember.Handlebars.compile('Keine Daten vorhanden	')
        })
      }).create();
      //return Ember.View.create();
    }).property()

  });

  P8UI.CollectionView = Ember.CollectionView.extend({
    tagName: 'tbody',
    clickItem: null,
    columnsMeta: null,
    itemViewClass: Ember.View.extend({
      template: Ember.computed(function(){
        var bodyTpl = "";
        for(var i=0; i<this.get('parentView.columns').length; i++) {
          bodyTpl += ''+this.getValueTmpl(i)+'	';
        }
        return Ember.Handlebars.compile(bodyTpl);
      }),
      _clickItem: function() {
        if(this.get('parentView').clickItem !== null) {
          this.get('parentView').clickItem(this.get('content'));
        }
      },

     getValueTmpl: function(column) {
        //console.log(this.get('columnsMeta.'+column+'.headerWidth'));
        var meta = this.get('parentView.columnsMeta.'+this.get('parentView.columns')[column].replace(".",''));
        var value = '';
        if(meta !== undefined && Ember.get(meta,'clickable') !== undefined && Ember.get(meta,'clickable') !== null) {
          value += '';
        }

        value += '{{view.content.'+this.get('parentView.columns')[column]+'}}';

        if(meta !== undefined && Ember.get(meta,'clickable') !== undefined && Ember.get(meta,'clickable') !== null) {
          value += '';
        }

        return value;
      }
    })

  });

  P8UI.ScrollView = Ember.View.extend({
    classNames: ['p8ui-scrollView'],
    alignBottomTo: null,
    _TO: false,
    _resize: function() {
      var self = this;

      var alignBottomTo = self.get('alignBottomTo');
      if(alignBottomTo !== null) {
        var alignTo = self.$().parents(alignBottomTo).filter(':first');
        self.$().css({'height': alignTo.height() - (self.$().offset().top) + alignTo.offset().top});
        self.$().mCustomScrollbar('update');
      }
    },
    _windowResizeListener: function(event){
      if(event.data.scrollView._TO !== false) {
        clearTimeout(event.data.scrollView._TO);
      }
      event.data.scrollView._TO = setTimeout(event.data.scrollView._resize.call(event.data.scrollView), 200);
    },

    didInsertElement: function() {

      var self = this;
      setTimeout(function(){
      self.$().mCustomScrollbar({
        set_width:false,
        set_height:false,
        //horizontalScroll:false,
        scrollInertia:550,
        scrollEasing:"easeOutCirc",
        mouseWheel:"auto",
        autoDraggerLength:true,
        scrollButtons:{
          enable:false,
          scrollType:"continuous",
          scrollSpeed:20,
          scrollAmount:40
        },
        advanced:{
          updateOnBrowserResize:false,
          updateOnContentResize:false,
          autoExpandHorizontalScroll:false
        },
        callbacks:{
          onScroll:function(){},
          onTotalScroll:function(){},
          onTotalScrollOffset:0
        }
      });

      setTimeout(function(){self._resize.call(self);},200);

      var alignBottomTo = self.get('alignBottomTo');
      if(alignBottomTo !== null) {
        jQuery(window).bind('resize', {scrollView: this}, self._windowResizeListener);
      }
      },200);
    },
    willDestroyElement: function() {
      jQuery(window).unbind('resize', this._windowResizeListener);
    }
  });
}());

(function() {

  function defineProperty(mv, i, invalidProps) {
    var validations = mv.get('validations');
    var validationProps = Ember.keys(mv.get('validations'));

    var invalidProp = 'invalid'+validationProps[i].capitalize();
    var errorProp = 'error'+validationProps[i].capitalize();
    var sourceProp = 'source.'+validationProps[i];
    var idx = i;

    invalidProps.push(invalidProp);

    Ember.defineProperty(mv, errorProp, Ember.computed(function() {
      var error = [];
      var v = this.get(sourceProp);

      for(var j=0; j<validations[validationProps[idx]].length; j++) {
        validations[validationProps[idx]][j](error,v);
      }
      return error;

    }).property(sourceProp));

    Ember.defineProperty(mv, invalidProp, Ember.computed(function() {
      return this.get(errorProp+'.length') > 0;
    }).property(errorProp));

    Ember.addObserver(mv, invalidProp, mv.evaluateValid);

  }

P8UI.InputValidator = Ember.Mixin.create({

    init: function() {
      var invalidProps = [];
      if(!Ember.isEmpty(this.get('validations'))) {
        var validationProps = Ember.keys(this.get('validations'));
        for(var i=0; i<validationProps.length; i++) {
          defineProperty(this, i, invalidProps);
        }
      }

      this._super();
      this.evaluateValid();
    },

    isValid: true,

    evaluateValid: function() {
      var validationProps = Ember.keys(this);
      for(var i=0; i<validationProps.length; i++) {
        if(validationProps[i].indexOf('invalid') === 0 && this.get(validationProps[i]) === true) {
          this.set('isValid', false);
          return;
        }
      }

      this.set('isValid', true);
    },

    source: null,
    invalid: function(field) {
      return this.get('error'+field.capitalize()+".length") > 0;
    },
    error: function(field) {
      return this.get('error'+field.capitalize());
    },

    singleErrorMessage: function(field) {
      if(this.get('error'+field.capitalize()+'.length') > 0) {
        return this.get('error'+field.capitalize())[0].message;
      } else {
        return null;
      }
    }
  });

  P8UI.ValidTextField = Ember.TextField.extend({
    //field:null,
    validator: null,
    _updateId: null,
    _updateValidation: function() {
      var self = this;
      this.$().data('tooltip').options.title = this.get('validator').singleErrorMessage(self.get('name'));
      Ember.run.cancel(this.get('updateId'));
      var a = Ember.run.later( function(){
        if(!Ember.isEmpty(self.get('validator'))) {
          if(self.get('validator').invalid(self.get('name'))) {
            self.$().tooltip('show');
          } else {
        if(!Ember.isEmpty(self.$())) {
          self.$().tooltip('hide');
        }
          }
        }
      },300);
      this.set('updateId', a);
    },
    hasErrors: function() {
      return !this.get('validator').invalid(this.get('name'));
    },
    init: function() {
      this.addObserver('validator.invalid'+this.get('name').capitalize(), this, this._updateValidation);
      this._super();
    },
    didInsertElement: function() {
      var self = this;

      Ember.run( function(){
        self.$().tooltip({
          html: true,
          trigger: 'focus',
          width : '420',
          placement : 'bottom',
          delay: { show: 0, hide: 0}
        });
      });

    },

    willDestroyElement : function() {
      this.removeObserver('validator.invalid'+this.get('name').capitalize(), this, this._updateValidation);
      this.$().tooltip('destroy');
    }
  });
}());

(function() {

P8UI.DatetimePickerField = Ember.View.extend({
  defaultClass: 'span12',
  format: "dd.MM.yyyy hh:mm",
  picker: null,
  templateName: 'datetimepicker',
  validator: null,
  didInsertElement: function() {
    var onChangeDate, self;
    self = this;
    onChangeDate = function(ev) {
      return self.set("value", ev.date);
    };
    var r = this.$('.datetimepicker').datetimepicker({
      format: self.get('format'),
      language: 'de',
      collapse: true
    }).on("changeDate", onChangeDate);
    this.set('picker', r);
    this.suspendValueChange(function() {
      r.datetimepicker('setValue', self.get('value'));
    });
    return r;
  },
  suspendValueChange: function(cb) {
    this._suspendValueChange = true;
    cb();
    this._suspendValueChange = false;
  },
  valueChanged: function() {
    if (this._suspendValueChange) { return; }
    var content = this.get("value");
    if(Ember.isEmpty(content)) {
      //this.get("picker").setContent(null);
    } else {
      this.$('.datetimepicker').datetimepicker('setValue', this.get('value'));
    }
  }.observes("value")
});
}());

(function() {
P8UI.TinymceView = Ember.TextArea.extend({
  editor: null,
  theme: "modern",
  skin: 'light',
  menubar:false,
  statusbar: false,
  height: 300,
  plugins: [
            "autolink link lists hr ",
            "contextmenu paste"
 ],
 content_css: "res/tinymce/content.css",
 toolbar: "link | bold italic underline | bullist numlist outdent indent",
 valid_elements : "a[href|target=_blank],strong/b,em/i,strike,u,-ol[type|compact],-ul[type|compact],-li,br,-span,div[align],br,#p[style],ol,ul,li",
  _suspendValueChange: false,
  didInsertElement: function(){
      var id = "#" + this.get("elementId");
      var view = this;
      tinymce.init({
         selector: id,
         theme: this.get('theme'),
         skin: this.get('skin'),
         menubar:this.get('theme'),
         statusbar: this.get('menubar'),
         height: this.get('height'),
         plugins: this.get('plugins'),
         content_css: this.get('content_css'),
         toolbar: this.get('toolbar'),
         valid_elements: this.get('valid_elements'),
         setup : function(ed) {
              view.set("editor", ed);
              ed.on("keyup change", function() {
                  view.suspendValueChange(function() {
                      view.set("value", ed.getContent());
                  });
              });
         }
      });
  },
  suspendValueChange: function(cb) {
      this._suspendValueChange = true;
      cb();
      this._suspendValueChange = false;
  },
  valueChanged: function() {
      if (this._suspendValueChange) { return; }
      var content = this.get("value");
      if(Ember.isEmpty(content)) {
        this.get("editor").setContent("");
      } else {
        this.get("editor").setContent(content);
      }
  }.observes("value"),
  willClearRender: function() {
    /*var id = "#" + this.get("elementId");
    tinyMCE.execCommand('mceFocus', false, id);
    tinyMCE.execCommand('mceRemoveControl', id);

    this.get("editor").remove();*/
  }
});

}());<commit_msg>clean code<commit_after>(function() {
  var get = Ember.get;

  window.P8UI = Ember.Namespace.create();
  var P8UI = Ember.Namespace.create();
  if(window) {
    window.P8UI = P8UI;
  }

  P8UI.HiddenInput = Ember.View.extend({
    tagName: 'input',
    attributeBindings: ['name', 'value', 'type'],
    value: "",
    name:"",
    type:"hidden"
  });

  function hasClickable(meta) {
    return meta !== undefined && Ember.get(meta,'clickable') !== undefined && Ember.get(meta,'clickable') !== false && Ember.get(meta,'clickable') !== null;
  }

  P8UI.CollectionItemViewClass = Ember.View.extend({
    template: Ember.computed(function(){
      var bodyTpl = "";
      for(var i=0; i<this.get('parentView.columns').length; i++) {
        bodyTpl += ''+this.getValueTmpl(i)+'	';
      }
      return Ember.Handlebars.compile(bodyTpl);
    }),

    actions: {
      _clickItem: function() {
        if(this.get('parentView').clickItem !== null) {
          this.get('parentView').clickItem(this.get('content'));
        }
      }
    },

   getValueTmpl: function(column) {
      //console.log(this.get('columnsMeta.'+column+'.headerWidth'));
      var meta = this.get('parentView.columnsMeta.'+this.get('parentView.columns')[column].replace(".",''));
      var value = '';
      var action = '_clickItem';
      if(hasClickable(meta)) {
        if(Ember.get(meta, 'clickable') !== true) {
          action = Ember.get(meta, 'clickable');
        }
        value += '';
      }

      if(!Ember.isNone(Ember.get(meta, 'template'))) {
        value += Ember.get(meta, 'template').apply(this, [meta]);
      } else {
        value += '{{view.content.'+this.get('parentView.columns')[column]+'}}';
      }

      if(hasClickable(meta)) {
        value += '';
      }

      return value;
    }
  });

  P8UI.CollectionView = Ember.CollectionView.extend({
    tagName: 'tbody',
    clickItem: null,
    columnsMeta: null,
    itemViewClass: P8UI.CollectionItemViewClass.extend()
  });

  P8UI.TableView = Ember.ContainerView.extend({
    tagName: 'table',
    content: null,
    columns: null,
    columnsMeta: null,
    classNames: ['p8ui-tableView'],
    childViews: ['headerView', 'bodyView'],
    clickItem: null,
    collectionView: P8UI.CollectionView
  });

  P8UI.TableView.reopen({
    headerView: Ember.computed(function() {
      return Ember.View.extend({
        tagName: 'thead',
        columns: get(this, 'columns'),
        columnsMeta: get(this, 'columnsMeta'),
        template: Ember.computed(function() {
          var bodyTpl = "
";
          for(var i=0; i<this.get('columns').length; i++) {
            var col = this.get('columns')[i].replace(".",'');
            bodyTpl += '';

            if(get(this, 'columnsMeta.'+col+'.headerValue') !== undefined && get(this, 'columnsMeta.'+col+'.headerValue') !== null) {
              bodyTpl += get(this, 'columnsMeta.'+col+'.headerValue');
            } else {
              bodyTpl += col;
            }

            bodyTpl += '	';
          }
          bodyTpl += "
";
          return Ember.Handlebars.compile(bodyTpl);
        })
      }).create();
    }).property(),

    bodyView:  Ember.computed(function() {
      return this.get('collectionView').extend({
        clickItem: this.get('clickItem'),
        columnsMeta: this.get('columnsMeta'),
        content: Ember.computed(function() {
          return get(this.get('tableView'), 'content');
        }).property('tableView.content').cacheable(),
        tableView: this,
        columns: get(this, 'columns'),
        emptyView: Ember.View.extend({
          template: Ember.Handlebars.compile('Keine Daten vorhanden	')
        })
      }).create();
      //return Ember.View.create();
    }).property()

  });

  P8UI.ScrollView = Ember.View.extend({
    classNames: ['p8ui-scrollView'],
    alignBottomTo: null,
    _TO: false,
    _resize: function() {
      var self = this;

      var alignBottomTo = self.get('alignBottomTo');
      if(alignBottomTo !== null) {
        var alignTo = self.$().parents(alignBottomTo).filter(':first');
        self.$().css({'height': alignTo.height() - (self.$().offset().top) + alignTo.offset().top});
        self.$().mCustomScrollbar('update');
      }
    },
    _windowResizeListener: function(event){
      if(event.data.scrollView._TO !== false) {
        clearTimeout(event.data.scrollView._TO);
      }
      event.data.scrollView._TO = setTimeout(event.data.scrollView._resize.call(event.data.scrollView), 200);
    },

    didInsertElement: function() {

      var self = this;
      setTimeout(function(){
      self.$().mCustomScrollbar({
        set_width:false,
        set_height:false,
        //horizontalScroll:false,
        scrollInertia:550,
        scrollEasing:"easeOutCirc",
        mouseWheel:"auto",
        autoDraggerLength:true,
        scrollButtons:{
          enable:false,
          scrollType:"continuous",
          scrollSpeed:20,
          scrollAmount:40
        },
        advanced:{
          updateOnBrowserResize:false,
          updateOnContentResize:false,
          autoExpandHorizontalScroll:false
        },
        callbacks:{
          onScroll:function(){},
          onTotalScroll:function(){},
          onTotalScrollOffset:0
        }
      });

      setTimeout(function(){self._resize.call(self);},200);

      var alignBottomTo = self.get('alignBottomTo');
      if(alignBottomTo !== null) {
        jQuery(window).bind('resize', {scrollView: this}, self._windowResizeListener);
      }
      },200);
    },
    willDestroyElement: function() {
      jQuery(window).unbind('resize', this._windowResizeListener);
    }
  });
}());

(function() {

  function defineProperty(mv, i, invalidProps, dependencies) {

    var validations = mv.get('validations');
    var validationProps = Ember.keys(mv.get('validations'));

    var invalidProp = 'invalid'+validationProps[i].capitalize();
    var errorProp = 'error'+validationProps[i].capitalize();
    var sourceProp = 'source.'+validationProps[i];
    var idx = i;

    invalidProps.push(invalidProp);

    Ember.defineProperty(mv, errorProp, Ember.computed(function() {
      var error = [];
      var v = this.get(sourceProp);

      for(var j=0; j<validations[validationProps[idx]].length; j++) {
        validations[validationProps[idx]][j](error,v, this);
      }
      return error;

    }).property(sourceProp));

    Ember.defineProperty(mv, invalidProp, Ember.computed(function() {
      return this.get(errorProp+'.length') > 0;
    }).property(errorProp));

    Ember.addObserver(mv, invalidProp, mv.evaluateValid);

    if(!Ember.isEmpty(dependencies)) {

      var observerf = function(a,b,c) {
        mv.notifyPropertyChange(errorProp);
      };

      for(var x=0; x<dependencies.length; x++) {
        Ember.addObserver(mv, 'source.'+dependencies[x], observerf);
      }
    }

  }

P8UI.InputValidator = Ember.Mixin.create({

    init: function() {
      var invalidProps = [];
      if(!Ember.isEmpty(this.get('validations'))) {
        var validationProps = Ember.keys(this.get('validations'));
        for(var i=0; i<validationProps.length; i++) {
          if(validationProps[i] !== '_dependencies') {
            defineProperty(this, i, invalidProps, this.get('validations._dependencies.'+validationProps[i]));
          }
        }
      }

      this._super();
      this.evaluateValid();
    },

    isValid: true,

    evaluateValid: function() {
      var validationProps = Ember.keys(this);
      for(var i=0; i<validationProps.length; i++) {
        if(validationProps[i].indexOf('invalid') === 0 && this.get(validationProps[i]) === true) {
          this.set('isValid', false);
          return;
        }
      }

      this.set('isValid', true);
    },

    source: null,
    invalid: function(field) {
      return this.get('error'+field.capitalize()+".length") > 0;
    },
    error: function(field) {
      return this.get('error'+field.capitalize());
    },

    singleErrorMessage: function(field) {
      if(this.get('error'+field.capitalize()+'.length') > 0) {
        return this.get('error'+field.capitalize())[0].message;
      } else {
        return null;
      }
    }
  });

  P8UI.ValidTextField = Ember.TextField.extend({
    //field:null,
    validator: null,
    _updateId: null,
    _updateValidation: function() {
      var self = this;
      this.$().data('bs.tooltip').options.title = this.get('validator').singleErrorMessage(self.get('name'));
      Ember.run.cancel(this.get('updateId'));
      var a = Ember.run.later( function(){
        if(!Ember.isEmpty(self.get('validator'))) {
          if(self.get('validator').invalid(self.get('name'))) {
            self.$().tooltip('show');
          } else {
        if(!Ember.isEmpty(self.$())) {
          self.$().tooltip('hide');
        }
          }
        }
      },300);
      this.set('updateId', a);
    },
    hasErrors: function() {
      return !this.get('validator').invalid(this.get('name'));
    },
    init: function() {
      this.addObserver('validator.invalid'+this.get('name').capitalize(), this, this._updateValidation);
      this._super();
    },
    didInsertElement: function() {
      var self = this;

      Ember.run( function(){
        self.$().tooltip({
          html: true,
          trigger: 'focus',
          width : '420',
          placement : 'bottom',
          delay: { show: 0, hide: 0}
        });
      });

    },

    willDestroyElement : function() {
      this.removeObserver('validator.invalid'+this.get('name').capitalize(), this, this._updateValidation);
      this.$().tooltip('destroy');
    }
  });
}());

(function() {

P8UI.DatetimePickerField = Ember.View.extend({
  defaultClass: 'span12',
  format: "dd.MM.yyyy hh:mm",
  picker: null,
  templateName: 'datetimepicker',
  validator: null,
  didInsertElement: function() {
    var onChangeDate, self;
    self = this;
    onChangeDate = function(ev) {
      return self.set("value", ev.date);
    };
    var r = this.$('.datetimepicker').datetimepicker({
      format: self.get('format'),
      language: 'de',
      collapse: true
    }).on("changeDate", onChangeDate);
    this.set('picker', r);
    this.suspendValueChange(function() {
      r.datetimepicker('setValue', self.get('value'));
    });
    return r;
  },
  suspendValueChange: function(cb) {
    this._suspendValueChange = true;
    cb();
    this._suspendValueChange = false;
  },
  valueChanged: function() {
    if (this._suspendValueChange) { return; }
    var content = this.get("value");
    if(Ember.isEmpty(content)) {
      //this.get("picker").setContent(null);
    } else {
      this.$('.datetimepicker').datetimepicker('setValue', this.get('value'));
    }
  }.observes("value")
});
}());

(function() {
P8UI.TinymceView = Ember.TextArea.extend({
  editor: null,
  theme: "modern",
  skin: 'light',
  menubar:false,
  statusbar: false,
  height: 300,
  plugins: [
            "autolink link lists hr ",
            "contextmenu paste"
 ],
 content_css: "res/tinymce/content.css",
 toolbar: "link | bold italic underline | bullist numlist outdent indent",
 valid_elements : "a[href|target=_blank],strong/b,em/i,strike,u,-ol[type|compact],-ul[type|compact],-li,br,-span,div[align],br,#p[style],ol,ul,li",
  _suspendValueChange: false,
  didInsertElement: function(){
      var id = "#" + this.get("elementId");
      var view = this;
      tinymce.init({
         selector: id,
         theme: this.get('theme'),
         skin: this.get('skin'),
         menubar:this.get('theme'),
         statusbar: this.get('menubar'),
         height: this.get('height'),
         plugins: this.get('plugins'),
         content_css: this.get('content_css'),
         toolbar: this.get('toolbar'),
         valid_elements: this.get('valid_elements'),
         setup : function(ed) {
              view.set("editor", ed);
              ed.on("keyup change", function() {
                  view.suspendValueChange(function() {
                      view.set("value", ed.getContent());
                  });
              });
         }
      });
  },
  suspendValueChange: function(cb) {
      this._suspendValueChange = true;
      cb();
      this._suspendValueChange = false;
  },
  valueChanged: function() {
      if (this._suspendValueChange) { return; }
      var content = this.get("value");
      if(Ember.isEmpty(content)) {
        this.get("editor").setContent("");
      } else {
        this.get("editor").setContent(content);
      }
  }.observes("value"),
  willClearRender: function() {
    /*var id = "#" + this.get("elementId");
    tinyMCE.execCommand('mceFocus', false, id);
    tinyMCE.execCommand('mceRemoveControl', id);

    this.get("editor").remove();*/
  }
});

}());<|endoftext|>


<commit_before>
import React from 'react';
import {
  Component ,
  Navigator ,
  View ,
  Text,
  StyleSheet,
  TouchableHighlight,
  NavigatorIOS,
  ListView,
  Image
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import HTMLView from 'react-native-htmlview';
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})

import Article from './Article'

export default class Essence extends Component {
  constructor (props){
    super(props);
  }

  componentWillMount (){
    const { getList } = this.props.essence;
    getList()
  }

  to(){
    const { navigator } = this.props;
    console.log(this)
    navigator.push({
      name : '文章',
      component : Article,
    })
  }

  _renderRow(rowData, sectionID, rowID, highlightRow){
    console.log(rowData, sectionID, rowID, highlightRow)
    return (
      <View style={[styles.rows]} key={rowData.id}>
        <Image
          style={styles.article}
          source={{uri: rowData.author.avatar_url}}
        />
        <View
          style={styles.content}
        >
          <Text
            style={[styles.articleTitle]}
            numberOfLines={1}
          >
            {rowData.title}
          </Text>
          <View style={[styles.articleDec]}>
            <Text style={{flex:1}}>131/3133</Text>
            <View style={{flex:2,flexDirection:'row',alignItems : 'center'}}>
              <Text style={[styles.tag]}>{rowData.tab}</Text>
              {
                rowData.good ?
                <Text style={[styles.tag]}>精华</Text> : null
              }
              {
                rowData.top ?
                <Text style={[styles.tag]}>置顶</Text> : null
              }
            </View>
            <Text style={{flex:1,textAlign:'right'}}>3天前</Text>
          </View>
        </View>
      </View>
    )
  }
  render (){
    const { data } = this.props.Essence;
    return (
      <View style={[styles.container]}>
        {
          data ?
          <ListView
            dataSource={ds.cloneWithRows(data)}
            renderRow={this._renderRow.bind(this)}
          /> :
          null
        }

      </View>
    )
  }
}

const styles = StyleSheet.create({
  container : {
    flex : 1,
    // alignItems : 'center',
    // justifyContent : 'center',
    // backgroundColor : 'yellow',
    paddingTop:65,
    paddingVertical : 5
  },
  flex1 : {
    flex : 1
  },
  rows : {
    padding : 10,
    borderBottomWidth : 1,
    borderStyle : 'solid',
    borderBottomColor : '#eee',
    flexDirection : 'row',
    height : 80
  },
  tag : {
    marginRight : 4
  },
  article : {
    width : 50,
    height : 50,
    borderRadius : 25
  },
  articleTitle : {
    fontSize : 16,
    flex : 1
  },
  articleDec : {
    flexDirection : 'row',
    alignItems : 'center',
    flex : 1
  },
  content : {
    flex : 1,
    paddingLeft : 8
  },
  link : {
    padding : 10,
    paddingHorizontal: 20,
    borderRadius : 3,
    borderColor: 'blue',
    borderWidth : 1,
    marginTop:20,
    // color : '#fff'
  }
})

// <View style={[styles.rows]} key={rowData.id}>
//   <Image
//     style={styles.article}
//     source={{uri: rowData.author.avatar_url}}
//   />
//   <View
//     style={styles.content}
//   >
//     <Text
//       style={[styles.articleTitle]}
//       numberOfLines={1}
//     >
//       {rowData.title}
//     </Text>
//     <View style={[styles.articleDec]}>
//       <Text style={{flex:1}}>131/3133</Text>
//       <Text style={{flex:1 }}>
//         <Text style={[styles.flex1]}>{rowData.tab}</Text>
//         {
//           rowData.good ?
//           <Text style={[styles.flex1]}>精华</Text> : null
//         }
//         {
//           rowData.top ?
//           <Text style={[styles.flex1]}>置顶</Text> : null
//         }
//       </Text>
//       <Text style={{flex:1,textAlign:'right'}}>3天前</Text>
//     </View>
//   </View>
// </View>
<commit_msg>ui update<commit_after>
import React from 'react';
import {
  Component ,
  Navigator ,
  View ,
  Text,
  StyleSheet,
  TouchableHighlight,
  NavigatorIOS,
  ListView,
  Image
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import HTMLView from 'react-native-htmlview';
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2})

import Article from './Article'

export default class Essence extends Component {
  constructor (props){
    super(props);
  }

  componentWillMount (){
    const { getList } = this.props.essence;
    getList()
  }

  to(){
    const { navigator } = this.props;
    console.log(this)
    navigator.push({
      name : '文章',
      component : Article,
    })
  }

  _renderRow(rowData, sectionID, rowID, highlightRow){
    console.log(rowData, sectionID, rowID, highlightRow)
    return (
      <View style={[styles.rows]} key={rowData.id}>
        <Image
          style={styles.article}
          source={{uri: rowData.author.avatar_url}}
        />
        <View
          style={styles.content}
        >
          <Text
            style={[styles.articleTitle]}
            numberOfLines={1}
          >
            {rowData.title}
          </Text>
          <View style={[styles.articleDec]}>
            <View style={{flex:2,flexDirection:'row',}}>
              <View style={{flexDirection:'row', justifyContent : 'center'}} >
                <Icon name="ios-eye-outline" size={22} color="#333" />
                <Text style={[styles.dec]}>{rowData.visit_count}</Text>
              </View>
              <View style={{flexDirection:'row', justifyContent : 'center'}} >
                <Icon name="ios-text-outline" size={22} color="#333" />
                <Text style={[styles.dec]}>{rowData.reply_count}</Text>
              </View>
            </View>
            <View style={{flex:2,flexDirection:'row',alignItems : 'center'}}>
              <Text style={[styles.tag]}>{rowData.tab}</Text>
              {
                rowData.good ?
                <Text style={[styles.tag]}>精华</Text> : null
              }
              {
                rowData.top ?
                <Text style={[styles.tag]}>置顶</Text> : null
              }
            </View>
            <Text style={{flex:1,textAlign:'right'}}>3天前</Text>
          </View>
        </View>
      </View>
    )
  }
  render (){
    const { data } = this.props.Essence;
    return (
      <View style={[styles.container]}>
        {
          data ?
          <ListView
            dataSource={ds.cloneWithRows(data)}
            renderRow={this._renderRow.bind(this)}
          /> :
          null
        }

      </View>
    )
  }
}

const styles = StyleSheet.create({
  container : {
    flex : 1,
    // alignItems : 'center',
    // justifyContent : 'center',
    // backgroundColor : 'yellow',
    paddingTop:65,
    paddingVertical : 5
  },
  flex1 : {
    flex : 1
  },
  rows : {
    padding : 10,
    borderBottomWidth : 1,
    borderStyle : 'solid',
    borderBottomColor : '#eee',
    flexDirection : 'row',
    height : 80
  },
  dec : {
    lineHeight:20,
    marginHorizontal : 5
  },
  tag : {
    color : '#4a2',
    marginHorizontal : 3
  },
  article : {
    width : 50,
    height : 50,
    borderRadius : 25
  },
  articleTitle : {
    fontSize : 16,
    flex : 1
  },
  articleDec : {
    flexDirection : 'row',
    alignItems : 'center',
    flex : 1
  },
  content : {
    flex : 1,
    paddingLeft : 8
  },
  link : {
    padding : 10,
    paddingHorizontal: 20,
    borderRadius : 3,
    borderColor: 'blue',
    borderWidth : 1,
    marginTop:20,
    // color : '#fff'
  }
})

// <View style={[styles.rows]} key={rowData.id}>
//   <Image
//     style={styles.article}
//     source={{uri: rowData.author.avatar_url}}
//   />
//   <View
//     style={styles.content}
//   >
//     <Text
//       style={[styles.articleTitle]}
//       numberOfLines={1}
//     >
//       {rowData.title}
//     </Text>
//     <View style={[styles.articleDec]}>
//       <Text style={{flex:1}}>131/3133</Text>
//       <Text style={{flex:1 }}>
//         <Text style={[styles.flex1]}>{rowData.tab}</Text>
//         {
//           rowData.good ?
//           <Text style={[styles.flex1]}>精华</Text> : null
//         }
//         {
//           rowData.top ?
//           <Text style={[styles.flex1]}>置顶</Text> : null
//         }
//       </Text>
//       <Text style={{flex:1,textAlign:'right'}}>3天前</Text>
//     </View>
//   </View>
// </View>
<|endoftext|>


<commit_before>import React from 'react';
import {
  Col,
  ControlLabel,
  FormControl,
  FormGroup,
  Grid,
  Row,
} from 'react-bootstrap';
import Router from 'next/router';
import classNames from 'classnames';

import withData from '../lib/withData'
import withIntl from '../lib/withIntl';
import { addGetLoggedInUserFunction, addSearchQueryData } from '../graphql/queries';

import Body from '../components/Body';
import Button from '../components/Button';
import CollectiveCard from '../components/CollectiveCard';
import ErrorPage from '../components/ErrorPage';
import Footer from '../components/Footer';
import Header from '../components/Header';
import LoadingGrid from '../components/LoadingGrid';
import { Link } from '../server/pages';

import colors from '../constants/colors';

class SearchPage extends React.Component {
  static getInitialProps({ query = {} }) {
    return {
      limit: query.limit || 20,
      offset: query.offset || 0,
      term: query.q || '',
    };
  }

  state = {
    loadingUserLogin: true,
    LoggedInUser: {},
  }

  async componentDidMount() {
    const { getLoggedInUser } = this.props;
    try {
      const LoggedInUser = getLoggedInUser && await getLoggedInUser();
      this.setState({
        loadingUserLogin: false,
        LoggedInUser,
      });
    } catch (error) {
      this.setState({ loadingUserLogin: false });
    }
  }

  refetch = (event) => {
    event.preventDefault();

    const { target: form } = event;
    const { url } = this.props;
    const { q } = form;

    Router.push({ pathname: url.pathname, query: { q: q.value } });
  }

  render() {
    const { data: { error, loading, search }, term = '' } = this.props;
    const { loadingUserLogin, LoggedInUser } = this.state;

    const {
      collectives,
      limit = 20,
      offset,
      total = 0,
    } = search || {};

    if (error) {
      return <ErrorPage {...error} />;
    }

    const showCollectives = !loading && term.trim() !== "" && !!collectives;

    return (

          <Grid>
            <Row>
              <Col xs={12}>
                <form method="GET" onSubmit={this.refetch}>
                  <FormGroup controlId="search" bsSize="large">
                    <ControlLabel className="h1">Search Open Collective</ControlLabel>

                      <FormControl type="search" name="q" placeholder="open source" className="search-input" defaultValue={term} />
                      <Button type="submit" className="blue" style={{ padding: '0 2rem' }}></Button>

                  </FormGroup>
                </form>
              </Col>
            </Row>
            <Row className="results-row">
              {loading && (

                  <LoadingGrid />

              )}

              {showCollectives && collectives.map(collective => (
                <Col className="col" key={collective.slug}>
                  <CollectiveCard collective={collective} />
                </Col>
              ))}

              { /* TODO: add suggested collectives when the result is empty */ }
              {showCollectives && collectives.length === 0 && (

No collectives found matching your query: "{term}"

              )}
            </Row>
            {showCollectives && collectives.length !== 0 && total > limit && <Row>

                { Array(Math.ceil(total / limit)).fill(1).map((n, i) => (

                    <Link
                      href={{
                        query: {
                          limit,
                          offset: i * limit,
                          q: term,
                        }
                      }}
                      >
                      {`${n + i}`}
                    </Link>

                )) }

            </Row>}
          </Grid>

    )
  }
}

export { SearchPage as MockSearchPage };

export default withData(addGetLoggedInUserFunction(addSearchQueryData(withIntl(SearchPage))));
<commit_msg>refactor(pages/search): use grid-styled components for layout<commit_after>import React from 'react';
import {
  ControlLabel,
  FormControl,
  FormGroup,
} from 'react-bootstrap';
import { Box, Flex } from 'grid-styled';
import Router from 'next/router';
import classNames from 'classnames';
import styled from 'styled-components';

import withData from '../lib/withData'
import withIntl from '../lib/withIntl';
import { addGetLoggedInUserFunction, addSearchQueryData } from '../graphql/queries';

import Body from '../components/Body';
import Button from '../components/Button';
import CollectiveCard from '../components/CollectiveCard';
import ErrorPage from '../components/ErrorPage';
import Footer from '../components/Footer';
import Header from '../components/Header';
import LoadingGrid from '../components/LoadingGrid';
import { Link } from '../server/pages';

import colors from '../constants/colors';

const SearchInput = styled(FormControl)`
  &&& {
    border: none;
    border-bottom: 2px solid ${colors.blue};
    border-radius: 0;
    box-shadow: none;
    display: block;
    font-family: lato;
    height: 3.4rem;
    padding: 0;
  }
`;

const SearchButton = styled(Button).attrs({
  className: 'blue',
})`
  && {
    padding: 0 2rem;
  }
`;

const Container = Box.extend`
  max-width: 1200px;
`;

class SearchPage extends React.Component {
  static getInitialProps({ query = {} }) {
    return {
      limit: query.limit || 20,
      offset: query.offset || 0,
      term: query.q || '',
    };
  }

  state = {
    loadingUserLogin: true,
    LoggedInUser: {},
  }

  async componentDidMount() {
    const { getLoggedInUser } = this.props;
    try {
      const LoggedInUser = getLoggedInUser && await getLoggedInUser();
      this.setState({
        loadingUserLogin: false,
        LoggedInUser,
      });
    } catch (error) {
      this.setState({ loadingUserLogin: false });
    }
  }

  refetch = (event) => {
    event.preventDefault();

    const { target: form } = event;
    const { url } = this.props;
    const { q } = form;

    Router.push({ pathname: url.pathname, query: { q: q.value } });
  }

  render() {
    const { data: { error, loading, search }, term = '' } = this.props;
    const { loadingUserLogin, LoggedInUser } = this.state;

    const {
      collectives,
      limit = 20,
      offset,
      total = 0,
    } = search || {};

    if (error) {
      return <ErrorPage {...error} />;
    }

    const showCollectives = !loading && term.trim() !== "" && !!collectives;

    return (

          <Container mx="auto" px={3} w={[1, 0.85]}>
            <Box w={1}>
              <form method="GET" onSubmit={this.refetch}>
                <FormGroup controlId="search" bsSize="large">
                  <ControlLabel className="h1">Search Open Collective</ControlLabel>
                  <Flex alignItems="flex-end" my={3}>
                    <SearchInput type="search" name="q" placeholder="open source" defaultValue={term} />
                    <SearchButton type="submit"></SearchButton>
                  </Flex>
                </FormGroup>
              </form>
            </Box>
            <Flex justifyContent={['center', 'center', 'flex-start']} flexWrap="wrap">
              {loading && (
                <Flex py={3} w={1} justifyContent="center">
                  <LoadingGrid />
                </Flex>
              )}

              {showCollectives && collectives.map(collective => (
                <Flex key={collective.slug} my={3} mx={2}>
                  <CollectiveCard collective={collective} />
                </Flex>
              ))}

              { /* TODO: add suggested collectives when the result is empty */ }
              {showCollectives && collectives.length === 0 && (
                <Flex py={3} w={1} justifyContent="center">

No collectives found matching your query: "{term}"

                </Flex>
              )}
            </Flex>
            {showCollectives && collectives.length !== 0 && total > limit && <Flex justifyContent="center">

                { Array(Math.ceil(total / limit)).fill(1).map((n, i) => (

                    <Link
                      href={{
                        query: {
                          limit,
                          offset: i * limit,
                          q: term,
                        }
                      }}
                      >
                      {`${n + i}`}
                    </Link>

                )) }

            </Flex>}
          </Container>

    )
  }
}

export { SearchPage as MockSearchPage };

export default withData(addGetLoggedInUserFunction(addSearchQueryData(withIntl(SearchPage))));
<|endoftext|>


<commit_before>var _ = require('lodash'),
  sinon = require('sinon'),
  Parse = require('parse'),
  db = {};

if (typeof Parse.Parse != 'undefined') {
  Parse = Parse.Parse;
}

/**
 * Mocks a Parse API server, by intercepting requests and storing/querying data locally
 * in an in-memory DB.
 */
function mockDB() {
  stubRequests();
  stubSave();
}

/**
 * Intercepts a save() request and writes the results of the save() to our
 * in-memory DB
 */
function stubSave() {
  var realSave = Parse.Object.prototype.save;
  sinon.stub(Parse.Object.prototype, "save", function() {
    var options = this;
    return realSave.call(this).then(function(savedObj) {
      // save to our local db
      db[options.className] = db[options.className] || [];

      var newObject = storableFormat(savedObj, options.className);
      if (!_.find(db[options.className], function(obj) { return obj.id == options.id; })) {
        db[options.className].push(newObject);
      }
      return savedObj;
    });
  });
}

/**
 * Unstubs Parse SDK requests and clears the local DB.
 */
function cleanUp() {
  db = {};
  Parse.Object.prototype.save.restore();
  Parse._request.restore();
}

/**
 * Intercepts calls to Parse._request, and returns the appropriate
 * successful response based on the request parameters
 */
function stubRequests() {
  sinon.stub(Parse, '_request', function(options) {
    var response, status, xhr;
    switch (options.method) {
    case "GET":
      response = stubGetRequest(options);
      status = "200";
      break;
    case "POST":
      response = stubPostOrPutRequest(options);
      status = "201";
      break;
    case "PUT":
      response = stubPostOrPutRequest(options);
      status = "200";
      break;
    default:
      throw new Error("unknown request type");
    }

    xhr = {}; // TODO
    return Parse.Promise.when([response, status, xhr]);
  });
}

/**
 * Stubs a GET request (Parse.Query.find(), get(), first())
 */
function stubGetRequest(options) {
  var matches = recursivelyMatch(options.className, options.data.where);
  matches = queryMatchesAfterIncluding(matches, options.data.include);
  ret = { "results": matches };
  return ret;
}

/**
 * Stubs a POST or PUT request (Parse.Object.save())
 */
function stubPostOrPutRequest(options) {
  if (options.route == "batch") {
    // batch request. handle them seperately.
    return _.map(options.data.requests, function(request) {
      return { success: { updatedAt: (new Date()).toJSON() } };
    });
  }

  if (options.objectId) {
    return Parse.Promise.as({ updatedAt: (new Date()).toJSON() });
  }

  var promise = new Parse.Promise.as({
    id:  _.uniqueId(),
    createdAt: (new Date()).toJSON(),
    updatedAt: (new Date()).toJSON()
  });
  return promise;
}

/**
 * Simple wrapper around promises known to be executed synchronously
 * useful in test setup for seeding the local DB.
 */
function promiseResultSync(promise) {
  var result;
  promise.then(function(res) {
    result = res;
  });

  return result;
}

/**
 * Converts a fetched Parse object to its JSON format stored in the
 * local DB
 */
function storableFormat(object, className) {
  var storableData = {
    id: object.id,
    createdAt: object.createdAt.toJSON(),
    updatedAt: object.updatedAt.toJSON(),
    className: className,
  };
  _.each(object.attributes, function(v, k) {
    if (v.id) {
      storableData[k] = {
        __type: "Pointer",
        objectId: v.id,
        className: v.className
      };
    } else {
      storableData[k] = v;
    }
  });

  return storableData;
}

/**
 * Given a set of matches of a GET query (e.g. find()), returns fully
 * fetched Parse Objects that include the nested objects requested by
 * Parse.Query.include()
 */
function queryMatchesAfterIncluding(matches, includeClause) {
  if (!includeClause) {
    return matches;
  }

  includeClauses = includeClause.split(",");
  matches = _.map(matches, function(match) {
    for (var i = 0; i < includeClauses.length; i++) {
      var paths = includeClauses[i].split(".");
      match = objectAfterReplacingAtIncludePaths(match, paths);
    }
    return match;
  });

  return matches;
}

/**
 * Recursive function that traverses an include path and replaces pointers
 * with fully fetched objects
 */
function objectAfterReplacingAtIncludePaths(object, paths) {
  if (paths.length != 0) {
    var path = paths.shift();
    if (!object[path]) {
      return object;
    }

    var obj = fetchedObject(object[path]);
    object[path] = _.cloneDeep(objectAfterReplacingAtIncludePaths(obj, paths));
  }
  return object
};

/**
 * Given an object, a pointer, or a JSON representation of a Parse Object,
 * return a fully fetched version of the Object.
 */
function fetchedObject(objectOrPointer) {
  var className, objectId;
  if (objectOrPointer.__type == "Object") {
    // fully formed, no need to look up
    return objectOrPointer;
  } else if (objectOrPointer.id && objectOrPointer.className) {
    objectId = objectOrPointer.id;
    className = objectOrPointer.className;
  } else {
    className = objectOrPointer["className"];
    objectId = objectOrPointer["objectId"];
  }
  var storedItem = _.find(db[className], function(obj) { return obj.id == objectId; });
  if (storedItem) {
    storedItem.__type = "Object";
  }
  return storedItem;
}

/**
 * Given a class name and a where clause, returns DB matches by applying
 * the where clause (recursively if nested)
 */
var recursivelyMatch = function(className, whereClause) {
  var classMatches = _.cloneDeep(db[className]);
  var matches = _.filter(classMatches, queryFilter(whereClause));
  return matches;
}

/**
 * Returns a function that filters query matches on a where clause
 */
function queryFilter(whereClause) {
  if (whereClause["$or"]) {
    return function(object) {
      return _.reduce(whereClause["$or"], function(result, subclause) {
        return result || queryFilter(subclause)(object);
      }, false);
    }
  }

  return function(object) {
    if (whereClause.objectId && typeof whereClause.objectId != "object") {
      // this is a get() request. simply match on ID
      return object.id == whereClause.objectId;
    }

    return _.reduce(whereClause, function(result, whereParams, key) {
      var match = evaluateObject(object, whereParams, key);
      return result && match;
    }, true);
  };
}

// special case objectId queries
function keyAfterSpecialCasingId(key) {
  if (key === "objectId") {
    key = "id";
  }
  return key;
}

function evaluateObject(object, whereParams, key) {
  if (typeof whereParams == "object" && whereParams) {
    if (whereParams["$in"]) {
      // containedIn
      key = keyAfterSpecialCasingId(key);
      return _.find(whereParams["$in"], function(target) {
        return objectsAreEqual(target, object[key]);
      });
    } else if (whereParams["$nin"]) {
      // notContainedIn
      key = keyAfterSpecialCasingId(key);
      return _.find(whereParams["$nin"], function(target) {
        return !objectsAreEqual(target, object[key]);
      });
    } else if (whereParams["__type"] == "Pointer") {
      // match on an object
      var storedItem = fetchedObject(whereParams);
      return storedItem && object[key] && (object[key].id == storedItem.objectId);
    } else if (whereParams["$select"]) {
      var foreignKey = whereParams["$select"]["key"];
      var query = whereParams["$select"]["query"];
      var matches = recursivelyMatch(query.className, query.where);
      var objectMatches = _.filter(
        matches,
        function(match) { return object[key] == match[foreignKey]; }
      );
      return objectMatches.length > 0;
    } else {
      throw new Error("Parse-MockDB: unknown query where clause: " + JSON.stringify(whereParams));
    }
  } else if (whereParams) {
    // simple match
    return object[key] == whereParams;
  } else {
    return true;
  }
}

/**
 * Evaluates whether 2 objects are the same, independent of their representation
 * (e.g. Pointer, Object)
 */
function objectsAreEqual(obj1, obj2) {
  if (obj1 == undefined || obj2 == undefined) {
    return false;
  }

  // scalar values
  if (obj1 == obj2) {
    return true;
  }

  // both pointers
  if (obj1.objectId !== undefined && obj1.objectId == obj2.objectId) {
    return true;
  }

  // both objects
  if (obj1.id !== undefined && obj1.id == obj2.id) {
    return true;
  }

  // one pointer, one object
  if (obj1.id !== undefined && obj1.id == obj2.objectId) {
    return true;
  } else if (obj2.id !== undefined && obj2.id == obj1.objectId) {
    return true;
  }

  return false;
}

Parse.MockDB = {
  mockDB: mockDB,
  cleanUp: cleanUp,
  promiseResultSync: promiseResultSync,
};

module.exports = Parse.MockDB;
<commit_msg>fix empty notIncludedIn bug<commit_after>var _ = require('lodash'),
  sinon = require('sinon'),
  Parse = require('parse'),
  db = {};

if (typeof Parse.Parse != 'undefined') {
  Parse = Parse.Parse;
}

/**
 * Mocks a Parse API server, by intercepting requests and storing/querying data locally
 * in an in-memory DB.
 */
function mockDB() {
  stubRequests();
  stubSave();
}

/**
 * Intercepts a save() request and writes the results of the save() to our
 * in-memory DB
 */
function stubSave() {
  var realSave = Parse.Object.prototype.save;
  sinon.stub(Parse.Object.prototype, "save", function() {
    var options = this;
    return realSave.call(this).then(function(savedObj) {
      // save to our local db
      db[options.className] = db[options.className] || [];

      var newObject = storableFormat(savedObj, options.className);
      if (!_.find(db[options.className], function(obj) { return obj.id == options.id; })) {
        db[options.className].push(newObject);
      }
      return savedObj;
    });
  });
}

/**
 * Unstubs Parse SDK requests and clears the local DB.
 */
function cleanUp() {
  db = {};
  Parse.Object.prototype.save.restore();
  Parse._request.restore();
}

/**
 * Intercepts calls to Parse._request, and returns the appropriate
 * successful response based on the request parameters
 */
function stubRequests() {
  sinon.stub(Parse, '_request', function(options) {
    var response, status, xhr;
    switch (options.method) {
    case "GET":
      response = stubGetRequest(options);
      status = "200";
      break;
    case "POST":
      response = stubPostOrPutRequest(options);
      status = "201";
      break;
    case "PUT":
      response = stubPostOrPutRequest(options);
      status = "200";
      break;
    default:
      throw new Error("unknown request type");
    }

    xhr = {}; // TODO
    return Parse.Promise.when([response, status, xhr]);
  });
}

/**
 * Stubs a GET request (Parse.Query.find(), get(), first())
 */
function stubGetRequest(options) {
  var matches = recursivelyMatch(options.className, options.data.where);
  matches = queryMatchesAfterIncluding(matches, options.data.include);
  ret = { "results": matches };
  return ret;
}

/**
 * Stubs a POST or PUT request (Parse.Object.save())
 */
function stubPostOrPutRequest(options) {
  if (options.route == "batch") {
    // batch request. handle them seperately.
    return _.map(options.data.requests, function(request) {
      return { success: { updatedAt: (new Date()).toJSON() } };
    });
  }

  if (options.objectId) {
    return Parse.Promise.as({ updatedAt: (new Date()).toJSON() });
  }

  var promise = new Parse.Promise.as({
    id:  _.uniqueId(),
    createdAt: (new Date()).toJSON(),
    updatedAt: (new Date()).toJSON()
  });
  return promise;
}

/**
 * Simple wrapper around promises known to be executed synchronously
 * useful in test setup for seeding the local DB.
 */
function promiseResultSync(promise) {
  var result;
  promise.then(function(res) {
    result = res;
  });

  return result;
}

/**
 * Converts a fetched Parse object to its JSON format stored in the
 * local DB
 */
function storableFormat(object, className) {
  var storableData = {
    id: object.id,
    createdAt: object.createdAt.toJSON(),
    updatedAt: object.updatedAt.toJSON(),
    className: className,
  };
  _.each(object.attributes, function(v, k) {
    if (v.id) {
      storableData[k] = {
        __type: "Pointer",
        objectId: v.id,
        className: v.className
      };
    } else {
      storableData[k] = v;
    }
  });

  return storableData;
}

/**
 * Given a set of matches of a GET query (e.g. find()), returns fully
 * fetched Parse Objects that include the nested objects requested by
 * Parse.Query.include()
 */
function queryMatchesAfterIncluding(matches, includeClause) {
  if (!includeClause) {
    return matches;
  }

  includeClauses = includeClause.split(",");
  matches = _.map(matches, function(match) {
    for (var i = 0; i < includeClauses.length; i++) {
      var paths = includeClauses[i].split(".");
      match = objectAfterReplacingAtIncludePaths(match, paths);
    }
    return match;
  });

  return matches;
}

/**
 * Recursive function that traverses an include path and replaces pointers
 * with fully fetched objects
 */
function objectAfterReplacingAtIncludePaths(object, paths) {
  if (paths.length != 0) {
    var path = paths.shift();
    if (!object[path]) {
      return object;
    }

    var obj = fetchedObject(object[path]);
    object[path] = _.cloneDeep(objectAfterReplacingAtIncludePaths(obj, paths));
  }
  return object
};

/**
 * Given an object, a pointer, or a JSON representation of a Parse Object,
 * return a fully fetched version of the Object.
 */
function fetchedObject(objectOrPointer) {
  var className, objectId;
  if (objectOrPointer.__type == "Object") {
    // fully formed, no need to look up
    return objectOrPointer;
  } else if (objectOrPointer.id && objectOrPointer.className) {
    objectId = objectOrPointer.id;
    className = objectOrPointer.className;
  } else {
    className = objectOrPointer["className"];
    objectId = objectOrPointer["objectId"];
  }
  var storedItem = _.find(db[className], function(obj) { return obj.id == objectId; });
  if (storedItem) {
    storedItem.__type = "Object";
  }
  return storedItem;
}

/**
 * Given a class name and a where clause, returns DB matches by applying
 * the where clause (recursively if nested)
 */
var recursivelyMatch = function(className, whereClause) {
  var classMatches = _.cloneDeep(db[className]);
  var matches = _.filter(classMatches, queryFilter(whereClause));
  return matches;
}

/**
 * Returns a function that filters query matches on a where clause
 */
function queryFilter(whereClause) {
  if (whereClause["$or"]) {
    return function(object) {
      return _.reduce(whereClause["$or"], function(result, subclause) {
        return result || queryFilter(subclause)(object);
      }, false);
    }
  }

  return function(object) {
    if (whereClause.objectId && typeof whereClause.objectId != "object") {
      // this is a get() request. simply match on ID
      return object.id == whereClause.objectId;
    }

    return _.reduce(whereClause, function(result, whereParams, key) {
      var match = evaluateObject(object, whereParams, key);
      return result && match;
    }, true);
  };
}

// special case objectId queries
function keyAfterSpecialCasingId(key) {
  if (key === "objectId") {
    key = "id";
  }
  return key;
}

function evaluateObject(object, whereParams, key) {
  if (typeof whereParams == "object" && whereParams) {
    if (whereParams["$in"]) {
      // containedIn
      key = keyAfterSpecialCasingId(key);
      return _.find(whereParams["$in"], function(target) {
        return objectsAreEqual(target, object[key]);
      });
    } else if (whereParams["$nin"]) {
      // notContainedIn
      if (whereParams["$nin"].length == 0) {
        return true;
      }
      key = keyAfterSpecialCasingId(key);
      return _.find(whereParams["$nin"], function(target) {
        return !objectsAreEqual(target, object[key]);
      });
    } else if (whereParams["__type"] == "Pointer") {
      // match on an object
      var storedItem = fetchedObject(whereParams);
      return storedItem && object[key] && (object[key].id == storedItem.objectId);
    } else if (whereParams["$select"]) {
      var foreignKey = whereParams["$select"]["key"];
      var query = whereParams["$select"]["query"];
      var matches = recursivelyMatch(query.className, query.where);
      var objectMatches = _.filter(
        matches,
        function(match) { return object[key] == match[foreignKey]; }
      );
      return objectMatches.length > 0;
    } else {
      throw new Error("Parse-MockDB: unknown query where clause: " + JSON.stringify(whereParams));
    }
  } else if (whereParams) {
    // simple match
    return object[key] == whereParams;
  } else {
    return true;
  }
}

/**
 * Evaluates whether 2 objects are the same, independent of their representation
 * (e.g. Pointer, Object)
 */
function objectsAreEqual(obj1, obj2) {
  if (obj1 == undefined || obj2 == undefined) {
    return false;
  }

  // scalar values
  if (obj1 == obj2) {
    return true;
  }

  // both pointers
  if (obj1.objectId !== undefined && obj1.objectId == obj2.objectId) {
    return true;
  }

  // both objects
  if (obj1.id !== undefined && obj1.id == obj2.id) {
    return true;
  }

  // one pointer, one object
  if (obj1.id !== undefined && obj1.id == obj2.objectId) {
    return true;
  } else if (obj2.id !== undefined && obj2.id == obj1.objectId) {
    return true;
  }

  return false;
}

Parse.MockDB = {
  mockDB: mockDB,
  cleanUp: cleanUp,
  promiseResultSync: promiseResultSync,
};

module.exports = Parse.MockDB;
<|endoftext|>