<commit_before>/**
 * info window module, allow to edit the infowindow appearance
 * and the fields that will be shown
 */

cdb.admin.mod = cdb.admin.mod || {};

(function() {

var Field = cdb.core.View.extend({

  tagName: 'li',

  events: {
    'click .switch': 'toggle',
    'click .title': 'toggleTitle'
  },

  template: _.template(
    '<%- fieldName %>\

\
      \
        \
        title?\
      \
      \
        \
      \

'),

  initialize: function() {
    this.fieldName = this.options.field;
    this.position = this.options.position;
    this.model.bind('change:fields', this.fieldChange, this);
  },

  fieldChange: function() {
    if(this.model.containsField(this.fieldName)) {
      this.$('.switch').removeClass('disabled').addClass('enabled');
    } else {
      this.$('.switch').removeClass('enabled').addClass('disabled');
    }
    // title
    var t = this.model.getFieldProperty(this.fieldName, 'title');
    if(t) {
      this.$('.title').removeClass('disabled').addClass('enabled');
    } else {
      this.$('.title').removeClass('enabled').addClass('disabled');
    }
  },

  toggle: function(e) {
    e.preventDefault();
    if(!this.model.containsField(this.fieldName)) {
      this.model.addField(this.fieldName, this.position);
    } else {
      this.model.removeField(this.fieldName);
    }

    return false;
  },

  toggleTitle: function(e) {
    e.preventDefault();
    var t = this.model.getFieldProperty(this.fieldName, 'title');
    this.model.setFieldProperty(this.fieldName, 'title', !t);
    return false;
  },

  render: function() {
    this.$el.append(this.template(this));
    this.fieldChange();
    this.$el.attr('cid', this.cid);
    this.$el.addClass('drag_field');

    return this;
  }

});

cdb.admin.mod.InfoWindow = cdb.core.View.extend({

    buttonClass: 'infowindow_mod',
    className: 'infowindow_panel',
    type: 'tool',

    events: {
      'click .selectall': '_manageAll'
    },

    initialize: function() {
      var self = this;
      this.selectedAll = true;
      this.add_related_model(this.options.table);

      this.template = this.getTemplate('table/menu_modules/views/infowindow');
      this.model.bind('remove:fields', this.renderSelectAll, this);
      this.model.bind('add:fields', this.renderSelectAll, this);
      this.options.table.bind('change:schema', this.render, this);
      this.options.table.linkToInfowindow(this.model);
    },

    activated: function() {},

    // column names to be rendered
    getColumnNames: function() {
      var names = this.options.table.columnNames();
      return _(names).filter(function(c) {
        return !_.contains(this.model.SYSTEM_COLUMNS, c);
      });
    },

    renderFields: function() {
      var self = this;
      var $f = this.$('.fields');

      var names = this.getColumnNames();

      names.sort(function(a, b) {
        var pos_a = self.model.getFieldPos(a, 'position');
        var pos_b = self.model.getFieldPos(b, 'position');
        return pos_a - pos_b;
      });

      _(names).each(function(f) {
        var v = new Field({ model: self.model,  field: f , position: self.model.getFieldPos(f) });
        self.addView(v);
        $f.append(v.render().el);
      });

      // Sorteable list, except theme field!
      $f.sortable({
        items: 'li:gt(0)',
        stop: function(ev, ui) {
          self._reasignPositions();
        }
      });
    },

    // when fields are sorted in the iu we should recalculate all models positions
    _reasignPositions: function() {
      var self = this;
      this.$el.find('.drag_field').each(function(i, el) {
        var v = self._subviews[$(el).attr('cid')];
        v.model.setFieldProperty(v.fieldName, 'position', i);
        v.position = i;
      });

    },

    renderThemeCombo: function() {
      var themes = new cdb.forms.Combo({
        property: 'template_name',
        extra: [
          ['light',         'table/views/infowindow_light'],
          ['dark',          'table/views/infowindow_dark'],
          ['header blue',   'table/views/infowindow_light_header_blue'],
          ['header green',  'table/views/infowindow_light_header_green'],
          ['header yellow', 'table/views/infowindow_light_header_yellow'],
          ['header orange', 'table/views/infowindow_light_header_orange']
        ],
        model: this.model
      });
      var $f = this.$('.fields');
      var li = $('
');
      li
        .append(themes.render().el)
        .append('Theme');
      $f.append(li);
      this.addView(themes);
    },

    renderSelectAll: function() {
      var select_all = true
        , self = this;
      _(this.getColumnNames()).each(function(f) {
        if (!self.model.containsField(f)) {
          select_all = false;
        }
      });

      this.selectedAll = select_all;

      this._changeSelectAll();
    },

    render: function() {
      this.clearSubViews();
      this.$el.html(this.template({}));
      this.$el.find('.fields').sortable("destroy")
      this.renderThemeCombo();
      this.renderFields();
      this.$selectAll = this.$el.find(".selectall");
      this.renderSelectAll();

      // Custom scroll actions! :)
      this.custom_scroll = new cdb.admin.CustomScrolls({
        el: this.$el.find(".panel_content div.wrapper")
      });
      this.addView(this.custom_scroll);

      return this;
    },

    _manageAll: function(e) {
      var self = this;
      this.killEvent(e);
      if (this.working) return;
        self.model.clearFields();
      if (!self.selectedAll) {
        var names = this.getColumnNames()
        var count = _.size(names) - 1;
        var promises = [];

        _(names).each(function(f) {
          self.working = true;
          promises.push(self.model._addField(f));
        });

        $.when.apply($, promises).then(function() {
          self.working = false;
          self.model.sortFields();
          self.model.trigger('change:fields');

          self.selectedAll = !this.selectedAll;

          self._changeSelectAll();

        })
      } else {
        self.model.trigger('change:fields');
        self.selectedAll = !this.selectedAll;

        self._changeSelectAll();
      }

      return 0;
    },

    _changeSelectAll: function() {
      if (!this.working) {
        this.$selectAll.removeClass("working");
        if (!this.selectedAll) {
          this.$selectAll.addClass("disabled").removeClass("enabled");
        } else {
          this.$selectAll.addClass("enabled").removeClass("disabled");
        }
      } else {
        this.$selectAll.addClass("enabled working");
      }
    }
});

})();
<commit_msg>fixed bad context<commit_after>/**
 * info window module, allow to edit the infowindow appearance
 * and the fields that will be shown
 */

cdb.admin.mod = cdb.admin.mod || {};

(function() {

var Field = cdb.core.View.extend({

  tagName: 'li',

  events: {
    'click .switch': 'toggle',
    'click .title': 'toggleTitle'
  },

  template: _.template(
    '<%- fieldName %>\

\
      \
        \
        title?\
      \
      \
        \
      \

'),

  initialize: function() {
    this.fieldName = this.options.field;
    this.position = this.options.position;
    this.model.bind('change:fields', this.fieldChange, this);
  },

  fieldChange: function() {
    if(this.model.containsField(this.fieldName)) {
      this.$('.switch').removeClass('disabled').addClass('enabled');
    } else {
      this.$('.switch').removeClass('enabled').addClass('disabled');
    }
    // title
    var t = this.model.getFieldProperty(this.fieldName, 'title');
    if(t) {
      this.$('.title').removeClass('disabled').addClass('enabled');
    } else {
      this.$('.title').removeClass('enabled').addClass('disabled');
    }
  },

  toggle: function(e) {
    e.preventDefault();
    if(!this.model.containsField(this.fieldName)) {
      this.model.addField(this.fieldName, this.position);
    } else {
      this.model.removeField(this.fieldName);
    }

    return false;
  },

  toggleTitle: function(e) {
    e.preventDefault();
    var t = this.model.getFieldProperty(this.fieldName, 'title');
    this.model.setFieldProperty(this.fieldName, 'title', !t);
    return false;
  },

  render: function() {
    this.$el.append(this.template(this));
    this.fieldChange();
    this.$el.attr('cid', this.cid);
    this.$el.addClass('drag_field');

    return this;
  }

});

cdb.admin.mod.InfoWindow = cdb.core.View.extend({

    buttonClass: 'infowindow_mod',
    className: 'infowindow_panel',
    type: 'tool',

    events: {
      'click .selectall': '_manageAll'
    },

    initialize: function() {
      var self = this;
      this.selectedAll = true;
      this.add_related_model(this.options.table);

      this.template = this.getTemplate('table/menu_modules/views/infowindow');
      this.model.bind('remove:fields', this.renderSelectAll, this);
      this.model.bind('add:fields', this.renderSelectAll, this);
      this.options.table.bind('change:schema', this.render, this);
      this.options.table.linkToInfowindow(this.model);
    },

    activated: function() {},

    // column names to be rendered
    getColumnNames: function() {
      var self = this;
      var names = this.options.table.columnNames();
      return _(names).filter(function(c) {
        return !_.contains(self.model.SYSTEM_COLUMNS, c);
      });
    },

    renderFields: function() {
      var self = this;
      var $f = this.$('.fields');

      var names = this.getColumnNames();

      names.sort(function(a, b) {
        var pos_a = self.model.getFieldPos(a, 'position');
        var pos_b = self.model.getFieldPos(b, 'position');
        return pos_a - pos_b;
      });

      _(names).each(function(f) {
        var v = new Field({ model: self.model,  field: f , position: self.model.getFieldPos(f) });
        self.addView(v);
        $f.append(v.render().el);
      });

      // Sorteable list, except theme field!
      $f.sortable({
        items: 'li:gt(0)',
        stop: function(ev, ui) {
          self._reasignPositions();
        }
      });
    },

    // when fields are sorted in the iu we should recalculate all models positions
    _reasignPositions: function() {
      var self = this;
      this.$el.find('.drag_field').each(function(i, el) {
        var v = self._subviews[$(el).attr('cid')];
        v.model.setFieldProperty(v.fieldName, 'position', i);
        v.position = i;
      });

    },

    renderThemeCombo: function() {
      var themes = new cdb.forms.Combo({
        property: 'template_name',
        extra: [
          ['light',         'table/views/infowindow_light'],
          ['dark',          'table/views/infowindow_dark'],
          ['header blue',   'table/views/infowindow_light_header_blue'],
          ['header green',  'table/views/infowindow_light_header_green'],
          ['header yellow', 'table/views/infowindow_light_header_yellow'],
          ['header orange', 'table/views/infowindow_light_header_orange']
        ],
        model: this.model
      });
      var $f = this.$('.fields');
      var li = $('
');
      li
        .append(themes.render().el)
        .append('Theme');
      $f.append(li);
      this.addView(themes);
    },

    renderSelectAll: function() {
      var select_all = true
        , self = this;
      _(this.getColumnNames()).each(function(f) {
        if (!self.model.containsField(f)) {
          select_all = false;
        }
      });

      this.selectedAll = select_all;

      this._changeSelectAll();
    },

    render: function() {
      this.clearSubViews();
      this.$el.html(this.template({}));
      this.$el.find('.fields').sortable("destroy")
      this.renderThemeCombo();
      this.renderFields();
      this.$selectAll = this.$el.find(".selectall");
      this.renderSelectAll();

      // Custom scroll actions! :)
      this.custom_scroll = new cdb.admin.CustomScrolls({
        el: this.$el.find(".panel_content div.wrapper")
      });
      this.addView(this.custom_scroll);

      return this;
    },

    _manageAll: function(e) {
      var self = this;
      this.killEvent(e);
      if (this.working) return;
        self.model.clearFields();
      if (!self.selectedAll) {
        var names = this.getColumnNames()
        var count = _.size(names) - 1;
        var promises = [];

        _(names).each(function(f) {
          self.working = true;
          promises.push(self.model._addField(f));
        });

        $.when.apply($, promises).then(function() {
          self.working = false;
          self.model.sortFields();
          self.model.trigger('change:fields');

          self.selectedAll = !this.selectedAll;

          self._changeSelectAll();

        })
      } else {
        self.model.trigger('change:fields');
        self.selectedAll = !this.selectedAll;

        self._changeSelectAll();
      }

      return 0;
    },

    _changeSelectAll: function() {
      if (!this.working) {
        this.$selectAll.removeClass("working");
        if (!this.selectedAll) {
          this.$selectAll.addClass("disabled").removeClass("enabled");
        } else {
          this.$selectAll.addClass("enabled").removeClass("disabled");
        }
      } else {
        this.$selectAll.addClass("enabled working");
      }
    }
});

})();
<|endoftext|>


<commit_before>// FIXME on Firefox when the extension is reloaded when the sidear is open, it breaks.
import './compatibility'
import landmarkName from './landmarkName'
import { defaultInterfaceSettings, dismissalStates } from './defaults'
import disconnectingPortErrorCheck from './disconnectingPortErrorCheck'

let port = null

//
// Creating a landmarks tree in response to info from content script
//

// Handle incoming landmarks message response
//
// If there are landmarks, then the response will be a list of objects that
// represent the landmarks.
//
//     [ { label: X, role: Y, depth: Z, selector: @ }, { . . . }, . . . ]
//
// If we got some landmarks from the page, make the tree of them. If there was
// an error, let the user know.
function handleLandmarksMessage(data) {
	const display = document.getElementById('landmarks')
	removeChildNodes(display)

	// Content script would normally send back an array of landmarks
	if (Array.isArray(data)) {
		if (data.length === 0) {
			addText(display, browser.i18n.getMessage('noLandmarksFound'))
		} else {
			makeLandmarksTree(data, display)
		}
	} else {
		addText(display, browser.i18n.getMessage('errorNoConnection'))
	}
}

// Go through the landmarks identified for the page and create an HTML
// nested list to mirror the structure of those landmarks
function makeLandmarksTree(landmarks, container) {
	let previousDepth = 0
	const root = document.createElement('ul')  // start of tree
	let base = root                            // anchor for sub-trees
	let previousItem = null                    // last item to be created

	landmarks.forEach(function(landmark, index) {
		const depthChange = landmark.depth - previousDepth
		const absDepthChange = Math.abs(depthChange)

		function whenDepthIncreases() {
			base = document.createElement('ul')
			previousItem.appendChild(base)
		}

		function whenDepthDecreases() {
			// The parent of base is an
, the grandparent is the

			base = base.parentElement.parentElement
		}

		// If the depth has changed, insert/step back the appropriate number of
		// levels

		if (absDepthChange > 0) {
			const operation =
				depthChange > 0 ? whenDepthIncreases : whenDepthDecreases
			for (let i = 0; i < absDepthChange; i++) {
				operation()
			}
		}

		// If nesting hasn't changed, stick with the current base

		// Create the
 for this landmark
		const item = document.createElement('li')
		const button = makeButtonAlreadyTranslated(
			function() {
				focusLandmark(index)
			},
			landmarkName(landmark))
		item.appendChild(button)

		if (INTERFACE === 'devtools') {
			const inspectButton = makeSymbolButton(
				function() {
					const inspectorCall = "inspect(document.querySelector('"
						+ landmark.selector  // comes from our own code
						+ "'))"
					browser.devtools.inspectedWindow.eval(inspectorCall)
				},
				'inspectButtonName',
				'🔍',
				landmarkName(landmark))
			inspectButton.title = landmark.selector
			item.appendChild(inspectButton)
		}

		base.appendChild(item)  // add to current base

		// Housekeeping
		previousDepth = landmark.depth
		previousItem = item
	})

	container.appendChild(root)
}

//
// DOM manipulation utilities
//

// Remove all nodes contained within a node
function removeChildNodes(element) {
	while (element.firstChild) {
		element.removeChild(element.firstChild)
	}
}

// Append text paragraph to the given element
function addText(element, message) {
	const newPara = document.createElement('p')
	const newParaText = document.createTextNode(message)
	newPara.appendChild(newParaText)
	element.appendChild(newPara)
}

function makeButton(onClick, nameMessage) {
	return makeButtonAlreadyTranslated(
		onClick,
		browser.i18n.getMessage(nameMessage))
}

function makeSymbolButton(onClick, nameMessage, symbol, context) {
	return makeButtonAlreadyTranslated(
		onClick,
		browser.i18n.getMessage(nameMessage),
		symbol,
		context)
}

function makeButtonAlreadyTranslated(onClick, name, symbol, context) {
	const button = document.createElement('button')
	button.className = 'browser-style'
	button.appendChild(document.createTextNode(symbol ? symbol : name))
	if (symbol) {
		button.setAttribute('aria-label', name + ' ' + context)
		button.style.border = 'none'
		button.style.background = 'none'
	}
	button.onclick = onClick
	return button
}

//
// General utilities
//

// When a landmark's corresponding button in the UI is clicked, focus it
function focusLandmark(index) {
	port.postMessage({
		name: 'focus-landmark',
		index: index
	})
}

if (INTERFACE === 'sidebar') {
	//
	// Sidebar - Live updates and Preferences note
	//

	// The sidebar can be open even if the user has chosen the pop-up as the
	// primary GUI for Landmarks. In this case, a note can be created in the
	// sidebar to explain this to the user.
	const noteId = 'note'

	function createNote() {  // eslint-disable-line no-inner-declarations
		browser.storage.sync.get(dismissalStates, function(items) {
			if (!items.dismissedSidebarNotAlone) {
				const para = document.createElement('p')
				para.appendChild(document.createTextNode(
					browser.i18n.getMessage('hintSidebarIsNotPrimary')))

				const optionsButton = makeButton(
					function() {
						browser.runtime.openOptionsPage()
					},
					'hintSidebarIsNotPrimaryOptions')

				const dismissButton = makeButton(
					function() {
						browser.storage.sync.set({
							dismissedSidebarNotAlone: true
						}, function() {
							removeNote()
						})
					},
					'hintDismiss')

				// Contains buttons; allows for them to be flexbox'd
				const buttons = document.createElement('div')
				buttons.appendChild(optionsButton)
				buttons.appendChild(dismissButton)

				const note = document.createElement('div')
				note.id = noteId
				note.appendChild(para)
				note.appendChild(buttons)

				document.body.insertBefore(note, document.body.firstChild)
			}
		})
	}

	function removeNote() {  // eslint-disable-line no-inner-declarations
		const message = document.getElementById(noteId)
		if (message) message.remove()
	}

	// Should we create the note in the sidebar when it opens?
	browser.storage.sync.get(defaultInterfaceSettings, function(items) {
		if (items.interface === 'popup') {
			createNote()
		}
	})

	// What about if the sidebar is open and the user changes their preference?
	browser.storage.onChanged.addListener(function(changes) {
		if (changes.hasOwnProperty('interface')) {
			switch (changes.interface.newValue) {
				case 'sidebar': removeNote()
					break
				case 'popup': createNote()
					break
				default:
					throw Error(`Unknown interface type "${changes.interface.newValue}`)
			}
		}
	})
}

//
// Management
//

// When the pop-up (or sidebar) opens, translate the heading and grab and
// process the list of page landmarks
function main() {
	document.getElementById('heading').innerText =
		browser.i18n.getMessage('popupHeading')

	if (INTERFACE === 'devtools') {
		port = browser.runtime.connect({ name: INTERFACE })
		port.postMessage({
			name: 'init',
			tabId: browser.devtools.inspectedWindow.tabId
		})
	} else {
		port = browser.runtime.connect({ name: INTERFACE })
	}

	port.onDisconnect.addListener(function() {
		disconnectingPortErrorCheck()
		if (INTERFACE === 'devtools'
			&& (BROWSER === 'chrome' || BROWSER === 'opera')) {
			// DevTools page doesn't get reloaded when the extension does
			const para = document.createElement('p')
			const warning = document.createTextNode(
				browser.i18n.getMessage('devToolsConnectionError'))
			para.appendChild(warning)
			document.body.insertBefore(para,
				document.querySelector('h1').nextSibling)
			document.body.style.backgroundColor = '#fee'
			// TODO make this use the note styles; will need some refactoring
		}
	})

	port.onMessage.addListener(function(message) {
		switch (message.name) {
			case 'landmarks':
				handleLandmarksMessage(message.data)
				break
			default:
				throw Error(`Unkown message ${JSON.stringify(message)}`)
		}
	})

	port.postMessage({ name: 'get-landmarks' })
}

main()
<commit_msg>Remove FIXME note (not a problem)<commit_after>import './compatibility'
import landmarkName from './landmarkName'
import { defaultInterfaceSettings, dismissalStates } from './defaults'
import disconnectingPortErrorCheck from './disconnectingPortErrorCheck'

let port = null

//
// Creating a landmarks tree in response to info from content script
//

// Handle incoming landmarks message response
//
// If there are landmarks, then the response will be a list of objects that
// represent the landmarks.
//
//     [ { label: X, role: Y, depth: Z, selector: @ }, { . . . }, . . . ]
//
// If we got some landmarks from the page, make the tree of them. If there was
// an error, let the user know.
function handleLandmarksMessage(data) {
	const display = document.getElementById('landmarks')
	removeChildNodes(display)

	// Content script would normally send back an array of landmarks
	if (Array.isArray(data)) {
		if (data.length === 0) {
			addText(display, browser.i18n.getMessage('noLandmarksFound'))
		} else {
			makeLandmarksTree(data, display)
		}
	} else {
		addText(display, browser.i18n.getMessage('errorNoConnection'))
	}
}

// Go through the landmarks identified for the page and create an HTML
// nested list to mirror the structure of those landmarks
function makeLandmarksTree(landmarks, container) {
	let previousDepth = 0
	const root = document.createElement('ul')  // start of tree
	let base = root                            // anchor for sub-trees
	let previousItem = null                    // last item to be created

	landmarks.forEach(function(landmark, index) {
		const depthChange = landmark.depth - previousDepth
		const absDepthChange = Math.abs(depthChange)

		function whenDepthIncreases() {
			base = document.createElement('ul')
			previousItem.appendChild(base)
		}

		function whenDepthDecreases() {
			// The parent of base is an
, the grandparent is the

			base = base.parentElement.parentElement
		}

		// If the depth has changed, insert/step back the appropriate number of
		// levels

		if (absDepthChange > 0) {
			const operation =
				depthChange > 0 ? whenDepthIncreases : whenDepthDecreases
			for (let i = 0; i < absDepthChange; i++) {
				operation()
			}
		}

		// If nesting hasn't changed, stick with the current base

		// Create the
 for this landmark
		const item = document.createElement('li')
		const button = makeButtonAlreadyTranslated(
			function() {
				focusLandmark(index)
			},
			landmarkName(landmark))
		item.appendChild(button)

		if (INTERFACE === 'devtools') {
			const inspectButton = makeSymbolButton(
				function() {
					const inspectorCall = "inspect(document.querySelector('"
						+ landmark.selector  // comes from our own code
						+ "'))"
					browser.devtools.inspectedWindow.eval(inspectorCall)
				},
				'inspectButtonName',
				'🔍',
				landmarkName(landmark))
			inspectButton.title = landmark.selector
			item.appendChild(inspectButton)
		}

		base.appendChild(item)  // add to current base

		// Housekeeping
		previousDepth = landmark.depth
		previousItem = item
	})

	container.appendChild(root)
}

//
// DOM manipulation utilities
//

// Remove all nodes contained within a node
function removeChildNodes(element) {
	while (element.firstChild) {
		element.removeChild(element.firstChild)
	}
}

// Append text paragraph to the given element
function addText(element, message) {
	const newPara = document.createElement('p')
	const newParaText = document.createTextNode(message)
	newPara.appendChild(newParaText)
	element.appendChild(newPara)
}

function makeButton(onClick, nameMessage) {
	return makeButtonAlreadyTranslated(
		onClick,
		browser.i18n.getMessage(nameMessage))
}

function makeSymbolButton(onClick, nameMessage, symbol, context) {
	return makeButtonAlreadyTranslated(
		onClick,
		browser.i18n.getMessage(nameMessage),
		symbol,
		context)
}

function makeButtonAlreadyTranslated(onClick, name, symbol, context) {
	const button = document.createElement('button')
	button.className = 'browser-style'
	button.appendChild(document.createTextNode(symbol ? symbol : name))
	if (symbol) {
		button.setAttribute('aria-label', name + ' ' + context)
		button.style.border = 'none'
		button.style.background = 'none'
	}
	button.onclick = onClick
	return button
}

//
// General utilities
//

// When a landmark's corresponding button in the UI is clicked, focus it
function focusLandmark(index) {
	port.postMessage({
		name: 'focus-landmark',
		index: index
	})
}

if (INTERFACE === 'sidebar') {
	//
	// Sidebar - Live updates and Preferences note
	//

	// The sidebar can be open even if the user has chosen the pop-up as the
	// primary GUI for Landmarks. In this case, a note can be created in the
	// sidebar to explain this to the user.
	const noteId = 'note'

	function createNote() {  // eslint-disable-line no-inner-declarations
		browser.storage.sync.get(dismissalStates, function(items) {
			if (!items.dismissedSidebarNotAlone) {
				const para = document.createElement('p')
				para.appendChild(document.createTextNode(
					browser.i18n.getMessage('hintSidebarIsNotPrimary')))

				const optionsButton = makeButton(
					function() {
						browser.runtime.openOptionsPage()
					},
					'hintSidebarIsNotPrimaryOptions')

				const dismissButton = makeButton(
					function() {
						browser.storage.sync.set({
							dismissedSidebarNotAlone: true
						}, function() {
							removeNote()
						})
					},
					'hintDismiss')

				// Contains buttons; allows for them to be flexbox'd
				const buttons = document.createElement('div')
				buttons.appendChild(optionsButton)
				buttons.appendChild(dismissButton)

				const note = document.createElement('div')
				note.id = noteId
				note.appendChild(para)
				note.appendChild(buttons)

				document.body.insertBefore(note, document.body.firstChild)
			}
		})
	}

	function removeNote() {  // eslint-disable-line no-inner-declarations
		const message = document.getElementById(noteId)
		if (message) message.remove()
	}

	// Should we create the note in the sidebar when it opens?
	browser.storage.sync.get(defaultInterfaceSettings, function(items) {
		if (items.interface === 'popup') {
			createNote()
		}
	})

	// What about if the sidebar is open and the user changes their preference?
	browser.storage.onChanged.addListener(function(changes) {
		if (changes.hasOwnProperty('interface')) {
			switch (changes.interface.newValue) {
				case 'sidebar': removeNote()
					break
				case 'popup': createNote()
					break
				default:
					throw Error(`Unknown interface type "${changes.interface.newValue}`)
			}
		}
	})
}

//
// Management
//

// When the pop-up (or sidebar) opens, translate the heading and grab and
// process the list of page landmarks
function main() {
	document.getElementById('heading').innerText =
		browser.i18n.getMessage('popupHeading')

	if (INTERFACE === 'devtools') {
		port = browser.runtime.connect({ name: INTERFACE })
		port.postMessage({
			name: 'init',
			tabId: browser.devtools.inspectedWindow.tabId
		})
	} else {
		port = browser.runtime.connect({ name: INTERFACE })
	}

	port.onDisconnect.addListener(function() {
		disconnectingPortErrorCheck()
		if (INTERFACE === 'devtools'
			&& (BROWSER === 'chrome' || BROWSER === 'opera')) {
			// DevTools page doesn't get reloaded when the extension does
			const para = document.createElement('p')
			const warning = document.createTextNode(
				browser.i18n.getMessage('devToolsConnectionError'))
			para.appendChild(warning)
			document.body.insertBefore(para,
				document.querySelector('h1').nextSibling)
			document.body.style.backgroundColor = '#fee'
			// TODO make this use the note styles; will need some refactoring
		}
	})

	port.onMessage.addListener(function(message) {
		switch (message.name) {
			case 'landmarks':
				handleLandmarksMessage(message.data)
				break
			default:
				throw Error(`Unkown message ${JSON.stringify(message)}`)
		}
	})

	port.postMessage({ name: 'get-landmarks' })
}

main()
<|endoftext|>


<commit_before>var ModalsServiceModel = require('../../../../../javascripts/cartodb3/components/modals/modals-service-model');
var StackLayoutModel = require('../../../../../javascripts/cartodb3/components/stack-layout/stack-layout-model');
var AnalysisDefinitionNodesCollection = require('../../../../../javascripts/cartodb3/data/analysis-definition-nodes-collection');
var LayerDefinitionsCollection = require('../../../../../javascripts/cartodb3/data/layer-definitions-collection');
var LayersView = require('../../../../../javascripts/cartodb3/editor/layers/layers-view');
var EditorModel = require('../../../../../javascripts/cartodb3/data/editor-model');

describe('editor/layers/layers-view', function () {
  beforeEach(function () {
    this.stackLayoutModel = new StackLayoutModel(null, {
      stackLayoutItems: []
    });
    this.modals = new ModalsServiceModel();
    this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
      configModel: {}
    });

    this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
      configModel: {},
      analysisDefinitionsCollection: {},
      analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
      mapId: 'm-123'
    });
    this.layerDefinitionsCollection.add({
      id: 'l1',
      options: {
        type: 'CartoDB',
        table_name: 'foo_bar',
        cartocss: ''
      }
    });

    this.analysis = jasmine.createSpyObj('vis.analysis', ['findNodeById']);

    this.view = new LayersView({
      configModel: {},
      userModel: {},
      editorModel: new EditorModel(),
      analysis: this.analysis,
      modals: this.modals,
      stackLayoutModel: this.stackLayoutModel,
      analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
      layerDefinitionsCollection: this.layerDefinitionsCollection
    });

    this.view.render();
  });

  it('should have no leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });

  describe('._onNodeCreated', function () {
    describe('when new nodeModel is invalid', function () {
      beforeEach(function () {
        spyOn(this.stackLayoutModel, 'nextStep');
        this.nodeModel = this.analysisDefinitionNodesCollection.add({
          id: 'a1',
          type: 'point-in-polygon',
          primary_source_name: 'polygons_source',
          params: {
            points_source: {
              id: 'a0',
              type: 'source',
              table_name: 'points',
              params: {
                query: 'SELECT * FROM points'
              }
            },
            polygons_source: {
              id: 'b0',
              type: 'source',
              // should be invalid due to missing query and/or table_name
              params: {}
            }
          }
        }, {silent: true});

        this.view._onNodeCreated(this.layerDefinitionsCollection.first(), this.nodeModel);
      });

      it('should change stack layout to edit new analysis', function () {
        expect(this.stackLayoutModel.nextStep).toHaveBeenCalled();
        expect(this.stackLayoutModel.nextStep.calls.argsFor(0)[0]).toBe(this.layerDefinitionsCollection.first());
        expect(this.stackLayoutModel.nextStep.calls.argsFor(0)[1]).toEqual('layers');
        expect(this.stackLayoutModel.nextStep.calls.argsFor(0)[2].id).toEqual('a1');
      });
    });
  });
});
<commit_msg>Added more tests<commit_after>var ModalsServiceModel = require('../../../../../javascripts/cartodb3/components/modals/modals-service-model');
var StackLayoutModel = require('../../../../../javascripts/cartodb3/components/stack-layout/stack-layout-model');
var AnalysisDefinitionNodesCollection = require('../../../../../javascripts/cartodb3/data/analysis-definition-nodes-collection');
var LayerDefinitionsCollection = require('../../../../../javascripts/cartodb3/data/layer-definitions-collection');
var LayersView = require('../../../../../javascripts/cartodb3/editor/layers/layers-view');
var EditorModel = require('../../../../../javascripts/cartodb3/data/editor-model');

describe('editor/layers/layers-view', function () {
  beforeEach(function () {
    this.stackLayoutModel = new StackLayoutModel(null, {
      stackLayoutItems: []
    });
    this.modals = new ModalsServiceModel();
    this.analysisDefinitionNodesCollection = new AnalysisDefinitionNodesCollection(null, {
      configModel: {}
    });

    this.layerDefinitionsCollection = new LayerDefinitionsCollection(null, {
      configModel: {},
      analysisDefinitionsCollection: {},
      analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
      mapId: 'm-123'
    });
    this.layerDefinitionsCollection.add({
      id: 'l1',
      options: {
        type: 'CartoDB',
        table_name: 'foo_bar',
        cartocss: ''
      }
    });

    this.analysis = jasmine.createSpyObj('vis.analysis', ['findNodeById']);

    this.view = new LayersView({
      configModel: {},
      userModel: {},
      editorModel: new EditorModel(),
      analysis: this.analysis,
      modals: this.modals,
      stackLayoutModel: this.stackLayoutModel,
      analysisDefinitionNodesCollection: this.analysisDefinitionNodesCollection,
      layerDefinitionsCollection: this.layerDefinitionsCollection
    });

    this.view.render();
  });

  it('should have no leaks', function () {
    expect(this.view).toHaveNoLeaks();
  });

  describe('._onNodeCreated', function () {
    describe('when new nodeModel is invalid', function () {
      beforeEach(function () {
        spyOn(this.stackLayoutModel, 'nextStep');
        this.nodeModel = this.analysisDefinitionNodesCollection.add({
          id: 'a1',
          type: 'point-in-polygon',
          primary_source_name: 'polygons_source',
          params: {
            points_source: {
              id: 'a0',
              type: 'source',
              table_name: 'points',
              params: {
                query: 'SELECT * FROM points'
              }
            },
            polygons_source: {
              id: 'b0',
              type: 'source',
              // should be invalid due to missing query and/or table_name
              params: {}
            }
          }
        }, {silent: true});

        this.view._onNodeCreated(this.layerDefinitionsCollection.first(), this.nodeModel);
      });

      it('should change stack layout to edit new analysis', function () {
        expect(this.stackLayoutModel.nextStep).toHaveBeenCalled();
        expect(this.stackLayoutModel.nextStep.calls.argsFor(0)[0]).toBe(this.layerDefinitionsCollection.first());
        expect(this.stackLayoutModel.nextStep.calls.argsFor(0)[1]).toEqual('layers');
        expect(this.stackLayoutModel.nextStep.calls.argsFor(0)[2].id).toEqual('a1');
      });
    });
  });

  describe('._onOrderOfLayersChanged', function () {
    beforeEach(function () {
      spyOn(this.layerDefinitionsCollection, 'moveLayer').and.callFake(function () {});

      this.layerDefinitionsCollection.at(0).set('order', 0);
      this.layerDefinitionsCollection.add({
        id: 'l2',
        order: 1,
        options: {
          type: 'CartoDB',
          table_name: 'foo_bar2',
          cartocss: ''
        }
      });

      this.view.render();
    });

    it('should move layers from top to bottom', function () {
      // Simulate drag and drop by moving the layer on top to the bottom
      var layer0 = this.view.$el.find('.js-layer')[0];
      layer0.remove();
      this.view.$('.js-layers').append(layer0);
      this.view._onOrderOfLayersChanged('event', { item: layer0 });

      expect(this.layerDefinitionsCollection.moveLayer).toHaveBeenCalledWith({ from: 1, to: 0 });
    });

    it('should move layers from bottom to top', function () {
      // Simulate drag and drop by moving the layer at the bottom to the top
      var layer1 = this.view.$el.find('.js-layer')[1];
      layer1.remove();
      this.view.$('.js-layers').prepend(layer1);

      this.view._onOrderOfLayersChanged('event', { item: layer1 });

      expect(this.layerDefinitionsCollection.moveLayer).toHaveBeenCalledWith({ from: 0, to: 1 });
    });
  });
});
<|endoftext|>


<commit_before>/**
* Controls the actions in the project header (make public/private, watch button,
* forking, etc.)
*/
'use strict';

var $ = require('jquery');
var ko = require('knockout');
var bootbox = require('bootbox');
var Raven = require('raven-js');
require('bootstrap-editable');
require('knockout.punches');
ko.punches.enableAll();

var osfHelpers = require('js/osfHelpers');
var NodeActions = require('js/project.js');
var iconmap = require('js/iconmap');

// Modal language
var MESSAGES = {
    makeProjectPublicWarning: 'Once a project is made public, there is no way to guarantee that ' +
                        'access to the data it contains can be completely prevented. Users ' +
                        'should assume that once a project is made public, it will always ' +
                        'be public. Are you absolutely sure you would like to continue?',

    makeProjectPrivateWarning: 'Making a project private will prevent users from viewing it on this site, ' +
                        'but will have no impact on external sites, including Google\'s cache. ' +
                        'Would you like to continue?',

    makeComponentPublicWarning: 'Once a component is made public, there is no way to guarantee that ' +
                        'access to the data it contains can be completely prevented. Users ' +
                        'should assume that one a component is made public, it will always ' +
                        'be public. The rest of the project, including other components, ' +
                        'will not be made public. Are you absolutely sure you would like to continue?',

    makeComponentPrivateWarning: 'Making a component private will prevent users from viewing it on this site, ' +
                        'but will have no impact on external sites, including Google\'s cache. ' +
                        'Would you like to continue?',
    // TODO(hrybacki): Remove once Retraction/Embargoes goes is merged into production
    makeRegistrationPublicWarning: 'Important Note: Effective June 8, 2015, new registrations ' +
                        'will be made public immediately or can be embargoed for up to one year. There ' +
                        'will no longer be the option of creating a permanently private registration. This ' +
                        'registration occurred before June 8, 2015, so you do retain the option of keeping it ' +
                        'private. However, if you do choose to make the registration public now, then after ' +
                        'June 8, 2015 you will not be able to return it to private. Are you sure that you would like ' +
                        'to continue?',
};

// TODO(sloria): Fix this external dependency on nodeApiUrl
var URLS = {
    makePublic: window.nodeApiUrl + 'permissions/public/',
    makePrivate: window.nodeApiUrl + 'permissions/private/'
};
var PUBLIC = 'public';
var PRIVATE = 'private';
var PROJECT = 'project';
var COMPONENT = 'component';

function setPermissions(permissions, nodeType) {

    var msgKey;
    // TODO(hrybacki): Remove once Retraction/Embargoes goes is merged into production
    var isRegistration = window.contextVars.node.isRegistration;

    if (permissions === PUBLIC && isRegistration) { msgKey = 'makeRegistrationPublicWarning'; }
    else if(permissions === PUBLIC && nodeType === PROJECT) { msgKey = 'makeProjectPublicWarning'; }
    else if(permissions === PUBLIC && nodeType === COMPONENT) { msgKey = 'makeComponentPublicWarning'; }
    else if(permissions === PRIVATE && nodeType === PROJECT) { msgKey = 'makeProjectPrivateWarning'; }
    else { msgKey = 'makeComponentPrivateWarning'; }

    var urlKey = permissions === PUBLIC ? 'makePublic' : 'makePrivate';
    var message = MESSAGES[msgKey];

    var confirmModal = function (message) {
        bootbox.confirm({
            title: 'Warning',
            message: message,
            callback: function(result) {
                if (result) {
                    osfHelpers.postJSON(
                        URLS[urlKey],
                        {permissions: permissions}
                    ).done(function() {
                        window.location.reload();
                    }).fail(
                        osfHelpers.handleJSONError
                    );
                }
            }
        });
    };

    if (permissions === PUBLIC) {
        $.getJSON(
            window.nodeApiUrl + 'permissions/beforepublic/',
            {},
            function(data) {
                var alerts = '';
                var addonMessages = data.prompts;
                    for(var i=0; i<addonMessages.length; i++) {
                        alerts += '
' +
                                    addonMessages[i] + '
';
                    }
                confirmModal(alerts + message);
            }
        );
    } else {
        confirmModal(message);
    }
}

/**
 * The ProjectViewModel, scoped to the project header.
 * @param {Object} data The parsed project data returned from the project's API url.
 */
var ProjectViewModel = function(data) {
    var self = this;
    self._id = data.node.id;
    self.apiUrl = data.node.api_url;
    self.dateCreated = new osfHelpers.FormattableDate(data.node.date_created);
    self.dateModified = new osfHelpers.FormattableDate(data.node.date_modified);
    self.dateForked = new osfHelpers.FormattableDate(data.node.forked_date);
    self.parent = data.parent_node;
    self.doi = ko.observable(data.node.identifiers.doi);
    self.ark = ko.observable(data.node.identifiers.ark);
    self.idCreationInProgress = ko.observable(false);
    self.watchedCount = ko.observable(data.node.watched_count);
    self.userIsWatching = ko.observable(data.user.is_watching);
    self.dateRegistered = new osfHelpers.FormattableDate(data.node.registered_date);
    self.inDashboard = ko.observable(data.node.in_dashboard);
    self.dashboard = data.user.dashboard_id;
    self.userCanEdit = data.user.can_edit;
    self.userPermissions = data.user.permissions;
    self.description = data.node.description;
    self.title = data.node.title;
    self.category = data.node.category;
    self.isRegistration = data.node.is_registration;
    self.user = data.user;
    self.nodeIsPublic = data.node.is_public;
    self.nodeType = data.node.node_type;
    // The button text to display (e.g. "Watch" if not watching)
    self.watchButtonDisplay = ko.pureComputed(function() {
        return self.watchedCount().toString();
    });
    self.watchButtonAction = ko.pureComputed(function() {
        return self.userIsWatching() ? 'Unwatch' : 'Watch';
    });

    self.canBeOrganized = ko.pureComputed(function() {
        return !!(self.user.username && (self.nodeIsPublic || self.user.has_read_permissions));
    });

    // Add icon to title
    self.icon = '';
    var category = data.node.category_short;
    if (Object.keys(iconmap.componentIcons).indexOf(category) >=0 ){
        self.icon = iconmap.componentIcons[category];
    }
    else {
        self.icon = iconmap.projectIcons[category];
    }

    // Editable Title and Description
    if (self.userCanEdit) {
        var editableOptions = {
            type: 'text',
            pk: self._id,
            url: self.apiUrl + 'edit/',
            ajaxOptions: {
                type: 'POST',
                dataType: 'json',
                contentType: 'application/json'
            },
            params: function (params) {
                // Send JSON data
                return JSON.stringify(params);
            },
            success: function () {
                document.location.reload(true);
            },
            error: osfHelpers.handleEditableError,
            placement: 'bottom'
        };

        // TODO: Remove hardcoded selectors.
        $.fn.editable.defaults.mode = 'inline';
        $('#nodeTitleEditable').editable($.extend({}, editableOptions, {
            name: 'title',
            title: 'Edit Title',
            validate: function (value) {
                if ($.trim(value) === '') {
                    return 'Title cannot be blank.';
                }
            }
        }));
        $('#nodeDescriptionEditable').editable($.extend({}, editableOptions, {
            name: 'description',
            title: 'Edit Description',
            emptytext: 'No description',
            emptyclass: 'text-muted'
        }));
    }

    /**
     * Add project to the Project Organizer.
     */
    self.addToDashboard = function() {
        self.inDashboard(true);
        var jsonData = {
            'toNodeID': self.dashboard,
            'pointerID': self._id
        };
        osfHelpers.postJSON('/api/v1/pointer/', jsonData)
            .fail(function(data) {
                self.inDashboard(false);
                osfHelpers.handleJSONError(data);
        });
    };
    /**
     * Remove project from the Project Organizer.
     */
    self.removeFromDashboard = function() {
        self.inDashboard(false);
        var deleteUrl = '/api/v1/folder/' + self.dashboard + '/pointer/' + self._id;
        $.ajax({url: deleteUrl, type: 'DELETE'})
            .fail(function() {
                self.inDashboard(true);
                osfHelpers.growl('Error', 'The project could not be removed', 'danger');
        });
    };

    /**
     * Toggle the watch status for this project.
     */
    self.toggleWatch = function() {
        // Send POST request to node's watch API url and update the watch count
        if(self.userIsWatching()) {
            self.watchedCount(self.watchedCount() - 1);
        } else {
            self.watchedCount(self.watchedCount() + 1);
        }
        osfHelpers.postJSON(
            self.apiUrl + 'togglewatch/',
            {}
        ).done(function(data) {
            // Update watch count in DOM
            self.userIsWatching(data.watched);
            self.watchedCount(data.watchCount);
        }).fail(
            osfHelpers.handleJSONError
        );
    };

    self.makePublic = function() {
        return setPermissions(PUBLIC, self.nodeType);
    };

    self.makePrivate = function() {
        return setPermissions(PRIVATE, self.nodeType);
    };

    self.forkNode = function() {
        NodeActions.forkNode();
    };

    self.hasIdentifiers = ko.pureComputed(function() {
        return !!(self.doi() && self.ark());
    });

    self.canCreateIdentifiers = ko.pureComputed(function() {
        return !self.hasIdentifiers() &&
            self.isRegistration &&
            self.nodeIsPublic &&
            self.userPermissions.indexOf('admin') !== -1;
    });

    self.doiUrl = ko.pureComputed(function() {
        return self.doi() ? 'http://ezid.cdlib.org/id/doi:' + self.doi() : null;
    });

    self.arkUrl = ko.pureComputed(function() {
        return self.ark() ? 'http://ezid.cdlib.org/id/ark:/' + self.ark() : null;
    });

    self.askCreateIdentifiers = function() {
        var self = this;
        bootbox.confirm({
            title: 'Create identifiers',
            message: '
' +
                'Are you sure you want to create a DOI and ARK for this ' +
                self.nodeType + '?',
            callback: function(confirmed) {
                if (confirmed) {
                    self.createIdentifiers();
                }
            }
        });
    };

    self.createIdentifiers = function() {
        // Only show loading indicator for slow responses
        var timeout = setTimeout(function() {
            self.idCreationInProgress(true); // show loading indicator
        }, 500);
        var url = self.apiUrl + 'identifiers/';
        return $.post(
            url
        ).done(function(resp) {
            self.doi(resp.doi);
            self.ark(resp.ark);
        }).fail(function(xhr) {
            var message = 'We could not create the identifier at this time. ' +
                'The DOI/ARK acquisition service may be down right now. ' +
                'Please try again soon and/or contact ' +
                'support@osf.io';
            osfHelpers.growl('Error', message, 'danger');
            Raven.captureMessage('Could not create identifiers', {url: url, status: xhr.status});
        }).always(function() {
            clearTimeout(timeout);
            self.idCreationInProgress(false); // hide loading indicator
        });
    };
};

////////////////
// Public API //
////////////////

var defaults = {
    removeCss: '.user-quickedit'
};

function NodeControl (selector, data, options) {
    var self = this;
    self.selector = selector;
    self.$element = $(self.selector);
    self.data = data;
    self.viewModel = new ProjectViewModel(self.data);
    self.options = $.extend({}, defaults, options);
    self.init();
}

NodeControl.prototype.init = function() {
    var self = this;
    osfHelpers.applyBindings(self.viewModel, this.selector);
};

module.exports = {
    _ProjectViewModel: ProjectViewModel,
    NodeControl: NodeControl
};
<commit_msg>Adds extra warning text when making project public. closes Issue #3007 'Add extra warning to make public modal reminding users to check for sensitive info'.<commit_after>/**
* Controls the actions in the project header (make public/private, watch button,
* forking, etc.)
*/
'use strict';

var $ = require('jquery');
var ko = require('knockout');
var bootbox = require('bootbox');
var Raven = require('raven-js');
require('bootstrap-editable');
require('knockout.punches');
ko.punches.enableAll();

var osfHelpers = require('js/osfHelpers');
var NodeActions = require('js/project.js');
var iconmap = require('js/iconmap');

// Modal language
var MESSAGES = {
    makeProjectPublicWarning: 'Once a project is made public, there is no way to guarantee that ' +
                        'access to the data it contains can be completely prevented. Users ' +
                        'should assume that once a project is made public, it will always ' +
                        'be public. Review your project for sensitive or restricted information before making it public. Are you absolutely sure you would like to continue?',

    makeProjectPrivateWarning: 'Making a project private will prevent users from viewing it on this site, ' +
                        'but will have no impact on external sites, including Google\'s cache. ' +
                        'Would you like to continue?',

    makeComponentPublicWarning: 'Once a component is made public, there is no way to guarantee that ' +
                        'access to the data it contains can be completely prevented. Users ' +
                        'should assume that one a component is made public, it will always ' +
                        'be public. The rest of the project, including other components, ' +
                        'will not be made public. Review your component for sensitive or restricted information before making it public Are you absolutely sure you would like to continue?',

    makeComponentPrivateWarning: 'Making a component private will prevent users from viewing it on this site, ' +
                        'but will have no impact on external sites, including Google\'s cache. ' +
                        'Would you like to continue?',
    // TODO(hrybacki): Remove once Retraction/Embargoes goes is merged into production
    makeRegistrationPublicWarning: 'Important Note: Effective June 8, 2015, new registrations ' +
                        'will be made public immediately or can be embargoed for up to one year. There ' +
                        'will no longer be the option of creating a permanently private registration. This ' +
                        'registration occurred before June 8, 2015, so you do retain the option of keeping it ' +
                        'private. However, if you do choose to make the registration public now, then after ' +
                        'June 8, 2015 you will not be able to return it to private. Are you sure that you would like ' +
                        'to continue?',
};

// TODO(sloria): Fix this external dependency on nodeApiUrl
var URLS = {
    makePublic: window.nodeApiUrl + 'permissions/public/',
    makePrivate: window.nodeApiUrl + 'permissions/private/'
};
var PUBLIC = 'public';
var PRIVATE = 'private';
var PROJECT = 'project';
var COMPONENT = 'component';

function setPermissions(permissions, nodeType) {

    var msgKey;
    // TODO(hrybacki): Remove once Retraction/Embargoes goes is merged into production
    var isRegistration = window.contextVars.node.isRegistration;

    if (permissions === PUBLIC && isRegistration) { msgKey = 'makeRegistrationPublicWarning'; }
    else if(permissions === PUBLIC && nodeType === PROJECT) { msgKey = 'makeProjectPublicWarning'; }
    else if(permissions === PUBLIC && nodeType === COMPONENT) { msgKey = 'makeComponentPublicWarning'; }
    else if(permissions === PRIVATE && nodeType === PROJECT) { msgKey = 'makeProjectPrivateWarning'; }
    else { msgKey = 'makeComponentPrivateWarning'; }

    var urlKey = permissions === PUBLIC ? 'makePublic' : 'makePrivate';
    var message = MESSAGES[msgKey];

    var confirmModal = function (message) {
        bootbox.confirm({
            title: 'Warning',
            message: message,
            callback: function(result) {
                if (result) {
                    osfHelpers.postJSON(
                        URLS[urlKey],
                        {permissions: permissions}
                    ).done(function() {
                        window.location.reload();
                    }).fail(
                        osfHelpers.handleJSONError
                    );
                }
            }
        });
    };

    if (permissions === PUBLIC) {
        $.getJSON(
            window.nodeApiUrl + 'permissions/beforepublic/',
            {},
            function(data) {
                var alerts = '';
                var addonMessages = data.prompts;
                    for(var i=0; i<addonMessages.length; i++) {
                        alerts += '
' +
                                    addonMessages[i] + '
';
                    }
                confirmModal(alerts + message);
            }
        );
    } else {
        confirmModal(message);
    }
}

/**
 * The ProjectViewModel, scoped to the project header.
 * @param {Object} data The parsed project data returned from the project's API url.
 */
var ProjectViewModel = function(data) {
    var self = this;
    self._id = data.node.id;
    self.apiUrl = data.node.api_url;
    self.dateCreated = new osfHelpers.FormattableDate(data.node.date_created);
    self.dateModified = new osfHelpers.FormattableDate(data.node.date_modified);
    self.dateForked = new osfHelpers.FormattableDate(data.node.forked_date);
    self.parent = data.parent_node;
    self.doi = ko.observable(data.node.identifiers.doi);
    self.ark = ko.observable(data.node.identifiers.ark);
    self.idCreationInProgress = ko.observable(false);
    self.watchedCount = ko.observable(data.node.watched_count);
    self.userIsWatching = ko.observable(data.user.is_watching);
    self.dateRegistered = new osfHelpers.FormattableDate(data.node.registered_date);
    self.inDashboard = ko.observable(data.node.in_dashboard);
    self.dashboard = data.user.dashboard_id;
    self.userCanEdit = data.user.can_edit;
    self.userPermissions = data.user.permissions;
    self.description = data.node.description;
    self.title = data.node.title;
    self.category = data.node.category;
    self.isRegistration = data.node.is_registration;
    self.user = data.user;
    self.nodeIsPublic = data.node.is_public;
    self.nodeType = data.node.node_type;
    // The button text to display (e.g. "Watch" if not watching)
    self.watchButtonDisplay = ko.pureComputed(function() {
        return self.watchedCount().toString();
    });
    self.watchButtonAction = ko.pureComputed(function() {
        return self.userIsWatching() ? 'Unwatch' : 'Watch';
    });

    self.canBeOrganized = ko.pureComputed(function() {
        return !!(self.user.username && (self.nodeIsPublic || self.user.has_read_permissions));
    });

    // Add icon to title
    self.icon = '';
    var category = data.node.category_short;
    if (Object.keys(iconmap.componentIcons).indexOf(category) >=0 ){
        self.icon = iconmap.componentIcons[category];
    }
    else {
        self.icon = iconmap.projectIcons[category];
    }

    // Editable Title and Description
    if (self.userCanEdit) {
        var editableOptions = {
            type: 'text',
            pk: self._id,
            url: self.apiUrl + 'edit/',
            ajaxOptions: {
                type: 'POST',
                dataType: 'json',
                contentType: 'application/json'
            },
            params: function (params) {
                // Send JSON data
                return JSON.stringify(params);
            },
            success: function () {
                document.location.reload(true);
            },
            error: osfHelpers.handleEditableError,
            placement: 'bottom'
        };

        // TODO: Remove hardcoded selectors.
        $.fn.editable.defaults.mode = 'inline';
        $('#nodeTitleEditable').editable($.extend({}, editableOptions, {
            name: 'title',
            title: 'Edit Title',
            validate: function (value) {
                if ($.trim(value) === '') {
                    return 'Title cannot be blank.';
                }
            }
        }));
        $('#nodeDescriptionEditable').editable($.extend({}, editableOptions, {
            name: 'description',
            title: 'Edit Description',
            emptytext: 'No description',
            emptyclass: 'text-muted'
        }));
    }

    /**
     * Add project to the Project Organizer.
     */
    self.addToDashboard = function() {
        self.inDashboard(true);
        var jsonData = {
            'toNodeID': self.dashboard,
            'pointerID': self._id
        };
        osfHelpers.postJSON('/api/v1/pointer/', jsonData)
            .fail(function(data) {
                self.inDashboard(false);
                osfHelpers.handleJSONError(data);
        });
    };
    /**
     * Remove project from the Project Organizer.
     */
    self.removeFromDashboard = function() {
        self.inDashboard(false);
        var deleteUrl = '/api/v1/folder/' + self.dashboard + '/pointer/' + self._id;
        $.ajax({url: deleteUrl, type: 'DELETE'})
            .fail(function() {
                self.inDashboard(true);
                osfHelpers.growl('Error', 'The project could not be removed', 'danger');
        });
    };

    /**
     * Toggle the watch status for this project.
     */
    self.toggleWatch = function() {
        // Send POST request to node's watch API url and update the watch count
        if(self.userIsWatching()) {
            self.watchedCount(self.watchedCount() - 1);
        } else {
            self.watchedCount(self.watchedCount() + 1);
        }
        osfHelpers.postJSON(
            self.apiUrl + 'togglewatch/',
            {}
        ).done(function(data) {
            // Update watch count in DOM
            self.userIsWatching(data.watched);
            self.watchedCount(data.watchCount);
        }).fail(
            osfHelpers.handleJSONError
        );
    };

    self.makePublic = function() {
        return setPermissions(PUBLIC, self.nodeType);
    };

    self.makePrivate = function() {
        return setPermissions(PRIVATE, self.nodeType);
    };

    self.forkNode = function() {
        NodeActions.forkNode();
    };

    self.hasIdentifiers = ko.pureComputed(function() {
        return !!(self.doi() && self.ark());
    });

    self.canCreateIdentifiers = ko.pureComputed(function() {
        return !self.hasIdentifiers() &&
            self.isRegistration &&
            self.nodeIsPublic &&
            self.userPermissions.indexOf('admin') !== -1;
    });

    self.doiUrl = ko.pureComputed(function() {
        return self.doi() ? 'http://ezid.cdlib.org/id/doi:' + self.doi() : null;
    });

    self.arkUrl = ko.pureComputed(function() {
        return self.ark() ? 'http://ezid.cdlib.org/id/ark:/' + self.ark() : null;
    });

    self.askCreateIdentifiers = function() {
        var self = this;
        bootbox.confirm({
            title: 'Create identifiers',
            message: '
' +
                'Are you sure you want to create a DOI and ARK for this ' +
                self.nodeType + '?',
            callback: function(confirmed) {
                if (confirmed) {
                    self.createIdentifiers();
                }
            }
        });
    };

    self.createIdentifiers = function() {
        // Only show loading indicator for slow responses
        var timeout = setTimeout(function() {
            self.idCreationInProgress(true); // show loading indicator
        }, 500);
        var url = self.apiUrl + 'identifiers/';
        return $.post(
            url
        ).done(function(resp) {
            self.doi(resp.doi);
            self.ark(resp.ark);
        }).fail(function(xhr) {
            var message = 'We could not create the identifier at this time. ' +
                'The DOI/ARK acquisition service may be down right now. ' +
                'Please try again soon and/or contact ' +
                'support@osf.io';
            osfHelpers.growl('Error', message, 'danger');
            Raven.captureMessage('Could not create identifiers', {url: url, status: xhr.status});
        }).always(function() {
            clearTimeout(timeout);
            self.idCreationInProgress(false); // hide loading indicator
        });
    };
};

////////////////
// Public API //
////////////////

var defaults = {
    removeCss: '.user-quickedit'
};

function NodeControl (selector, data, options) {
    var self = this;
    self.selector = selector;
    self.$element = $(self.selector);
    self.data = data;
    self.viewModel = new ProjectViewModel(self.data);
    self.options = $.extend({}, defaults, options);
    self.init();
}

NodeControl.prototype.init = function() {
    var self = this;
    osfHelpers.applyBindings(self.viewModel, this.selector);
};

module.exports = {
    _ProjectViewModel: ProjectViewModel,
    NodeControl: NodeControl
};
<|endoftext|>


<commit_before>var path = require('path');
var AssetsPlugin = require('assets-webpack-plugin')
var CompressionPlugin = require('compression-webpack-plugin')
var assetsPluginInstance = new AssetsPlugin({
  filename: 'static/prd/assets.js',
  processOutput: function (assets) {
    return 'window.WEBPACK_ASSETS = ' + JSON.stringify(assets);
  }
})
var config = require('../config.json');

var compressPlugin = new CompressionPlugin({
  asset: "[path].gz[query]",
  algorithm: "gzip",
  test: /\.(js|css)$/,
  threshold: 10240,
  minRatio: 0.8,
});

function fileExist (filePath){
  try {
      return fs.statSync(filePath).isFile();
  } catch (err) {
      return false;
  }
};

function initPlugins(){
  if(config.plugins && Array.isArray(config.plugins)){
    config.plugins = config.plugins.filter(item=>{
      return fileExist(path.resolve(__dirname, 'node_modules/yapi-plugin-' + item + '/client.js'))
    })
  }
}

initPlugins();

function handleCommonsChunk(webpackConfig) {
  var commonsChunk = {
    vendors: {
      lib: ['react', 'redux',
        'redux-thunk',
        'react-dom',
        'redux-promise',
        'react-router',
        'react-router-dom',
        'prop-types',
        'axios',
        'moment'

      ],
      lib2: [
        'brace',
        'mockjs',
        'json5'
      ]
    }
  },
    chunks = [],
    filenameTpl = webpackConfig.output[this.env],
    vendors;

  if (typeof commonsChunk === 'object' && commonsChunk !== undefined) {
    if (typeof commonsChunk.name === 'string' && commonsChunk) {
      chunks.push(commonsChunk.name);
    }
    vendors = commonsChunk.vendors;
    if (typeof vendors === 'object' && vendors !== undefined) {
      var i = 0;
      for (var name in vendors) {
        if (vendors.hasOwnProperty(name) && vendors[name]) {
          i++;
          chunks.push(name);
          webpackConfig.entry[name] = Array.isArray(vendors[name]) ? vendors[name] : [vendors[name]];
        }
      }
      if (i > 0) {
        chunks.push('manifest');
      }

    }

    if (chunks.length > 0) {
      let chunkFilename = filenameTpl.filename
      chunkFilename = chunkFilename.replace("[ext]", '.js')
      webpackConfig.plugins.push(
        new this.webpack.optimize.CommonsChunkPlugin({
          name: chunks,
          filename: chunkFilename,
          minChunks: commonsChunk.minChunks ? commonsChunk.minChunks : 2
        })
      );

    }
  }
}

module.exports = {
  plugins: [{
    name: 'antd',
    options: {
      modifyQuery: function (defaultQuery) { // 可查看和编辑 defaultQuery
        defaultQuery.plugins = [];
        defaultQuery.plugins.push(["transform-runtime", {
          "polyfill": false,
          "regenerator": true
        }]);
        defaultQuery.plugins.push('transform-decorators-legacy');
        defaultQuery.plugins.push(["import", { libraryName: "antd"}])
        return defaultQuery;
      },
      exclude: /node_modules/
    }
  }],
  // devtool:  'cheap-source-map',
  config: function (ykit) {
    return {
      exports: [
        './index.js'
      ],
      modifyWebpackConfig: function (baseConfig) {

        var ENV_PARAMS = {};
        switch (this.env) {
          case 'local':
            ENV_PARAMS = 'dev';
            break;
          case 'dev':
            ENV_PARAMS = 'dev';
            break;
          case 'prd':
            ENV_PARAMS = 'production';
            break;
          default:
        }

        baseConfig.plugins.push(new this.webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify(ENV_PARAMS),
          'process.env.config': JSON.stringify(config)
        }))

        //初始化配置
        baseConfig.devtool = 'cheap-module-eval-source-map'
        baseConfig.context = path.resolve(__dirname, './client');
        baseConfig.resolve.alias.common = '/common';
        baseConfig.resolve.alias.plugins = '/plugins';
        baseConfig.output.prd.path = 'static/prd';
        baseConfig.output.prd.publicPath = '';
        baseConfig.output.prd.filename = '[name]@[chunkhash][ext]'

        //commonsChunk
        handleCommonsChunk.call(this, baseConfig)
        baseConfig.module.loaders.push({
          test: /\.less$/,
          loader: ykit.ExtractTextPlugin.extract(
            require.resolve('style-loader'),
            require.resolve('css-loader')
            + '?sourceMap!'
            + require.resolve('less-loader') + '?sourceMap'
          )
        })
        baseConfig.module.loaders.push({
          test: /\.(sass|scss)$/,
          loader: ykit.ExtractTextPlugin.extract(
            require.resolve('css-loader')
            + '?sourceMap!'
            + require.resolve('fast-sass-loader-china') + '?sourceMap'
          )
        })
        baseConfig.module.preLoaders.push({
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          loader: "eslint-loader"
        });

        if (this.env == 'prd') {
          baseConfig.plugins.push(assetsPluginInstance)
          baseConfig.plugins.push(compressPlugin)

        }
        return baseConfig;
      }
    }
  },
  server: {
    // true/false，默认 false，效果相当于 ykit server --hot
    hot: true,
    // true/false，默认 false，开启后可在当前打开的页面提示打包错误
    overlay: false
  },
  hooks: {},
  commands: []
};<commit_msg>fix: plugins path bug<commit_after>var path = require('path');
var AssetsPlugin = require('assets-webpack-plugin')
var CompressionPlugin = require('compression-webpack-plugin')
var assetsPluginInstance = new AssetsPlugin({
  filename: 'static/prd/assets.js',
  processOutput: function (assets) {
    return 'window.WEBPACK_ASSETS = ' + JSON.stringify(assets);
  }
})
var config = require('../config.json');

