<commit_before>/* See license.txt for terms of usage */

"use strict";

var self = require("sdk/self");

const { Cu, Ci, Cc } = require("chrome");
const { Trace, TraceError } = require("../core/trace.js");
const { logConsoleAPIMessage } = require("../console/console-message.js");

const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
const CATEGORY_WEBDEV = 3;

Cu.import("resource://gre/modules/Services.jsm");

const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});

// Devtools API
const utilsPath = "devtools/toolkit/webconsole/utils";
let WebConsoleUtils = devtools.require(utilsPath).Utils;

const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);

const PROMISE_URI = "resource://gre/modules/Promise.jsm";
let { Promise: promise } = Cu.import(PROMISE_URI, {});

/**
 * xxxHonza: we need better way how to customize console.* logs rendering.
 */
function ConsoleProxy(aWebConsole, aTarget)
{
  this.owner = aWebConsole;
  this.target = aTarget;

  this._onPageError = this._onPageError.bind(this);
  this._onLogMessage = this._onLogMessage.bind(this);
  this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
  this._onNetworkEvent = this._onNetworkEvent.bind(this);
  this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
  this._onFileActivity = this._onFileActivity.bind(this);
  this._onReflowActivity = this._onReflowActivity.bind(this);
  this._onTabNavigated = this._onTabNavigated.bind(this);
  this._onAttachConsole = this._onAttachConsole.bind(this);
  this._onCachedMessages = this._onCachedMessages.bind(this);
  this._connectionTimeout = this._connectionTimeout.bind(this);
  this._onLastPrivateContextExited = this._onLastPrivateContextExited.bind(this);
}

ConsoleProxy.prototype = {
  owner: null,
  target: null,
  client: null,
  webConsoleClient: null,
  connected: false,
  _connectTimer: null,
  _connectDefer: null,
  _disconnecter: null,
  _consoleActor: null,
  _hasNativeConsoleAPI: false,

  connect: function WCCP_connect()
  {
    if (this._connectDefer) {
      return this._connectDefer.promise;
    }

    this._connectDefer = promise.defer();

    let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT);
    this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this._connectTimer.initWithCallback(this._connectionTimeout,
                                        timeout, Ci.nsITimer.TYPE_ONE_SHOT);

    let connPromise = this._connectDefer.promise;
    connPromise.then(() => {
      this._connectTimer.cancel();
      this._connectTimer = null;
    }, () => {
      this._connectTimer = null;
    });

    let client = this.client = this.target.client;

    client.addListener("logMessage", this._onLogMessage);
    client.addListener("pageError", this._onPageError);
    client.addListener("consoleAPICall", this._onConsoleAPICall);
    client.addListener("networkEvent", this._onNetworkEvent);
    client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
    client.addListener("fileActivity", this._onFileActivity);
    client.addListener("reflowActivity", this._onReflowActivity);
    client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
    this.target.on("will-navigate", this._onTabNavigated);
    this.target.on("navigate", this._onTabNavigated);

    this._consoleActor = this.target.form.consoleActor;
    if (!this.target.chrome) {
      let tab = this.target.form;
      this.owner.onLocationChange(tab.url, tab.title);
    }
    this._attachConsole();

    return connPromise;
  },

  _connectionTimeout: function WCCP__connectionTimeout()
  {
    let error = {
      error: "timeout",
      message: l10n.getStr("connectionTimeout"),
    };

    this._connectDefer.reject(error);
  },

  _attachConsole: function WCCP__attachConsole()
  {
    let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
                     "FileActivity"];
    this.client.attachConsole(this._consoleActor, listeners,
                              this._onAttachConsole);
  },

  _onAttachConsole: function WCCP__onAttachConsole(aResponse, aWebConsoleClient)
  {
    if (aResponse.error) {
      Cu.reportError("attachConsole failed: " + aResponse.error + " " +
                     aResponse.message);
      this._connectDefer.reject(aResponse);
      return;
    }

    this.webConsoleClient = aWebConsoleClient;

    this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;

    let msgs = ["PageError", "ConsoleAPI"];
    this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);

    this.owner._updateReflowActivityListener();
  },

  _onCachedMessages: function WCCP__onCachedMessages(aResponse)
  {
    Trace.sysout("console-proxy _onCachedMessages", arguments);

    // xxxHonza: cached messages slip through the original proxy FIX ME.
    return;

    if (aResponse.error) {
      Cu.reportError("Web Console getCachedMessages error: " + aResponse.error +
                     " " + aResponse.message);
      this._connectDefer.reject(aResponse);
      return;
    }

    if (!this._connectTimer) {
      // This happens if the promise is rejected (eg. a timeout), but the
      // connection attempt is successful, nonetheless.
      Cu.reportError("Web Console getCachedMessages error: invalid state.");
    }

    this.owner.displayCachedMessages(aResponse.messages);

    if (!this._hasNativeConsoleAPI) {
      this.owner.logWarningAboutReplacedAPI();
    }

    this.connected = true;
    this._connectDefer.resolve(this);
  },

  _onPageError: function WCCP__onPageError(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handlePageError(aPacket.pageError);
    }
  },

  _onLogMessage: function WCCP__onLogMessage(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleLogMessage(aPacket);
    }
  },

  _onConsoleAPICall: function WCCP__onConsoleAPICall(aType, aPacket)
  {
    // Custom logging
    if (aPacket.message.level == "log") {
      this.owner.outputMessage(CATEGORY_WEBDEV,
        logConsoleAPIMessage, [aPacket.message]);
    }

    /*if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleConsoleAPICall(aPacket.message);
    }*/
  },

  _onNetworkEvent: function WCCP__onNetworkEvent(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleNetworkEvent(aPacket.eventActor);
    }
  },

  _onNetworkEventUpdate: function WCCP__onNetworkEvenUpdatet(aType, aPacket)
  {
    if (this.owner) {
      this.owner.handleNetworkEventUpdate(aPacket.from, aPacket.updateType,
                                          aPacket);
    }
  },

  _onFileActivity: function WCCP__onFileActivity(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleFileActivity(aPacket.uri);
    }
  },

  _onReflowActivity: function WCCP__onReflowActivity(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleReflowActivity(aPacket);
    }
  },

  _onLastPrivateContextExited:
  function WCCP__onLastPrivateContextExited(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.jsterm.clearPrivateMessages();
    }
  },

  _onTabNavigated: function WCCP__onTabNavigated(aEvent, aPacket)
  {
    if (!this.owner) {
      return;
    }

    this.owner.handleTabNavigated(aEvent, aPacket);
  },

  releaseActor: function WCCP_releaseActor(aActor)
  {
    if (this.client) {
      this.client.release(aActor);
    }
  },

  disconnect: function WCCP_disconnect()
  {
    if (this._disconnecter) {
      return this._disconnecter.promise;
    }

    this._disconnecter = promise.defer();

    if (!this.client) {
      this._disconnecter.resolve(null);
      return this._disconnecter.promise;
    }

    this.client.removeListener("logMessage", this._onLogMessage);
    this.client.removeListener("pageError", this._onPageError);
    this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
    this.client.removeListener("networkEvent", this._onNetworkEvent);
    this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
    this.client.removeListener("fileActivity", this._onFileActivity);
    this.client.removeListener("reflowActivity", this._onReflowActivity);
    this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited);
    this.target.off("will-navigate", this._onTabNavigated);
    this.target.off("navigate", this._onTabNavigated);

    this.client = null;
    this.webConsoleClient = null;
    this.target = null;
    this.connected = false;
    this.owner = null;
    this._disconnecter.resolve(null);

    return this._disconnecter.promise;
  },
};

// Exports from this module
exports.ConsoleProxy = ConsoleProxy;
<commit_msg>Tracing updated<commit_after>/* See license.txt for terms of usage */

"use strict";

var self = require("sdk/self");

const { Cu, Ci, Cc } = require("chrome");
const { Trace, TraceError } = require("../core/trace.js");
const { logConsoleAPIMessage } = require("../console/console-message.js");

const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
const CATEGORY_WEBDEV = 3;

Cu.import("resource://gre/modules/Services.jsm");

const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});

// Devtools API
const utilsPath = "devtools/toolkit/webconsole/utils";
let WebConsoleUtils = devtools.require(utilsPath).Utils;

const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
let l10n = new WebConsoleUtils.l10n(STRINGS_URI);

const PROMISE_URI = "resource://gre/modules/Promise.jsm";
let { Promise: promise } = Cu.import(PROMISE_URI, {});

/**
 * xxxHonza: we need better way how to customize console.* logs rendering.
 */
function ConsoleProxy(aWebConsole, aTarget)
{
  this.owner = aWebConsole;
  this.target = aTarget;

  this._onPageError = this._onPageError.bind(this);
  this._onLogMessage = this._onLogMessage.bind(this);
  this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
  this._onNetworkEvent = this._onNetworkEvent.bind(this);
  this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
  this._onFileActivity = this._onFileActivity.bind(this);
  this._onReflowActivity = this._onReflowActivity.bind(this);
  this._onTabNavigated = this._onTabNavigated.bind(this);
  this._onAttachConsole = this._onAttachConsole.bind(this);
  this._onCachedMessages = this._onCachedMessages.bind(this);
  this._connectionTimeout = this._connectionTimeout.bind(this);
  this._onLastPrivateContextExited = this._onLastPrivateContextExited.bind(this);
}

