Fix project after git clean

This commit is contained in:
Damien Corneau 2015-05-18 18:14:42 +09:00
parent 9b249eab2c
commit 92013605b4
8 changed files with 3255 additions and 12 deletions

22
.gitignore vendored
View file

@ -24,16 +24,18 @@ zeppelin-web/bower_components
**nbproject/
**node/
logs/
run/
metastore_db/
*.log
jobs/
zan-repo/
drivers/
warehouse/
notebook/
local-repo/
# project level
/logs/
/run/
/metastore_db/
/*.log
/jobs/
/zan-repo/
/drivers/
/warehouse/
/notebook/
/local-repo/
**/sessions/
**/data/

View file

@ -37,6 +37,7 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warSourceDirectory>dist</warSourceDirectory>
<webXml>dist\WEB-INF\web.xml</webXml>
</configuration>
</plugin>

View file

@ -113,11 +113,11 @@ angular
templateUrl: 'app/home/home.html'
})
.when('/notebook/:noteId', {
templateUrl: 'app/notebook/notebooks.html',
templateUrl: 'app/notebook/notebook.html',
controller: 'NotebookCtrl'
})
.when('/notebook/:noteId/paragraph/:paragraphId?', {
templateUrl: 'app/notebook/notebooks.html',
templateUrl: 'app/notebook/notebook.html',
controller: 'NotebookCtrl'
})
.when('/interpreter', {

View file

@ -0,0 +1,494 @@
/* global confirm:false, alert:false */
/* jshint loopfunc: true */
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
/**
* @ngdoc function
* @name zeppelinWebApp.controller:NotebookCtrl
* @description
* # NotebookCtrl
* Controller of notes, manage the note (update)
*
*/
angular.module('zeppelinWebApp').controller('NotebookCtrl', function($scope, $route, $routeParams, $location, $rootScope, $http) {
$scope.note = null;
$scope.showEditor = false;
$scope.editorToggled = false;
$scope.tableToggled = false;
$scope.viewOnly = false;
$scope.looknfeelOption = [ 'default', 'simple', 'report'];
$scope.cronOption = [
{name: 'None', value : undefined},
{name: '1m', value: '0 0/1 * * * ?'},
{name: '5m', value: '0 0/5 * * * ?'},
{name: '1h', value: '0 0 0/1 * * ?'},
{name: '3h', value: '0 0 0/3 * * ?'},
{name: '6h', value: '0 0 0/6 * * ?'},
{name: '12h', value: '0 0 0/12 * * ?'},
{name: '1d', value: '0 0 0 * * ?'}
];
$scope.interpreterSettings = [];
$scope.interpreterBindings = [];
var angularObjectRegistry = {};
$scope.getCronOptionNameFromValue = function(value) {
if (!value) {
return '';
}
for (var o in $scope.cronOption) {
if ($scope.cronOption[o].value===value) {
return $scope.cronOption[o].name;
}
}
return value;
};
/** Init the new controller */
var initNotebook = function() {
$rootScope.$emit('sendNewEvent', {op: 'GET_NOTE', data: {id: $routeParams.noteId}});
};
initNotebook();
/** Remove the note and go back tot he main page */
/** TODO(anthony): In the nearly future, go back to the main page and telle to the dude that the note have been remove */
$scope.removeNote = function(noteId) {
var result = confirm('Do you want to delete this notebook?');
if (result) {
$rootScope.$emit('sendNewEvent', {op: 'DEL_NOTE', data: {id: noteId}});
$location.path('/#');
}
};
$scope.runNote = function() {
var result = confirm('Run all paragraphs?');
if (result) {
$scope.$broadcast('runParagraph');
}
};
$scope.toggleAllEditor = function() {
if ($scope.editorToggled) {
$scope.$broadcast('closeEditor');
} else {
$scope.$broadcast('openEditor');
}
$scope.editorToggled = !$scope.editorToggled;
};
$scope.showAllEditor = function() {
$scope.$broadcast('openEditor');
};
$scope.hideAllEditor = function() {
$scope.$broadcast('closeEditor');
};
$scope.toggleAllTable = function() {
if ($scope.tableToggled) {
$scope.$broadcast('closeTable');
} else {
$scope.$broadcast('openTable');
}
$scope.tableToggled = !$scope.tableToggled;
};
$scope.showAllTable = function() {
$scope.$broadcast('openTable');
};
$scope.hideAllTable = function() {
$scope.$broadcast('closeTable');
};
$scope.isNoteRunning = function() {
var running = false;
if(!$scope.note){ return false; }
for (var i=0; i<$scope.note.paragraphs.length; i++) {
if ( $scope.note.paragraphs[i].status === 'PENDING' || $scope.note.paragraphs[i].status === 'RUNNING') {
running = true;
break;
}
}
return running;
};
$scope.setLookAndFeel = function(looknfeel) {
$scope.note.config.looknfeel = looknfeel;
$scope.setConfig();
};
/** Set cron expression for this note **/
$scope.setCronScheduler = function(cronExpr) {
$scope.note.config.cron = cronExpr;
$scope.setConfig();
};
/** Update note config **/
$scope.setConfig = function(config) {
if(config) {
$scope.note.config = config;
}
$rootScope.$emit('sendNewEvent', {op: 'NOTE_UPDATE', data: {id: $scope.note.id, name: $scope.note.name, config : $scope.note.config}});
};
/** Update the note name */
$scope.sendNewName = function() {
$scope.showEditor = false;
if ($scope.note.name) {
$rootScope.$emit('sendNewEvent', {op: 'NOTE_UPDATE', data: {id: $scope.note.id, name: $scope.note.name, config : $scope.note.config}});
}
};
/** update the current note */
$scope.$on('setNoteContent', function(event, note) {
$scope.paragraphUrl = $routeParams.paragraphId;
$scope.asIframe = $routeParams.asIframe;
if ($scope.paragraphUrl) {
note = cleanParagraphExcept($scope.paragraphUrl, note);
$rootScope.$emit('setIframe', $scope.asIframe);
}
if ($scope.note === null) {
$scope.note = note;
} else {
updateNote(note);
}
initializeLookAndFeel();
//open interpreter binding setting when there're none selected
getInterpreterBindings(getInterpreterBindingsCallBack);
});
var initializeLookAndFeel = function() {
if (!$scope.note.config.looknfeel) {
$scope.note.config.looknfeel = 'default';
} else {
$scope.viewOnly = $scope.note.config.looknfeel === 'report' ? true : false;
}
$rootScope.$emit('setLookAndFeel', $scope.note.config.looknfeel);
};
var cleanParagraphExcept = function(paragraphId, note) {
var noteCopy = {};
noteCopy.id = note.id;
noteCopy.name = note.name;
noteCopy.config = note.config;
noteCopy.info = note.info;
noteCopy.paragraphs = [];
for (var i=0; i<note.paragraphs.length; i++) {
if (note.paragraphs[i].id === paragraphId) {
noteCopy.paragraphs[0] = note.paragraphs[i];
if (!noteCopy.paragraphs[0].config) {
noteCopy.paragraphs[0].config = {};
}
noteCopy.paragraphs[0].config.editorHide = true;
noteCopy.paragraphs[0].config.tableHide = false;
break;
}
}
return noteCopy;
};
$scope.$on('moveParagraphUp', function(event, paragraphId) {
var newIndex = -1;
for (var i=0; i<$scope.note.paragraphs.length; i++) {
if ($scope.note.paragraphs[i].id === paragraphId) {
newIndex = i-1;
break;
}
}
if (newIndex<0 || newIndex>=$scope.note.paragraphs.length) {
return;
}
$rootScope.$emit('sendNewEvent', { op: 'MOVE_PARAGRAPH', data : {id: paragraphId, index: newIndex}});
});
// create new paragraph on current position
$scope.$on('insertParagraph', function(event, paragraphId) {
var newIndex = -1;
for (var i=0; i<$scope.note.paragraphs.length; i++) {
if ($scope.note.paragraphs[i].id === paragraphId) {
newIndex = i+1;
break;
}
}
if (newIndex === $scope.note.paragraphs.length) {
alert('Cannot insert after the last paragraph.');
return;
}
if (newIndex < 0 || newIndex > $scope.note.paragraphs.length) {
return;
}
$rootScope.$emit('sendNewEvent', { op: 'INSERT_PARAGRAPH', data : {index: newIndex}});
});
$scope.$on('moveParagraphDown', function(event, paragraphId) {
var newIndex = -1;
for (var i=0; i<$scope.note.paragraphs.length; i++) {
if ($scope.note.paragraphs[i].id === paragraphId) {
newIndex = i+1;
break;
}
}
if (newIndex<0 || newIndex>=$scope.note.paragraphs.length) {
return;
}
$rootScope.$emit('sendNewEvent', { op: 'MOVE_PARAGRAPH', data : {id: paragraphId, index: newIndex}});
});
$scope.$on('moveFocusToPreviousParagraph', function(event, currentParagraphId){
var focus = false;
for (var i=$scope.note.paragraphs.length-1; i>=0; i--) {
if (focus === false ) {
if ($scope.note.paragraphs[i].id === currentParagraphId) {
focus = true;
continue;
}
} else {
var p = $scope.note.paragraphs[i];
if (!p.config.hide && !p.config.editorHide && !p.config.tableHide) {
$scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id);
break;
}
}
}
});
$scope.$on('moveFocusToNextParagraph', function(event, currentParagraphId){
var focus = false;
for (var i=0; i<$scope.note.paragraphs.length; i++) {
if (focus === false ) {
if ($scope.note.paragraphs[i].id === currentParagraphId) {
focus = true;
continue;
}
} else {
var p = $scope.note.paragraphs[i];
if (!p.config.hide && !p.config.editorHide && !p.config.tableHide) {
$scope.$broadcast('focusParagraph', $scope.note.paragraphs[i].id);
break;
}
}
}
});
var updateNote = function(note) {
/** update Note name */
if (note.name !== $scope.note.name) {
console.log('change note name: %o to %o', $scope.note.name, note.name);
$scope.note.name = note.name;
}
$scope.note.config = note.config;
$scope.note.info = note.info;
var newParagraphIds = note.paragraphs.map(function(x) {return x.id;});
var oldParagraphIds = $scope.note.paragraphs.map(function(x) {return x.id;});
var numNewParagraphs = newParagraphIds.length;
var numOldParagraphs = oldParagraphIds.length;
/** add a new paragraph */
if (numNewParagraphs > numOldParagraphs) {
for (var index in newParagraphIds) {
if (oldParagraphIds[index] !== newParagraphIds[index]) {
$scope.note.paragraphs.splice(index, 0, note.paragraphs[index]);
break;
}
}
}
/** update or move paragraph */
if (numNewParagraphs === numOldParagraphs) {
for (var idx in newParagraphIds) {
var newEntry = note.paragraphs[idx];
if (oldParagraphIds[idx] === newParagraphIds[idx]) {
$scope.$broadcast('updateParagraph', {paragraph: newEntry});
} else {
// move paragraph
var oldIdx = oldParagraphIds.indexOf(newParagraphIds[idx]);
$scope.note.paragraphs.splice(oldIdx, 1);
$scope.note.paragraphs.splice(idx, 0, newEntry);
// rebuild id list since paragraph has moved.
oldParagraphIds = $scope.note.paragraphs.map(function(x) {return x.id;});
}
}
}
/** remove paragraph */
if (numNewParagraphs < numOldParagraphs) {
for (var oldidx in oldParagraphIds) {
if(oldParagraphIds[oldidx] !== newParagraphIds[oldidx]) {
$scope.note.paragraphs.splice(oldidx, 1);
break;
}
}
}
};
var getInterpreterBindings = function(callback) {
$http.get(getRestApiBase()+ '/notebook/interpreter/bind/' +$scope.note.id).
success(function(data, status, headers, config) {
$scope.interpreterBindings = data.body;
$scope.interpreterBindingsOrig = jQuery.extend(true, [], $scope.interpreterBindings); // to check dirty
if (callback) {
callback();
}
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
};
var getInterpreterBindingsCallBack = function() {
var selected = false;
for (var i in $scope.interpreterBindings) {
var setting = $scope.interpreterBindings[i];
if (setting.selected) {
selected = true;
break;
}
}
if (!selected) {
// make default selection
var selectedIntp = {};
for (var i in $scope.interpreterBindings) {
var setting = $scope.interpreterBindings[i];
if (!selectedIntp[setting.group]) {
setting.selected = true;
selectedIntp[setting.group] = true;
}
}
$scope.showSetting = true;
}
};
$scope.interpreterSelectionListeners = {
accept : function(sourceItemHandleScope, destSortableScope) {return true;},
itemMoved: function (event) {},
orderChanged: function(event) {}
};
$scope.openSetting = function() {
$scope.showSetting = true;
getInterpreterBindings();
};
$scope.closeSetting = function() {
if (isSettingDirty()) {
var result = confirm('Changes will be discarded');
if (!result) {
return;
}
}
$scope.showSetting = false;
};
$scope.saveSetting = function() {
var selectedSettingIds = [];
for (var no in $scope.interpreterBindings) {
var setting = $scope.interpreterBindings[no];
if (setting.selected) {
selectedSettingIds.push(setting.id);
}
}
$http.put(getRestApiBase() + '/notebook/interpreter/bind/' + $scope.note.id,
selectedSettingIds).
success(function(data, status, headers, config) {
console.log('Interpreter binding %o saved', selectedSettingIds);
$scope.showSetting = false;
}).
error(function(data, status, headers, config) {
console.log('Error %o %o', status, data.message);
});
};
$scope.toggleSetting = function() {
if ($scope.showSetting) {
$scope.closeSetting();
} else {
$scope.openSetting();
}
};
var isSettingDirty = function() {
if (angular.equals($scope.interpreterBindings, $scope.interpreterBindingsOrig)) {
return false;
} else {
return true;
}
};
$scope.$on('angularObjectUpdate', function(event, data) {
if (data.noteId === $scope.note.id) {
var scope = $rootScope.compiledScope;
var varName = data.angularObject.name;
if (angular.equals(data.angularObject.object, scope[varName])) {
// return when update has no change
return;
}
if (!angularObjectRegistry[varName]) {
angularObjectRegistry[varName] = {
interpreterGroupId : data.interpreterGroupId,
}
}
angularObjectRegistry[varName].skipEmit = true;
if (!angularObjectRegistry[varName].clearWatcher) {
angularObjectRegistry[varName].clearWatcher = scope.$watch(varName, function(newValue, oldValue) {
if (angularObjectRegistry[varName].skipEmit) {
angularObjectRegistry[varName].skipEmit = false;
return;
}
$rootScope.$emit('sendNewEvent', {
op: 'ANGULAR_OBJECT_UPDATED',
data: {
noteId: $routeParams.noteId,
name:varName,
value:newValue,
interpreterGroupId:angularObjectRegistry[varName].interpreterGroupId
}
});
});
}
scope[varName] = data.angularObject.object;
}
});
var isFunction = function(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
});

View file

@ -0,0 +1,508 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.paragraph-col {
margin: 0 0 0 0px;
padding: 0 0 0 0px;
}
.paragraph {
padding: 2px 8px 4px 8px;
min-height: 32px;
}
.paragraph .tableDisplay .hljs {
background: none;
}
.paragraph .ace_print-margin {
background: none !important;
}
.paragraphAsIframe{
padding: 0px 0px 0px 0px;
margin-top: -79px;
margin-left: -10px;
margin-right: -10px;
}
.paragraphAsIframe .control {
background-color: rgba(255,255,255,0.9);
border-top: 1px solid #EFEFEF;
display: none;
float: right;
color: #999;
margin-top: -9px;
margin-right:0px;
position:absolute;
clear:both;
right:25px;
/*z-index:10;*/
}
.paragraphAsIframe table {
margin-bottom: 0px;
}
.paragraphAsIframe .editor {
width: 100%;
border-left: 4px solid #EEEEEE;
background: rgba(255, 255, 255, 0.9);
}
.paragraphAsIframe .text {
white-space: pre;
display: block;
unicode-bidi: embed;
display: block !important;
margin: 0 0 10px!important;
font-size: 12px!important;
line-height: 1.42857143!important;
word-break: break-all!important;
word-wrap: break-word!important;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
}
.ace_marker-layer .ace_selection {
z-index: 0 !important;
}
.ace_marker-layer .ace_selected-word {
z-index: 0 !important;
}
.labelBtn {
padding: .2em .6em .3em;
font-size: 75%;
font-weight: bold;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
border-radius: .25em;
}
.note-jump {
margin-top: 20px;
}
.noteBtnfa {
margin-left: 3px;
}
.control span {
margin-left: 4px;
}
.control {
padding: 4px;
}
.paragraph-space {
margin-bottom: 5px;
padding: 10px !important;
}
.paragraph .control {
background-color: rgba(255,255,255,0.85);
/*display: none;*/
float: right;
color: #999;
margin-top: 1px;
margin-right: 5px;
position:absolute;
clear:both;
right:15px;
top: 16px;
text-align:right;
font-size:12px;
}
.paragraph .control li{
font-size:12px;
margin-bottom:4px;
color: #333333;
}
.paragraph table {
margin-bottom: 0px;
}
.paragraph .title {
margin: 3px 0px 0px 0px;
height: 20px;
font-size: 12px;
}
.paragraph .title div {
width: 80%;
font-weight: bold;
font-family: 'Roboto', sans-serif;
font-size: 17px !important;
text-transform: capitalize;
}
.paragraph .title input {
width: 80%;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 0px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
text-transform: capitalize;
font-family: 'Roboto', sans-serif;
font-size: 14px!important;
}
.paragraph .editor {
width: 100%;
border-left: 4px solid #DDDDDD;
background: rgba(255, 255, 255, 0.0);
margin: 7px 0 2px 0px;
}
.paragraph .text {
white-space: pre;
display: block;
unicode-bidi: embed;
display: block !important;
margin: 0 0 0px !important;
line-height: 1.42857143 !important;
word-break: break-all !important;
word-wrap: break-word !important;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
font-size: 12px !important;
margin-bottom: 5px !important;
}
.paragraph p {
margin : 0 0 0 0px;
}
.paragraph div svg {
width : 100%;
}
.ace-tm {
background-color: #FFFFFF;
color: black;
}
.ace_editor {
position: relative;
overflow: hidden;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
font-size: 12px;
line-height: normal;
direction: ltr;
}
.ace_hidden-cursors {opacity:0}
/** Remove z-index to ace */
.ace_text-input, .ace_gutter, .ace_layer,
.emacs-mode, .ace_text-layer, .ace_cursor-layer,
.ace_cursor, .ace_scrollbar {
z-index:auto !important;
}
/** Force opacity:0 to textarea in writing texts **/
.ace_text-input.ace_composition {
opacity: 0 !important;
}
#main .emacs-mode .ace_cursor {
background-color:#C0C0C0!important;
border: none !important;
}
.paragraph .status {
font-size: 10px;
color: #AAAAAA;
text-align: right;
visibility: hidden;
}
.paragraph .runControl {
font-size: 1px;
color: #AAAAAA;
height:4px;
margin: 1px 0px 0px 0px;
}
.paragraph .runControl .progress {
position: relative;
width:100%;
height:4px;
z-index:100;
border-radius: 0px;
}
.paragraph .runControl .progress .progress-bar {
z-index:100;
}
.paragraph .executionTime {
color: #999;
font-size: 10px;
font-family: 'Roboto', sans-serif;
}
.disable {
opacity:0.4!important;
pointer-events: none;
}
.noteAction {
margin-left: -10px;
margin-right: -10px;
margin-top: -10px;
font-family: 'Roboto', sans-serif;
}
.noteAction li{
font-size:12px;
margin-bottom:4px;
color: #333333;
}
.new_h3 {
margin-top: 1px;
padding-top: 7px;
}
.form-control2 {
width: 100%;
margin-left: 15px;
font-size: 29px;
line-height: 1.42857143;
color: #555;
background-color: #fff;
background-image: none;
border: 1px solid #ccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
}
.form-control-static2 {
padding-top: 7px;
font-size: 29px;
margin-left: 15px;
padding-bottom: 7px;
margin-bottom: 0;
display: inline-block;
}
/* panel default */
.panel-default {
/* border: none; */
border-color: #DDDDDD;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
}
.panel-group .panel-default {
/*border: solid #e5e5e5;
border-width: 1px 1px 2px;*/
}
.panel-default > .panel-heading {
color: #34495e;
background-color: #ffffff;
border-color: #e5e5e5;
}
.panel-default > .panel-heading + .panel-collapse .panel-body {
border-top-color: #e5e5e5;
}
.panel-default > .panel-heading > .dropdown .caret {
border-color: #ecf0f1 transparent;
}
.panel-default > .panel-footer + .panel-collapse .panel-body {
border-bottom-color: #e5e5e5;
}
.panel-body-heading {
position: relative;
display: block;
padding-left: 10px;
padding-top: 15px;
padding-bottom: 15px;
}
.paragraph-margin {
margin-right: 2px;
margin-left: 2px;
}
.tableDisplay img {
display: block;
max-width: 100%;
height: auto;
}
.tableDisplay .btn-group span {
margin: 10px 0px 0px 10px;
font-size:12px;
}
.tableDisplay .btn-group span a {
cursor:pointer;
}
.tableDisplay .option {
padding: 5px 5px 5px 5px;
font-size:12px;
height:auto;
overflow : auto;
/*min-height: 200px;*/
border-top: 1px solid #ecf0f1;
}
.tableDisplay .option .columns {
height: 100%;
}
.tableDisplay .option .columns ul {
background-color: white;
/*min-width: 100px;*/
width:auto;
padding: 3px 3px 3px 3px;
height : 150px;
border: 1px solid #CCC;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
-webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s;
-o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;
}
.tableDisplay .option .columns ul li {
margin: 3px 3px 3px 3px;
}
.tableDisplay .option .columns ul li span {
cursor: pointer;
margin-left: 3px;
margin-top: 3px;
}
.tableDisplay .option .columns ul li div ul { /* aggregation menu */
width:auto;
height:auto;
}
.tableDisplay .option .columns ul li div ul li a {
padding: 0px;
margin: 0px;
cursor: pointer;
}
.tableDisplay .option .columns a:focus,
.tableDisplay .option .columns a:hover {
text-decoration: none;
outline: 0;
outline-offset: 0px;
}
.graphContainer {
position:relative;
margin-bottom: 5px;
overflow: visible;
}
.noOverflow {
overflow: hidden !important;
}
.graphContainer .table {
overflow: hidden;
margin-bottom: 5px !important;
}
.allFields {
margin-bottom: 10px;
}
.noDot {
list-style-type: none;
padding-left:5px;
}
.liVertical {
display:block;
float:left;
padding: 5px;
}
.row {
margin-right: 0px !important
}
.lightBold {
font-weight: 500;
}
.resizable-helper {
border: 3px solid #DDDDDD;
}
/* note setting panel */
.setting {
background-color: white;
padding: 10px 15px 15px 15px;
margin-left: -10px;
margin-right: -10px;
font-family: 'Roboto', sans-serif;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
border-bottom: 1px solid #E5E5E5;
}
.setting .interpreterSettings {
list-style-type: none;
background-color: #EFEFEF;
padding: 10px 10px 10px 10px;
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.15);
border: 1px solid #E5E5E5;
}
.setting .interpreterSettings div div {
margin: 2px 0px 2px 0px;
}
.setting .interpreterSettings div div {
cursor: pointer;
}
.setting .modal-header {
border: 0px;
}
.setting .modal-body {
border: 0px;
}
.setting .modal-footer {
border: 0px;
}

View file

@ -0,0 +1,170 @@
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Here the controller <NotebookCtrl> is not needed because explicitly set in the app.js (route) -->
<div>
<div class="noteAction" ng-show="note.id && !paragraphUrl">
<h3 class="new_h3">
<input type="text" class="form-control2" placeholder="{{note.name || 'Note ' + note.id}}" style="width:200px;"
ng-show="showEditor" ng-model="note.name" ng-enter="sendNewName()" ng-delete="showEditor = false" autofocus/>
<p class="form-control-static2" ng-click="showEditor = true" ng-show="!showEditor">{{note.name || 'Note ' + note.id}}</p>
<span class="labelBtn btn-group">
<button type="button"
class="btn btn-default btn-xs"
ng-click="runNote()"
ng-if="!isNoteRunning()"
tooltip-placement="top" tooltip="Run all the note">
<i class="icon-control-play"></i>
</button>
<button type="button"
class="btn btn-default btn-xs"
ng-click="toggleAllEditor()"
ng-hide="viewOnly"
tooltip-placement="top" tooltip="Show/hide the code">
<i ng-class="editorToggled ? 'fa icon-size-actual' : 'fa icon-size-fullscreen'"></i></button>
<button type="button"
class="btn btn-default btn-xs"
ng-click="toggleAllTable()"
ng-hide="viewOnly"
tooltip-placement="top" tooltip="Show/hide the output">
<i ng-class="tableToggled ? 'fa icon-notebook' : 'fa icon-book-open'"></i></button>
<button type="button"
class="btn btn-default btn-xs"
ng-click="removeNote(note.id)"
ng-hide="viewOnly"
tooltip-placement="top" tooltip="Remove the notebook">
<i class="icon-trash"></i></button>
</span>
<span ng-hide="viewOnly">
<div class="labelBtn btn-group">
<div class="btn btn-default btn-xs dropdown-toggle"
type="button"
data-toggle="dropdown"
ng-class="{ 'btn-info' : note.config.cron, 'btn-danger' : note.info.cron, 'btn-default' : !note.config.cron}">
<span class="fa fa-clock-o"></span> {{getCronOptionNameFromValue(note.config.cron)}}
</div>
<ul class="dropdown-menu" role="menu" style="width:300px">
<li>
<div style="padding:10px 20px 0 20px;font-weight:normal;word-wrap:break-word">
Run note with cron scheduler.
Either choose from<br/>preset or write your own <a href="http://www.quartz-scheduler.org/documentation/quartz-1.x/tutorials/crontrigger" target=_blank>cron expression</a>.
<br/><br/>
<span>- Preset</span>
<a ng-repeat="cr in cronOption"
type="button"
ng-click="setCronScheduler(cr.value)"
style="cursor:pointer"
dropdown-input>{{cr.name}}</a>
<br/><br/>
<span>- Cron expression</span>
<input type="text"
ng-model="note.config.cron"
ng-change="setCronScheduler(note.config.cron)"
dropdown-input>
</input>
<p ng-show="note.info.cron"
style="color:red">
{{note.info.cron}}
</p>
</div>
</li>
</ul>
</div>
</span>
<div class="pull-right"
style="margin-top:15px; margin-right:15px; font-size:15px;">
<span style="position:relative; top:3px; margin-right:4px; cursor:pointer"
data-toggle="modal"
data-target="#shortcutModal"
tooltip-placement="top" tooltip="List of shortcut">
<i class="icon-question"></i>
</span>
<span style="position:relative; top:2px; margin-right:4px; cursor:pointer;"
ng-click="toggleSetting()"
tooltip-placement="top" tooltip="Interpreter binding">
<i class="fa fa-cog"
ng-style="{color: showSetting ? '#3071A9' : 'black' }"></i>
</span>
<span class="btn-group">
<button type="button"
class="btn btn-default btn-xs dropdown-toggle"
data-toggle="dropdown">
{{note.config.looknfeel}} <span class="caret"></span>
</button>
<ul class="dropdown-menu pull-right" role="menu">
<li ng-repeat="looknfeel in looknfeelOption">
<a style="cursor:pointer"
ng-click="setLookAndFeel(looknfeel)">{{looknfeel}}</a>
</li>
</ul>
</span>
</div>
</h3>
</div>
<!-- settings -->
<div ng-show="showSetting"
class="setting">
<div>
<h4>Settings</h4>
</div>
<hr />
<div>
<h5>Interpreter binding</h5>
<p>
Bind interpreter for this note.
Click to Bind/Unbind interpreter.
Drag and drop to reorder interpreters. <br />
The first interpreter on the list becomes default. To create/remove interpreters, go to <a href="/#/interpreter">Interpreter</a> menu.
</p>
<div class="interpreterSettings"
as-sortable="interpreterSelectionListeners" data-ng-model="interpreterBindings">
<div data-ng-repeat="item in interpreterBindings" as-sortable-item>
<div as-sortable-item-handle
ng-click="item.selected = !item.selected"
class="btn"
ng-class="{'btn-info': item.selected, 'btn-default': !item.selected}"><font style="font-size:16px">{{item.name}}</font> <small><span ng-repeat="intp in item.interpreters"><span ng-show="!$first">, </span>%{{intp.name}}</span></small></div>
</div>
</div>
</div>
<br />
<div>
<button class="btn btn-primary" ng-click="saveSetting()">Save</button>
<button class="btn btn-default" ng-click="closeSetting()">Cancel</button>
</div>
</div>
<div class="note-jump"></div>
<!-- Include the paragraphs according to the note -->
<div id="{{currentParagraph.id}}_paragraphColumn_main"
ng-repeat="currentParagraph in note.paragraphs"
ng-controller="ParagraphCtrl"
ng-Init="init(currentParagraph)"
ng-class="columnWidthClass(currentParagraph.config.colWidth)"
class="paragraph-col">
<div id="{{currentParagraph.id}}_paragraphColumn"
ng-include src="'app/notebook/paragraph/paragraph.html'"
ng-class="{'paragraph-space box paragraph-margin': !asIframe, 'focused': paragraphFocused}"
ng-hide="currentParagraph.config.tableHide && viewOnly">
</div>
</div>
<div style="clear:both;height:10px"></div>
</div>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,449 @@
<!--
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div id="{{paragraph.id}}_container"
ng-class="{'paragraph outlineOnFocus': !asIframe, 'paragraphAsIframe': asIframe}">
<div ng-show="paragraph.config.title"
id="{{paragraph.id}}_title"
class="title">
<input type="text"
placeholder="Edit title"
ng-model="paragraph.title"
ng-show="showTitleEditor"
ng-delete="showTitleEditor = false"
ng-enter="setTitle(); showTitleEditor = false"/>
<div ng-click="showTitleEditor = !asIframe && !viewOnly"
ng-show="!showTitleEditor"
ng-bind-html="paragraph.title || 'Untitled'">
</div>
</div>
<div>
<div ng-show="!paragraph.config.editorHide">
<div id="{{paragraph.id}}_editor"
style="opacity: 1;"
class="editor"
ui-ace="{
onLoad : aceLoaded,
onChange: aceChanged,
require : ['ace/ext/language_tools']
}"
ng-model="paragraph.text"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }">
</div>
</div>
<div id="{{paragraph.id}}_runControl" class="runControl">
<div ng-if="(getProgress()<=0 || getProgress()>=100) && (paragraph.status=='RUNNING' )">
<div id="{{paragraph.id}}_progress"
class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" style="width:100%;"></div>
<span class="sr-only"></span>
</div>
</div>
<div ng-if="getProgress()>0 && getProgress()<100 && paragraph.status=='RUNNING'">
<div id="{{paragraph.id}}_progress"
class="progress">
<div class="progress-bar" role="progressbar" style="width:{{getProgress()}}%;"></div>
<span class="sr-only">{{getProgress()}}%</span>
</div>
</div>
</div>
<form id="{{paragraph.id}}_form" role="form"
ng-show="!paragraph.config.tableHide && !asIframe"
class="form-horizontal">
<div class="form-group"
ng-repeat="formulaire in paragraph.settings.forms"
ng-Init="loadForm(formulaire, paragraph.settings.params)">
<label class="col-sm-1 control-label input-sm" ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }">{{formulaire.name}}</label>
<div class="col-sm-3">
<input class="form-control input-sm"
ng-if="!paragraph.settings.forms[formulaire.name].options"
ng-enter="runParagraph(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
name="{{formulaire.name}}">
</input>
<select class="form-control input-sm"
ng-if="paragraph.settings.forms[formulaire.name].options"
ng-change="runParagraph(getEditorValue())"
ng-model="paragraph.settings.params[formulaire.name]"
ng-class="{'disable': paragraph.status == 'RUNNING' || paragraph.status == 'PENDING' }"
name="{{formulaire.name}}"
ng-options="option.value as (option.displayName||option.value) for option in paragraph.settings.forms[formulaire.name].options"
>
<!--
<option
ng-repeat="option in paragraph.settings.forms[formulaire.name].options"
value="{{option.value}}"
>{{option.displayName || option.value}}
</option>
-->
</select>
</div>
</div>
</form>
<!-- Rendering -->
<div class='tableDisplay' ng-show="!paragraph.config.tableHide">
<div id="{{paragraph.id}}_switch"
ng-if="paragraph.result.type == 'TABLE' && !asIframe && !viewOnly"
class="btn-group"
style='margin-bottom: 10px;'>
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active': isGraphMode('table')}"
ng-click="setGraphMode('table', true)" ><i class="fa fa-table"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active': isGraphMode('multiBarChart')}"
ng-click="setGraphMode('multiBarChart', true)"><i class="fa fa-bar-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active': isGraphMode('pieChart')}"
ng-click="setGraphMode('pieChart', true)"><i class="fa fa-pie-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active': isGraphMode('stackedAreaChart')}"
ng-click="setGraphMode('stackedAreaChart', true)"><i class="fa fa-area-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active': isGraphMode('lineChart')}"
ng-click="setGraphMode('lineChart', true)"><i class="fa fa-line-chart"></i>
</button>
<button type="button" class="btn btn-default btn-sm"
ng-class="{'active': isGraphMode('scatterChart')}"
ng-click="setGraphMode('scatterChart', true)"><i class="cf cf-scatter-chart"></i>
</button>
</div>
<span ng-if="getResultType()=='TABLE' && getGraphMode()!='table' && !asIframe && !viewOnly"
style="margin-left:10px; cursor:pointer; display: inline-block; vertical-align:top; position: relative; line-height:30px;">
<a class="btnText" ng-if="paragraph.config.graph.optionOpen"
ng-click="toggleGraphOption()">
settings <span class="fa fa-caret-up"></span>
</a>
<a class="btnText" ng-if="!paragraph.config.graph.optionOpen"
ng-click="toggleGraphOption()" >
settings <span class="fa fa-caret-down"></span>
</a>
</span>
<div class="option lightBold" style="overflow: visible;"
ng-if="getResultType()=='TABLE' && getGraphMode()!='table'
&& paragraph.config.graph.optionOpen && !asIframe && !viewOnly">
All fields:
<div class="allFields row">
<ul class="noDot">
<li class="liVertical" ng-repeat="col in paragraph.result.columnNames">
<div class="btn btn-default btn-xs"
data-drag="true"
data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
ng-model="paragraph.result.columnNames"
jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
{{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
</div>
</li>
</ul>
</div>
<div class="row" ng-if="getGraphMode()!='scatterChart'">
<div class="col-md-4">
<span class="columns lightBold">
Keys
<ul data-drop="true"
ng-model="paragraph.config.graph.keys"
jqyoui-droppable="{multiple:true, onDrop:'onGraphOptionChange()'}"
class="list-unstyled">
<li ng-repeat="item in paragraph.config.graph.keys">
<button class="btn btn-primary btn-xs">
{{item.name}} <span class="fa fa-close" ng-click="removeGraphOptionKeys($index)"></span>
</button>
</li>
</ul>
</span>
</div>
<div class="col-md-4">
<span class="columns lightBold">
Groups
<ul data-drop="true"
ng-model="paragraph.config.graph.groups"
jqyoui-droppable="{multiple:true, onDrop:'onGraphOptionChange()'}"
class="list-unstyled">
<li ng-repeat="item in paragraph.config.graph.groups">
<button class="btn btn-success btn-xs">
{{item.name}} <span class="fa fa-close" ng-click="removeGraphOptionGroups($index)"></span>
</button>
</li>
</ul>
</span>
</div>
<div class="col-md-4">
<span class="columns lightBold">
Values
<ul data-drop="true"
ng-model="paragraph.config.graph.values"
jqyoui-droppable="{multiple:true, onDrop:'onGraphOptionChange()'}"
class="list-unstyled">
<li ng-repeat="item in paragraph.config.graph.values">
<div class="btn-group">
<div class="btn btn-info btn-xs dropdown-toggle"
type="button"
data-toggle="dropdown">
{{item.name | limitTo: 30}}{{item.name.length > 30 ? '...' : ''}}
<font style="color:#EEEEEE;"><span class="lightBold" style="text-transform: uppercase;">{{item.aggr}}</span></font>
<span class="fa fa-close" ng-click="removeGraphOptionValues($index)"></span>
</div>
<ul class="dropdown-menu" role="menu">
<li ng-click="setGraphOptionValueAggr($index, 'sum')"><a>sum</a></li>
<li ng-click="setGraphOptionValueAggr($index, 'count')"><a>count</a></li>
<li ng-click="setGraphOptionValueAggr($index, 'avg')"><a>avg</a></li>
<li ng-click="setGraphOptionValueAggr($index, 'min')"><a>min</a></li>
<li ng-click="setGraphOptionValueAggr($index, 'max')"><a>max</a></li>
</ul>
</div>
</li>
</ul>
</span>
</div>
</div>
<div class="row" ng-if="getGraphMode()=='scatterChart'">
<div class="col-md-3">
<span class="columns lightBold">
xAxis
<ul data-drop="true"
ng-model="paragraph.config.graph.scatter.xAxis"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.scatter.xAxis">
<button class="btn btn-primary btn-xs">
{{paragraph.config.graph.scatter.xAxis.name}} <span class="fa fa-close" ng-click="removeScatterOptionXaxis($index)"></span>
</button>
</li>
</ul>
</span>
</div>
<div class="col-md-3">
<span class="columns lightBold">
yAxis
<ul data-drop="true"
ng-model="paragraph.config.graph.scatter.yAxis"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.scatter.yAxis">
<button class="btn btn-success btn-xs">
{{paragraph.config.graph.scatter.yAxis.name}} <span class="fa fa-close" ng-click="removeScatterOptionYaxis($index)"></span>
</button>
</li>
</ul>
</span>
</div>
<div class="col-md-3">
<span class="columns lightBold">
group
<ul data-drop="true"
ng-model="paragraph.config.graph.scatter.group"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.scatter.group">
<button class="btn btn-info btn-xs">
{{paragraph.config.graph.scatter.group.name}} <span class="fa fa-close" ng-click="removeScatterOptionGroup($index)"></span>
</button>
</li>
</ul>
</span>
</div>
<div class="col-md-3">
<span class="columns lightBold">
size
<a tabindex="0" class="fa fa-info-circle" role="button" popover-placement="top"
popover-trigger="focus"
popover-html-unsafe="<li>Size option is valid only when you drop numeric field here.</li>
<li>When data in each axis are discrete, 'number of values in corresponding coordinate' will be used as size.</li>
<li>Zeppelin consider values as discrete when the values contain string value or the number of distinct values are bigger than 5% of total number of values.</li>
<li>Size field button turns to grey when the option you chose is not valid.</li>"></a>
<ul data-drop="true"
ng-model="paragraph.config.graph.scatter.size"
jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
class="list-unstyled"
style="height:36px">
<li ng-if="paragraph.config.graph.scatter.size">
<button class="btn btn-xs" style="color:white" ng-class="{'btn-warning': isValidSizeOption(paragraph.config.graph.scatter, paragraph.result.rows)}">
{{paragraph.config.graph.scatter.size.name}} <span class="fa fa-close" ng-click="removeScatterOptionSize($index)"></span>
</button>
</li>
</ul>
</span>
</div>
</div>
</div>
<div id="p{{paragraph.id}}_graph"
class="graphContainer"
ng-class="{'noOverflow': getGraphMode()=='table'}"
ng-if="getResultType()=='TABLE'"
allowresize="{{!asIframe && !viewOnly}}"
resizable on-resize="setGraphHeight();">
<div ng-if="getGraphMode()=='table'"
id="p{{paragraph.id}}_table"
class="table">
</div>
<div ng-if="getGraphMode()=='multiBarChart'"
id="p{{paragraph.id}}_multiBarChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='pieChart'"
id="p{{paragraph.id}}_pieChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='stackedAreaChart'"
id="p{{paragraph.id}}_stackedAreaChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='lineChart'"
id="p{{paragraph.id}}_lineChart">
<svg></svg>
</div>
<div ng-if="getGraphMode()=='scatterChart'"
id="p{{paragraph.id}}_scatterChart">
<svg></svg>
</div>
</div>
<div id="{{paragraph.id}}_comment"
class="text"
ng-if="getResultType()=='TABLE' && paragraph.result.comment"
ng-Init="loadResultType(paragraph.result)"
ng-bind-html="paragraph.result.comment">
</div>
<div id="{{paragraph.id}}_text"
class="text"
ng-if="paragraph.result.type == 'TEXT'"
ng-Init="loadResultType(paragraph.result)"
ng-bind="paragraph.result.msg">
</div>
<div id="p{{paragraph.id}}_html"
ng-if="paragraph.result.type == 'HTML'"
ng-Init="loadResultType(paragraph.result)">
</div>
<div id="p{{paragraph.id}}_angular"
ng-if="paragraph.result.type == 'ANGULAR'"
ng-Init="loadResultType(paragraph.result)">
</div>
<img id="{{paragraph.id}}_img"
ng-if="paragraph.result.type == 'IMG'"
ng-Init="loadResultType(paragraph.result)"
ng-src="{{getBase64ImageSrc(paragraph.result.msg)}}">
</img>
<div id="{{paragraph.id}}_error"
class="error"
ng-if="paragraph.status == 'ERROR'"
ng-bind="paragraph.errorMessage">
</div>
<div id="{{paragraph.id}}_executionTime" class="executionTime" ng-bind-html="getExecutionTime()">
</div>
</div>
</div>
<div id="{{paragraph.id}}_control" class="control" ng-show="!asIframe">
<span>
{{paragraph.status}}
</span>
<span ng-if="paragraph.status=='RUNNING'">
{{getProgress()}}%
</span>
<!-- Run / Cancel button -->
<span class="icon-control-play" style="cursor:pointer;color:#3071A9" tooltip-placement="top" tooltip="Run this paragraph"
ng-click="runParagraph(getEditorValue())"
ng-show="paragraph.status!='RUNNING' && paragraph.status!='PENDING'"></span>
<span class="icon-control-pause" style="cursor:pointer;color:#CD5C5C" tooltip-placement="top" tooltip="Cancel"
ng-click="cancelParagraph()"
ng-show="paragraph.status=='RUNNING' || paragraph.status=='PENDING'"></span>
<span class="{{paragraph.config.editorHide ? 'icon-size-fullscreen' : 'icon-size-actual'}}" style="cursor:pointer;" tooltip-placement="top" tooltip="{{(paragraph.config.editorHide ? 'Show' : 'Hide') + ' editor'}}"
ng-click="toggleEditor()"></span>
<span class="{{paragraph.config.tableHide ? 'icon-notebook' : 'icon-book-open'}}" style="cursor:pointer;" tooltip-placement="top" tooltip="{{(paragraph.config.tableHide ? 'Show' : 'Hide') + ' output'}}"
ng-click="toggleOutput()"></span>
<span class="dropdown navbar-right">
<span class="icon-settings" style="cursor:pointer"
data-toggle="dropdown"
type="button">
</span>
<ul class="dropdown-menu" role="menu" style="width:200px;">
<li>
<a class="fa fa-arrows-h dropdown"> Width
<form style="display:inline; margin-left:5px;">
<select ng-model="paragraph.config.colWidth"
class="selectpicker"
ng-change="changeColWidth()"
ng-options="col for col in colWidthOption"></select>
</form>
</a>
</li>
<li>
<a class="icon-arrow-up" style="cursor:pointer"
ng-click="moveUp()"> Move Up</a>
</li>
<li>
<a class="icon-arrow-down" style="cursor:pointer"
ng-click="moveDown()"> Move Down</a>
</li>
<li>
<a class="icon-plus" style="cursor:pointer"
ng-click="insertNew()"> Insert New</a>
</li>
<li>
<!-- paragraph handler -->
<a class="fa fa-font" style="cursor:pointer"
ng-click="hideTitle()"
ng-show="paragraph.config.title"> Hide title</a>
<a class="fa fa-font" style="cursor:pointer"
ng-click="showTitle()"
ng-show="!paragraph.config.title"> Show title</a>
</li>
<li><a class="icon-share-alt" style="cursor:pointer"
ng-click="goToSingleParagraph()"> Link this paragraph</a>
</li>
<li>
<!-- remove paragraph -->
<a class="fa fa-times" style="cursor:pointer"
ng-click="removeParagraph()"> Remove</a>
</li>
</ul>
</span>
</div>
</div>