var compressPlugin = new CompressionPlugin({
  asset: "[path].gz[query]",
  algorithm: "gzip",
  test: /\.(js|css)$/,
  threshold: 10240,
  minRatio: 0.8,
});

function fileExist (filePath){
  try {
      return fs.statSync(filePath).isFile();
  } catch (err) {
      return false;
  }
};

function initPlugins(){
  if(config.plugins && Array.isArray(config.plugins)){
    config.plugins = config.plugins.filter(item=>{
      return fileExist(path.resolve(__dirname, 'plugins/yapi-plugin-' + item + '/client.js'))
    })
  }
}

initPlugins();

function handleCommonsChunk(webpackConfig) {
  var commonsChunk = {
    vendors: {
      lib: ['react', 'redux',
        'redux-thunk',
        'react-dom',
        'redux-promise',
        'react-router',
        'react-router-dom',
        'prop-types',
        'axios',
        'moment'

      ],
      lib2: [
        'brace',
        'mockjs',
        'json5'
      ]
    }
  },
    chunks = [],
    filenameTpl = webpackConfig.output[this.env],
    vendors;

  if (typeof commonsChunk === 'object' && commonsChunk !== undefined) {
    if (typeof commonsChunk.name === 'string' && commonsChunk) {
      chunks.push(commonsChunk.name);
    }
    vendors = commonsChunk.vendors;
    if (typeof vendors === 'object' && vendors !== undefined) {
      var i = 0;
      for (var name in vendors) {
        if (vendors.hasOwnProperty(name) && vendors[name]) {
          i++;
          chunks.push(name);
          webpackConfig.entry[name] = Array.isArray(vendors[name]) ? vendors[name] : [vendors[name]];
        }
      }
      if (i > 0) {
        chunks.push('manifest');
      }

    }

    if (chunks.length > 0) {
      let chunkFilename = filenameTpl.filename
      chunkFilename = chunkFilename.replace("[ext]", '.js')
      webpackConfig.plugins.push(
        new this.webpack.optimize.CommonsChunkPlugin({
          name: chunks,
          filename: chunkFilename,
          minChunks: commonsChunk.minChunks ? commonsChunk.minChunks : 2
        })
      );

    }
  }
}