ConsoleProxy.prototype = {
  owner: null,
  target: null,
  client: null,
  webConsoleClient: null,
  connected: false,
  _connectTimer: null,
  _connectDefer: null,
  _disconnecter: null,
  _consoleActor: null,
  _hasNativeConsoleAPI: false,

  connect: function WCCP_connect()
  {
    if (this._connectDefer) {
      return this._connectDefer.promise;
    }

    this._connectDefer = promise.defer();

    let timeout = Services.prefs.getIntPref(PREF_CONNECTION_TIMEOUT);
    this._connectTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this._connectTimer.initWithCallback(this._connectionTimeout,
                                        timeout, Ci.nsITimer.TYPE_ONE_SHOT);

    let connPromise = this._connectDefer.promise;
    connPromise.then(() => {
      this._connectTimer.cancel();
      this._connectTimer = null;
    }, () => {
      this._connectTimer = null;
    });

    let client = this.client = this.target.client;

    client.addListener("logMessage", this._onLogMessage);
    client.addListener("pageError", this._onPageError);
    client.addListener("consoleAPICall", this._onConsoleAPICall);
    client.addListener("networkEvent", this._onNetworkEvent);
    client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
    client.addListener("fileActivity", this._onFileActivity);
    client.addListener("reflowActivity", this._onReflowActivity);
    client.addListener("lastPrivateContextExited", this._onLastPrivateContextExited);
    this.target.on("will-navigate", this._onTabNavigated);
    this.target.on("navigate", this._onTabNavigated);

    this._consoleActor = this.target.form.consoleActor;
    if (!this.target.chrome) {
      let tab = this.target.form;
      this.owner.onLocationChange(tab.url, tab.title);
    }
    this._attachConsole();

    return connPromise;
  },

  _connectionTimeout: function WCCP__connectionTimeout()
  {
    let error = {
      error: "timeout",
      message: l10n.getStr("connectionTimeout"),
    };

    this._connectDefer.reject(error);
  },

  _attachConsole: function WCCP__attachConsole()
  {
    let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
                     "FileActivity"];
    this.client.attachConsole(this._consoleActor, listeners,
                              this._onAttachConsole);
  },

  _onAttachConsole: function WCCP__onAttachConsole(aResponse, aWebConsoleClient)
  {
    if (aResponse.error) {
      Cu.reportError("attachConsole failed: " + aResponse.error + " " +
                     aResponse.message);
      this._connectDefer.reject(aResponse);
      return;
    }

    this.webConsoleClient = aWebConsoleClient;

    this._hasNativeConsoleAPI = aResponse.nativeConsoleAPI;

    let msgs = ["PageError", "ConsoleAPI"];
    this.webConsoleClient.getCachedMessages(msgs, this._onCachedMessages);

    this.owner._updateReflowActivityListener();
  },

  _onCachedMessages: function WCCP__onCachedMessages(aResponse)
  {
    Trace.sysout("console-proxy _onCachedMessages", arguments);

    // xxxHonza: cached messages slip through the original proxy FIX ME.
    return;

    if (aResponse.error) {
      Cu.reportError("Web Console getCachedMessages error: " + aResponse.error +
                     " " + aResponse.message);
      this._connectDefer.reject(aResponse);
      return;
    }

    if (!this._connectTimer) {
      // This happens if the promise is rejected (eg. a timeout), but the
      // connection attempt is successful, nonetheless.
      Cu.reportError("Web Console getCachedMessages error: invalid state.");
    }

    this.owner.displayCachedMessages(aResponse.messages);

    if (!this._hasNativeConsoleAPI) {
      this.owner.logWarningAboutReplacedAPI();
    }

    this.connected = true;
    this._connectDefer.resolve(this);
  },

  _onPageError: function WCCP__onPageError(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handlePageError(aPacket.pageError);
    }
  },

  _onLogMessage: function WCCP__onLogMessage(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleLogMessage(aPacket);
    }
  },

  _onConsoleAPICall: function WCCP__onConsoleAPICall(aType, aPacket)
  {
    Trace.sysout("consoleProxy._onConsoleAPICall;", arguments);

    // Custom logging
    if (aPacket.message.level == "log") {
      this.owner.outputMessage(CATEGORY_WEBDEV,
        logConsoleAPIMessage, [aPacket.message]);
    }

    /*if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleConsoleAPICall(aPacket.message);
    }*/
  },

  _onNetworkEvent: function WCCP__onNetworkEvent(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleNetworkEvent(aPacket.eventActor);
    }
  },

  _onNetworkEventUpdate: function WCCP__onNetworkEvenUpdatet(aType, aPacket)
  {
    if (this.owner) {
      this.owner.handleNetworkEventUpdate(aPacket.from, aPacket.updateType,
                                          aPacket);
    }
  },

  _onFileActivity: function WCCP__onFileActivity(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleFileActivity(aPacket.uri);
    }
  },

  _onReflowActivity: function WCCP__onReflowActivity(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.handleReflowActivity(aPacket);
    }
  },

  _onLastPrivateContextExited:
  function WCCP__onLastPrivateContextExited(aType, aPacket)
  {
    if (this.owner && aPacket.from == this._consoleActor) {
      this.owner.jsterm.clearPrivateMessages();
    }
  },

  _onTabNavigated: function WCCP__onTabNavigated(aEvent, aPacket)
  {
    if (!this.owner) {
      return;
    }

    this.owner.handleTabNavigated(aEvent, aPacket);
  },

  releaseActor: function WCCP_releaseActor(aActor)
  {
    if (this.client) {
      this.client.release(aActor);
    }
  },

  disconnect: function WCCP_disconnect()
  {
    if (this._disconnecter) {
      return this._disconnecter.promise;
    }

    this._disconnecter = promise.defer();

    if (!this.client) {
      this._disconnecter.resolve(null);
      return this._disconnecter.promise;
    }

    this.client.removeListener("logMessage", this._onLogMessage);
    this.client.removeListener("pageError", this._onPageError);
    this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
    this.client.removeListener("networkEvent", this._onNetworkEvent);
    this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
    this.client.removeListener("fileActivity", this._onFileActivity);
    this.client.removeListener("reflowActivity", this._onReflowActivity);
    this.client.removeListener("lastPrivateContextExited", this._onLastPrivateContextExited);
    this.target.off("will-navigate", this._onTabNavigated);
    this.target.off("navigate", this._onTabNavigated);

    this.client = null;
    this.webConsoleClient = null;
    this.target = null;
    this.connected = false;
    this.owner = null;
    this._disconnecter.resolve(null);

    return this._disconnecter.promise;
  },
};

// Exports from this module
exports.ConsoleProxy = ConsoleProxy;
<|endoftext|>


<commit_before>const cm = require('sdk/context-menu');
const _ = require('sdk/l10n').get;
const launchVideo = require('./launch-video');
const sendMetricsData = require('./send-metrics-data');

const getYouTubeUrl = require('./get-youtube-url');
const getVimeoUrl = require('./get-vimeo-url');
const getVineUrl = require('./get-vine-url');
const getSoundcloudUrl = require('./get-soundcloud-url');

const contextMenuLabel = _('content_menu_msg');
const contextMenuContentScript = `
self.on('click', function (node, data) {
  self.postMessage(node.href || node['data-feed-url']);
});`;

const contentScriptVimeoCase = `self.on('click', function (node, data) {
  self.postMessage('https://vimeo.com/' + node.href);
});`

const contextMenuMediaContentScript = `
self.on('click', function (node, data) {
  console.log('contextMenuMediacontentscript', self, node, data);
  let host = '';
  try {
    host = new URL(node.href || node.src).host;
  }
  catch (err) {
    console.log('error parsing domain from ', url, err);
    return;
  }

  self.postMessage({
    src: node.href || node.src,
    url: window.location.href,
    domain: host // new URL(node.href || node.src).host
  });
});`;

const contextMenuSoundcloudContentScript = `
self.on('click', function (node) {
  self.postMessage({
    url: node.href,
    domain: 'soundcloud.com'
  });
});`;

const URLS = {
  'vimeo': ['vimeo.com/'],
  'youtube': ['youtube.com/', 'youtu.be/'],
  'vine': ['vine.co/']
}

const items = [];

// Given a video service name from the URLS object, return an href *= selector
// for the corresponding urls.
// Arguments:
//   videoService: a key from URLS or '*' for all domains from URLS
//   shouldEncode: (optional) encode domains if true
function getSelectors(videoService, shouldEncode) {
  let domains = [];
  if (videoService in URLS) {
    domains = URLS[videoService]
  } else if (videoService === '*') {
    domains = Object.keys(URLS).map(name => URLS[name])
                    .reduce((prev, curr) => prev.concat(curr))
  } else {
    console.error(`Error: ${videoService} missing or not supported`) // eslint-disable-line no-console
  }
  const selectors = domains.map(url => `[href*="${shouldEncode ? encodeURIComponent(url) : url}"]`)
                           .reduce((prev, curr) => `${prev}, ${curr}`)

  return selectors;
}

module.exports = {
  init: init,
  destroy: destroy
};

function destroy() {
  items.forEach(i => i.destroy());
}

function getItem(opts) {
  items.push(cm.Item(opts));
}

