<commit_before>/* globals selectorLoader, analytics, communication, catcher, log, makeUuid, auth, senderror, startBackground, blobConverters */

"use strict";

this.main = (function() {
  let exports = {};

  const pasteSymbol = (window.navigator.platform.match(/Mac/i)) ? "\u2318" : "Ctrl";
  const { sendEvent } = analytics;

  let manifest = browser.runtime.getManifest();
  let backend;

  let hasSeenOnboarding;

  browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
    hasSeenOnboarding = !!result.hasSeenOnboarding;
    if (!hasSeenOnboarding) {
      setIconActive(false, null);
      // Note that the branded name 'Firefox Screenshots' is not localized:
      startBackground.photonPageActionPort.postMessage({
        type: "setProperties",
        title: "Firefox Screenshots"
      });
    }
  }).catch((error) => {
    log.error("Error getting hasSeenOnboarding:", error);
  });

  exports.setBackend = function(newBackend) {
    backend = newBackend;
    backend = backend.replace(/\/*$/, "");
  };

  exports.getBackend = function() {
    return backend;
  };

  communication.register("getBackend", () => {
    return backend;
  });

  function getOnboardingUrl() {
    return backend + "/#hello";
  }

  for (let permission of manifest.permissions) {
    if (/^https?:\/\//.test(permission)) {
      exports.setBackend(permission);
      break;
    }
  }

  function setIconActive(active, tabId) {
    let path = active ? "icons/icon-highlight-32-v2.svg" : "icons/icon-32-v2.svg";
    startBackground.photonPageActionPort.postMessage({
      type: "setProperties",
      iconPath: path
    });
  }

  function toggleSelector(tab) {
    return analytics.refreshTelemetryPref()
      .then(() => selectorLoader.toggle(tab.id, hasSeenOnboarding))
      .then(active => {
        setIconActive(active, tab.id);
        return active;
      })
      .catch((error) => {
        if (error.message && /Missing host permission for the tab/.test(error.message)) {
          error.noReport = true;
        }
        error.popupMessage = "UNSHOOTABLE_PAGE";
        throw error;
      });
  }

  function startSelectionWithOnboarding(tab) {
    return analytics.refreshTelemetryPref().then(() => {
      return selectorLoader.testIfLoaded(tab.id);
    }).then((isLoaded) => {
      if (!isLoaded) {
        sendEvent("start-shot", "site-request");
        setIconActive(true, tab.id);
        selectorLoader.toggle(tab.id, false);
      }
    });
  }

  function shouldOpenMyShots(url) {
    return /^about:(?:newtab|blank|home)/i.test(url) || /^resource:\/\/activity-streams\//i.test(url);
  }

  // This is called by startBackground.js, directly in response to clicks on the Photon page action
  exports.onClicked = catcher.watchFunction((tab) => {
    if (shouldOpenMyShots(tab.url)) {
      if (!hasSeenOnboarding) {
        catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
          sendEvent("goto-onboarding", "selection-button");
          return forceOnboarding();
        }));
        return;
      }
      catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
        sendEvent("goto-myshots", "about-newtab");
      }));
      catcher.watchPromise(
        auth.authHeaders()
        .then(() => browser.tabs.update({url: backend + "/shots"})));
    } else {
      catcher.watchPromise(
        toggleSelector(tab)
          .then(active => {
            const event = active ? "start-shot" : "cancel-shot";
            sendEvent(event, "toolbar-button");
          }, (error) => {
            if ((!hasSeenOnboarding) && error.popupMessage == "UNSHOOTABLE_PAGE") {
              sendEvent("goto-onboarding", "selection-button");
              return forceOnboarding();
            }
            throw error;
          }));
    }
  });

  function forceOnboarding() {
    return browser.tabs.create({url: getOnboardingUrl()});
  }

  exports.onClickedContextMenu = catcher.watchFunction((info, tab) => {
    if (!tab) {
      // Not in a page/tab context, ignore
      return;
    }
    if (!urlEnabled(tab.url)) {
      senderror.showError({
        popupMessage: "UNSHOOTABLE_PAGE"
      });
      return;
    }
    catcher.watchPromise(
      toggleSelector(tab)
        .then(() => sendEvent("start-shot", "context-menu")));
  });

  function urlEnabled(url) {
    if (shouldOpenMyShots(url)) {
      return true;
    }
    if (isShotOrMyShotPage(url) || /^(?:about|data|moz-extension):/i.test(url) || isBlacklistedUrl(url)) {
      return false;
    }
    return true;
  }

  function isShotOrMyShotPage(url) {
    // It's okay to take a shot of any pages except shot pages and My Shots
    if (!url.startsWith(backend)) {
      return false;
    }
    let path = url.substr(backend.length).replace(/^\/*/, "").replace(/[?#].*/, "");
    if (path == "shots") {
      return true;
    }
    if (/^[^/]{1,4000}\/[^/]{1,4000}$/.test(path)) {
      // Blocks {:id}/{:domain}, but not /, /privacy, etc
      return true;
    }
    return false;
  }

  function isBlacklistedUrl(url) {
    // These specific domains are not allowed for general WebExtension permission reasons
    // Discussion: https://bugzilla.mozilla.org/show_bug.cgi?id=1310082
    // List of domains copied from: https://dxr.mozilla.org/mozilla-central/source/browser/app/permissions#18-19
    // Note we disable it here to be informative, the security check is done in WebExtension code
    const badDomains = ["addons.mozilla.org", "testpilot.firefox.com"];
    let domain = url.replace(/^https?:\/\//i, "");
    domain = domain.replace(/\/.*/, "").replace(/:.*/, "");
    domain = domain.toLowerCase();
    return badDomains.includes(domain);
  }

  communication.register("sendEvent", (sender, ...args) => {
    catcher.watchPromise(sendEvent(...args));
    // We don't wait for it to complete:
    return null;
  });

  communication.register("openMyShots", (sender) => {
    return catcher.watchPromise(
      auth.authHeaders()
      .then(() => browser.tabs.create({url: backend + "/shots"})));
  });

  communication.register("openShot", (sender, {url, copied}) => {
    if (copied) {
      const id = makeUuid();
      return browser.notifications.create(id, {
        type: "basic",
        iconUrl: "../icons/copy.png",
        title: browser.i18n.getMessage("notificationLinkCopiedTitle"),
        message: browser.i18n.getMessage("notificationLinkCopiedDetails", pasteSymbol)
      });
    }
  });

  communication.register("copyShotToClipboard", (sender, blob) => {
    return blobConverters.blobToArray(blob).then(buffer => {
      return browser.clipboard.setImageData(
        buffer, blob.type.split("/", 2)[1]).then(() => {
          return browser.notifications.create({
            type: "basic",
            iconUrl: "../icons/copy.png",
            title: browser.i18n.getMessage("notificationImageCopiedTitle"),
            message: browser.i18n.getMessage("notificationImageCopiedDetails")
          });
        });
    })
  });

  communication.register("downloadShot", (sender, info) => {
    // 'data:' urls don't work directly, let's use a Blob
    // see http://stackoverflow.com/questions/40269862/save-data-uri-as-file-using-downloads-download-api
    const blob = blobConverters.dataUrlToBlob(info.url);
    let url = URL.createObjectURL(blob);
    let downloadId;
    let onChangedCallback = catcher.watchFunction(function(change) {
      if (!downloadId || downloadId != change.id) {
        return;
      }
      if (change.state && change.state.current != "in_progress") {
        URL.revokeObjectURL(url);
        browser.downloads.onChanged.removeListener(onChangedCallback);
      }
    });
    browser.downloads.onChanged.addListener(onChangedCallback)
    catcher.watchPromise(communication.sendToBootstrap("incrementDownloadCount"));
    return browser.windows.getLastFocused().then(windowInfo => {
      return windowInfo.incognito;
    }).then((incognito) => {
      return browser.downloads.download({
        url,
        incognito,
        filename: info.filename
      }).then((id) => {
        downloadId = id;
      });
    });
  });

  communication.register("closeSelector", (sender) => {
    setIconActive(false, sender.tab.id);
  });

  catcher.watchPromise(communication.sendToBootstrap("getOldDeviceInfo").then((deviceInfo) => {
    if (deviceInfo === communication.NO_BOOTSTRAP || !deviceInfo) {
      return;
    }
    deviceInfo = JSON.parse(deviceInfo);
    if (deviceInfo && typeof deviceInfo == "object") {
      return auth.setDeviceInfoFromOldAddon(deviceInfo).then((updated) => {
        if (updated === communication.NO_BOOTSTRAP) {
          throw new Error("bootstrap.js disappeared unexpectedly");
        }
        if (updated) {
          return communication.sendToBootstrap("removeOldAddon");
        }
      });
    }
  }));

  communication.register("hasSeenOnboarding", () => {
    hasSeenOnboarding = true;
    catcher.watchPromise(browser.storage.local.set({hasSeenOnboarding}));
    setIconActive(false, null);
    startBackground.photonPageActionPort.postMessage({
      type: "setProperties",
      title: browser.i18n.getMessage("contextMenuLabel")
    });
  });

  communication.register("abortFrameset", () => {
    sendEvent("abort-start-shot", "frame-page");
    // Note, we only show the error but don't report it, as we know that we can't
    // take shots of these pages:
    senderror.showError({
      popupMessage: "UNSHOOTABLE_PAGE"
    });
  });

  communication.register("abortNoDocumentBody", (sender, tagName) => {
    tagName = String(tagName || "").replace(/[^a-z0-9]/ig, "");
    sendEvent("abort-start-shot", `document-is-${tagName}`);
    // Note, we only show the error but don't report it, as we know that we can't
    // take shots of these pages:
    senderror.showError({
      popupMessage: "UNSHOOTABLE_PAGE"
    });
  });

  // Note: this signal is only needed until bug 1357589 is fixed.
  communication.register("openTermsPage", () => {
    return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/about/legal/terms/services/"}));
  });

  // Note: this signal is also only needed until bug 1357589 is fixed.
  communication.register("openPrivacyPage", () => {
    return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/privacy/firefox-cloud/"}));
  });

  // A Screenshots page wants us to start/force onboarding
  communication.register("requestOnboarding", (sender) => {
    return startSelectionWithOnboarding(sender.tab);
  });

  communication.register("isHistoryEnabled", () => {
    return catcher.watchPromise(communication.sendToBootstrap("getHistoryPref").then(historyEnabled => {
      return historyEnabled;
    }));
  });

  communication.register("getPlatformOs", () => {
    return catcher.watchPromise(browser.runtime.getPlatformInfo().then(platformInfo => {
      return platformInfo.os;
    }));
  });

  return exports;
})();
<commit_msg>Add paste symbol for image copy notification. (#3790)<commit_after>/* globals selectorLoader, analytics, communication, catcher, log, makeUuid, auth, senderror, startBackground, blobConverters */

"use strict";

this.main = (function() {
  let exports = {};

  const pasteSymbol = (window.navigator.platform.match(/Mac/i)) ? "\u2318" : "Ctrl";
  const { sendEvent } = analytics;

  let manifest = browser.runtime.getManifest();
  let backend;

  let hasSeenOnboarding;

  browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
    hasSeenOnboarding = !!result.hasSeenOnboarding;
    if (!hasSeenOnboarding) {
      setIconActive(false, null);
      // Note that the branded name 'Firefox Screenshots' is not localized:
      startBackground.photonPageActionPort.postMessage({
        type: "setProperties",
        title: "Firefox Screenshots"
      });
    }
  }).catch((error) => {
    log.error("Error getting hasSeenOnboarding:", error);
  });

  exports.setBackend = function(newBackend) {
    backend = newBackend;
    backend = backend.replace(/\/*$/, "");
  };

  exports.getBackend = function() {
    return backend;
  };

  communication.register("getBackend", () => {
    return backend;
  });

  function getOnboardingUrl() {
    return backend + "/#hello";
  }

  for (let permission of manifest.permissions) {
    if (/^https?:\/\//.test(permission)) {
      exports.setBackend(permission);
      break;
    }
  }

  function setIconActive(active, tabId) {
    let path = active ? "icons/icon-highlight-32-v2.svg" : "icons/icon-32-v2.svg";
    startBackground.photonPageActionPort.postMessage({
      type: "setProperties",
      iconPath: path
    });
  }

  function toggleSelector(tab) {
    return analytics.refreshTelemetryPref()
      .then(() => selectorLoader.toggle(tab.id, hasSeenOnboarding))
      .then(active => {
        setIconActive(active, tab.id);
        return active;
      })
      .catch((error) => {
        if (error.message && /Missing host permission for the tab/.test(error.message)) {
          error.noReport = true;
        }
        error.popupMessage = "UNSHOOTABLE_PAGE";
        throw error;
      });
  }

  function startSelectionWithOnboarding(tab) {
    return analytics.refreshTelemetryPref().then(() => {
      return selectorLoader.testIfLoaded(tab.id);
    }).then((isLoaded) => {
      if (!isLoaded) {
        sendEvent("start-shot", "site-request");
        setIconActive(true, tab.id);
        selectorLoader.toggle(tab.id, false);
      }
    });
  }

  function shouldOpenMyShots(url) {
    return /^about:(?:newtab|blank|home)/i.test(url) || /^resource:\/\/activity-streams\//i.test(url);
  }

  // This is called by startBackground.js, directly in response to clicks on the Photon page action
  exports.onClicked = catcher.watchFunction((tab) => {
    if (shouldOpenMyShots(tab.url)) {
      if (!hasSeenOnboarding) {
        catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
          sendEvent("goto-onboarding", "selection-button");
          return forceOnboarding();
        }));
        return;
      }
      catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
        sendEvent("goto-myshots", "about-newtab");
      }));
      catcher.watchPromise(
        auth.authHeaders()
        .then(() => browser.tabs.update({url: backend + "/shots"})));
    } else {
      catcher.watchPromise(
        toggleSelector(tab)
          .then(active => {
            const event = active ? "start-shot" : "cancel-shot";
            sendEvent(event, "toolbar-button");
          }, (error) => {
            if ((!hasSeenOnboarding) && error.popupMessage == "UNSHOOTABLE_PAGE") {
              sendEvent("goto-onboarding", "selection-button");
              return forceOnboarding();
            }
            throw error;
          }));
    }
  });

  function forceOnboarding() {
    return browser.tabs.create({url: getOnboardingUrl()});
  }

  exports.onClickedContextMenu = catcher.watchFunction((info, tab) => {
    if (!tab) {
      // Not in a page/tab context, ignore
      return;
    }
    if (!urlEnabled(tab.url)) {
      senderror.showError({
        popupMessage: "UNSHOOTABLE_PAGE"
      });
      return;
    }
    catcher.watchPromise(
      toggleSelector(tab)
        .then(() => sendEvent("start-shot", "context-menu")));
  });

  function urlEnabled(url) {
    if (shouldOpenMyShots(url)) {
      return true;
    }
    if (isShotOrMyShotPage(url) || /^(?:about|data|moz-extension):/i.test(url) || isBlacklistedUrl(url)) {
      return false;
    }
    return true;
  }

  function isShotOrMyShotPage(url) {
    // It's okay to take a shot of any pages except shot pages and My Shots
    if (!url.startsWith(backend)) {
      return false;
    }
    let path = url.substr(backend.length).replace(/^\/*/, "").replace(/[?#].*/, "");
    if (path == "shots") {
      return true;
    }
    if (/^[^/]{1,4000}\/[^/]{1,4000}$/.test(path)) {
      // Blocks {:id}/{:domain}, but not /, /privacy, etc
      return true;
    }
    return false;
  }

  function isBlacklistedUrl(url) {
    // These specific domains are not allowed for general WebExtension permission reasons
    // Discussion: https://bugzilla.mozilla.org/show_bug.cgi?id=1310082
    // List of domains copied from: https://dxr.mozilla.org/mozilla-central/source/browser/app/permissions#18-19
    // Note we disable it here to be informative, the security check is done in WebExtension code
    const badDomains = ["addons.mozilla.org", "testpilot.firefox.com"];
    let domain = url.replace(/^https?:\/\//i, "");
    domain = domain.replace(/\/.*/, "").replace(/:.*/, "");
    domain = domain.toLowerCase();
    return badDomains.includes(domain);
  }

  communication.register("sendEvent", (sender, ...args) => {
    catcher.watchPromise(sendEvent(...args));
    // We don't wait for it to complete:
    return null;
  });

  communication.register("openMyShots", (sender) => {
    return catcher.watchPromise(
      auth.authHeaders()
      .then(() => browser.tabs.create({url: backend + "/shots"})));
  });

  communication.register("openShot", (sender, {url, copied}) => {
    if (copied) {
      const id = makeUuid();
      return browser.notifications.create(id, {
        type: "basic",
        iconUrl: "../icons/copy.png",
        title: browser.i18n.getMessage("notificationLinkCopiedTitle"),
        message: browser.i18n.getMessage("notificationLinkCopiedDetails", pasteSymbol)
      });
    }
  });

  communication.register("copyShotToClipboard", (sender, blob) => {
    return blobConverters.blobToArray(blob).then(buffer => {
      return browser.clipboard.setImageData(
        buffer, blob.type.split("/", 2)[1]).then(() => {
          return browser.notifications.create({
            type: "basic",
            iconUrl: "../icons/copy.png",
            title: browser.i18n.getMessage("notificationImageCopiedTitle"),
            message: browser.i18n.getMessage("notificationImageCopiedDetails", pasteSymbol)
          });
        });
    })
  });

  communication.register("downloadShot", (sender, info) => {
    // 'data:' urls don't work directly, let's use a Blob
    // see http://stackoverflow.com/questions/40269862/save-data-uri-as-file-using-downloads-download-api
    const blob = blobConverters.dataUrlToBlob(info.url);
    let url = URL.createObjectURL(blob);
    let downloadId;
    let onChangedCallback = catcher.watchFunction(function(change) {
      if (!downloadId || downloadId != change.id) {
        return;
      }
      if (change.state && change.state.current != "in_progress") {
        URL.revokeObjectURL(url);
        browser.downloads.onChanged.removeListener(onChangedCallback);
      }
    });
    browser.downloads.onChanged.addListener(onChangedCallback)
    catcher.watchPromise(communication.sendToBootstrap("incrementDownloadCount"));
    return browser.windows.getLastFocused().then(windowInfo => {
      return windowInfo.incognito;
    }).then((incognito) => {
      return browser.downloads.download({
        url,
        incognito,
        filename: info.filename
      }).then((id) => {
        downloadId = id;
      });
    });
  });

  communication.register("closeSelector", (sender) => {
    setIconActive(false, sender.tab.id);
  });

  catcher.watchPromise(communication.sendToBootstrap("getOldDeviceInfo").then((deviceInfo) => {
    if (deviceInfo === communication.NO_BOOTSTRAP || !deviceInfo) {
      return;
    }
    deviceInfo = JSON.parse(deviceInfo);
    if (deviceInfo && typeof deviceInfo == "object") {
      return auth.setDeviceInfoFromOldAddon(deviceInfo).then((updated) => {
        if (updated === communication.NO_BOOTSTRAP) {
          throw new Error("bootstrap.js disappeared unexpectedly");
        }
        if (updated) {
          return communication.sendToBootstrap("removeOldAddon");
        }
      });
    }
  }));

  communication.register("hasSeenOnboarding", () => {
    hasSeenOnboarding = true;
    catcher.watchPromise(browser.storage.local.set({hasSeenOnboarding}));
    setIconActive(false, null);
    startBackground.photonPageActionPort.postMessage({
      type: "setProperties",
      title: browser.i18n.getMessage("contextMenuLabel")
    });
  });

  communication.register("abortFrameset", () => {
    sendEvent("abort-start-shot", "frame-page");
    // Note, we only show the error but don't report it, as we know that we can't
    // take shots of these pages:
    senderror.showError({
      popupMessage: "UNSHOOTABLE_PAGE"
    });
  });

  communication.register("abortNoDocumentBody", (sender, tagName) => {
    tagName = String(tagName || "").replace(/[^a-z0-9]/ig, "");
    sendEvent("abort-start-shot", `document-is-${tagName}`);
    // Note, we only show the error but don't report it, as we know that we can't
    // take shots of these pages:
    senderror.showError({
      popupMessage: "UNSHOOTABLE_PAGE"
    });
  });

  // Note: this signal is only needed until bug 1357589 is fixed.
  communication.register("openTermsPage", () => {
    return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/about/legal/terms/services/"}));
  });

  // Note: this signal is also only needed until bug 1357589 is fixed.
  communication.register("openPrivacyPage", () => {
    return catcher.watchPromise(browser.tabs.create({url: "https://www.mozilla.org/privacy/firefox-cloud/"}));
  });

  // A Screenshots page wants us to start/force onboarding
  communication.register("requestOnboarding", (sender) => {
    return startSelectionWithOnboarding(sender.tab);
  });

  communication.register("isHistoryEnabled", () => {
    return catcher.watchPromise(communication.sendToBootstrap("getHistoryPref").then(historyEnabled => {
      return historyEnabled;
    }));
  });

  communication.register("getPlatformOs", () => {
    return catcher.watchPromise(browser.runtime.getPlatformInfo().then(platformInfo => {
      return platformInfo.os;
    }));
  });

  return exports;
})();
<|endoftext|>


<commit_before><commit_msg>递归方式隐藏照片墙<commit_after><|endoftext|>


<commit_before>/*global require, module */

var _ = require('lodash'),
    request = require('request'),
    colors = require('colors/safe');

/**
 * Default Poll Interval used by the backend.
 * We set the default poll interval to high, so
 * if any of the frontend needs faster than this
 * time, then would have to pass in the 'interval'
 * in their request.
 */
var POLL_INTERVAL = 10*1000;

/**
 * Aggregator
 * receives resourceObj, aggregate them,
 * and send poll responses back through socket
 *
 * @param {Object} SockJS connection
 */
function Aggregator (conn) {
  // make "new" optional
  if ( !(this instanceof Aggregator) ) {
    return new Aggregator(conn);
  }

  conn.on('data', _.bind(onSocketData, this));
  conn.on('close', _.bind(onSocketClose, this));

  this.connection = conn;

  // WebSocket local resource pool. Key here is the resource id
  // as send from the backend. The FE has to guarantee that the
  // the resource id is unique within a websocket connection.
  this.polledResources = {};
}

/**
 * Logs the error message to console.
 */
Aggregator.prototype.log = function () {
  console.log(
    colors.cyan('sock'),
    colors.dim(this.connection.id),
    _(arguments).join(' ')
  );
};

/**
 * Checks if the 'id' received from the client is already registered -- This
 * check was added because for whatever reason 'Safari' was sending multiple
 * requests to backend with same ids. As this is happens only once during
 * the start of poll, it's safe to make this check. The assumption here is
 * that the frontend is sending in unique ids within the websocket session.
 *
 * Upon check if it's not duplicate, we invoke doPoll that would make the
 * first call and set the interval for the timeout.
 */
Aggregator.prototype.startPolling = function (resource) {
  // WARN: This assumes that the browser side ids are unique for a websocket session.
  // This check is needed for Safari.
  if(this.polledResources[resource.id]) {
    // console.log("Resource id " + resource.id + " already registered.");
    return;
  }
  resource.interval = resource.interval || POLL_INTERVAL;
  this.polledResources[resource.id] = resource;
  _.bind(doPoll, this, resource)();
}

/**
 * This method is called regularly by 'doPoll' to register the next interval
 * for timeout. Every resource handle has a flag is used to indicate if the
 * the resource has been requested to be stopped, if it's already stopped, then
 * there is nothing for us to do. If it's not then we go ahead and register
 * the interval timeout.
 */
Aggregator.prototype.scheduleAnotherIteration = function (resource) {
  if (resource.stop) {
    // Don't reschedule another iteration if the resource has been stopped
    return;
  }
  // console.log(Object.keys(this.polledResources).length + ':' + Math.floor(Date.now()/1000) +
  //    ': scheduling for: ' + resource.id + ', interval: ' + resource.interval + ' - ' + resource.url);
  resource.timerId = setTimeout(_.bind(doPoll, this, resource), resource.interval);
};

/**
 * Stops the polling of a resource. The resources that has been requested to be stopped
 * is removed from the websocket local poll and the timeouts are cleared and stop flag
 * is set to true.
 */
Aggregator.prototype.stopPolling = function (resource) {
  var thisResource = removeFromObj(this.polledResources, resource.id);
  if (thisResource === undefined) {
    return;
  }
  clearTimeout(thisResource.timerId);
  thisResource.stop = true;
};

/**
 * Iterates through the websocket local resources and clears the timers and sets
 * the flag. This is called when the websocket is closing the connection.
 */
Aggregator.prototype.stopPollingAll = function() {
  for (var id in this.polledResources) {
    var resource = this.polledResources[id];
    clearTimeout(resource.timerId);
    resource.stop = true;
  }
}

/**
 * Removes the resource id from the websocket connection local resource pool.
 */
function removeFromObj(obj, key) {
  var el = obj[key];
  delete obj[key];
  return el;
}

/**
 * 'doPoll' is the doer - it makes the resource request call to backend and
 * sends the response back once it receives it. Upon completion of the request
 * it schedulers the interval for next trigger.
 */
function doPoll (resource) {
    var that = this,
        callBack = _.bind(this.scheduleAnotherIteration, that, resource);

    request(resource, function(error, response, body) {
      if (error) {
        emitResponse.call(that, resource, error);
        return;
      }

      emitResponse.call(that, resource, false, response, body);

    }).on('response', callBack)
    .on('error', callBack);
}

/**
 * @private emitResponse
 *
 * sends data back to the client through socket
 *
 * @param  {object} resource that was requested
 * @param  {error|null} error
 * @param  {object} response
 * @param  {string} body
 */
function emitResponse (resource, error, response, body) {
  resource.timerId = undefined;
  resource.stop = undefined;
  if(error) { // still emit a warning
    this.log(resource.url, error);
    this.connection.write(JSON.stringify({
      resource: resource,
      error: error,
      warning: error.toString()
    }));

  } else {

    // this.log('emit', resource.url);
    this.connection.write(JSON.stringify({
      resource: resource,
      statusCode: response.statusCode,
      response: body
    }));
  }
}

/**
 * @private onSocketData
 * @param  {string} message received via socket
 */
function onSocketData (message) {
  try {
    message = JSON.parse(message);
    var r = message.resource;

    switch(message.action) {
      case 'poll-start':
        this.startPolling(r);
        break;
      case 'request':
        request(r, _.bind(emitResponse, this, r));
        break;
      case 'poll-stop':
        this.stopPolling(r);
        break;
    }
  }
  catch (e) {
    console.error(e);
  }
}

/**
 * @private onSocketClose
 */
function onSocketClose () {
  this.log('socket closed');
  this.stopPollingAll();
  this.polledResources = {};
}

module.exports = Aggregator;
<commit_msg>Removed lodash and used native javascript bind<commit_after>/*global require, module */

var request = require('request'),
    colors = require('colors/safe');

/**
 * Default Poll Interval used by the backend.
 * We set the default poll interval to high, so
 * if any of the frontend needs faster than this
 * time, then would have to pass in the 'interval'
 * in their request.
 */
var POLL_INTERVAL = 10*1000;

/**
 * Aggregator
 * receives resourceObj, aggregate them,
 * and send poll responses back through socket
 *
 * @param {Object} SockJS connection
 */
function Aggregator (conn) {
  // make "new" optional
  if ( !(this instanceof Aggregator) ) {
    return new Aggregator(conn);
  }

  conn.on('data', onSocketData.bind(this));
  conn.on('close', onSocketClose.bind(this));

  this.connection = conn;

  // WebSocket local resource pool. Key here is the resource id
  // as send from the backend. The FE has to guarantee that the
  // the resource id is unique within a websocket connection.
  this.polledResources = {};
}

/**
 * Checks if the 'id' received from the client is already registered -- This
 * check was added because for whatever reason 'Safari' was sending multiple
 * requests to backend with same ids. As this is happens only once during
 * the start of poll, it's safe to make this check. The assumption here is
 * that the frontend is sending in unique ids within the websocket session.
 *
 * Upon check if it's not duplicate, we invoke doPoll that would make the
 * first call and set the interval for the timeout.
 */
Aggregator.prototype.startPolling = function (resource) {
  // WARN: This assumes that the browser side ids are unique for a websocket session.
  // This check is needed for Safari.
  if(this.polledResources[resource.id]) {
    // console.log("Resource id " + resource.id + " already registered.");
    return;
  }
  resource.interval = resource.interval || POLL_INTERVAL;
  this.polledResources[resource.id] = resource;
  doPoll.bind(this, resource)();
}

/**
 * This method is called regularly by 'doPoll' to register the next interval
 * for timeout. Every resource handle has a flag is used to indicate if the
 * the resource has been requested to be stopped, if it's already stopped, then
 * there is nothing for us to do. If it's not then we go ahead and register
 * the interval timeout.
 */
Aggregator.prototype.scheduleAnotherIteration = function (resource) {
  if (resource.stop) {
    // Don't reschedule another iteration if the resource has been stopped
    return;
  }
  // console.log(Object.keys(this.polledResources).length + ':' + Math.floor(Date.now()/1000) +
  //    ': scheduling for: ' + resource.id + ', interval: ' + resource.interval + ' - ' + resource.url);
  resource.timerId = setTimeout(doPoll.bind(this, resource), resource.interval);
};

/**
 * Stops the polling of a resource. The resources that has been requested to be stopped
 * is removed from the websocket local poll and the timeouts are cleared and stop flag
 * is set to true.
 */
Aggregator.prototype.stopPolling = function (resource) {
  var thisResource = removeFromObj(this.polledResources, resource.id);
  if (thisResource === undefined) {
    return;
  }
  clearTimeout(thisResource.timerId);
  thisResource.stop = true;
};

/**
 * Iterates through the websocket local resources and clears the timers and sets
 * the flag. This is called when the websocket is closing the connection.
 */
Aggregator.prototype.stopPollingAll = function() {
  for (var id in this.polledResources) {
    var resource = this.polledResources[id];
    clearTimeout(resource.timerId);
    resource.stop = true;
  }
}

/**
 * Removes the resource id from the websocket connection local resource pool.
 */
function removeFromObj(obj, key) {
  var el = obj[key];
  delete obj[key];
  return el;
}

/**
 * 'doPoll' is the doer - it makes the resource request call to backend and
 * sends the response back once it receives it. Upon completion of the request
 * it schedulers the interval for next trigger.
 */
function doPoll (resource) {
    var that = this,
        callBack = this.scheduleAnotherIteration.bind(that, resource);

    request(resource, function(error, response, body) {
      if (error) {
        emitResponse.call(that, resource, error);
        return;
      }

      emitResponse.call(that, resource, false, response, body);

    }).on('response', callBack)
    .on('error', callBack);
}

/**
 * @private emitResponse
 *
 * sends data back to the client through socket
 *
 * @param  {object} resource that was requested
 * @param  {error|null} error
 * @param  {object} response
 * @param  {string} body
 */
function emitResponse (resource, error, response, body) {
  resource.timerId = undefined;
  resource.stop = undefined;
  if(error) { // still emit a warning
    this.connection.write(JSON.stringify({
      resource: resource,
      error: error,
      warning: error.toString()
    }));

  } else {

    this.connection.write(JSON.stringify({
      resource: resource,
      statusCode: response.statusCode,
      response: body
    }));
  }
}

/**
 * @private onSocketData
 * @param  {string} message received via socket
 */
function onSocketData (message) {
  try {
    message = JSON.parse(message);
    var r = message.resource;

    switch(message.action) {
      case 'poll-start':
        this.startPolling(r);
        break;
      case 'request':
        request(r, emitResponse.bind(this, r));
        break;
      case 'poll-stop':
        this.stopPolling(r);
        break;
    }
  }
  catch (e) {
    console.error(e);
  }
}

/**
 * @private onSocketClose
 */
function onSocketClose () {
  this.stopPollingAll();
  this.polledResources = {};
}

module.exports = Aggregator;
<|endoftext|>


<commit_before>/*!
* Copyright 2002 - 2013 Webdetails, a Pentaho company.  All rights reserved.
*
* This software was developed by Webdetails and is provided under the terms
* of the Mozilla Public License, Version 2.0, or any later version. You may not use
* this file except in compliance with the license. If you need a copy of the license,
* please go to  http://mozilla.org/MPL/2.0/. The Initial Developer is Webdetails.
*
* Software distributed under the Mozilla Public License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to
* the license for the specific language governing your rights and limitations.
*/

if(!Object.create){
    /** @ignore */
    Object.create = (function(){

        var Klass = function(){},
            proto = Klass.prototype;

        /** @private */
        function create(baseProto){
            Klass.prototype = baseProto || {};
            var instance = new Klass();
            Klass.prototype = proto;

            return instance;
        }

        return create;
    }());
}

/* Some utility functions, backward compatibility with older browsers */

/**
 * Utility function to add a method to a function's prototype
 * @global
 * @param {string} name - method name
 * @param {function} func - function to register
 *
 */

Function.prototype.method = Function.prototype.method || function(name, func) {
    this.prototype[name] = func;
    return this;
};

if ( !String.prototype.endsWith ) {
  String.prototype.endsWith = function(str){
    return (this.match(str+"$")==str);
  };
}

if (!Object.keys) {
  Object.keys = (function () {
    var hasOwnProperty = Object.prototype.hasOwnProperty,
        hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
        dontEnums = [
          'toString',
          'toLocaleString',
          'valueOf',
          'hasOwnProperty',
          'isPrototypeOf',
          'propertyIsEnumerable',
          'constructor'
        ],
        dontEnumsLength = dontEnums.length

    return function (obj) {
      if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object')

      var result = []

      for (var prop in obj) {
        if (hasOwnProperty.call(obj, prop)) result.push(prop)
      }

      if (hasDontEnumBug) {
        for (var i=0; i < dontEnumsLength; i++) {
          if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i])
        }
      }
      return result
    }
  })()
};

// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.com/#x15.4.4.19
if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {

    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If IsCallable(callback) is false, throw a TypeError exception.
    // See: http://es5.github.com/#x9.11
    if ({}.toString.call(callback) != "[object Function]") {
      throw new TypeError(callback + " is not a function");
    }

    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
    if (thisArg) {
      T = thisArg;
    }

    // 6. Let A be a new array created as if by the expression new Array(len) where Array is
    // the standard built-in constructor with that name and len is the value of len.
    A = new Array(len);

    // 7. Let k be 0
    k = 0;

    // 8. Repeat, while k < len
    while(k < len) {

      var kValue, mappedValue;

      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {

        // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
        kValue = O[ k ];

        // ii. Let mappedValue be the result of calling the Call internal method of callback
        // with T as the this value and argument list containing kValue, k, and O.
        mappedValue = callback.call(T, kValue, k, O);

        // iii. Call the DefineOwnProperty internal method of A with arguments
        // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true},
        // and false.

        // In browsers that support Object.defineProperty, use the following:
        // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });

        // For best browser support, use the following:
        A[ k ] = mappedValue;
      }
      // d. Increase k by 1.
      k++;
    }

    // 9. return A
    return A;
  };
}