module.exports = {
  plugins: [{
    name: 'antd',
    options: {
      modifyQuery: function (defaultQuery) { // 可查看和编辑 defaultQuery
        defaultQuery.plugins = [];
        defaultQuery.plugins.push(["transform-runtime", {
          "polyfill": false,
          "regenerator": true
        }]);
        defaultQuery.plugins.push('transform-decorators-legacy');
        defaultQuery.plugins.push(["import", { libraryName: "antd"}])
        return defaultQuery;
      },
      exclude: /node_modules/
    }
  }],
  // devtool:  'cheap-source-map',
  config: function (ykit) {
    return {
      exports: [
        './index.js'
      ],
      modifyWebpackConfig: function (baseConfig) {

        var ENV_PARAMS = {};
        switch (this.env) {
          case 'local':
            ENV_PARAMS = 'dev';
            break;
          case 'dev':
            ENV_PARAMS = 'dev';
            break;
          case 'prd':
            ENV_PARAMS = 'production';
            break;
          default:
        }

        baseConfig.plugins.push(new this.webpack.DefinePlugin({
          'process.env.NODE_ENV': JSON.stringify(ENV_PARAMS),
          'process.env.config': JSON.stringify(config)
        }))

        //初始化配置
        baseConfig.devtool = 'cheap-module-eval-source-map'
        baseConfig.context = path.resolve(__dirname, './client');
        baseConfig.resolve.alias.common = '/common';
        baseConfig.resolve.alias.plugins = '/plugins';
        baseConfig.output.prd.path = 'static/prd';
        baseConfig.output.prd.publicPath = '';
        baseConfig.output.prd.filename = '[name]@[chunkhash][ext]'

        //commonsChunk
        handleCommonsChunk.call(this, baseConfig)
        baseConfig.module.loaders.push({
          test: /\.less$/,
          loader: ykit.ExtractTextPlugin.extract(
            require.resolve('style-loader'),
            require.resolve('css-loader')
            + '?sourceMap!'
            + require.resolve('less-loader') + '?sourceMap'
          )
        })
        baseConfig.module.loaders.push({
          test: /\.(sass|scss)$/,
          loader: ykit.ExtractTextPlugin.extract(
            require.resolve('css-loader')
            + '?sourceMap!'
            + require.resolve('fast-sass-loader-china') + '?sourceMap'
          )
        })
        baseConfig.module.preLoaders.push({
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          loader: "eslint-loader"
        });

        if (this.env == 'prd') {
          baseConfig.plugins.push(assetsPluginInstance)
          baseConfig.plugins.push(compressPlugin)

        }
        return baseConfig;
      }
    }
  },
  server: {
    // true/false，默认 false，效果相当于 ykit server --hot
    hot: true,
    // true/false，默认 false，开启后可在当前打开的页面提示打包错误
    overlay: false
  },
  hooks: {},
  commands: []
};<|endoftext|>