function init() {
  const onMessageVimeoHandler = (url) => {
    sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'vimeo.com'});
    launchVideo({url: url,
                 domain: 'vimeo.com',
                 getUrlFn: getVimeoUrl});
  };

  const onMessageYouTubeHandler = (url) => {
    sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'youtube.com'});
    launchVideo({url: url,
                 domain: 'youtube.com',
                 getUrlFn: getYouTubeUrl});
  };

  const onMessageMediaHandler = (opts) => {
    sendMetricsData({object: 'contextmenu', method: 'activate', domain: opts.domain});
    if (opts.domain === 'soundcloud.com') opts.getUrlFn = getSoundcloudUrl;
    launchVideo(opts);
  };

  function audioPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp3|.opus|.weba|.ogg|.wav|.flac)')
      .exec(context.srcURL || context.linkURL);
  }

  function videoPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp4|.ogg|.3gp|.ogv|.spx|.oga|.mkv|.webm)')
      .exec(context.srcURL || context.linkURL);
  }

  function soundcloudPredicateContext(context) {
    return new RegExp(/https:\/\/soundcloud.com\/[a-zA-Z]*(.+)\/[a-zA-Z]*(.+)/)
      .exec(context.linkURL);
  }

  /*
   * <audio> handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.PredicateContext(audioPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: onMessageMediaHandler
  });

  /*
   * <video> handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.PredicateContext(videoPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: onMessageMediaHandler
  });

  /*
   * Soundcloud handling
   */

  /*
   * Track links
   */
  getItem({
    label: contextMenuLabel,
    context: cm.PredicateContext(soundcloudPredicateContext),
    contentScript: contextMenuSoundcloudContentScript,
    onMessage: onMessageMediaHandler
  });

  /*
   * YouTube handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.SelectorContext(getSelectors('youtube')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Thumbnail
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-thumbnail > .yt-uix.sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Title Link
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-content .yt-lockup-title .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Related Title link
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.watch-sidebar-section .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  /*
   * Vimeo handling
   */

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('[class*="faux_player"]')
    ],
    contentScript: `self.on('click', function (node, data) {
      self.postMessage('https://vimeo.com/' + node.getAttribute('data-clip-id'));
    });`,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: cm.SelectorContext(getSelectors('vimeo')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-header a')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.iris_video-vital__overlay.iris_link-box')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-img-thumb')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  /*
   * Vine.co handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.SelectorContext(getSelectors('vine')),
    contentScript: contextMenuContentScript,
    onMessage: function(url) {
      sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'vine.co'});
      launchVideo({url: url,
                   domain: 'vine.co',
                   getUrlFn: getVineUrl});
    }
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['https://vine.co/*']),
      cm.SelectorContext('video')
    ],
    contentScript: `self.on('click', function (node, data) {
      self.postMessage(node.poster);
    });`,
    onMessage: function(url) {
      const mp4 = url.replace(/thumbs/, 'videos').split(/\.jpg/)[0];
      sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'vine.co'});
      launchVideo({url: url,
                   domain: 'vine.co',
                   src: mp4});
    }
  });

  /*
   * google.com search results handling
   */
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.google.com']),
      cm.SelectorContext(getSelectors('*', true)),
    ],
    contentScript: contextMenuContentScript,
    onMessage: function(url) {
      const regex = /url=(https?[^;]*)/.exec(url)[1];
      const decoded = decodeURIComponent(regex).split('&usg')[0];
      let getUrlFn, domain;
      if (decoded.indexOf('youtube.com' || 'youtu.be') > -1) {
        getUrlFn = getYouTubeUrl;
        domain = 'youtube.com';
      } else if (decoded.indexOf('vimeo.com')  > -1) {
        getUrlFn = getVimeoUrl;
        domain = 'vimeo.com';
      } else if (decoded.indexOf('vine.co') > -1) {
        getUrlFn = getVineUrl;
        domain = 'vine.co';
      }
      if (domain && getUrlFn) {
        sendMetricsData({object: 'contextmenu', method: 'activate', domain: domain});
        launchVideo({url: decoded,
                     domain: domain,
                     getUrlFn: getUrlFn});
      }
    }
  });
}
<commit_msg>Handle context menu on yt home page thumbnails (#531)<commit_after>const cm = require('sdk/context-menu');
const _ = require('sdk/l10n').get;
const launchVideo = require('./launch-video');
const sendMetricsData = require('./send-metrics-data');

const getYouTubeUrl = require('./get-youtube-url');
const getVimeoUrl = require('./get-vimeo-url');
const getVineUrl = require('./get-vine-url');
const getSoundcloudUrl = require('./get-soundcloud-url');

const contextMenuLabel = _('content_menu_msg');
const contextMenuContentScript = `
self.on('click', function (node, data) {
  self.postMessage(node.href || node['data-feed-url']);
});`;

const contentScriptVimeoCase = `self.on('click', function (node, data) {
  self.postMessage('https://vimeo.com/' + node.href);
});`

const contextMenuMediaContentScript = `
self.on('click', function (node, data) {
  console.log('contextMenuMediacontentscript', self, node, data);
  let host = '';
  try {
    host = new URL(node.href || node.src).host;
  }
  catch (err) {
    console.log('error parsing domain from ', url, err);
    return;
  }

  self.postMessage({
    src: node.href || node.src,
    url: window.location.href,
    domain: host // new URL(node.href || node.src).host
  });
});`;

const contextMenuSoundcloudContentScript = `
self.on('click', function (node) {
  self.postMessage({
    url: node.href,
    domain: 'soundcloud.com'
  });
});`;

const URLS = {
  'vimeo': ['vimeo.com/'],
  'youtube': ['youtube.com/', 'youtu.be/'],
  'vine': ['vine.co/']
}

const items = [];

// Given a video service name from the URLS object, return an href *= selector
// for the corresponding urls.
// Arguments:
//   videoService: a key from URLS or '*' for all domains from URLS
//   shouldEncode: (optional) encode domains if true
function getSelectors(videoService, shouldEncode) {
  let domains = [];
  if (videoService in URLS) {
    domains = URLS[videoService]
  } else if (videoService === '*') {
    domains = Object.keys(URLS).map(name => URLS[name])
                    .reduce((prev, curr) => prev.concat(curr))
  } else {
    console.error(`Error: ${videoService} missing or not supported`) // eslint-disable-line no-console
  }
  const selectors = domains.map(url => `[href*="${shouldEncode ? encodeURIComponent(url) : url}"]`)
                           .reduce((prev, curr) => `${prev}, ${curr}`)

  return selectors;
}

module.exports = {
  init: init,
  destroy: destroy
};

function destroy() {
  items.forEach(i => i.destroy());
}

function getItem(opts) {
  items.push(cm.Item(opts));
}

function init() {
  const onMessageVimeoHandler = (url) => {
    sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'vimeo.com'});
    launchVideo({url: url,
                 domain: 'vimeo.com',
                 getUrlFn: getVimeoUrl});
  };

  const onMessageYouTubeHandler = (url) => {
    sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'youtube.com'});
    launchVideo({url: url,
                 domain: 'youtube.com',
                 getUrlFn: getYouTubeUrl});
  };

  const onMessageMediaHandler = (opts) => {
    sendMetricsData({object: 'contextmenu', method: 'activate', domain: opts.domain});
    if (opts.domain === 'soundcloud.com') opts.getUrlFn = getSoundcloudUrl;
    launchVideo(opts);
  };

  function audioPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp3|.opus|.weba|.ogg|.wav|.flac)')
      .exec(context.srcURL || context.linkURL);
  }

  function videoPredicateContext(context) {
    return new RegExp('^(https?:)?//*.+(.mp4|.ogg|.3gp|.ogv|.spx|.oga|.mkv|.webm)')
      .exec(context.srcURL || context.linkURL);
  }

  function soundcloudPredicateContext(context) {
    return new RegExp(/https:\/\/soundcloud.com\/[a-zA-Z]*(.+)\/[a-zA-Z]*(.+)/)
      .exec(context.linkURL);
  }

  /*
   * <audio> handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.PredicateContext(audioPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: onMessageMediaHandler
  });

  /*
   * <video> handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.PredicateContext(videoPredicateContext),
    contentScript: contextMenuMediaContentScript,
    onMessage: onMessageMediaHandler
  });

  /*
   * Soundcloud handling
   */

  /*
   * Track links
   */
  getItem({
    label: contextMenuLabel,
    context: cm.PredicateContext(soundcloudPredicateContext),
    contentScript: contextMenuSoundcloudContentScript,
    onMessage: onMessageMediaHandler
  });

  /*
   * YouTube handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.SelectorContext(getSelectors('youtube')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Thumbnail
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-thumbnail > .yt-uix.sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Home page thumbnails
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-thumbnail > .yt-uix.sessionlink .video-thumb').parentNode
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Title Link
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.yt-lockup-content .yt-lockup-title .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  // Related Title link
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.youtube.com']),
      cm.SelectorContext('.watch-sidebar-section .yt-uix-sessionlink')
    ],
    contentScript: contextMenuContentScript,
    onMessage: onMessageYouTubeHandler
  });

  /*
   * Vimeo handling
   */

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('[class*="faux_player"]')
    ],
    contentScript: `self.on('click', function (node, data) {
      self.postMessage('https://vimeo.com/' + node.getAttribute('data-clip-id'));
    });`,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: cm.SelectorContext(getSelectors('vimeo')),
    contentScript: contextMenuContentScript,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-header a')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.iris_video-vital__overlay.iris_link-box')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.vimeo.com']),
      cm.SelectorContext('.contextclip-img-thumb')
    ],
    contentScript: contentScriptVimeoCase,
    onMessage: onMessageVimeoHandler
  });

  /*
   * Vine.co handling
   */
  getItem({
    label: contextMenuLabel,
    context: cm.SelectorContext(getSelectors('vine')),
    contentScript: contextMenuContentScript,
    onMessage: function(url) {
      sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'vine.co'});
      launchVideo({url: url,
                   domain: 'vine.co',
                   getUrlFn: getVineUrl});
    }
  });

  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['https://vine.co/*']),
      cm.SelectorContext('video')
    ],
    contentScript: `self.on('click', function (node, data) {
      self.postMessage(node.poster);
    });`,
    onMessage: function(url) {
      const mp4 = url.replace(/thumbs/, 'videos').split(/\.jpg/)[0];
      sendMetricsData({object: 'contextmenu', method: 'activate', domain: 'vine.co'});
      launchVideo({url: url,
                   domain: 'vine.co',
                   src: mp4});
    }
  });

  /*
   * google.com search results handling
   */
  getItem({
    label: contextMenuLabel,
    context: [
      cm.URLContext(['*.google.com']),
      cm.SelectorContext(getSelectors('*', true)),
    ],
    contentScript: contextMenuContentScript,
    onMessage: function(url) {
      const regex = /url=(https?[^;]*)/.exec(url)[1];
      const decoded = decodeURIComponent(regex).split('&usg')[0];
      let getUrlFn, domain;
      if (decoded.indexOf('youtube.com' || 'youtu.be') > -1) {
        getUrlFn = getYouTubeUrl;
        domain = 'youtube.com';
      } else if (decoded.indexOf('vimeo.com')  > -1) {
        getUrlFn = getVimeoUrl;
        domain = 'vimeo.com';
      } else if (decoded.indexOf('vine.co') > -1) {
        getUrlFn = getVineUrl;
        domain = 'vine.co';
      }
      if (domain && getUrlFn) {
        sendMetricsData({object: 'contextmenu', method: 'activate', domain: domain});
        launchVideo({url: decoded,
                     domain: domain,
                     getUrlFn: getUrlFn});
      }
    }
  });
}
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// VERSION 1.0.4