// Implementation of Array.indexOf (for IE <9)
// Reference: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
        "use strict";
        if (this == null) {
            throw new TypeError();
        }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) {
            return -1;
        }
        var n = 0;
        if (arguments.length > 0) {
            n = Number(arguments[1]);
            if (n != n) { // shortcut for verifying if it's NaN
                n = 0;
            } else if (n != 0 && n != Infinity && n != -Infinity) {
                n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= len) {
            return -1;
        }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
            if (k in t && t[k] === searchElement) {
                return k;
            }
        }
        return -1;
    }
}

// Implementation of Array.lastIndexOf (for IE <9)
// Reference: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
if (!Array.prototype.lastIndexOf)
{
  Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/)
  {
    "use strict";

    if (this == null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (len === 0)
      return -1;

    var n = len;
    if (arguments.length > 1)
    {
      n = Number(arguments[1]);
      if (n != n)
        n = 0;
      else if (n != 0 && n != (1 / 0) && n != -(1 / 0))
        n = (n > 0 || -1) * Math.floor(Math.abs(n));
    }

    var k = n >= 0
          ? Math.min(n, len - 1)
          : len - Math.abs(n);

    for (; k >= 0; k--)
    {
      if (k in t && t[k] === searchElement)
        return k;
    }
    return -1;
  };
}