<commit_before>
// When the page loads, all FAQs and responses will be shown and all topics in the menu bar will NOT be expanded. When
// the user clicks on a topic in the menu bar, it will expand and at the same time close any other topics that are open.
// If the user clicks the same topic again, the side panel menu will collapse, but stay on the same topic of questions.
// Page will automatically show and hide the FAQs and responses for a certain topic depending on if it is selected or not.

"use strict";
(function(){

    window.onload = function(){
        let panelTopics = document.querySelectorAll(".panel-grouping");
        for (let i = 0; i < panelTopics.length; i++) {
            panelTopics[i].querySelector(".topic").onclick = function() {
                let plusMinus = panelTopics[i].querySelector("span");
                let nameID = panelTopics[i].querySelector(".topic a").innerText.toLowerCase().split(" ").join("-");
                let openedTopic = (plusMinus && plusMinus.innerText !== "-" || nameID === "view-all-questions");
                closeAllPanelTopics(panelTopics, openedTopic);
                if (openedTopic) {
                    openSelectedTopic(panelTopics[i], plusMinus, nameID);
                }
            };
        }
    };

    /**
     * Closes all expanded panels in the menu bar and hides all FAQ's and responses.
     * @param {object} panelTopics - list of all the panel topics on FAQ page
     * @param {boolean} topicItemsActive - true/false if the side panel is already expanded
     */
    function closeAllPanelTopics(panelTopics, topicItemsActive) {
        for (let i = 0; i < panelTopics.length; i++) {
            let nameID = panelTopics[i].querySelector(".topic a").innerText.toLowerCase().split(" ").join("-");
            panelTopics[i].querySelector(".subtopics").classList.remove("subtopics-active");
            if (nameID !== "view-all-questions") {
                panelTopics[i].querySelector("span").innerText = "+";
                if (topicItemsActive) {
                    console.log(nameID)
                    document.getElementById(nameID).classList.remove("topic-items-active");
                }
            }
        }
    }

    /**
     * Expands the chosen topic in the menu bar and shows the FAQ's and responses.
     * @param {object} topic - the selected menu bar topic
     * @param {element} plusMinus - the selected menu bar topic's plus/minus sign element.
     * @param {String} nameID - the id of FAQ topic selected
     */
    function openSelectedTopic(topic, plusMinus, nameID) {
        if (nameID === "view-all-questions") {
            let questions = document.querySelectorAll(".topic-items");
            for (let i = 0; i < questions.length; i++) {
                questions[i].classList.add("topic-items-active");
            }
        } else {
            plusMinus.innerText = "-";
            topic.querySelector(".subtopics").classList.add("subtopics-active");
            document.getElementById(nameID).classList.add("topic-items-active");
        }
    }
})();<commit_msg>removes console.log from faq.js to help debug<commit_after>
// When the page loads, all FAQs and responses will be shown and all topics in the menu bar will NOT be expanded. When
// the user clicks on a topic in the menu bar, it will expand and at the same time close any other topics that are open.
// If the user clicks the same topic again, the side panel menu will collapse, but stay on the same topic of questions.
// Page will automatically show and hide the FAQs and responses for a certain topic depending on if it is selected or not.