this.PinnedItems = {
	get actions() { return $('actions'); },
	get tray() { return $('pinnedTabs'); },

	icons: new Map(),
	_delayedUpdates: new Set(),
	_activeItem: null,

	[Symbol.iterator]: function* () {
		for(let icon of this.icons.values()) {
			yield icon;
		}
	},

	get: function(tab) {
		return this.icons.get(tab);
	},

	handleEvent: function(e) {
		let tab = e.target;

		switch(e.type) {
			case "TabOpen":
				if(tab.pinned) {
					this.add(tab);
				}
				break;

			case "TabClose":
				if(tab.pinned) {
					this.remove(tab);
				}
				break;

			case "TabMove":
				if(tab.pinned) {
					this.arrange(tab);
				}
				break;

			case 'TabPinned':
				this.add(tab);
				break;

			case 'TabUnpinned':
				this.remove(tab);
				break;

			// watch for icon changes on app tabs
			case 'TabAttrModified':
				this.updateIcon(tab);
				break;

			case 'TabSelect':
				this.makeActive(tab);
				break;

			case 'dragover':
				if(DraggingTab) {
					DraggingTab.canDrop(e, this.tray);
				}
				break;
		}
	},

	// Watching "busy" and "progress" attributes in tabs, at least "progress" doesn't fire TabAttrModified events, and "busy" seems a bit unreliable.
	attrWatcher: function(tab) {
		if(!!this.icons.has(tab)) { return; }
		let icon = this.icons.get(tab);

		if(tab.hasAttribute("busy") != icon.hasAttribute("busy") || tab.hasAttribute("progress") != icon.hasAttribute("progress")) {
			this.updateIcon(tab);
		}
	},

	init: function() {
		// To optimize drag handlers.
		this.tray._appTabsContainer = true;

		Tabs.listen("TabOpen", this);
		Tabs.listen("TabClose", this);
		Tabs.listen("TabMove", this);
		Tabs.listen("TabPinned", this);
		Tabs.listen("TabUnpinned", this);
		Tabs.listen("TabAttrModified", this);
		Tabs.listen("TabSelect", this);

		Listeners.add(this.actions, 'dragover', this);

		for(let tab of Tabs.pinned) {
			this.add(tab);
		}
	},

	uninit: function() {
		Tabs.unlisten("TabOpen", this);
		Tabs.unlisten("TabClose", this);
		Tabs.unlisten("TabMove", this);
		Tabs.unlisten("TabPinned", this);
		Tabs.unlisten("TabUnpinned", this);
		Tabs.unlisten("TabAttrModified", this);
		Tabs.unlisten("TabSelect", this);

		Listeners.remove(this.actions, 'dragover', this);

		for(let icon of this.icons.values()) {
			this.destroy(icon);
		}
		this.icons.clear();
		removeAttribute(this.tray, 'visible');
	},

	// Show the pinned tabs group only when there are pinned tabs.
	updateTray: function() {
		toggleAttribute(this.tray, 'visible', this.icons.size);
	},

	// Update apptab icons based on xulTabs which have been updated while the TabView hasn't been visible
	flushUpdates: function() {
		for(let tab of this._delayedUpdates) {
			this._updateIcon(tab);
		}
		this._delayedUpdates.clear();
	},

	updateIcon: function(tab) {
		if(!UI.isTabViewVisible()) {
			this._delayedUpdates.add(tab);
		} else {
			this._updateIcon(tab);
		}
	},

	// Update images of any apptab icons that point to passed in xultab
	_updateIcon: function(tab) {
		if(!tab.pinned) { return; }

		if(this.icons.has(tab)) {
			let icon = this.icons.get(tab);
			let busy = tab.hasAttribute('busy');
			let progress = tab.hasAttribute('progress');
			if(busy || progress) {
				icon.style.backgroundImage = '';
				toggleAttribute(icon, 'busy', busy);
				toggleAttribute(icon, 'progress', progress);
				return;
			}
			else {
				removeAttribute(icon, 'busy');
				removeAttribute(icon, 'progress');
				// Show the previous icon if there was one, until it is updated (if necessary).
				if(!icon.style.backgroundImage && icon._iconUrl) {
					icon.style.backgroundImage = "url('"+icon._iconUrl+"')";
				}
			}
		}

		this.getFavIconUrl(tab, (iconUrl) => {
			// The tab might have been removed or unpinned while waiting.
			if(!Utils.isValidXULTab(tab) || !tab.pinned || !this.icons.has(tab)) { return; }

			let icon = this.icons.get(tab);
			icon._iconUrl = iconUrl;
			icon.setAttribute('title', tab.getAttribute('label'));
			if(!icon.hasAttribute('busy') && !icon.hasAttribute('progress')) {
				icon.style.backgroundImage = "url('"+iconUrl+"')";
			}
		});
	},

	// Gets the fav icon url for app tab.
	getFavIconUrl: function(tab, callback) {
		FavIcons.getFavIconUrlForTab(tab, function(iconUrl) {
			callback(iconUrl || FavIcons.defaultFavicon);
		});
	},

	// Adds the given xul:tab as an app tab in the apptab tray
	add: function(tab, sibling) {
		let icon = this.icons.get(tab);
		if(!icon) {
			icon = document.createElement("input");
			icon.isAnAppItem = true;
			icon.parent = this.tray;
			icon.container = icon; // for equivalency with tab items in drag handlers
			icon.tab = tab;
			icon._iconUrl = '';
			icon.classList.add("appTabIcon");
			icon.setAttribute('type', 'button');
			icon.setAttribute('draggable', 'true');
			icon.zoomIn = function() {
				UI.goToTab(this.tab);
			};
			icon.handleEvent = function(e) {
				switch(e.type) {
					case 'click':
						// left-clicks only
						if(e.button != 0) { break; }

						this.zoomIn();
						break;

					case 'dragenter':
						if(DraggingTab) {
							DraggingTab.dropHere(this);
						}
						break;

					case 'dragover':
						if(DraggingTab) {
							DraggingTab.canDrop(e, this.parent);
						}
						break;

					case 'dragstart':
						new TabDrag(e, this);
						break;
				}
			};
			icon.addEventListener("click", icon);
			icon.addEventListener("dragover", icon);
			icon.addEventListener("dragenter", icon);
			icon.addEventListener("dragstart", icon);

			this.icons.set(tab, icon);
			tab._tabViewAppItem = icon;
			Watchers.addAttributeWatcher(tab, [ "busy", "progress" ], this);
		}

		if(sibling && sibling.isAnAppItem) {
			this.tray.insertBefore(icon, sibling);
		} else {
			this.tray.appendChild(icon);
		}
		this.updateTray();

		this.updateIcon(tab);

		if(tab == Tabs.selected) {
			this.makeActive(tab);
		}
	},

	// Removes the given xul:tab as an app tab in the apptab tray
	remove: function(tab) {
		// make sure any closed tabs are removed from the delay update list
		this._delayedUpdates.delete(tab);

		let icon = this.icons.get(tab);
		if(icon) {
			this.icons.delete(tab);
			this.destroy(icon);
			this.updateTray();
		}
	},

	destroy: function(icon) {
		icon.remove();
		Watchers.removeAttributeWatcher(icon.tab, [ "busy", "progress" ], this);
		delete icon.tab._tabViewAppItem;
	},

	// Arranges the given xul:tab as an app tab in the group's apptab tray
	arrange: function(tab) {
		let icon = this.icons.get(tab);
		if(icon && this.tray.childNodes[tab._tPos] != icon) {
			// so that the indexes match
			icon.remove();

			let sibling = this.tray.childNodes[tab._tPos] || null;
			this.tray.insertBefore(icon, sibling);
		}
	},

	reorderTabsBasedOnAppItemOrder: function() {
		let tabs = [];
		for(let icon of this.tray.childNodes) {
			tabs.push(icon.tab);
		}
		GroupItems.reorderTabsBasedOnGivenOrder(tabs);
	},

	makeActive: function(tab) {
		if(this._activeItem) {
			this._activeItem.classList.remove('activeAppTab');
			this._activeItem = null;
		}

		if(!tab.pinned) { return; }

		let icon = this.icons.get(tab);
		if(icon) {
			this._activeItem = icon;
			this._activeItem.classList.add('activeAppTab');
		}
	}
};
<commit_msg>#249 pinned tabs could not update their icons<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// VERSION 1.0.5

this.PinnedItems = {
	get actions() { return $('actions'); },
	get tray() { return $('pinnedTabs'); },

	icons: new Map(),
	_delayedUpdates: new Set(),
	_activeItem: null,

	[Symbol.iterator]: function* () {
		for(let icon of this.icons.values()) {
			yield icon;
		}
	},

	get: function(tab) {
		return this.icons.get(tab);
	},

	handleEvent: function(e) {
		let tab = e.target;

		switch(e.type) {
			case "TabOpen":
				if(tab.pinned) {
					this.add(tab);
				}
				break;

			case "TabClose":
				if(tab.pinned) {
					this.remove(tab);
				}
				break;

			case "TabMove":
				if(tab.pinned) {
					this.arrange(tab);
				}
				break;

			case 'TabPinned':
				this.add(tab);
				break;

			case 'TabUnpinned':
				this.remove(tab);
				break;

			// watch for icon changes on app tabs
			case 'TabAttrModified':
				this.updateIcon(tab);
				break;

			case 'TabSelect':
				this.makeActive(tab);
				break;

			case 'dragover':
				if(DraggingTab) {
					DraggingTab.canDrop(e, this.tray);
				}
				break;
		}
	},

	// Watching "busy" and "progress" attributes in tabs, at least "progress" doesn't fire TabAttrModified events, and "busy" seems a bit unreliable.
	attrWatcher: function(tab) {
		if(!this.icons.has(tab)) { return; }
		let icon = this.icons.get(tab);

		if(tab.hasAttribute("busy") != icon.hasAttribute("busy") || tab.hasAttribute("progress") != icon.hasAttribute("progress")) {
			this.updateIcon(tab);
		}
	},

	init: function() {
		// To optimize drag handlers.
		this.tray._appTabsContainer = true;

		Tabs.listen("TabOpen", this);
		Tabs.listen("TabClose", this);
		Tabs.listen("TabMove", this);
		Tabs.listen("TabPinned", this);
		Tabs.listen("TabUnpinned", this);
		Tabs.listen("TabAttrModified", this);
		Tabs.listen("TabSelect", this);

		Listeners.add(this.actions, 'dragover', this);

		for(let tab of Tabs.pinned) {
			this.add(tab);
		}
	},

	uninit: function() {
		Tabs.unlisten("TabOpen", this);
		Tabs.unlisten("TabClose", this);
		Tabs.unlisten("TabMove", this);
		Tabs.unlisten("TabPinned", this);
		Tabs.unlisten("TabUnpinned", this);
		Tabs.unlisten("TabAttrModified", this);
		Tabs.unlisten("TabSelect", this);

		Listeners.remove(this.actions, 'dragover', this);

		for(let icon of this.icons.values()) {
			this.destroy(icon);
		}
		this.icons.clear();
		removeAttribute(this.tray, 'visible');
	},

	// Show the pinned tabs group only when there are pinned tabs.
	updateTray: function() {
		toggleAttribute(this.tray, 'visible', this.icons.size);
	},

	// Update apptab icons based on xulTabs which have been updated while the TabView hasn't been visible
	flushUpdates: function() {
		for(let tab of this._delayedUpdates) {
			this._updateIcon(tab);
		}
		this._delayedUpdates.clear();
	},

	updateIcon: function(tab) {
		if(!UI.isTabViewVisible()) {
			this._delayedUpdates.add(tab);
		} else {
			this._updateIcon(tab);
		}
	},

	// Update images of any apptab icons that point to passed in xultab
	_updateIcon: function(tab) {
		if(!tab.pinned) { return; }

		if(this.icons.has(tab)) {
			let icon = this.icons.get(tab);
			let busy = tab.hasAttribute('busy');
			let progress = tab.hasAttribute('progress');
			if(busy || progress) {
				icon.style.backgroundImage = '';
				toggleAttribute(icon, 'busy', busy);
				toggleAttribute(icon, 'progress', progress);
				return;
			}
			else {
				removeAttribute(icon, 'busy');
				removeAttribute(icon, 'progress');
				// Show the previous icon if there was one, until it is updated (if necessary).
				if(!icon.style.backgroundImage && icon._iconUrl) {
					icon.style.backgroundImage = "url('"+icon._iconUrl+"')";
				}
			}
		}

		this.getFavIconUrl(tab, (iconUrl) => {
			// The tab might have been removed or unpinned while waiting.
			if(!Utils.isValidXULTab(tab) || !tab.pinned || !this.icons.has(tab)) { return; }

			let icon = this.icons.get(tab);
			icon._iconUrl = iconUrl;
			icon.setAttribute('title', tab.getAttribute('label'));
			if(!icon.hasAttribute('busy') && !icon.hasAttribute('progress')) {
				icon.style.backgroundImage = "url('"+iconUrl+"')";
			}
		});
	},

	// Gets the fav icon url for app tab.
	getFavIconUrl: function(tab, callback) {
		FavIcons.getFavIconUrlForTab(tab, function(iconUrl) {
			callback(iconUrl || FavIcons.defaultFavicon);
		});
	},

	// Adds the given xul:tab as an app tab in the apptab tray
	add: function(tab, sibling) {
		let icon = this.icons.get(tab);
		if(!icon) {
			icon = document.createElement("input");
			icon.isAnAppItem = true;
			icon.parent = this.tray;
			icon.container = icon; // for equivalency with tab items in drag handlers
			icon.tab = tab;
			icon._iconUrl = '';
			icon.classList.add("appTabIcon");
			icon.setAttribute('type', 'button');
			icon.setAttribute('draggable', 'true');
			icon.zoomIn = function() {
				UI.goToTab(this.tab);
			};
			icon.handleEvent = function(e) {
				switch(e.type) {
					case 'click':
						// left-clicks only
						if(e.button != 0) { break; }

						this.zoomIn();
						break;

					case 'dragenter':
						if(DraggingTab) {
							DraggingTab.dropHere(this);
						}
						break;

					case 'dragover':
						if(DraggingTab) {
							DraggingTab.canDrop(e, this.parent);
						}
						break;

					case 'dragstart':
						new TabDrag(e, this);
						break;
				}
			};
			icon.addEventListener("click", icon);
			icon.addEventListener("dragover", icon);
			icon.addEventListener("dragenter", icon);
			icon.addEventListener("dragstart", icon);

			this.icons.set(tab, icon);
			tab._tabViewAppItem = icon;
			Watchers.addAttributeWatcher(tab, [ "busy", "progress" ], this, false, false);
		}

		if(sibling && sibling.isAnAppItem) {
			this.tray.insertBefore(icon, sibling);
		} else {
			this.tray.appendChild(icon);
		}
		this.updateTray();

		this.updateIcon(tab);

		if(tab == Tabs.selected) {
			this.makeActive(tab);
		}
	},

	// Removes the given xul:tab as an app tab in the apptab tray
	remove: function(tab) {
		// make sure any closed tabs are removed from the delay update list
		this._delayedUpdates.delete(tab);

		let icon = this.icons.get(tab);
		if(icon) {
			this.icons.delete(tab);
			this.destroy(icon);
			this.updateTray();
		}
	},

	destroy: function(icon) {
		icon.remove();
		Watchers.removeAttributeWatcher(icon.tab, [ "busy", "progress" ], this, false, false);
		delete icon.tab._tabViewAppItem;
	},

	// Arranges the given xul:tab as an app tab in the group's apptab tray
	arrange: function(tab) {
		let icon = this.icons.get(tab);
		if(icon && this.tray.childNodes[tab._tPos] != icon) {
			// so that the indexes match
			icon.remove();

			let sibling = this.tray.childNodes[tab._tPos] || null;
			this.tray.insertBefore(icon, sibling);
		}
	},

	reorderTabsBasedOnAppItemOrder: function() {
		let tabs = [];
		for(let icon of this.tray.childNodes) {
			tabs.push(icon.tab);
		}
		GroupItems.reorderTabsBasedOnGivenOrder(tabs);
	},

	makeActive: function(tab) {
		if(this._activeItem) {
			this._activeItem.classList.remove('activeAppTab');
			this._activeItem = null;
		}

		if(!tab.pinned) { return; }

		let icon = this.icons.get(tab);
		if(icon) {
			this._activeItem = icon;
			this._activeItem.classList.add('activeAppTab');
		}
	}
};
<|endoftext|>