if ( !Array.prototype.reduce ) {
  Array.prototype.reduce = function reduce(accumulator){
    var i, l = this.length, curr;

    if(typeof accumulator !== "function") // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception."
      throw new TypeError("First argument is not callable");

    if((l == 0 || l === null) && (arguments.length <= 1))// == on purpose to test 0 and false.
      throw new TypeError("Array length is 0 and no second argument");

    if(arguments.length <= 1){
      curr = this[0]; // Increase i to start searching the secondly defined element in the array
      i = 1; // start accumulating at the second element
    }
    else{
      curr = arguments[1];
    }

    for(i = i || 0 ; i < l ; ++i){
      if(i in this)
        curr = accumulator.call(undefined, curr, this[i], i, this);
    }

    return curr;
  };
}

if ( !Date.prototype.toISOString ) {
  ( function() {

    function pad(number) {
      if ( number < 10 ) {
        return '0' + number;
      }
      return number;
    }

    Date.prototype.toISOString = function() {
      return this.getUTCFullYear() +
        '-' + pad( this.getUTCMonth() + 1 ) +
        '-' + pad( this.getUTCDate() ) +
        'T' + pad( this.getUTCHours() ) +
        ':' + pad( this.getUTCMinutes() ) +
        ':' + pad( this.getUTCSeconds() ) +
        '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice( 2, 5 ) +
        'Z';
    };

  }() );
}