"use strict";
(function(){

    window.onload = function(){
        let panelTopics = document.querySelectorAll(".panel-grouping");
        for (let i = 0; i < panelTopics.length; i++) {
            panelTopics[i].querySelector(".topic").onclick = function() {
                let plusMinus = panelTopics[i].querySelector("span");
                let nameID = panelTopics[i].querySelector(".topic a").innerText.toLowerCase().split(" ").join("-");
                let openedTopic = (plusMinus && plusMinus.innerText !== "-" || nameID === "view-all-questions");
                closeAllPanelTopics(panelTopics, openedTopic);
                if (openedTopic) {
                    openSelectedTopic(panelTopics[i], plusMinus, nameID);
                }
            };
        }
    };

    /**
     * Closes all expanded panels in the menu bar and hides all FAQ's and responses.
     * @param {object} panelTopics - list of all the panel topics on FAQ page
     * @param {boolean} topicItemsActive - true/false if the side panel is already expanded
     */
    function closeAllPanelTopics(panelTopics, topicItemsActive) {
        for (let i = 0; i < panelTopics.length; i++) {
            let nameID = panelTopics[i].querySelector(".topic a").innerText.toLowerCase().split(" ").join("-");
            panelTopics[i].querySelector(".subtopics").classList.remove("subtopics-active");
            if (nameID !== "view-all-questions") {
                panelTopics[i].querySelector("span").innerText = "+";
                if (topicItemsActive) {
                    document.getElementById(nameID).classList.remove("topic-items-active");
                }
            }
        }
    }

    /**
     * Expands the chosen topic in the menu bar and shows the FAQ's and responses.
     * @param {object} topic - the selected menu bar topic
     * @param {element} plusMinus - the selected menu bar topic's plus/minus sign element.
     * @param {String} nameID - the id of FAQ topic selected
     */
    function openSelectedTopic(topic, plusMinus, nameID) {
        if (nameID === "view-all-questions") {
            let questions = document.querySelectorAll(".topic-items");
            for (let i = 0; i < questions.length; i++) {
                questions[i].classList.add("topic-items-active");
            }
        } else {
            plusMinus.innerText = "-";
            topic.querySelector(".subtopics").classList.add("subtopics-active");
            document.getElementById(nameID).classList.add("topic-items-active");
        }
    }
})();<|endoftext|>