<commit_before>"use strict";

var zlib = require('zlib');
var P = require('bluebird');
var uuid = require('cassandra-uuid').TimeUuid;
var preq = require('preq');

var HyperSwitch = require('hyperswitch');
var URI = HyperSwitch.URI;

var mwUtil = require('../lib/mwUtil');

var spec = HyperSwitch.utils.loadSpec(__dirname + '/key_rev_value.yaml');

function ArchivalBucket(options) {
}

ArchivalBucket.prototype._latestName = function(bucket) {
    return bucket + '.latest';
};

ArchivalBucket.prototype._archiveName = function(bucket) {
    return bucket;
};

ArchivalBucket.prototype.createBucket = function(hyper, req) {
    var self = this;
    var rp = req.params;
    var latestConfig = Object.assign({}, req.body, {
        revisionRetentionPolicy: { type: 'latest_hash' }
    });
    latestConfig.options = latestConfig.options || {};
    latestConfig.options.compression = [];
    latestConfig.valueType = 'blob';

    return P.join(
        hyper.put({
            uri: new URI([rp.domain, 'sys', 'key_rev_value', self._latestName(rp.bucket)]),
            headers: req.headers,
            body: latestConfig
        }),
        hyper.put({
            uri: new URI([rp.domain, 'sys', 'key_rev_value', self._archiveName(rp.bucket)]),
            headers: req.headers,
            body: req.body
        })
    )
    .then(function() {
        return { status: 201 }; });
};

function requestURI(rp, bucket) {
    var requestPath = [rp.domain, 'sys', 'key_rev_value', bucket, rp.key];
    if (rp.revision) {
        requestPath.push('' + rp.revision);
        if (rp.tid) {
            requestPath.push(rp.tid);
        }
    }
    return new URI(requestPath);
}

ArchivalBucket.prototype.getRevision = function(hyper, req) {
    var self = this;
    var rp = req.params;
    return hyper.get({
        uri: requestURI(rp, self._latestName(rp.bucket)),
        headers: req.headers
    })
    .then(function(res) {
        res.headers['content-encoding'] = 'gzip';
        if (/^application\/json/.test(res.headers['content-type'])) {
            return mwUtil.decodeBody(res)
            .then(function(res) {
                res.body = JSON.parse(res.body);
                return res;
            });
        }
        return res;
    })
    .catch({ status: 404 }, function() {
        return hyper.get({
            uri: requestURI(rp, self._archiveName(rp.bucket)),
            headers: req.headers
        });
    });
};

ArchivalBucket.prototype.listRevisions = function(hyper, req) {
    var self = this;
    var rp = req.params;
    return hyper.get({
        uri: new URI([rp.domain, 'sys', 'key_rev_value', self._archiveName(rp.bucket), rp.key, '']),
        query: req.query
    });
};

ArchivalBucket.prototype.putRevision = function(hyper, req) {
    var self = this;
    var rp = req.params;
    rp.tid = rp.tid || uuid.now().toString();
    if (/^application\/json/.test(req.headers['content-type'])) {
        req.body = JSON.stringify(req.body);
    }

    // Custom impl for 0.10 compatibility.
    // When we drop it following lines can be replaced with
    // a promisified convenience method
    var gzip = zlib.createGzip({ level: 6 });
    var prepare = new P(function(resolve, reject) {
        var chunks = [];
        gzip.on('data', function(chunk) {
            chunks.push(chunk);
        });
        gzip.on('end', function() {
            resolve(Buffer.concat(chunks));
        });
        gzip.on('error', reject);

        gzip.end(req.body);
    });

    return P.join(
        prepare.then(function(data) {
            return hyper.put({
                uri: requestURI(rp, self._latestName(rp.bucket)),
                headers: req.headers,
                body: data
            });
        }),
        hyper.put({
            uri: requestURI(rp, self._archiveName(rp.bucket)),
            headers: req.headers,
            body: req.body
        })
    )
    .spread(function(res1) { return res1; });
};

module.exports = function(options) {
    var archivalBucket = new ArchivalBucket(options);

    return {
        spec: spec, // Re-export from spec module
        operations: {
            createBucket: archivalBucket.createBucket.bind(archivalBucket),
            listRevisions: archivalBucket.listRevisions.bind(archivalBucket),
            getRevision: archivalBucket.getRevision.bind(archivalBucket),
            putRevision: archivalBucket.putRevision.bind(archivalBucket)
        }
    };
};
<commit_msg>Removed non-needed require<commit_after>"use strict";

var zlib = require('zlib');
var P = require('bluebird');
var uuid = require('cassandra-uuid').TimeUuid;

var HyperSwitch = require('hyperswitch');
var URI = HyperSwitch.URI;

var mwUtil = require('../lib/mwUtil');

var spec = HyperSwitch.utils.loadSpec(__dirname + '/key_rev_value.yaml');

function ArchivalBucket(options) {
}

ArchivalBucket.prototype._latestName = function(bucket) {
    return bucket + '.latest';
};

ArchivalBucket.prototype._archiveName = function(bucket) {
    return bucket;
};

ArchivalBucket.prototype.createBucket = function(hyper, req) {
    var self = this;
    var rp = req.params;
    var latestConfig = Object.assign({}, req.body, {
        revisionRetentionPolicy: { type: 'latest_hash' }
    });
    latestConfig.options = latestConfig.options || {};
    latestConfig.options.compression = [];
    latestConfig.valueType = 'blob';

    return P.join(
        hyper.put({
            uri: new URI([rp.domain, 'sys', 'key_rev_value', self._latestName(rp.bucket)]),
            headers: req.headers,
            body: latestConfig
        }),
        hyper.put({
            uri: new URI([rp.domain, 'sys', 'key_rev_value', self._archiveName(rp.bucket)]),
            headers: req.headers,
            body: req.body
        })
    )
    .then(function() {
        return { status: 201 }; });
};

function requestURI(rp, bucket) {
    var requestPath = [rp.domain, 'sys', 'key_rev_value', bucket, rp.key];
    if (rp.revision) {
        requestPath.push('' + rp.revision);
        if (rp.tid) {
            requestPath.push(rp.tid);
        }
    }
    return new URI(requestPath);
}

ArchivalBucket.prototype.getRevision = function(hyper, req) {
    var self = this;
    var rp = req.params;
    return hyper.get({
        uri: requestURI(rp, self._latestName(rp.bucket)),
        headers: req.headers
    })
    .then(function(res) {
        res.headers['content-encoding'] = 'gzip';
        if (/^application\/json/.test(res.headers['content-type'])) {
            return mwUtil.decodeBody(res)
            .then(function(res) {
                res.body = JSON.parse(res.body);
                return res;
            });
        }
        return res;
    })
    .catch({ status: 404 }, function() {
        return hyper.get({
            uri: requestURI(rp, self._archiveName(rp.bucket)),
            headers: req.headers
        });
    });
};

ArchivalBucket.prototype.listRevisions = function(hyper, req) {
    var self = this;
    var rp = req.params;
    return hyper.get({
        uri: new URI([rp.domain, 'sys', 'key_rev_value', self._archiveName(rp.bucket), rp.key, '']),
        query: req.query
    });
};

ArchivalBucket.prototype.putRevision = function(hyper, req) {
    var self = this;
    var rp = req.params;
    rp.tid = rp.tid || uuid.now().toString();
    if (/^application\/json/.test(req.headers['content-type'])) {
        req.body = JSON.stringify(req.body);
    }

    // Custom impl for 0.10 compatibility.
    // When we drop it following lines can be replaced with
    // a promisified convenience method
    var gzip = zlib.createGzip({ level: 6 });
    var prepare = new P(function(resolve, reject) {
        var chunks = [];
        gzip.on('data', function(chunk) {
            chunks.push(chunk);
        });
        gzip.on('end', function() {
            resolve(Buffer.concat(chunks));
        });
        gzip.on('error', reject);

        gzip.end(req.body);
    });

    return P.join(
        prepare.then(function(data) {
            return hyper.put({
                uri: requestURI(rp, self._latestName(rp.bucket)),
                headers: req.headers,
                body: data
            });
        }),
        hyper.put({
            uri: requestURI(rp, self._archiveName(rp.bucket)),
            headers: req.headers,
            body: req.body
        })
    )
    .spread(function(res1) { return res1; });
};

module.exports = function(options) {
    var archivalBucket = new ArchivalBucket(options);

    return {
        spec: spec, // Re-export from spec module
        operations: {
            createBucket: archivalBucket.createBucket.bind(archivalBucket),
            listRevisions: archivalBucket.listRevisions.bind(archivalBucket),
            getRevision: archivalBucket.getRevision.bind(archivalBucket),
            putRevision: archivalBucket.putRevision.bind(archivalBucket)
        }
    };
};
<|endoftext|>


<commit_before>import Vue from 'vue';

import itemCaretComponent from '~/groups/components/item_caret.vue';

import mountComponent from '../../helpers/vue_mount_component_helper';

const createComponent = (isGroupOpen = false) => {
  const Component = Vue.extend(itemCaretComponent);

  return mountComponent(Component, {
    isGroupOpen,
  });
};

describe('ItemCaretComponent', () => {
  describe('template', () => {
    it('should render component template correctly', () => {
      const vm = createComponent();
      vm.$mount();
      expect(vm.$el.classList.contains('folder-caret')).toBeTruthy();
      vm.$destroy();
    });

    it('should render caret down icon if `isGroupOpen` prop is `true`', () => {
      const vm = createComponent(true);
      vm.$mount();
      expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(1);
      expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(0);
      vm.$destroy();
    });

    it('should render caret right icon if `isGroupOpen` prop is `false`', () => {
      const vm = createComponent();
      vm.$mount();
      expect(vm.$el.querySelectorAll('i.fa.fa-caret-down').length).toBe(0);
      expect(vm.$el.querySelectorAll('i.fa.fa-caret-right').length).toBe(1);
      vm.$destroy();
    });
  });
});
<commit_msg>Update selector to test SVG icon name<commit_after>import Vue from 'vue';