<commit_msg>cherry-pick and applied to require-js '[SP-1543] Backport of BISERVER-12091 - Can only display dashboard once on IE8 (5.2 Suite)'<commit_after>/*!
 * Copyright 2002 - 2014 Webdetails, a Pentaho company.  All rights reserved.
 *
 * This software was developed by Webdetails and is provided under the terms
 * of the Mozilla Public License, Version 2.0, or any later version. You may not use
 * this file except in compliance with the license. If you need a copy of the license,
 * please go to  http://mozilla.org/MPL/2.0/. The Initial Developer is Webdetails.
 *
 * Software distributed under the Mozilla Public License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or  implied. Please refer to
 * the license for the specific language governing your rights and limitations.
 */

if(!Object.create){
    /** @ignore */
    Object.create = (function(){

        var Klass = function(){},
            proto = Klass.prototype;

        /** @private */
        function create(baseProto){
            Klass.prototype = baseProto || {};
            var instance = new Klass();
            Klass.prototype = proto;

            return instance;
        }

        return create;
    }());
}

/* Some utility functions, backward compatibility with older browsers */

/**
 * Utility function to add a method to a function's prototype
 * @global
 * @param {string} name - method name
 * @param {function} func - function to register
 *
 */

Function.prototype.method = Function.prototype.method || function(name, func) {
    this.prototype[name] = func;
    return this;
};

if ( !String.prototype.endsWith ) {
  String.prototype.endsWith = function(str){
    return (this.match(str+"$")==str);
  };
}

if (!Object.keys) {
  Object.keys = (function () {
    var hasOwnProperty = Object.prototype.hasOwnProperty,
        hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
        dontEnums = [
          'toString',
          'toLocaleString',
          'valueOf',
          'hasOwnProperty',
          'isPrototypeOf',
          'propertyIsEnumerable',
          'constructor'
        ],
        dontEnumsLength = dontEnums.length

    return function (obj) {
      if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) throw new TypeError('Object.keys called on non-object')

      var result = []

      for (var prop in obj) {
        if (hasOwnProperty.call(obj, prop)) result.push(prop)
      }

      if (hasDontEnumBug) {
        for (var i=0; i < dontEnumsLength; i++) {
          if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i])
        }
      }
      return result
    }
  })()
};

// Production steps of ECMA-262, Edition 5, 15.4.4.19
// Reference: http://es5.github.com/#x15.4.4.19
if (!Array.prototype.map) {
  Array.prototype.map = function(callback, thisArg) {

    var T, A, k;

    if (this == null) {
      throw new TypeError(" this is null or not defined");
    }

    // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
    var O = Object(this);

    // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
    // 3. Let len be ToUint32(lenValue).
    var len = O.length >>> 0;

    // 4. If IsCallable(callback) is false, throw a TypeError exception.
    // See: http://es5.github.com/#x9.11
    if ({}.toString.call(callback) != "[object Function]") {
      throw new TypeError(callback + " is not a function");
    }

    // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
    if (thisArg) {
      T = thisArg;
    }

    // 6. Let A be a new array created as if by the expression new Array(len) where Array is
    // the standard built-in constructor with that name and len is the value of len.
    A = new Array(len);

    // 7. Let k be 0
    k = 0;

    // 8. Repeat, while k < len
    while(k < len) {

      var kValue, mappedValue;

      // a. Let Pk be ToString(k).
      //   This is implicit for LHS operands of the in operator
      // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
      //   This step can be combined with c
      // c. If kPresent is true, then
      if (k in O) {

        // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
        kValue = O[ k ];

        // ii. Let mappedValue be the result of calling the Call internal method of callback
        // with T as the this value and argument list containing kValue, k, and O.
        mappedValue = callback.call(T, kValue, k, O);

        // iii. Call the DefineOwnProperty internal method of A with arguments
        // Pk, Property Descriptor {Value: mappedValue, Writable: true, Enumerable: true, Configurable: true},
        // and false.

        // In browsers that support Object.defineProperty, use the following:
        // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });

        // For best browser support, use the following:
        A[ k ] = mappedValue;
      }
      // d. Increase k by 1.
      k++;
    }

    // 9. return A
    return A;
  };
}

// Implementation of Array.indexOf (for IE <9)
// Reference: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
        "use strict";
        if (this == null) {
            throw new TypeError();
        }
        var t = Object(this);
        var len = t.length >>> 0;
        if (len === 0) {
            return -1;
        }
        var n = 0;
        if (arguments.length > 0) {
            n = Number(arguments[1]);
            if (n != n) { // shortcut for verifying if it's NaN
                n = 0;
            } else if (n != 0 && n != Infinity && n != -Infinity) {
                n = (n > 0 || -1) * Math.floor(Math.abs(n));
            }
        }
        if (n >= len) {
            return -1;
        }
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
        for (; k < len; k++) {
            if (k in t && t[k] === searchElement) {
                return k;
            }
        }
        return -1;
    }
}

// Implementation of Array.lastIndexOf (for IE <9)
// Reference: https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/lastIndexOf
if (!Array.prototype.lastIndexOf)
{
  Array.prototype.lastIndexOf = function(searchElement /*, fromIndex*/)
  {
    "use strict";

    if (this == null)
      throw new TypeError();

    var t = Object(this);
    var len = t.length >>> 0;
    if (len === 0)
      return -1;

    var n = len;
    if (arguments.length > 1)
    {
      n = Number(arguments[1]);
      if (n != n)
        n = 0;
      else if (n != 0 && n != (1 / 0) && n != -(1 / 0))
        n = (n > 0 || -1) * Math.floor(Math.abs(n));
    }

    var k = n >= 0
          ? Math.min(n, len - 1)
          : len - Math.abs(n);

    for (; k >= 0; k--)
    {
      if (k in t && t[k] === searchElement)
        return k;
    }
    return -1;
  };
}

if ( !Array.prototype.reduce ) {
  Array.prototype.reduce = function reduce(accumulator){
    var i, l = this.length, curr;

    if(typeof accumulator !== "function") // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception."
      throw new TypeError("First argument is not callable");

    if((l == 0 || l === null) && (arguments.length <= 1))// == on purpose to test 0 and false.
      throw new TypeError("Array length is 0 and no second argument");

    if(arguments.length <= 1){
      curr = this[0]; // Increase i to start searching the secondly defined element in the array
      i = 1; // start accumulating at the second element
    }
    else{
      curr = arguments[1];
    }

    for(i = i || 0 ; i < l ; ++i){
      if(i in this)
        curr = accumulator.call(undefined, curr, this[i], i, this);
    }

    return curr;
  };
}

if ( !Date.prototype.toISOString ) {
  ( function() {

    function pad(number) {
      if ( number < 10 ) {
        return '0' + number;
      }
      return number;
    }

    Date.prototype.toISOString = function() {
      return this.getUTCFullYear() +
        '-' + pad( this.getUTCMonth() + 1 ) +
        '-' + pad( this.getUTCDate() ) +
        'T' + pad( this.getUTCHours() ) +
        ':' + pad( this.getUTCMinutes() ) +
        ':' + pad( this.getUTCSeconds() ) +
        '.' + (this.getUTCMilliseconds() / 1000).toFixed(3).slice( 2, 5 ) +
        'Z';
    };

  }() );
}

if(!Array.prototype.forEach){
  Array.prototype.forEach = function(f, o) {
    for (var n = this.length >>> 0, i = 0; n > i; i++) i in this && f.call(o, this[i], i, this);
  }
}

if (!Date.now) {
  Date.now = function() { return new Date().valueOf(); }
}

<|endoftext|>


<commit_before>Kwf.onJElementReady('.kwcAdvancedCommunityVideo', function(el, config) {
    el.data('config', config);
}, {priority: -1});

Kwf.onJElementWidthChange('.kwcAdvancedCommunityVideo', function(el) {

    var config = el.data('config');
    var iframe = el.find('iframe');

    if(config.fullWidth) {
        var size = {};
        switch(config.ratio) {
            case "16x9":
                size = {
                    width: $(el).width(),
                    height: ($(el).width() / 16) * 9
                };
                break;
            case "4x3":
                size = {
                    width: $(el).width(),
                    height: ($(el).width() / 4) * 3
                };
                break;
            default:
                return false;
        }
        iframe.width(size.width).height(size.height);
    }

}, {defer: true});
<commit_msg>Bugfix continous playing video in iframe - store iframe variables in data and remove / create iframe dynamicly<commit_after>Kwf.onJElementReady('.kwcAdvancedCommunityVideo', function(el, config) {
    el.data('config', config);
}, {priority: -1});

Kwf.onJElementWidthChange('.kwcAdvancedCommunityVideo', function(el, config) {

    var config = el.data('config');
    var iframe = el.find('iframe');

    if(config.fullWidth) {
        var size = {};
        switch(config.ratio) {
            case "16x9":
                size = {
                    width: $(el).width(),
                    height: ($(el).width() / 16) * 9
                };
                break;
            case "4x3":
                size = {
                    width: $(el).width(),
                    height: ($(el).width() / 4) * 3
                };
                break;
            default:
                return false;
        }
        iframe.width(size.width).height(size.height);
    }

}, {defer: true});

Kwf.onJElementHide('.kwcAdvancedCommunityVideo', function(el) {
    var iframe = el.find('iframe');
    iframe.parent().data('source', iframe.attr('src'));
    iframe.parent().data('iframeWidth', iframe.attr('width'));
    iframe.parent().data('iframeHeight', iframe.attr('height'));
    iframe.remove();
});

Kwf.onJElementShow('.kwcAdvancedCommunityVideo', function(el) {
    var iframeParent = el.find('.communityVideoPlayer');
    if(iframeParent.data('source')) {
        iframeParent.html('<iframe src="'+iframeParent.data('source')+'" width="'+iframeParent.data('iframeWidth')+'" height="'+iframeParent.data('iframeHeight')+'" frameborder="0" allowfullscreen="true"></iframe>');
    }
});<|endoftext|>