import itemCaretComponent from '~/groups/components/item_caret.vue';

import mountComponent from '../../helpers/vue_mount_component_helper';

const createComponent = (isGroupOpen = false) => {
  const Component = Vue.extend(itemCaretComponent);

  return mountComponent(Component, {
    isGroupOpen,
  });
};

describe('ItemCaretComponent', () => {
  describe('template', () => {
    it('should render component template correctly', () => {
      const vm = createComponent();
      expect(vm.$el.classList.contains('folder-caret')).toBeTruthy();
      expect(vm.$el.querySelectorAll('svg').length).toBe(1);
      vm.$destroy();
    });

    it('should render caret down icon if `isGroupOpen` prop is `true`', () => {
      const vm = createComponent(true);
      expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-down');
      vm.$destroy();
    });

    it('should render caret right icon if `isGroupOpen` prop is `false`', () => {
      const vm = createComponent();
      expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('angle-right');
      vm.$destroy();
    });
  });
});
<|endoftext|>


<commit_before>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.provide("frappe.ui.form");

frappe.ui.form.AssignTo = Class.extend({
	init: function(opts) {
		var me = this;

		$.extend(this, opts);
		this.btn = this.parent.find(".add-assignment").on("click", function() { me.add(); });
		this.btn_wrapper = this.btn.parent();

		this.refresh();
	},
	refresh: function() {
		if(this.frm.doc.__islocal) {
			this.parent.toggle(false);
			return;
		}
		this.parent.toggle(true);
		this.render(this.frm.get_docinfo().assignments);
	},
	render: function(d) {
		var me = this;
		this.frm.get_docinfo().assignments = d;
		this.parent.find(".assignment-row").remove();

		if(me.primary_action) {
			me.primary_action.remove();
			me.primary_action = null;
		}

		if(this.dialog) {
			this.dialog.hide();
		}

		if(d && d.length) {
			for(var i=0; i<d.length; i++) {
				var info = frappe.user_info(d[i].owner);
				info.assign_to_name = d[i].name
				info.owner = d[i].owner;
				info.avatar = frappe.avatar(d[i].owner);
				info.description = d[i].description || "";

				info._fullname = info.fullname;
				if(info.fullname.length > 10) {
					info._fullname = info.fullname.substr(0, 10) + '...';
				}

				$(repl('
\
					×\
					%(avatar)s\
					%(_fullname)s\

', info))
					.insertBefore(this.parent.find('.add-assignment'));

				if(d[i].owner===frappe.session.user) {
					me.primary_action = this.frm.page.add_menu_item(__("Assignment Complete"), function() {
						me.remove(frappe.session.user);
					}, "fa fa-check", "btn-success")
				}

				if(!(d[i].owner === frappe.session.user || me.frm.perm[0].write)) {
					me.parent.find('a.close').remove();
				}
			}

			// set remove
			this.parent.find('a.close').click(function() {
				me.remove($(this).attr('data-owner'));
				return false;
			});

			//this.btn_wrapper.addClass("hide");
		} else {
			//this.btn_wrapper.removeClass("hide");
		}
	},
	add: function() {
		var me = this;

		if(this.frm.is_new()) {
			frappe.throw(__("Please save the document before assignment"));
			return;
		}

		if(!me.assign_to) {
			me.assign_to = new frappe.ui.form.AssignToDialog({
				obj: me,
				method: 'frappe.desk.form.assign_to.add',
				doctype: me.frm.doctype,
				docname: me.frm.docname,
				callback: function(r) {
					me.render(r.message);
				}
			});
		}
		me.assign_to.dialog.clear();

		if(me.frm.meta.title_field) {
			me.assign_to.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field])
		}

		me.assign_to.dialog.show();
		me.assign_to = null;
	},
	remove: function(owner) {
		var me = this;

		if(this.frm.is_new()) {
			frappe.throw(__("Please save the document before removing assignment"));
			return;
		}

		frappe.call({
			method:'frappe.desk.form.assign_to.remove',
			args: {
				doctype: me.frm.doctype,
				name: me.frm.docname,
				assign_to: owner
			},
			callback:function(r,rt) {
				me.render(r.message);
			}
		});
	}
});

frappe.ui.form.AssignToDialog = Class.extend({
	init: function(opts){
		var me = this
		var dialog = new frappe.ui.Dialog({
			title: __('Add to To Do'),
			fields: [
				{fieldtype: 'Link', fieldname:'assign_to', options:'User',
					label:__("Assign To"), reqd:true, filters: {'user_type': 'System User'}},
				{fieldtype:'Check', fieldname:'myself', label:__("Assign to me"), "default":0},
				{fieldtype:'Small Text', fieldname:'description', label:__("Comment")},
				{fieldtype: 'Section Break'},
				{fieldtype: 'Column Break'},
				{fieldtype:'Date', fieldname:'date', label: __("Complete By")},
				{fieldtype:'Check', fieldname:'notify',
					label:__("Notify by Email")},
				{fieldtype: 'Column Break'},
				{fieldtype:'Select', fieldname:'priority', label: __("Priority"),
					options:[
						{value:'Low', label:__('Low')},
						{value:'Medium', label:__('Medium')},
						{value:'High', label:__('High')}],
					'default':'Medium'},
			],
			primary_action: function() { frappe.ui.add_assignment(opts, this) },
			primary_action_label: __("Add")
		})
		$.extend(me, dialog);

		me.dialog = dialog;

		me.dialog.fields_dict.assign_to.get_query = "frappe.core.doctype.user.user.user_query";

		var myself = me.dialog.get_input("myself").on("click", function() {
			me.toggle_myself(this);
		});
		me.toggle_myself(myself);
	},
	toggle_myself: function(myself) {
		var me = this;
		if($(myself).prop("checked")) {
			me.dialog.set_value("assign_to", frappe.session.user);
			me.dialog.set_value("notify", 0);
			me.dialog.get_field("notify").$wrapper.toggle(false);
			me.dialog.get_field("assign_to").$wrapper.toggle(false);
		} else {
			me.dialog.set_value("assign_to", "");
			me.dialog.get_field("notify").$wrapper.toggle(true);
			me.dialog.get_field("assign_to").$wrapper.toggle(true);
		}
	},

});

frappe.ui.add_assignment = function(opts, dialog) {
	var assign_to = dialog.fields_dict.assign_to.get_value();
	var args = dialog.get_values();
	if(args && assign_to) {
		return frappe.call({
			method: opts.method,
			args: $.extend(args, {
				doctype: opts.doctype,
				name: opts.docname,
				assign_to: assign_to,
				bulk_assign:  opts.bulk_assign || false,
				re_assign: opts.re_assign || false
			}),
			callback: function(r,rt) {
				if(!r.exc) {
					if(opts.callback){
						opts.callback(r);
					}
					dialog && dialog.hide();
				}
			},
			btn: this
		});
	}
}
<commit_msg>fix(todo): Use priority in source document, if available, for ToDo<commit_after>// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt

frappe.provide("frappe.ui.form");

frappe.ui.form.AssignTo = Class.extend({
	init: function(opts) {
		var me = this;

		$.extend(this, opts);
		this.btn = this.parent.find(".add-assignment").on("click", function() { me.add(); });
		this.btn_wrapper = this.btn.parent();

		this.refresh();
	},
	refresh: function() {
		if(this.frm.doc.__islocal) {
			this.parent.toggle(false);
			return;
		}
		this.parent.toggle(true);
		this.render(this.frm.get_docinfo().assignments);
	},
	render: function(d) {
		var me = this;
		this.frm.get_docinfo().assignments = d;
		this.parent.find(".assignment-row").remove();

		if(me.primary_action) {
			me.primary_action.remove();
			me.primary_action = null;
		}

		if(this.dialog) {
			this.dialog.hide();
		}

		if(d && d.length) {
			for(var i=0; i<d.length; i++) {
				var info = frappe.user_info(d[i].owner);
				info.assign_to_name = d[i].name
				info.owner = d[i].owner;
				info.avatar = frappe.avatar(d[i].owner);
				info.description = d[i].description || "";

				info._fullname = info.fullname;
				if(info.fullname.length > 10) {
					info._fullname = info.fullname.substr(0, 10) + '...';
				}

				$(repl('
\
					×\
					%(avatar)s\
					%(_fullname)s\

', info))
					.insertBefore(this.parent.find('.add-assignment'));

				if(d[i].owner===frappe.session.user) {
					me.primary_action = this.frm.page.add_menu_item(__("Assignment Complete"), function() {
						me.remove(frappe.session.user);
					}, "fa fa-check", "btn-success")
				}

				if(!(d[i].owner === frappe.session.user || me.frm.perm[0].write)) {
					me.parent.find('a.close').remove();
				}
			}

			// set remove
			this.parent.find('a.close').click(function() {
				me.remove($(this).attr('data-owner'));
				return false;
			});

			//this.btn_wrapper.addClass("hide");
		} else {
			//this.btn_wrapper.removeClass("hide");
		}
	},
	add: function() {
		var me = this;

		if(this.frm.is_new()) {
			frappe.throw(__("Please save the document before assignment"));
			return;
		}

		if(!me.assign_to) {
			me.assign_to = new frappe.ui.form.AssignToDialog({
				obj: me,
				method: 'frappe.desk.form.assign_to.add',
				doctype: me.frm.doctype,
				docname: me.frm.docname,
				callback: function(r) {
					me.render(r.message);
				}
			});
		}
		me.assign_to.dialog.clear();

		if(me.frm.meta.title_field) {
			me.assign_to.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field])
		}

		me.assign_to.dialog.show();
		me.assign_to = null;
	},
	remove: function(owner) {
		var me = this;

		if(this.frm.is_new()) {
			frappe.throw(__("Please save the document before removing assignment"));
			return;
		}

		frappe.call({
			method:'frappe.desk.form.assign_to.remove',
			args: {
				doctype: me.frm.doctype,
				name: me.frm.docname,
				assign_to: owner
			},
			callback:function(r,rt) {
				me.render(r.message);
			}
		});
	}
});

frappe.ui.form.AssignToDialog = Class.extend({
	init: function(opts){
		var me = this
		var dialog = new frappe.ui.Dialog({
			title: __('Add to To Do'),
			fields: [
				{ fieldtype: 'Link', fieldname: 'assign_to', options: 'User', label: __("Assign To"), reqd: true, filters: { 'user_type': 'System User' }},
				{ fieldtype: 'Check', fieldname: 'myself', label: __("Assign to me"), "default": 0 },
				{ fieldtype: 'Small Text', fieldname: 'description', label: __("Comment") },
				{ fieldtype: 'Section Break' },
				{ fieldtype: 'Column Break' },
				{ fieldtype: 'Date', fieldname: 'date', label: __("Complete By") },
				{ fieldtype: 'Check', fieldname: 'notify', label: __("Notify by Email") },
				{ fieldtype: 'Column Break' },
				{ fieldtype: 'Select', fieldname: 'priority', label: __("Priority"),
					options: [
						{ value: 'Low', label: __('Low') },
						{ value: 'Medium', label: __('Medium') },
						{ value: 'High', label: __('High') }
					],
					// Pick up priority from the source document, if it exists and is available in ToDo
					'default': ["Low", "Medium", "High"].includes(opts.obj.frm.doc.priority) ? opts.obj.frm.doc.priority : 'Medium'
				},
			],
			primary_action: function() { frappe.ui.add_assignment(opts, this) },
			primary_action_label: __("Add")
		})
		$.extend(me, dialog);

		me.dialog = dialog;

		me.dialog.fields_dict.assign_to.get_query = "frappe.core.doctype.user.user.user_query";

		var myself = me.dialog.get_input("myself").on("click", function() {
			me.toggle_myself(this);
		});
		me.toggle_myself(myself);
	},
	toggle_myself: function(myself) {
		var me = this;
		if($(myself).prop("checked")) {
			me.dialog.set_value("assign_to", frappe.session.user);
			me.dialog.set_value("notify", 0);
			me.dialog.get_field("notify").$wrapper.toggle(false);
			me.dialog.get_field("assign_to").$wrapper.toggle(false);
		} else {
			me.dialog.set_value("assign_to", "");
			me.dialog.get_field("notify").$wrapper.toggle(true);
			me.dialog.get_field("assign_to").$wrapper.toggle(true);
		}
	},

});

frappe.ui.add_assignment = function(opts, dialog) {
	var assign_to = dialog.fields_dict.assign_to.get_value();
	var args = dialog.get_values();
	if(args && assign_to) {
		return frappe.call({
			method: opts.method,
			args: $.extend(args, {
				doctype: opts.doctype,
				name: opts.docname,
				assign_to: assign_to,
				bulk_assign:  opts.bulk_assign || false,
				re_assign: opts.re_assign || false
			}),
			callback: function(r,rt) {
				if(!r.exc) {
					if(opts.callback){
						opts.callback(r);
					}
					dialog && dialog.hide();
				}
			},
			btn: this
		});
	}
}
<|endoftext|>


<commit_before>$(document).ready(function() {
    initConnectionGraph()
    // drawMap();
    setTimeout( "drawMap()",500 );
    //console.log($.browser)

	$('#rightsContent').hide();
    $('#licenceContent').hide();
	$('#dataformats').hide();
	$(document).on('click', '#toggleRightsContent', function(e){
		e.preventDefault();
        if($('#toggleRightsContent').html()=='view details')
        {
            $('#toggleRightsContent').html('hide details')
        }else{
            $('#toggleRightsContent').html('view details')
        };
		$('#rightsContent').slideToggle();
	}).on('click', '#gotodata', function(e){
		e.preventDefault();
		$('#dataformats').slideToggle();
	}).on('click', '#toggleLicenceContent', function(e){
        e.preventDefault();
        if($('#toggleLicenceContent').html()=='view details')
        {
            $('#toggleLicenceContent').html('hide details')
        }else{
            $('#toggleLicenceContent').html('view details')
        };
        $('#licenceContent').slideToggle();

    });

    $('a[qtip_popup]').qtip({
        content: {
            text: $('a[qtip_popup]').attr('qtip_popup')
        },
        show: {
            delay: 1000,
            solo: false,
            ready: true
        },
        hide: {
            delay: 1000,
            fixed: true,
        },
        position: {viewport: $(window),my: 'bottom center',at: 'top center'},
        style: {
            classes: 'qtip-bootstrap',
            def: 'false',
            width:135
        }

    });

    $('a[qtip]').mouseover(function(){
        $(this).qtip('hide');
        $('a[qtip]').qtip({
            content:{
                text:function(e,api){
                    var tip = $(this).attr('qtip');
                    var content = tip;
                    if(tip.indexOf('#')==0 || tip.indexOf('.')==0) {
                        if($(tip.toString()).length) {
                            content = $(tip.toString()).html();
                        }
                    }
                    return content;
                }
            },
            show: {
                event: 'mouseover, click',
                ready: true
            },
            hide: {
                delay: 1000,
                fixed: true,
            },
            position: {target:'mouse', adjust: { mouse: false }, viewport: $(window) },
            style: {classes: 'qtip-light qtip-shadow qtip-normal qtip-bootstrap'}
        });
    });

});

$(document).on('click', '.ro_preview', function(event){
	event.preventDefault();
	$(this).qtip({
		show:{event:'click'},
        hide: {
            delay: 1000,
            fixed: true,
        },
		content: "Loading...",
		events: {
			show: function( event, api ) {
				api.elements.content.html('Loading...');
				var element = api.elements.target;
				if ($(element).attr('ro_id')) {
					var url = base_url+'registry_object/preview/?ro_id='+$(element).attr('ro_id')+'&omit='+$('#ro_id').val();
				} else if($(element).attr('identifier_relation_id')) {
					var url = base_url+'registry_object/preview/?identifier_relation_id='+$(element).attr('identifier_relation_id')
				} else if($(element).attr('identifier_doi')) {
					var url = base_url+'registry_object/preview/?identifier_doi='+$(element).attr('identifier_doi')
				}
				if (url) {
					$.ajax({
						url: url,
						success: function(content) {
							api.elements.content.html(content);
						}
					});
				} else {
					return 'Error displaying preview';
				}
			}
		},
		position: {target:'mouse', adjust: { mouse: false }, viewport: $('body') },
		style: {classes: 'qtip-light qtip-shadow qtip-normal qtip-bootstrap'},
		show: {
			event:event.type,
			ready:'true'
		}
	},event);
});

function traverseAndSelectChildren(tree, select_id) {
	//console.log(tree);
    for (var i = tree.length - 1; i >= 0; i--) {
		if (tree[i].registry_object_id == select_id) {
			tree[i].select = true;
			tree[i].expand = true;
			tree[i].focus = true;
           		   // tree[i].activate = true;
		} else {
			if (tree[i].children) {
				tree[i].children = traverseAndSelectChildren(tree[i].children, select_id);
			}
		}
	}
	return tree;
}

function drawMap(){//drawing the map on the left side

    if($('#spatial_coverage_map').length > 0){//if there is a coverage
        var latlng = new google.maps.LatLng(-25.397, 133.644);
        var myOptions = {
            zoom: 2,disableDefaultUI: true,center:latlng,panControl: true,zoomControl: true,mapTypeControl: true,scaleControl: true, scrollwheel:false,
            streetViewControl: false,overviewMapControl: true,mapTypeId: google.maps.MapTypeId.TERRAIN
        };
        var map2 = new google.maps.Map(document.getElementById("spatial_coverage_map"),myOptions);
        var bounds = new google.maps.LatLngBounds();

        //draw coverages
        var coverages = $('p.coverage');
        //console.log(coverages.html());
        // console.log(coverages.text());

        var mapContainsOnlyMarkers = true; // if there is only marker, then zoom out to a default depth (markers get "bounded" at max zoom level)
        var locationText = [];

        $.each(coverages, function(){
            // setTimeout('500');

            coverage = $(this).text();
            if (coverage!='') {
	            split = coverage.split(' ');
	            if(split.length>1) {
	                mapContainsOnlyMarkers = false;
	                coords = [];
	                $.each(split, function(){
	                    coord = stringToLatLng(this);
	                    coords.push(coord);
	                    bounds.extend(coord);
	                });
	                poly = new google.maps.Polygon({
	                    paths: coords,
	                    strokeColor: "#FF0000",
	                    strokeOpacity: 0.8,
	                    strokeWeight: 2,
	                    fillColor: "#FF0000",
	                    fillOpacity: 0.35
	                });
	                poly.setMap(map2);
	            }else{
	                var marker = new google.maps.Marker({
	                    map: map2,
	                    position: stringToLatLng($(this).html()),
	                    draggable: false,
	                    raiseOnDrag:false,
	                    visible:true
	                });
	                bounds.extend(stringToLatLng($(this).html()));
	            }
	        }
        });

        //DEPRECATED the drawing of centres
        // var centers = $('p.spatial_coverage_center');
        // $.each(centers, function(){
        // 	if($(this).html() !=''){
        // 		drawable = true;
        // 		var marker = new google.maps.Marker({
        // 		    map: map2,
        // 		    position: stringToLatLng($(this).html()),
        // 		    draggable: false,
        // 		    raiseOnDrag:false,
        // 		    visible:true
        // 		});
        // 	}
        // });

        // console.log(bounds);
        map2.fitBounds(bounds);

        if (mapContainsOnlyMarkers)
        {
            // CC-197/CC-304 - Center map on markers
            // fitBounds tends to wrap to max zoom level on markers
            // we still want a "good" fit if there are multiple markers, but
            // if we're zoomed to close, lets zoom out once the map loads!
            var listener = google.maps.event.addListenerOnce(map2, "idle", function() {
                if (map2.getZoom() > 3) map2.setZoom(3);
            });
        }

        google.maps.event.trigger(map2, 'resize');
        map2.setZoom( map2.getZoom() );
    }
}

function initConnectionGraph() {

	// Attach the dynatree widget to an existing
 element
	// and pass the tree options as an argument to the dynatree() function:
	if ($('#connectionTree').attr('mydata')) {
		var data = JSON.parse(decodeURIComponent($('#connectionTree').attr('mydata')));
		$('#connectionTree').removeAttr('mydata');
		var ro_id = $('#connectionTree').attr('ro_id');
		$('#connectionTree').removeAttr('ro_id');
		data = traverseAndSelectChildren(data, ro_id);

		/* Generate the tree */
		$("#connectionTree").dynatree({
			debugLevel: 0,
			children: data,
			onActivate: function(node) {
			// If this has more parts, open them...
			    if (node.data.children)	{
				    node.expand();
			    }
			},

			onPostInit: function (isReloading, isError) {
				// Hackery to make the nodes representing THIS registry object
				// visible, but not highlighted
				nodes = this.getSelectedNodes();
				for (var i = nodes.length - 1; i >= 0; i--) {
					nodes[i].activate();
					nodes[i].deactivate();
				};
			},

			onRender: function(node, nodeSpan) {
				var preview_url;
				if (node.data.status=='PUBLISHED')
				{
					preview_url = base_url + "preview/" + node.data.slug;
				}
				else
				{
					var draftText = '[DRAFT] ';
					$('a', nodeSpan).prepend(draftText);
					preview_url = base_url + "preview/?registry_object_id=" + node.data.registry_object_id;
				}

				/* Change the icon in the tree */
				if (node.data['class']=="collection")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "-38px -155px");
				}
				else if (node.data['class']=="party")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "-19px -155px");
				}
				else if (node.data['class']=="service")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "0px -156px");
				}
				else if (node.data['class']=="activity")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "-57px -155px");
				}

				$('a',$(nodeSpan)).attr('title', $(nodeSpan).text());
				$('a',$(nodeSpan)).attr('href', base_url + node.data.slug +"/"+node.data.registry_object_id);
                $('a',$(nodeSpan)).attr('ro_id', node.data.registry_object_id);
                $('a',$(nodeSpan)).addClass('ro_preview');
                if($(nodeSpan).text().length>100){
                    $('a',$(nodeSpan)).text($(nodeSpan).text().substring(0,100)+' ...');
                }
				if (node.data['class']=="more")
				{
					$(nodeSpan).find("span.dynatree-icon").remove();
					var a = $('a',$(nodeSpan));

					// Bind the accordion classes and attributes
					a.addClass('view_all_connection');
					if (node.data.status=='PUBLISHED')
					{
						a.attr('ro_slug', node.data.slug);
					}
					else
					{
						a.attr('ro_id', node.data.registry_object_id);
					}

					a.attr('relation_type','nested_collection');
					a.attr('page', 2);
                }
			}

	    });
        window.scrollTo(0,0);
	}

}