<commit_before>Global.AuthClientService = {

   aasService:'AuthClientService',
   aasPublished: true,

   aasCommandAttributes: { ConfirmUserCommand: { CanExecuteOnStart: true } },

   SignUp: function (firstName, lastName, email) {

       $('.RegisterAlert').removeClass('alert-danger');
       $('.RegisterAlert').html('');

       var message = '';

       var re = /\b[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/;
       if (!email || !re.test(email)) {
           message = 'Invalid email';
       }

       if (!lastName) {
           message = 'Invalid last name';
       }

       if (!firstName) {
           message = 'Invalid first name';
       }

       if (!message) {
           var userValid = Aspectize.Host.ExecuteCommand('Server/InscriptionService.IsEmailAvailable', email);

           if (!userValid) {
               message = 'Email is not available';
           }
       }

       if (message) {
           $('.RegisterAlert').addClass('alert-danger');
           $('.RegisterAlert').html(message);
           $('.RegisterAlert').alert();
           return;
       }

       var cmd = Aspectize.Host.PrepareCommand();

       cmd.Attributes.aasAsynchronousCall = true;
       cmd.Attributes.aasShowWaiting = true;

       cmd.OnComplete = function (result) {

           if (result) {
               $('.HideFormOnSuccess').hide();
               $('.RegisterAlert').addClass('alert-success');
               $('.RegisterAlert').html('Register succeeded ! Check your emails, to confirm your registration.');
               $('.RegisterAlert').alert();
           } else {
               $('.RegisterAlert').addClass('alert-danger');
               $('.RegisterAlert').html('Register failed');
               $('.RegisterAlert').alert();
           }
       }

       cmd.Call('Server/InscriptionService.SignUp', firstName, lastName, email);

   },

   ResetRegister: function() {
       $('.RegisterAlert').removeClass('alert-danger');
       $('.RegisterAlert').html('');
       $('.HideFormOnSuccess').show();
       $('#Register-TxtFirstName').val('');
       $('#Register-TxtLastName').val('');
       $('#Register-TxtEmail').val('');
   },

   Login: function (email, pwd) {

       Aspectize.Host.ExecuteCommand('Browser/SecurityServices.Authenticate', email, pwd);

       var currentUser = Aspectize.ExecutingContext.GetCurrentUser();

       if (!currentUser.IsAuthenticated) {

           $('.LoginAlert').addClass('alert-danger');
           $('.LoginAlert').html('Invalid user name or password.');
           $('.LoginAlert').alert();
       } else {
           Aspectize.Host.ExecuteCommand('Browser/UIService.ShowView', 'MyBoards');
       }
   },

   ConfirmUserCommand: function (code) {

       if (Aspectize.Host.UrlArgs.StartingViewOrCommandName === 'AuthClientService.ConfirmUserCommand') {

           var cmd = Aspectize.Host.PrepareCommand();

           cmd.Attributes.aasDataName = 'UserConfirmation';
           cmd.Attributes.aasAsynchronousCall = true;

           cmd.OnComplete = function (result) {

               if (result) {
                   var uiService = Aspectize.Host.GetService('UIService');

                   uiService.SetContextValue('ConfirmationCode', code);

                   Aspectize.Host.ExecuteCommand('Browser/BootStrapClientService.ShowModal', 'ConfirmUser', false, 'static');
               }
               else {
                   var href = window.location.href;

                   window.location.href = href.split('?')[0];
               }
           }

            cmd.Call('Server/InscriptionService.GetInscription', code);
       }
   },

   ValidateInscription: function (login, pwd, pwdConfirm, rememberMe) {
       if (pwd && pwd == pwdConfirm) {
           $('.ConfirmUserAlert').removeClass('alert-danger');
           $('.ConfirmUserAlert').html('');

           var uiService = Aspectize.Host.GetService('UIService');

           var code = uiService.GetContextValue('ConfirmationCode');

           var pwdHash = Aspectize.Md5HashBase64(pwd);

           Aspectize.Host.ExecuteCommand('Server/InscriptionService.ValidateInscription', code, login, pwdHash);
           Aspectize.Host.ExecuteCommand('Browser/SecurityServices.Authenticate', login, pwd, rememberMe);
           Aspectize.Host.ExecuteCommand('Browser/BootStrapClientService.CloseModal', 'ConfirmUser');
           Aspectize.Host.ExecuteCommand('Browser/UIService.ShowView', 'MyBoards');
       }
       else {
           $('.ConfirmUserAlert').addClass('alert-danger');
           $('.ConfirmUserAlert').html("Password and confirmation don't match.");
           $('.ConfirmUserAlert').alert();
       }
   }

};

<commit_msg>Remove obsolete API<commit_after>Global.AuthClientService = {

   aasService:'AuthClientService',
   aasPublished: true,

   aasCommandAttributes: { ConfirmUserCommand: { CanExecuteOnStart: true } },

   SignUp: function (firstName, lastName, email) {

       $('.RegisterAlert').removeClass('alert-danger');
       $('.RegisterAlert').html('');

       var message = '';

       var re = /\b[a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}\b/;
       if (!email || !re.test(email)) {
           message = 'Invalid email';
       }

       if (!lastName) {
           message = 'Invalid last name';
       }

       if (!firstName) {
           message = 'Invalid first name';
       }

       if (!message) {
           var userValid = Aspectize.Host.ExecuteCommand('Server/InscriptionService.IsEmailAvailable', email);

           if (!userValid) {
               message = 'Email is not available';
           }
       }

       if (message) {
           $('.RegisterAlert').addClass('alert-danger');
           $('.RegisterAlert').html(message);
           $('.RegisterAlert').alert();
           return;
       }

       var cmd = Aspectize.Host.PrepareCommand();

       cmd.Attributes.aasAsynchronousCall = true;
       cmd.Attributes.aasShowWaiting = true;

       cmd.OnComplete = function (result) {

           if (result) {
               $('.HideFormOnSuccess').hide();
               $('.RegisterAlert').addClass('alert-success');
               $('.RegisterAlert').html('Register succeeded ! Check your emails, to confirm your registration.');
               $('.RegisterAlert').alert();
           } else {
               $('.RegisterAlert').addClass('alert-danger');
               $('.RegisterAlert').html('Register failed');
               $('.RegisterAlert').alert();
           }
       }

       cmd.Call('Server/InscriptionService.SignUp', firstName, lastName, email);

   },

   ResetRegister: function() {
       $('.RegisterAlert').removeClass('alert-danger');
       $('.RegisterAlert').html('');
       $('.HideFormOnSuccess').show();
       $('#Register-TxtFirstName').val('');
       $('#Register-TxtLastName').val('');
       $('#Register-TxtEmail').val('');
   },

   Login: function (email, pwd) {

       Aspectize.Host.ExecuteCommand('Browser/SecurityServices.Authenticate', email, pwd);

       var currentUser = Aspectize.ExecutingContext.GetCurrentUser();

       if (!currentUser.IsAuthenticated) {

           $('.LoginAlert').addClass('alert-danger');
           $('.LoginAlert').html('Invalid user name or password.');
           $('.LoginAlert').alert();
       } else {
           Aspectize.Host.ExecuteCommand('Browser/UIService.ShowView', 'MyBoards');
       }
   },

   ConfirmUserCommand: function (code) {

       if (Aspectize.Host.UrlArgs.StartingViewOrCommandName === 'AuthClientService.ConfirmUserCommand') {

           var cmd = Aspectize.Host.PrepareCommand();

           cmd.Attributes.aasDataName = 'UserConfirmation';
           cmd.Attributes.aasAsynchronousCall = true;

           cmd.OnComplete = function (result) {

               if (result) {
                   var uiService = Aspectize.Host.GetService('UIService');

                   uiService.SetContextValue('ConfirmationCode', code);

                   Aspectize.Host.ExecuteCommand('Browser/BootStrapClientService.ShowModal', 'ConfirmUser', false, 'static');
               }
               else {
                   var href = window.location.href;

                   window.location.href = href.split('?')[0];
               }
           }

            cmd.Call('Server/InscriptionService.GetInscription', code);
       }
   },

   ValidateInscription: function (login, pwd, pwdConfirm, rememberMe) {
       if (pwd && pwd == pwdConfirm) {
           $('.ConfirmUserAlert').removeClass('alert-danger');
           $('.ConfirmUserAlert').html('');

           var uiService = Aspectize.Host.GetService('UIService');

           var code = uiService.GetContextValue('ConfirmationCode');

           var pwdHash = Aspectize.Host.ExecuteCommand(aas.Services.Browser.SecurityServices.ComputeHashForStorage(pwd));

           Aspectize.Host.ExecuteCommand('Server/InscriptionService.ValidateInscription', code, login, pwdHash);
           Aspectize.Host.ExecuteCommand('Browser/SecurityServices.Authenticate', login, pwd, rememberMe);
           Aspectize.Host.ExecuteCommand('Browser/BootStrapClientService.CloseModal', 'ConfirmUser');
           Aspectize.Host.ExecuteCommand('Browser/UIService.ShowView', 'MyBoards');
       }
       else {
           $('.ConfirmUserAlert').addClass('alert-danger');
           $('.ConfirmUserAlert').html("Password and confirmation don't match.");
           $('.ConfirmUserAlert').alert();
       }
   }

};

<|endoftext|>


<commit_before>import R from 'ramda';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { withRouter, browserHistory as history } from 'react-router';
import { getFormInitialValues, SubmissionError } from 'redux-form';
import PartnerProfileProjectImplementationManagement from './partnerProfileProjectImplementationManagement';
import PartnerProfileProjectImplementationFinancialControls from './partnerProfileProjectImplementationFinancialControls';
import PartnerProfileProjectImplementationInternalControls from './partnerProfileProjectImplementationInternalControls';
import PartnerProfileProjectImplementationBankingInfo from './partnerProfileProjectImplementationBankingInfo';
import PartnerProfileProjectImplementationAudit from './partnerProfileProjectImplementationAudit';
import PartnerProfileProjectImplementationReporting from './partnerProfileProjectImplementationReporting';
import PartnerProfileStepperContainer from '../partnerProfileStepperContainer';
import { patchPartnerProfile } from '../../../../reducers/partnerProfileDetailsUpdate';
import { flatten } from '../../../../helpers/jsonMapper';
import { changedValues } from '../../../../helpers/apiHelper';
import { loadPartnerDetails } from '../../../../reducers/partnerProfileDetails';
import { emptyMsg } from '../partnerProfileEdit';

const STEPS = readOnly => [
  {
    component: <PartnerProfileProjectImplementationManagement readOnly={readOnly} />,
    label: 'Programme Management',
    name: 'program_management',
  },
  {
    component: <PartnerProfileProjectImplementationFinancialControls readOnly={readOnly} />,
    label: 'Financial Controls',
    name: 'financial_controls',
  },
  {
    component: <PartnerProfileProjectImplementationInternalControls readOnly={readOnly} />,
    label: 'Internal Controls',
    name: 'internal_control',
  },
  {
    component: <PartnerProfileProjectImplementationBankingInfo readOnly={readOnly} />,
    label: 'Banking Information',
    name: 'banking_information',
  },
  {
    component: <PartnerProfileProjectImplementationAudit readOnly={readOnly} />,
    label: 'Audit & Assessments',
    name: 'audit',
  },
  {
    component: <PartnerProfileProjectImplementationReporting readOnly={readOnly} />,
    label: 'Reporting',
    name: 'report',
  },
];

class PartnerProfileProjectImplementation extends Component {
  constructor(props) {
    super(props);

    this.state = {
      actionOnSubmit: {},
    };

    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleNext = this.handleNext.bind(this);
    this.handleExit = this.handleExit.bind(this);
  }

  onSubmit() {
    const { partnerId, tabs, params: { type } } = this.props;

    if (this.state.actionOnSubmit === 'next') {
      const index = tabs.findIndex(itab => itab.path === type);
      history.push({
        pathname: `/profile/${partnerId}/edit/${tabs[index + 1].path}`,
      });
    } else if (this.state.actionOnSubmit === 'exit') {
      history.push(`/profile/${partnerId}/overview`);
    }
  }

  handleNext() {
    this.setState({ actionOnSubmit: 'next' });
  }

  handleExit() {
    this.setState({ actionOnSubmit: 'exit' });
  }

  handleSubmit(formValues) {
    const { initialValues, updateTab, partnerId, loadPartnerProfileDetails } = this.props;

    const projectImplementation = flatten(formValues.project_impl);
    const initprojectImplementation = flatten(initialValues.project_impl);
    let changedItems = changedValues(initprojectImplementation, projectImplementation);

    if (changedItems.audit_reports) {
      changedItems = R.map((item) => {
        if (!R.is(Number, item.most_recent_audit_report)) {
          return R.dissoc('most_recent_audit_report', item);
        }
        return item;
      }, changedValues(initprojectImplementation, projectImplementation).audit_reports);
    }

    let patchValues = R.assoc('audit_reports', changedItems, changedValues(initprojectImplementation, projectImplementation));

    if (R.isEmpty(patchValues.audit_reports)) {
      patchValues = R.dissoc('audit_reports', patchValues);
    }

    if (!R.isEmpty(patchValues)) {
      return updateTab(partnerId, 'project-implementation', patchValues)
        .then(() => loadPartnerProfileDetails(partnerId).then(() => this.onSubmit()))
        .catch((error) => {
          const errorMsg = error.response.data.non_field_errors || 'Error while saving sections. Please try again.';

          throw new SubmissionError({
            ...error.response.data,
            _error: errorMsg,
          });
        });
    }

    throw new SubmissionError({
      _error: emptyMsg,
    });
  }

  render() {
    const { readOnly } = this.props;

    return (
      <PartnerProfileStepperContainer
        name="project_impl"
        readOnly={readOnly}
        handleNext={this.handleNext}
        handleExit={this.handleExit}
        onSubmit={this.handleSubmit}
        steps={STEPS(readOnly)}
      />
    );
  }
}

PartnerProfileProjectImplementation.propTypes = {
  readOnly: PropTypes.bool,
  partnerId: PropTypes.string,
  updateTab: PropTypes.func,
  initialValues: PropTypes.object,
  loadPartnerProfileDetails: PropTypes.func,
  params: PropTypes.object,
  tabs: PropTypes.array,
};

const mapState = (state, ownProps) => ({
  partnerId: ownProps.params.id,
  tabs: state.partnerProfileDetailsNav.tabs,
  initialValues: getFormInitialValues('partnerProfile')(state),
});

const mapDispatch = dispatch => ({
  loadPartnerProfileDetails: partnerId => dispatch(loadPartnerDetails(partnerId)),
  updateTab: (partnerId, tabName, body) => dispatch(patchPartnerProfile(partnerId, tabName, body)),
  dispatch,
});

const connectedPartnerProfileProjectImplementation =
  connect(mapState, mapDispatch)(PartnerProfileProjectImplementation);

export default withRouter(connectedPartnerProfileProjectImplementation);

<commit_msg>refactor audit reports logic<commit_after>import R from 'ramda';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { withRouter, browserHistory as history } from 'react-router';
import { getFormInitialValues, SubmissionError } from 'redux-form';
import PartnerProfileProjectImplementationManagement from './partnerProfileProjectImplementationManagement';
import PartnerProfileProjectImplementationFinancialControls from './partnerProfileProjectImplementationFinancialControls';
import PartnerProfileProjectImplementationInternalControls from './partnerProfileProjectImplementationInternalControls';
import PartnerProfileProjectImplementationBankingInfo from './partnerProfileProjectImplementationBankingInfo';
import PartnerProfileProjectImplementationAudit from './partnerProfileProjectImplementationAudit';
import PartnerProfileProjectImplementationReporting from './partnerProfileProjectImplementationReporting';
import PartnerProfileStepperContainer from '../partnerProfileStepperContainer';
import { patchPartnerProfile } from '../../../../reducers/partnerProfileDetailsUpdate';
import { flatten } from '../../../../helpers/jsonMapper';
import { changedValues } from '../../../../helpers/apiHelper';
import { loadPartnerDetails } from '../../../../reducers/partnerProfileDetails';
import { emptyMsg } from '../partnerProfileEdit';

const STEPS = readOnly => [
  {
    component: <PartnerProfileProjectImplementationManagement readOnly={readOnly} />,
    label: 'Programme Management',
    name: 'program_management',
  },
  {
    component: <PartnerProfileProjectImplementationFinancialControls readOnly={readOnly} />,
    label: 'Financial Controls',
    name: 'financial_controls',
  },
  {
    component: <PartnerProfileProjectImplementationInternalControls readOnly={readOnly} />,
    label: 'Internal Controls',
    name: 'internal_control',
  },
  {
    component: <PartnerProfileProjectImplementationBankingInfo readOnly={readOnly} />,
    label: 'Banking Information',
    name: 'banking_information',
  },
  {
    component: <PartnerProfileProjectImplementationAudit readOnly={readOnly} />,
    label: 'Audit & Assessments',
    name: 'audit',
  },
  {
    component: <PartnerProfileProjectImplementationReporting readOnly={readOnly} />,
    label: 'Reporting',
    name: 'report',
  },
];

class PartnerProfileProjectImplementation extends Component {
  constructor(props) {
    super(props);

    this.state = {
      actionOnSubmit: {},
    };

    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleNext = this.handleNext.bind(this);
    this.handleExit = this.handleExit.bind(this);
  }

  onSubmit() {
    const { partnerId, tabs, params: { type } } = this.props;

    if (this.state.actionOnSubmit === 'next') {
      const index = tabs.findIndex(itab => itab.path === type);
      history.push({
        pathname: `/profile/${partnerId}/edit/${tabs[index + 1].path}`,
      });
    } else if (this.state.actionOnSubmit === 'exit') {
      history.push(`/profile/${partnerId}/overview`);
    }
  }

  handleNext() {
    this.setState({ actionOnSubmit: 'next' });
  }

  handleExit() {
    this.setState({ actionOnSubmit: 'exit' });
  }

  handleSubmit(formValues) {
    const { initialValues, updateTab, partnerId, loadPartnerProfileDetails } = this.props;

    const projectImplementation = flatten(formValues.project_impl);
    const initprojectImplementation = flatten(initialValues.project_impl);
    let patchValues = changedValues(initprojectImplementation, projectImplementation);
debugger;
    if (patchValues.audit_reports) {
      const audits = R.map((item) => {
        if (!R.is(Number, item.most_recent_audit_report)) {
          return R.dissoc('most_recent_audit_report', item);
        }
        return item;
      }, patchValues.audit_reports);

      patchValues = R.assoc('audit_reports', audits, patchValues);
    }

    if (R.isEmpty(patchValues.audit_reports)) {
      patchValues = R.dissoc('audit_reports', patchValues);
    }

    if (!R.isEmpty(patchValues)) {
      return updateTab(partnerId, 'project-implementation', patchValues)
        .then(() => loadPartnerProfileDetails(partnerId).then(() => this.onSubmit()))
        .catch((error) => {
          const errorMsg = error.response.data.non_field_errors || 'Error while saving sections. Please try again.';

          throw new SubmissionError({
            ...error.response.data,
            _error: errorMsg,
          });
        });
    }

    throw new SubmissionError({
      _error: emptyMsg,
    });
  }

  render() {
    const { readOnly } = this.props;

    return (
      <PartnerProfileStepperContainer
        name="project_impl"
        readOnly={readOnly}
        handleNext={this.handleNext}
        handleExit={this.handleExit}
        onSubmit={this.handleSubmit}
        steps={STEPS(readOnly)}
      />
    );
  }
}

PartnerProfileProjectImplementation.propTypes = {
  readOnly: PropTypes.bool,
  partnerId: PropTypes.string,
  updateTab: PropTypes.func,
  initialValues: PropTypes.object,
  loadPartnerProfileDetails: PropTypes.func,
  params: PropTypes.object,
  tabs: PropTypes.array,
};

const mapState = (state, ownProps) => ({
  partnerId: ownProps.params.id,
  tabs: state.partnerProfileDetailsNav.tabs,
  initialValues: getFormInitialValues('partnerProfile')(state),
});

const mapDispatch = dispatch => ({
  loadPartnerProfileDetails: partnerId => dispatch(loadPartnerDetails(partnerId)),
  updateTab: (partnerId, tabName, body) => dispatch(patchPartnerProfile(partnerId, tabName, body)),
  dispatch,
});

const connectedPartnerProfileProjectImplementation =
  connect(mapState, mapDispatch)(PartnerProfileProjectImplementation);

export default withRouter(connectedPartnerProfileProjectImplementation);

<|endoftext|>


<commit_before><commit_msg>Pass deck selector to ContentQuestionAnswersList component<commit_after><|endoftext|>


<commit_before><commit_msg>Separating ternary to function<commit_after><|endoftext|>


<commit_before><commit_msg>F #3951: Fix provider step (#851)<commit_after><|endoftext|>


<commit_before>'use strict';

const fs = require('fs');

function reloadCustomAvatars() {
	let path = require('path');
	let newCustomAvatars = {};
	fs.readdirSync('./config/avatars').forEach(function (file) {
		let ext = path.extname(file);
		if (ext !== '.png' && ext !== '.gif') return;

		let user = toId(path.basename(file, ext));
		newCustomAvatars[user] = file;
		delete Config.customavatars[user];
	});

	// Make sure the manually entered avatars exist
	for (let a in Config.customavatars) {
		if (typeof Config.customavatars[a] === 'number') {
			newCustomAvatars[a] = Config.customavatars[a];
		} else {
			fs.exists('./config/avatars/' + Config.customavatars[a], function (user, file, isExists) {
				if (isExists) Config.customavatars[user] = file;
			}.bind(null, a, Config.customavatars[a]));
		}
	}

	Config.customavatars = newCustomAvatars;
}
reloadCustomAvatars();

if (Config.watchConfig) {
	fs.watchFile('./config/config.js', function (curr, prev) {
		if (curr.mtime <= prev.mtime) return;
		reloadCustomAvatars();
	});
}

const script = function () {
/*
	FILENAME=`mktemp`
	function cleanup {
		rm -f $FILENAME
	}
	trap cleanup EXIT

	set -xe

	timeout 10 wget "$1" -nv -O $FILENAME

	FRAMES=`identify $FILENAME | wc -l`
	if [ $FRAMES -gt 1 ]; then
		EXT=".gif"
	else
		EXT=".png"
	fi

	timeout 10 convert $FILENAME -layers TrimBounds -coalesce -adaptive-resize 80x80\> -background transparent -gravity center -extent 80x80 "$2$EXT"
*/
}.toString().match(/[^]*\/\*([^]*)\*\//)[1];

let pendingAdds = {};

exports.commands = {
	sca: 'customavatar',
	customavatars: 'customavatar',
	customavatar: function (target, room, user) {
		let parts = target.split(',');
		let cmd = parts[0].trim().toLowerCase();

		if (cmd in {'':1, show:1, view:1, display:1}) {
			let message = "";
			for (let a in Config.customavatars) {
				message += "" + Tools.escapeHTML(a) + ": " + Tools.escapeHTML(Config.customavatars[a]) + "
";
			}
			return this.sendReplyBox(message);
		}

		if (!this.can('pban') && !Gold.hasBadge(toId(user.name), 'vip')) return false;

		switch (cmd) {
		case 'set':
			let userid = toId(parts[1]);
			let targetUser = Users.getExact(userid);
			let avatar = parts.slice(2).join(',').trim();
			if (!this.can('pban') && Gold.hasBadge(toId(user.name), 'vip') && userid !== user.userid) return false;

			if (!userid) return this.sendReply("You didn't specify a user.");
			if (Config.customavatars[userid]) return this.errorReply(userid + " already has a custom avatar.");

			let hash2 = require('crypto').createHash('sha512').update(userid + '\u0000' + avatar).digest('hex').slice(0, 8);
			pendingAdds[hash2] = {userid: userid, avatar: avatar};
			parts[1] = hash2;

			if (!targetUser) {
				this.errorReply("Warning: " + userid + " is not online.");
				this.errorReply("If you want to continue, use: /customavatar forceset, " + hash2);
				return;
			}

	/* falls through */
		case 'forceset':
			if (user.avatarCooldown && !this.can('pban')) {
				let milliseconds = (Date.now() - user.avatarCooldown);
				let seconds = ((milliseconds / 1000) % 60);
				let remainingTime = Math.round(seconds - (5 * 60));
				if (((Date.now() - user.avatarCooldown) <= 5 * 60 * 1000)) return this.sendReply("You must wait " + (remainingTime - remainingTime * 2) + " seconds before setting another avatar.");
			}
			user.avatarCooldown = Date.now();

			let hash = parts[1].trim();
			if (!pendingAdds[hash]) return this.sendReply("Invalid hash.");

			userid = pendingAdds[hash].userid;
			avatar = pendingAdds[hash].avatar;
			delete pendingAdds[hash];

			require('child_process').execFile('bash', ['-c', script, '-', avatar, './config/avatars/' + userid], function (e, out, err) {
				if (e) {
					this.sendReply(userid + "'s custom avatar failed to be set. Script output:");
					(out + err).split('\n').forEach(this.sendReply.bind(this));
					return;
				}

				reloadCustomAvatars();

				let targetUser = Users.getExact(userid);
				if (targetUser) targetUser.avatar = Config.customavatars[userid];

				this.sendReply(userid + "'s custom avatar has been set.");

				Rooms.get('staff').add('|raw|' + Gold.nameColor(userid, true) + ' has received a custom avatar from ' + Gold.nameColor(user.name, true)).update();
				Users.get(userid).popup('|modal||html|ATTENTION!
 You have received a custom avatar from ' + Tools.escapeHTML(user.name) + ': ');
				room.update();
			}.bind(this));
			break;

		case 'remove':
		case 'delete':
			let targetUserid = toId(parts[1]);
			if (!this.can('pban') && Gold.hasBadge(toId(user.name), 'vip') && targetUserid !== user.userid) return false;
			if (!Config.customavatars[targetUserid]) return this.errorReply(targetUserid + " does not have a custom avatar.");

			if (Config.customavatars[targetUserid].toString().split('.').slice(0, -1).join('.') !== targetUserid) {
				return this.errorReply(targetUserid + "'s custom avatar (" + Config.customavatars[targetUserid] + ") cannot be removed with this script.");
			}

			if (Users.getExact(targetUserid)) Users.getExact(targetUserid).avatar = 1;

			fs.unlink('./config/avatars/' + Config.customavatars[targetUserid], function (e) {
				if (e) return this.errorReply(targetUserid + "'s custom avatar (" + Config.customavatars[targetUserid] + ") could not be removed: " + e.toString());

				delete Config.customavatars[targetUserid];
				this.sendReply(targetUserid + "'s custom avatar removed successfully");
			}.bind(this));
			break;

		case 'reload':
			if (!this.can('pban')) return false;
			reloadCustomAvatars();
			for (let leUsers of Users.users) {
				leUsers = leUsers[1];
				if (Config.customavatars[leUsers]) Users(leUsers).avatar = Config.customavatars[leUsers];
			}
			this.privateModCommand("(" + user.name + " has reloaded all custom avatars.)");
			break;

		default:
			return this.parse("/help customavatar");
		}
	},
	customavatarhelp: ["/customavatar [set/delete], [user] - Sets to deletes a users custom avatar.  Requires VIP, &, ~",
		"/customavatar reload - Reloads all current set custom avatars on the server. Requires ~"],
};
<commit_msg>Customavatar: fix permissions And fix the help command, too<commit_after>'use strict';

const fs = require('fs');

function reloadCustomAvatars() {
	let path = require('path');
	let newCustomAvatars = {};
	fs.readdirSync('./config/avatars').forEach(function (file) {
		let ext = path.extname(file);
		if (ext !== '.png' && ext !== '.gif') return;

		let user = toId(path.basename(file, ext));
		newCustomAvatars[user] = file;
		delete Config.customavatars[user];
	});

	// Make sure the manually entered avatars exist
	for (let a in Config.customavatars) {
		if (typeof Config.customavatars[a] === 'number') {
			newCustomAvatars[a] = Config.customavatars[a];
		} else {
			fs.exists('./config/avatars/' + Config.customavatars[a], function (user, file, isExists) {
				if (isExists) Config.customavatars[user] = file;
			}.bind(null, a, Config.customavatars[a]));
		}
	}

	Config.customavatars = newCustomAvatars;
}
reloadCustomAvatars();

if (Config.watchConfig) {
	fs.watchFile('./config/config.js', function (curr, prev) {
		if (curr.mtime <= prev.mtime) return;
		reloadCustomAvatars();
	});
}

const script = function () {
/*
	FILENAME=`mktemp`
	function cleanup {
		rm -f $FILENAME
	}
	trap cleanup EXIT

	set -xe

	timeout 10 wget "$1" -nv -O $FILENAME

	FRAMES=`identify $FILENAME | wc -l`
	if [ $FRAMES -gt 1 ]; then
		EXT=".gif"
	else
		EXT=".png"
	fi

	timeout 10 convert $FILENAME -layers TrimBounds -coalesce -adaptive-resize 80x80\> -background transparent -gravity center -extent 80x80 "$2$EXT"
*/
}.toString().match(/[^]*\/\*([^]*)\*\//)[1];

let pendingAdds = {};

exports.commands = {
	sca: 'customavatar',
	customavatars: 'customavatar',
	customavatar: function (target, room, user) {
		let globalUpper = user.can('pban');
		let vipUser = function (targetUser) {
			if (!targetUser) targetUser = user.userid;
			if (Gold.hasBadge(user.userid, 'vip') && targetUser === user.userid) {
				return true;
			}
			return false;
		}

		let parts = target.split(',');
		let cmd = parts[0].trim().toLowerCase();

		if (cmd in {'':1, show:1, view:1, display:1}) {
			let message = "";
			for (let a in Config.customavatars) {
				message += "" + Tools.escapeHTML(a) + ": " + Tools.escapeHTML(Config.customavatars[a]) + "
";
			}
			return this.sendReplyBox(message);
		}

		switch (cmd) {
		case 'set':
			let userid = toId(parts[1]);
			let targetUser = Users.getExact(userid);

			let avatar = parts.slice(2).join(',').trim();
			if (!globalUpper && !vipUser(userid)) return false;

			if (!userid) return this.sendReply("You didn't specify a user.");
			if (Config.customavatars[userid]) return this.errorReply(userid + " already has a custom avatar.");

			let hash2 = require('crypto').createHash('sha512').update(userid + '\u0000' + avatar).digest('hex').slice(0, 8);
			pendingAdds[hash2] = {userid: userid, avatar: avatar};
			parts[1] = hash2;

			if (!targetUser) {
				this.errorReply("Warning: " + userid + " is not online.");
				this.errorReply("If you want to continue, use: /customavatar forceset, " + hash2);
				return;
			}

	/* falls through */
		case 'forceset':
			if (user.avatarCooldown && !globalUpper) {
				let milliseconds = (Date.now() - user.avatarCooldown);
				let seconds = ((milliseconds / 1000) % 60);
				let remainingTime = Math.round(seconds - (5 * 60));
				if (((Date.now() - user.avatarCooldown) <= 5 * 60 * 1000)) return this.sendReply("You must wait " + (remainingTime - remainingTime * 2) + " seconds before setting another avatar.");
			}
			user.avatarCooldown = Date.now();

			let hash = parts[1].trim();
			if (!pendingAdds[hash]) return this.sendReply("Invalid hash.");

			userid = pendingAdds[hash].userid;
			avatar = pendingAdds[hash].avatar;
			delete pendingAdds[hash];

			require('child_process').execFile('bash', ['-c', script, '-', avatar, './config/avatars/' + userid], function (e, out, err) {
				if (e) {
					this.sendReply(userid + "'s custom avatar failed to be set. Script output:");
					(out + err).split('\n').forEach(this.sendReply.bind(this));
					return;
				}

				reloadCustomAvatars();

				let targetUser = Users.getExact(userid);
				if (targetUser) targetUser.avatar = Config.customavatars[userid];

				this.sendReply(userid + "'s custom avatar has been set.");

				Rooms.get('staff').add('|raw|' + Gold.nameColor(userid, true) + ' has received a custom avatar from ' + Gold.nameColor(user.name, true)).update();
				Users.get(userid).popup('|modal||html|ATTENTION!
 You have received a custom avatar from ' + Tools.escapeHTML(user.name) + ': ');
				room.update();
			}.bind(this));
			break;

		case 'remove':
		case 'delete':
			let targetUserid = toId(parts[1]);
			if (!globalUpper && !vipUser(targetUserid)) return false;
			if (!Config.customavatars[targetUserid]) return this.errorReply(targetUserid + " does not have a custom avatar.");

			if (Config.customavatars[targetUserid].toString().split('.').slice(0, -1).join('.') !== targetUserid) {
				return this.errorReply(targetUserid + "'s custom avatar (" + Config.customavatars[targetUserid] + ") cannot be removed with this script.");
			}

			if (Users.getExact(targetUserid)) Users.getExact(targetUserid).avatar = 1;

			fs.unlink('./config/avatars/' + Config.customavatars[targetUserid], function (e) {
				if (e) return this.errorReply(targetUserid + "'s custom avatar (" + Config.customavatars[targetUserid] + ") could not be removed: " + e.toString());

				delete Config.customavatars[targetUserid];
				this.sendReply(targetUserid + "'s custom avatar removed successfully");
			}.bind(this));
			break;

		case 'reload':
			if (!globalUpper) return false;
			reloadCustomAvatars();
			for (let leUsers of Users.users) {
				leUsers = leUsers[1];
				if (Config.customavatars[leUsers]) Users(leUsers).avatar = Config.customavatars[leUsers];
			}
			this.privateModCommand("(" + user.name + " has reloaded all custom avatars.)");
			break;

		default:
			return this.parse("/help customavatar");
		}
	},
	customavatarhelp: ["/customavatar [set/delete], [user], [link] - Sets or deletes a users custom avatar.  Requires VIP, &, ~",
		"/customavatar reload - Reloads all current set custom avatars on the server. Requires ~"],
};
<|endoftext|>


<commit_before>dojo.provide("dijit.form.TextBox");

dojo.require("dijit.form._FormWidget");

dojo.declare(
	"dijit.form.TextBox",
	dijit.form._FormWidget,
	{
		// summary:
		//		A generic textbox field.
		//		Serves as a base class to derive more specialized functionality in subclasses.

		//	trim: Boolean
		//		Removes leading and trailing whitespace if true.  Default is false.
		trim: false,

		//	uppercase: Boolean
		//		Converts all characters to uppercase if true.  Default is false.
		uppercase: false,

		//	lowercase: Boolean
		//		Converts all characters to lowercase if true.  Default is false.
		lowercase: false,

		//	propercase: Boolean
		//		Converts the first character of each word to uppercase if true.
		propercase: false,

		// maxLength: String
		//		HTML INPUT tag maxLength declaration.
		maxLength: "",

		templatePath: dojo.moduleUrl("dijit.form", "templates/TextBox.html"),
		baseClass: "dijitTextBox",

		attributeMap: dojo.mixin(dojo.clone(dijit.form._FormWidget.prototype.attributeMap),
			{maxLength:"focusNode"}),

		getDisplayedValue: function(){
			return this.filter(this.textbox.value);
		},

		getValue: function(){
			return this.parse(this.getDisplayedValue(), this.constraints);
		},

		setValue: function(value, /*Boolean, optional*/ priorityChange, /*String, optional*/ formattedValue){
			var filteredValue = this.filter(value);
			if((typeof filteredValue == typeof value) && (formattedValue == null || formattedValue == undefined)){
				formattedValue = this.format(filteredValue, this.constraints);
			}
			if(formattedValue != null && formattedValue != undefined){
				this.textbox.value = formattedValue;
			}
			dijit.form.TextBox.superclass.setValue.call(this, filteredValue, priorityChange);
		},

		setDisplayedValue: function(/*String*/value){
			this.textbox.value = value;
			this.setValue(this.getValue(), true);
		},

		forWaiValuenow: function(){
			return this.getDisplayedValue();
		},

		format: function(/* String */ value, /* Object */ constraints){
			// summary: Replacable function to convert a value to a properly formatted string
			return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
		},

		parse: function(/* String */ value, /* Object */ constraints){
			// summary: Replacable function to convert a formatted string to a value
			return value;
		},

		postCreate: function(){
			// setting the value here is needed since value="" in the template causes "undefined"
			// and setting in the DOM (instead of the JS object) helps with form reset actions
			this.textbox.setAttribute("value", this.getDisplayedValue());
			this.inherited('postCreate', arguments);

			if(this.srcNodeRef){
				dojo.style(this.textbox, "cssText", this.style);
				this.textbox.className += " " + this["class"];
			}
			this._layoutHack();
		},

		_layoutHack: function(){
			// summary: work around table sizing bugs on FF2 by forcing redraw
			if(dojo.isFF == 2 && this.domNode.tagName=="TABLE"){
				var node=this.domNode, _this = this;
				setTimeout(function(){
					var oldWidth = node.style.width;
					node.style.width = "0";
					setTimeout(function(){
						node.style.width = oldWidth;
					}, 0);
				 }, 0);
			}
		},

		filter: function(val){
			// summary: Apply various filters to textbox value
			if(val == undefined || val == null){ return ""; }
			else if(typeof val != "string"){ return val; }
			if(this.trim){
				val = dojo.trim(val);
			}
			if(this.uppercase){
				val = val.toUpperCase();
			}
			if(this.lowercase){
				val = val.toLowerCase();
			}
			if(this.propercase){
				val = val.replace(/[^\s]+/g, function(word){
					return word.substring(0,1).toUpperCase() + word.substring(1);
				});
			}
			return val;
		},

		// event handlers, you can over-ride these in your own subclasses
		_onBlur: function(){
			this.setValue(this.getValue(), (this.isValid ? this.isValid() : true));
		},

		onkeyup: function(){
			// TODO: it would be nice to massage the value (ie: automatic uppercase, etc) as the user types
			// but this messes up the cursor position if you are typing into the middle of a word, and
			// also trimming doesn't work correctly (it prevents spaces between words too!)
			// this.setValue(this.getValue());
		}
	}
);
<commit_msg>References #5042.  Changed width in layoutHack to opacity to prevent flickering.<commit_after>dojo.provide("dijit.form.TextBox");

dojo.require("dijit.form._FormWidget");

dojo.declare(
	"dijit.form.TextBox",
	dijit.form._FormWidget,
	{
		// summary:
		//		A generic textbox field.
		//		Serves as a base class to derive more specialized functionality in subclasses.

		//	trim: Boolean
		//		Removes leading and trailing whitespace if true.  Default is false.
		trim: false,

		//	uppercase: Boolean
		//		Converts all characters to uppercase if true.  Default is false.
		uppercase: false,

		//	lowercase: Boolean
		//		Converts all characters to lowercase if true.  Default is false.
		lowercase: false,

		//	propercase: Boolean
		//		Converts the first character of each word to uppercase if true.
		propercase: false,

		// maxLength: String
		//		HTML INPUT tag maxLength declaration.
		maxLength: "",

		templatePath: dojo.moduleUrl("dijit.form", "templates/TextBox.html"),
		baseClass: "dijitTextBox",

		attributeMap: dojo.mixin(dojo.clone(dijit.form._FormWidget.prototype.attributeMap),
			{maxLength:"focusNode"}),

		getDisplayedValue: function(){
			return this.filter(this.textbox.value);
		},

		getValue: function(){
			return this.parse(this.getDisplayedValue(), this.constraints);
		},

		setValue: function(value, /*Boolean, optional*/ priorityChange, /*String, optional*/ formattedValue){
			var filteredValue = this.filter(value);
			if((typeof filteredValue == typeof value) && (formattedValue == null || formattedValue == undefined)){
				formattedValue = this.format(filteredValue, this.constraints);
			}
			if(formattedValue != null && formattedValue != undefined){
				this.textbox.value = formattedValue;
			}
			dijit.form.TextBox.superclass.setValue.call(this, filteredValue, priorityChange);
		},

		setDisplayedValue: function(/*String*/value){
			this.textbox.value = value;
			this.setValue(this.getValue(), true);
		},

		forWaiValuenow: function(){
			return this.getDisplayedValue();
		},

		format: function(/* String */ value, /* Object */ constraints){
			// summary: Replacable function to convert a value to a properly formatted string
			return ((value == null || value == undefined) ? "" : (value.toString ? value.toString() : value));
		},

		parse: function(/* String */ value, /* Object */ constraints){
			// summary: Replacable function to convert a formatted string to a value
			return value;
		},

		postCreate: function(){
			// setting the value here is needed since value="" in the template causes "undefined"
			// and setting in the DOM (instead of the JS object) helps with form reset actions
			this.textbox.setAttribute("value", this.getDisplayedValue());
			this.inherited('postCreate', arguments);

			if(this.srcNodeRef){
				dojo.style(this.textbox, "cssText", this.style);
				this.textbox.className += " " + this["class"];
			}
			this._layoutHack();
		},

		_layoutHack: function(){
			// summary: work around table sizing bugs on FF2 by forcing redraw
			if(dojo.isFF == 2 && this.domNode.tagName=="TABLE"){
				var node=this.domNode, _this = this;
				setTimeout(function(){
					var old = node.style.opacity;
					node.style.opacity = "0.999";
					setTimeout(function(){
						node.style.opacity = old;
					}, 0);
				 }, 0);
			}
		},

		filter: function(val){
			// summary: Apply various filters to textbox value
			if(val == undefined || val == null){ return ""; }
			else if(typeof val != "string"){ return val; }
			if(this.trim){
				val = dojo.trim(val);
			}
			if(this.uppercase){
				val = val.toUpperCase();
			}
			if(this.lowercase){
				val = val.toLowerCase();
			}
			if(this.propercase){
				val = val.replace(/[^\s]+/g, function(word){
					return word.substring(0,1).toUpperCase() + word.substring(1);
				});
			}
			return val;
		},

		// event handlers, you can over-ride these in your own subclasses
		_onBlur: function(){
			this.setValue(this.getValue(), (this.isValid ? this.isValid() : true));
		},

		onkeyup: function(){
			// TODO: it would be nice to massage the value (ie: automatic uppercase, etc) as the user types
			// but this messes up the cursor position if you are typing into the middle of a word, and
			// also trimming doesn't work correctly (it prevents spaces between words too!)
			// this.setValue(this.getValue());
		}
	}
);
<|endoftext|>