function stringToLatLng(str){
    var word = str.split(',');
    if(word[0] && word[1]) {
    	var lat = word[1];
    	var lon = word[0];
    } else {
    	var word = str.split(' ');
    	var lat = word[1];
    	var lon = word[0];
    }
    var coord = new google.maps.LatLng(parseFloat(lat), parseFloat(lon));
    return coord;
}

<commit_msg>Portal CC-1674 Fixed repositioning of the popout<commit_after>$(document).ready(function() {
    initConnectionGraph()
    // drawMap();
    setTimeout( "drawMap()",500 );
    //console.log($.browser)

	$('#rightsContent').hide();
    $('#licenceContent').hide();
	$('#dataformats').hide();
	$(document).on('click', '#toggleRightsContent', function(e){
		e.preventDefault();
        if($('#toggleRightsContent').html()=='view details')
        {
            $('#toggleRightsContent').html('hide details')
        }else{
            $('#toggleRightsContent').html('view details')
        };
		$('#rightsContent').slideToggle();
	}).on('click', '#gotodata', function(e){
		e.preventDefault();
		$('#dataformats').slideToggle();
	}).on('click', '#toggleLicenceContent', function(e){
        e.preventDefault();
        if($('#toggleLicenceContent').html()=='view details')
        {
            $('#toggleLicenceContent').html('hide details')
        }else{
            $('#toggleLicenceContent').html('view details')
        };
        $('#licenceContent').slideToggle();

    });

    $('a[qtip_popup]').qtip({
        content: {
            text: $('a[qtip_popup]').attr('qtip_popup')
        },
        show: {
            delay: 1000,
            solo: false,
            ready: true
        },
        hide: {
            delay: 1000,
            fixed: true,
        },
        position: {viewport: $(window),my: 'bottom center',at: 'top center'},
        style: {
            classes: 'qtip-bootstrap',
            def: 'false',
            width:135
        }

    });

    $('a[qtip]').mouseover(function(){
        $(this).qtip('hide');
        $('a[qtip]').qtip({
            content:{
                text:function(e,api){
                    var tip = $(this).attr('qtip');
                    var content = tip;
                    if(tip.indexOf('#')==0 || tip.indexOf('.')==0) {
                        if($(tip.toString()).length) {
                            content = $(tip.toString()).html();
                        }
                    }
                    return content;
                }
            },
            show: {
                event: 'mouseover, click',
                ready: true
            },
            hide: {
                delay: 1000,
                fixed: true,
            },
            position: {target:'mouse', adjust: { mouse: false }, viewport: $(window) },
            style: {classes: 'qtip-light qtip-shadow qtip-normal qtip-bootstrap'}
        });
    });

});

$(document).on('click', '.ro_preview', function(event){
	event.preventDefault();
	$(this).qtip({
		show:{event:'click'},
        hide: {
            delay: 1000,
            fixed: true,
        },
		content: "Loading...",
		events: {
			show: function( event, api ) {
				api.elements.content.html('Loading...');
				var element = api.elements.target;
				if ($(element).attr('ro_id')) {
					var url = base_url+'registry_object/preview/?ro_id='+$(element).attr('ro_id')+'&omit='+$('#ro_id').val();
				} else if($(element).attr('identifier_relation_id')) {
					var url = base_url+'registry_object/preview/?identifier_relation_id='+$(element).attr('identifier_relation_id')
				} else if($(element).attr('identifier_doi')) {
					var url = base_url+'registry_object/preview/?identifier_doi='+$(element).attr('identifier_doi')
				}
				if (url) {
					$.ajax({
						url: url,
						success: function(content) {
							api.elements.content.html(content);
							api.reposition(null, false);
						}
					});
				} else {
					return 'Error displaying preview';
				}
			}
		},
		position: {
			target:'mouse',
			adjust: { mouse: false },
			viewport: $(window),
			my: 'left center'
		},
		style: {classes: 'qtip-light qtip-shadow qtip-normal qtip-bootstrap'},
		show: {
			event:event.type,
			ready:'true'
		}
	},event);
});

function traverseAndSelectChildren(tree, select_id) {
	//console.log(tree);
    for (var i = tree.length - 1; i >= 0; i--) {
		if (tree[i].registry_object_id == select_id) {
			tree[i].select = true;
			tree[i].expand = true;
			tree[i].focus = true;
           		   // tree[i].activate = true;
		} else {
			if (tree[i].children) {
				tree[i].children = traverseAndSelectChildren(tree[i].children, select_id);
			}
		}
	}
	return tree;
}

function drawMap(){//drawing the map on the left side

    if($('#spatial_coverage_map').length > 0){//if there is a coverage
        var latlng = new google.maps.LatLng(-25.397, 133.644);
        var myOptions = {
            zoom: 2,disableDefaultUI: true,center:latlng,panControl: true,zoomControl: true,mapTypeControl: true,scaleControl: true, scrollwheel:false,
            streetViewControl: false,overviewMapControl: true,mapTypeId: google.maps.MapTypeId.TERRAIN
        };
        var map2 = new google.maps.Map(document.getElementById("spatial_coverage_map"),myOptions);
        var bounds = new google.maps.LatLngBounds();

        //draw coverages
        var coverages = $('p.coverage');
        //console.log(coverages.html());
        // console.log(coverages.text());

        var mapContainsOnlyMarkers = true; // if there is only marker, then zoom out to a default depth (markers get "bounded" at max zoom level)
        var locationText = [];

        $.each(coverages, function(){
            // setTimeout('500');

            coverage = $(this).text();
            if (coverage!='') {
	            split = coverage.split(' ');
	            if(split.length>1) {
	                mapContainsOnlyMarkers = false;
	                coords = [];
	                $.each(split, function(){
	                    coord = stringToLatLng(this);
	                    coords.push(coord);
	                    bounds.extend(coord);
	                });
	                poly = new google.maps.Polygon({
	                    paths: coords,
	                    strokeColor: "#FF0000",
	                    strokeOpacity: 0.8,
	                    strokeWeight: 2,
	                    fillColor: "#FF0000",
	                    fillOpacity: 0.35
	                });
	                poly.setMap(map2);
	            }else{
	                var marker = new google.maps.Marker({
	                    map: map2,
	                    position: stringToLatLng($(this).html()),
	                    draggable: false,
	                    raiseOnDrag:false,
	                    visible:true
	                });
	                bounds.extend(stringToLatLng($(this).html()));
	            }
	        }
        });

        //DEPRECATED the drawing of centres
        // var centers = $('p.spatial_coverage_center');
        // $.each(centers, function(){
        // 	if($(this).html() !=''){
        // 		drawable = true;
        // 		var marker = new google.maps.Marker({
        // 		    map: map2,
        // 		    position: stringToLatLng($(this).html()),
        // 		    draggable: false,
        // 		    raiseOnDrag:false,
        // 		    visible:true
        // 		});
        // 	}
        // });

        // console.log(bounds);
        map2.fitBounds(bounds);

        if (mapContainsOnlyMarkers)
        {
            // CC-197/CC-304 - Center map on markers
            // fitBounds tends to wrap to max zoom level on markers
            // we still want a "good" fit if there are multiple markers, but
            // if we're zoomed to close, lets zoom out once the map loads!
            var listener = google.maps.event.addListenerOnce(map2, "idle", function() {
                if (map2.getZoom() > 3) map2.setZoom(3);
            });
        }

        google.maps.event.trigger(map2, 'resize');
        map2.setZoom( map2.getZoom() );
    }
}

function initConnectionGraph() {

	// Attach the dynatree widget to an existing
 element
	// and pass the tree options as an argument to the dynatree() function:
	if ($('#connectionTree').attr('mydata')) {
		var data = JSON.parse(decodeURIComponent($('#connectionTree').attr('mydata')));
		$('#connectionTree').removeAttr('mydata');
		var ro_id = $('#connectionTree').attr('ro_id');
		$('#connectionTree').removeAttr('ro_id');
		data = traverseAndSelectChildren(data, ro_id);

		/* Generate the tree */
		$("#connectionTree").dynatree({
			debugLevel: 0,
			children: data,
			onActivate: function(node) {
			// If this has more parts, open them...
			    if (node.data.children)	{
				    node.expand();
			    }
			},

			onPostInit: function (isReloading, isError) {
				// Hackery to make the nodes representing THIS registry object
				// visible, but not highlighted
				nodes = this.getSelectedNodes();
				for (var i = nodes.length - 1; i >= 0; i--) {
					nodes[i].activate();
					nodes[i].deactivate();
				};
			},

			onRender: function(node, nodeSpan) {
				var preview_url;
				if (node.data.status=='PUBLISHED')
				{
					preview_url = base_url + "preview/" + node.data.slug;
				}
				else
				{
					var draftText = '[DRAFT] ';
					$('a', nodeSpan).prepend(draftText);
					preview_url = base_url + "preview/?registry_object_id=" + node.data.registry_object_id;
				}

				/* Change the icon in the tree */
				if (node.data['class']=="collection")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "-38px -155px");
				}
				else if (node.data['class']=="party")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "-19px -155px");
				}
				else if (node.data['class']=="service")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "0px -156px");
				}
				else if (node.data['class']=="activity")
				{
					$(nodeSpan).find("span.dynatree-icon").css("background-position", "-57px -155px");
				}

				$('a',$(nodeSpan)).attr('title', $(nodeSpan).text());
				$('a',$(nodeSpan)).attr('href', base_url + node.data.slug +"/"+node.data.registry_object_id);
                $('a',$(nodeSpan)).attr('ro_id', node.data.registry_object_id);
                $('a',$(nodeSpan)).addClass('ro_preview');
                if($(nodeSpan).text().length>100){
                    $('a',$(nodeSpan)).text($(nodeSpan).text().substring(0,100)+' ...');
                }
				if (node.data['class']=="more")
				{
					$(nodeSpan).find("span.dynatree-icon").remove();
					var a = $('a',$(nodeSpan));

					// Bind the accordion classes and attributes
					a.addClass('view_all_connection');
					if (node.data.status=='PUBLISHED')
					{
						a.attr('ro_slug', node.data.slug);
					}
					else
					{
						a.attr('ro_id', node.data.registry_object_id);
					}

					a.attr('relation_type','nested_collection');
					a.attr('page', 2);
                }
			}

	    });
        window.scrollTo(0,0);
	}

}

function stringToLatLng(str){
    var word = str.split(',');
    if(word[0] && word[1]) {
    	var lat = word[1];
    	var lon = word[0];
    } else {
    	var word = str.split(' ');
    	var lat = word[1];
    	var lon = word[0];
    }
    var coord = new google.maps.LatLng(parseFloat(lat), parseFloat(lon));
    return coord;
}

<|endoftext|>