diff --git a/.gitignore b/.gitignore index f2515656..8dec467e 100644 --- a/.gitignore +++ b/.gitignore @@ -93,7 +93,7 @@ local.properties # PDT-specific .buildpath -# sbteclipse plugin +# sbteclipse plugin .target # TeXlipse plugin @@ -141,3 +141,8 @@ build/Release # Destination folder /www/ + +.idea + +.idea/workspace.xml + diff --git a/gulpfile.js b/gulpfile.js index fdd99d4f..229d989e 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -220,7 +220,16 @@ var gulp = require('gulp'), spawn = require('child_process').spawn, gutil = require('gulp-util'); - +/** +* Logs the error occured in the pipe without killing the gulp process +* emits an end event to the corresponding stream +* @function endErrorProcess +* @param {Error} err +*/ +function endErrorProcess(err){ + console.log(err); + this.emit('end'); +} /*================================================ = Report Errors to Console = ================================================*/ @@ -244,14 +253,16 @@ gulp.task('clean', function () { path.join(config.dest, 'l10n'), path.join(config.dest, 'app.manifest') ], { read: false }) - .pipe(rimraf()); + .pipe(rimraf()) + .on('error', endErrorProcess); }); gulp.task('clean:manifest', function () { return gulp.src([ path.join(config.dest, 'app.manifest') ], { read: false }) - .pipe(rimraf()); + .pipe(rimraf()) + .on('error', endErrorProcess); }); @@ -279,7 +290,8 @@ gulp.task('connect', function() { gulp.task('livereload', function () { gulp.src(path.join(config.dest, '*.html')) - .pipe(connect.reload()); + .pipe(connect.reload()) + .on('error', endErrorProcess); }); @@ -295,10 +307,12 @@ gulp.task('images', function () { progressive: true, svgoPlugins: [{removeViewBox: false}], use: [pngcrush()] - })); + })) + .on('error', endErrorProcess); } - return stream.pipe(gulp.dest(path.join(config.dest, 'images'))); + return stream.pipe(gulp.dest(path.join(config.dest, 'images'))) + .on('error', endErrorProcess); }); @@ -308,7 +322,8 @@ gulp.task('images', function () { gulp.task('fonts', function() { return gulp.src(config.vendor.fonts) - .pipe(gulp.dest(path.join(config.dest, 'fonts'))); + .pipe(gulp.dest(path.join(config.dest, 'fonts'))) + .on('error', endErrorProcess); }); /*================================== @@ -317,7 +332,8 @@ gulp.task('fonts', function() { gulp.task('l10n', function() { return gulp.src('src/l10n/**/*') - .pipe(gulp.dest(path.join(config.dest, 'l10n'))); + .pipe(gulp.dest(path.join(config.dest, 'l10n'))) + .on('error', endErrorProcess); }); @@ -358,7 +374,9 @@ function buildHtml (env) { return gulp.src(['src/html/**/*.html']) .pipe(replace('', inject.join('\n '))) - .pipe(gulp.dest(config.dest)); + .on('error', endErrorProcess) + .pipe(gulp.dest(config.dest)) + .on('error', endErrorProcess); } gulp.task('html', function() { @@ -377,10 +395,12 @@ gulp.task('html:production', function() { gulp.task('sass', function () { gulp.src('./src/sass/app.sass') .pipe(sourcemaps.init()) + .on('error', endErrorProcess) .pipe(sass({ includePaths: [ path.resolve(__dirname, 'src/sass'), path.resolve(__dirname, 'bower_components'), path.resolve(__dirname, 'bower_components/bootstrap-sass/assets/stylesheets') ] }).on('error', sass.logError)) .pipe(postcss([ autoprefixer({ browsers: ['last 2 versions', 'Android >= 4'] }) ])) + .on('error', endErrorProcess) /* Currently not working with sourcemaps .pipe(mobilizer('app.css', { 'app.css': { @@ -394,11 +414,15 @@ gulp.task('sass', function () { })) */ .pipe(gulpif(config.cssmin, cssmin())) + .on('error', endErrorProcess) .pipe(rename({suffix: '.min'})) + .on('error', endErrorProcess) .pipe(sourcemaps.write('.', { sourceMappingURLPrefix: '/css/' })) - .pipe(gulp.dest(path.join(config.dest, 'css'))); + .on('error', endErrorProcess) + .pipe(gulp.dest(path.join(config.dest, 'css'))) + .on('error', endErrorProcess); }); /*==================================================================== @@ -408,7 +432,9 @@ gulp.task('sass', function () { gulp.task('jshint', function() { return gulp.src('./src/js/**/*.js') .pipe(jshint()) - .pipe(jshint.reporter('jshint-stylish')); + .on('error', endErrorProcess) + .pipe(jshint.reporter('jshint-stylish')) + .on('error', endErrorProcess); }); @@ -422,44 +448,61 @@ gulp.task('js:app', function() { return streamqueue({ objectMode: true }, // Vendor: angular, mobile-angular-ui, etc. gulp.src(config.vendor.js) - .pipe(sourcemaps.init()), + .pipe(sourcemaps.init()) + .on('error', endErrorProcess), // app.js is configured gulp.src('./src/js/app.js') .pipe(sourcemaps.init()) + .on('error', endErrorProcess) .pipe(replace('value(\'config\', {}). // inject:app:config', 'value(\'config\', ' + JSON.stringify(config.app) + ').')) + .on('error', endErrorProcess) .pipe(babel({ presets: ['es2015'] - })), + })) + .on('error', endErrorProcess), // rest of app logic gulp.src(['./src/js/**/*.js', '!./src/js/app.js', '!./src/js/widgets.js']) .pipe(sourcemaps.init()) + .on('error', endErrorProcess) .pipe(babel({ presets: ['es2015'], plugins: ['transform-object-assign'] })) - .pipe(ngFilesort()), + .on('error', endErrorProcess) + .pipe(ngFilesort()) + .on('error', endErrorProcess), // app templates gulp.src(['src/templates/**/*.html']).pipe(templateCache({ module: 'Teem' })) .pipe(sourcemaps.init()) + .on('error', endErrorProcess) .pipe(babel({ presets: ['es2015'] })) + .on('error', endErrorProcess) ) .pipe(concat('app.js')) + .on('error', endErrorProcess) .pipe(ngAnnotate()) + .on('error', endErrorProcess) .pipe(gulpif(config.uglify, uglify())) + .on('error', endErrorProcess) .pipe(rename({suffix: '.min'})) + .on('error', endErrorProcess) .pipe(sourcemaps.write('.', { sourceMappingURLPrefix: '/js/' })) - .pipe(gulp.dest(path.join(config.dest, 'js'))); + .on('error', endErrorProcess) + .pipe(gulp.dest(path.join(config.dest, 'js'))) + .on('error', endErrorProcess); }); gulp.task('js:widgets', function() { return gulp.src('./src/js/widgets.js') .pipe(uglify()) - .pipe(gulp.dest(path.join(config.dest, 'js'))); + .on('error', endErrorProcess) + .pipe(gulp.dest(path.join(config.dest, 'js'))) + .on('error', endErrorProcess); }); @@ -481,7 +524,8 @@ gulp.task('cordova:sync:clean', function() { return gulp.src([dest], { read: false }) - .pipe(rimraf()); + .pipe(rimraf()) + .on('error', endErrorProcess); }); @@ -493,7 +537,8 @@ gulp.task('cordova:sync:copy', function() { return gulp.src([ source + '{cordova.js,cordova_plugins.js,plugins/**/*}']) - .pipe(gulp.dest(dest)); + .pipe(gulp.dest(dest)) + .on('error', endErrorProcess); }); gulp.task('cordova:sync', function(cb) { @@ -503,7 +548,8 @@ gulp.task('cordova:sync', function(cb) { gulp.task('cordova', function() { return gulp.src('src/vendor/cordova/**/*') - .pipe(gulp.dest(path.join(config.dest, 'js/cordova'))); + .pipe(gulp.dest(path.join(config.dest, 'js/cordova'))) + .on('error', endErrorProcess); }); @@ -530,7 +576,9 @@ function buildManifest (env) { exclude: 'app.manifest', hash: true })) - .pipe(gulp.dest(config.dest)); + .on('error', endErrorProcess) + .pipe(gulp.dest(config.dest)) + .on('error', endErrorProcess); } gulp.task('manifest', function(){ diff --git a/src/js/controllers/project.js b/src/js/controllers/project.js index 7aa0c4f1..48dbd209 100644 --- a/src/js/controllers/project.js +++ b/src/js/controllers/project.js @@ -53,6 +53,10 @@ angular.module('Teem') redirectTo: function(params) { return '/teems/' + params.id + '?tab=' + params.tab; } + }) + .when('/trello/get/',{ + controller: 'TrelloGetController', + template: '

Redirecting ....

' }); }]) .controller('FetchProject', [ @@ -89,9 +93,9 @@ angular.module('Teem') }]) .controller('ProjectCtrl', [ 'SessionSvc', '$scope', '$rootScope', '$location', '$route', '$timeout', 'swellRT', '$filter', - 'SharedState', 'ProjectsSvc', 'Loading', '$window', 'CommunitiesSvc', 'User', 'Selector', '$http', '$translate', + 'SharedState', 'ProjectsSvc', 'Loading', '$window', 'CommunitiesSvc', 'User', 'Selector', '$http', '$translate','trelloSvc', function (SessionSvc, $scope, $rootScope, $location, $route, $timeout, swellRT, $filter, - SharedState, ProjectsSvc, Loading, $window, CommunitiesSvc, User, Selector, $http, $translate) { + SharedState, ProjectsSvc, Loading, $window, CommunitiesSvc, User, Selector, $http, $translate, trelloSvc) { // Prevent users from forging the form parameter // and set the form order @@ -255,7 +259,7 @@ angular.module('Teem') $scope.pad.editing = false; }); - $scope.$on('$routeChangeStart', function(event, next, current) { + $scope.$on('$routeChangeStart', function(event, next, current) { if (current.params.tab !== undefined && $scope.project!== undefined) { $scope.project.setTimestampAccess(current.params.tab); } @@ -281,12 +285,12 @@ angular.module('Teem') var lastChange = $scope.project.lastChange(section); var lastAccess; - if ($scope.project.getTimestampAccess() && - $scope.project.getTimestampAccess()[section]) { - lastAccess = new Date(($scope.project.getTimestampAccess()[section]).last); - } else { - lastAccess = new Date(0); - } + if ($scope.project.getTimestampAccess() && + $scope.project.getTimestampAccess()[section]) { + lastAccess = new Date(($scope.project.getTimestampAccess()[section]).last); + } else { + lastAccess = new Date(0); + } return lastChange > lastAccess; }; @@ -361,7 +365,7 @@ angular.module('Teem') url }).then(function (response) { angular.forEach(response.data.geonames, function(v) { - v.value = { + v.value = { id: v.geonameId.toString(), latitide: v.lat, longitude: v.lng, @@ -417,11 +421,22 @@ angular.module('Teem') }; + $scope.getToken = function () { + trelloSvc.getToken(); + }; + $scope.archiveProject = function() { // TODO }; }]) + .controller('TrelloGetController', ['$location','ProjectsSvc','SessionSvc', function($location,ProjectsSvc,SessionSvc){ + let token = $location.hash().split('=')[1]; + localStorage.setItem('trelloTeemToken',token); + SessionSvc.onLoad(function(){ + ProjectsSvc.updateTrello(); + }); + }]) .directive( 'hideTabs', function (SharedState, $timeout) { diff --git a/src/js/directives/pad.js b/src/js/directives/pad.js index 8133d0f1..80d10821 100644 --- a/src/js/directives/pad.js +++ b/src/js/directives/pad.js @@ -1,13 +1,13 @@ 'use strict'; /** - * @ngdoc function - * @name Teem.controller:ChatCtrl - * @description - * # Chat Ctrl - * Show Pad for a given project - */ - +* @ngdoc function +* @name Teem.controller:ChatCtrl +* @description +* # Chat Ctrl +* Show Pad for a given project +*/ +let timer,styleAppended = false; angular.module('Teem') .directive('pad', function() { return { @@ -16,237 +16,409 @@ angular.module('Teem') $scope.editingDefault = attrs.editingDefault; }, controller: [ - 'SessionSvc', '$rootScope', '$scope', '$route', '$location', - '$timeout', 'SharedState', 'needWidget', '$element', - function(SessionSvc, $rootScope, $scope, $route, $location, - $timeout, SharedState, needWidget, $element) { - - var buttons = ['text_fields', 'format_bold', 'format_italic', 'format_strikethrough', - 'format_align_left', 'format_align_center', 'format_align_right', - 'format_list_bulleted', 'format_list_numbered']; - - var annotationMap = { - 'text_fields': 'paragraph/header=h3', - 'format_bold': 'style/fontWeight=bold', - 'format_italic': 'style/fontStyle=italic', - 'format_strikethrough': 'style/textDecoration=line-through', - 'format_align_left': 'paragraph/textAlign=left', - 'format_align_center': 'paragraph/textAlign=center', - 'format_align_right': 'paragraph/textAlign=right', - 'format_list_bulleted': 'paragraph/listStyleType=unordered', - 'format_list_numbered': 'paragraph/listStyleType=decimal' - }; - - var annotations = {}; - - function imgWidget(parentElement, before, state) { - state = state || before; - - if (!(state in $scope.project.attachments) || !$scope.project.attachments[state].file) { + 'SessionSvc', '$rootScope', '$scope', '$route', '$location', + '$timeout', 'SharedState', 'needWidget', '$element','linkPreview', + function(SessionSvc, $rootScope, $scope, $route, $location, + $timeout, SharedState, needWidget, $element, linkPreview) { + + var buttons = ['text_fields', 'format_bold', 'format_italic', 'format_strikethrough', + 'format_align_left', 'format_align_center', 'format_align_right', + 'format_list_bulleted', 'format_list_numbered']; + + var annotationMap = { + 'text_fields': 'paragraph/header=h3', + 'format_bold': 'style/fontWeight=bold', + 'format_italic': 'style/fontStyle=italic', + 'format_strikethrough': 'style/textDecoration=line-through', + 'format_align_left': 'paragraph/textAlign=left', + 'format_align_center': 'paragraph/textAlign=center', + 'format_align_right': 'paragraph/textAlign=right', + 'format_list_bulleted': 'paragraph/listStyleType=unordered', + 'format_list_numbered': 'paragraph/listStyleType=decimal' + }; + + var annotations = {}; + + function openLinkPopover(event,range){ + if(!styleAppended){ + let pStyle = document.createElement('style'); + pStyle.innerHTML = ` + #popover{ + width: 330px; + height: 270px; + margin: 0 5px; + border-radius: 6px; + } + div.popover-link-image{ + width: 330px; + height: 190px; + margin: 5px auto; + } + div.popover-link-description{ + width: 320px; + height: auto; + max-height: 40px; + margin: 0 auto; + overflow: auto; + word-wrap: break-all; + } + .popover-link-title{ + word-wrap: break-all; + text-overflow: ellpsis; + overflow: hidden; + white-space: nowrap; + } + .popover-link-address{ + color: #000; + margin-left: 5px; + overflow: auto; + word-wrap: break-all; + text-overflow: ellipsis; + white-space: nowrap; + } + .popover-link-title{ + margin-left: 5px; + } + #popover-container:after{ + content: ""; + position: absolute; + bottom: -25px; + left: 175px; + border-style: solid; + visibility: hidden; + width: 0; + z-index: 1; + } + #popover-container:before{ + content: ""; + position: absolute; + top: -11px; + left: -1px; + border-style: solid; + border-width: 0 10px 10px; + border-color: #F1F1F1 transparent; + display: block; + width: 0; + z-index: 0; + }`; + document.body.appendChild(pStyle); + styleAppended = true; + } + timer = $timeout(() => { + event.stopPropagation(); + let div = document.createElement('div'); + let btn = event.target; + console.dir(btn.offsetHeight); + let inHTML = ` + +
+
+ + + +
+
+ `; + linkPreview.getMetaData(btn.href) + .then((meta) => { + console.log(meta); + if(!meta){ + div.style.display = 'none'; return; } - - // cannot use spinner template directly here - parentElement.innerHTML = ` -
-
- - - + let urlImage = meta.image, + urlAuthor = meta.author, + urlTitle = meta.title, + urlDescription = meta.description; + if(urlImage && urlDescription){ + inHTML = `
+ + -
`; - - $scope.project.attachments[state].file.getUrl().then(url => { - parentElement.innerHTML = ``; - }); - } - - $scope.padWidgets = { - 'need': needWidget.getWidget($scope), - 'img': { - onInit: imgWidget, - onChangeState: imgWidget + + +
`; } - }; - - $scope.padAnnotations = { - 'paragraph/header': { - onAdd: function() { - $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); - $timeout(); - }, - onChange: function() { - $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); - $timeout(); - }, - onRemove: function() { - $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); - $timeout(); - } - }, - 'link': { - onEvent: function(range, event) { - if (event.type === 'click') { - event.stopPropagation(); - $scope.linkModal.open(range); - } + else if(urlDescription && !urlImage){ + div.style.height = '110px'; + inHTML = `
+ + + +
`; + } + else{ + if(!urlTitle){ + div.style.height = '110px'; + inHTML = `
+ + +
`; } + div.style.height = '110px'; + inHTML = `
+ + + +
`; } - }; - - function updateAllButtons() { - for (let btn of buttons) { - let [key, val] = annotationMap[btn].split('='); - $scope.buttons[btn] = (annotations && annotations[key] === val); + div.innerHTML = inHTML; + }) + .catch((err) => { + console.log(err); + }); + let clientRect = range.node.nextSibling ? + range.node.nextSibling.getBoundingClientRect() : + range.node.parentElement.getBoundingClientRect(); + div.innerHTML = inHTML; + div.style.width = '345px'; + div.style.height = '300px'; + div.style.position = 'absolute'; + div.style.border = '1px solid #F0F0F0'; + div.style.top = clientRect.top + 35 + 'px'; + div.style.left = clientRect.left + 'px'; + div.style.zIndex = 3; + div.style.backgroundColor = '#F2F2F2'; + div.style.paddingTop = '5px'; + div.id = 'popover-container'; + document.body.appendChild(div); + },700); + } + + function closeLinkPopover(delay){ + if(timer){ + $timeout.cancel(timer); + timer = null; + $timeout(() => { + if(document.getElementById('popover-container')){ + document.body.removeChild(document.getElementById('popover-container')); } - $timeout(); - } + }, delay); + } + } - function disableAllButtons() { - $scope.buttons = {}; - buttons.forEach(btn => $scope.buttons[btn] = false); - $timeout(); + function imgWidget(parentElement, before, state) { + state = state || before; + + if (!(state in $scope.project.attachments) || !$scope.project.attachments[state].file) { + return; } - $scope.padCreate = function(editor) { + // cannot use spinner template directly here + parentElement.innerHTML = ` +
+
+ + + +
+
`; + + $scope.project.attachments[state].file.getUrl().then(url => { + parentElement.innerHTML = ``; + }); + } + + $scope.padWidgets = { + 'need': needWidget.getWidget($scope), + 'img': { + onInit: imgWidget, + onChangeState: imgWidget + } + }; - $scope.linkModal = { - add: function(event) { + $scope.padAnnotations = { + 'paragraph/header': { + onAdd: function() { + $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); + $timeout(); + }, + onChange: function() { + $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); + $timeout(); + }, + onRemove: function() { + $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); + $timeout(); + } + }, + 'link': { + onEvent: function(range, event) { + if (event.type === 'click') { event.stopPropagation(); - let range = editor.getSelection(); - if (range.text) { - editor.setAnnotation('link', ''); - } + closeLinkPopover(0); $scope.linkModal.open(range); - }, - open: function(range) { - let annotation = editor.getAnnotationInRange(range, 'link'); - - $scope.linkModal.range = range; - $scope.linkModal.annotation = annotation; + } + else if(event.type === 'mouseover'){ + openLinkPopover(event,range); console.log(range); - let clientRect = range.node.nextSibling ? - range.node.nextSibling.getBoundingClientRect() : - range.node.parentElement.getBoundingClientRect(); - document.getElementById('link-modal').style.top = clientRect.top + 25 + 'px'; - document.getElementById('link-modal').style.left = clientRect.left + 'px'; - - $scope.linkModal.text = range.text; - $scope.linkModal.link = annotation ? annotation.value : ''; - $scope.linkModal.show = true; - - let emptyInput = !range.text ? 'text': 'link'; - let autofocus = document.querySelector('#link-modal [ng-model="linkModal.' + emptyInput + '"]'); - $timeout(() => autofocus && autofocus.focus()); - }, - change: function() { - let range = editor.setText($scope.linkModal.range, $scope.linkModal.text); - editor.setAnnotationInRange(range, 'link', $scope.linkModal.link); - $scope.linkModal.show = false; - $scope.linkModal.edit = false; - }, - clear: function() { - editor.clearAnnotationInRange($scope.linkModal.range, 'link'); - $scope.linkModal.show = false; - $scope.linkModal.edit = false; } - }; - - disableAllButtons(); + else if(event.type === 'mouseout'){ + closeLinkPopover(500); + } + } + } + }; - editor.onSelectionChanged(function(range) { - annotations = range.annotations; - updateAllButtons(); - }); + function updateAllButtons() { + for (let btn of buttons) { + let [key, val] = annotationMap[btn].split('='); + $scope.buttons[btn] = (annotations && annotations[key] === val); + } + $timeout(); + } + + function disableAllButtons() { + $scope.buttons = {}; + buttons.forEach(btn => $scope.buttons[btn] = false); + $timeout(); + } + + $scope.padCreate = function(editor) { + + $scope.linkModal = { + add: function(event) { + event.stopPropagation(); + let range = editor.getSelection(); + if (range.text) { + editor.setAnnotation('link', ''); + } + $scope.linkModal.open(range); + }, + open: function(range) { + let annotation = editor.getAnnotationInRange(range, 'link'); + + $scope.linkModal.range = range; + $scope.linkModal.annotation = annotation; + console.log(range); + let clientRect = range.node.nextSibling ? + range.node.nextSibling.getBoundingClientRect() : + range.node.parentElement.getBoundingClientRect(); + document.getElementById('link-modal').style.top = clientRect.top + 25 + 'px'; + document.getElementById('link-modal').style.left = clientRect.left + 'px'; + + $scope.linkModal.text = range.text; + $scope.linkModal.link = annotation ? annotation.value : ''; + $scope.linkModal.show = true; + + let emptyInput = !range.text ? 'text': 'link'; + let autofocus = document.querySelector('#link-modal [ng-model="linkModal.' + emptyInput + '"]'); + $timeout(() => autofocus && autofocus.focus()); + }, + change: function() { + let range = editor.setText($scope.linkModal.range, $scope.linkModal.text); + editor.setAnnotationInRange(range, 'link', $scope.linkModal.link); + $scope.linkModal.show = false; + $scope.linkModal.edit = false; + }, + clear: function() { + editor.clearAnnotationInRange($scope.linkModal.range, 'link'); + $scope.linkModal.show = false; + $scope.linkModal.edit = false; + } }; - $scope.padReady = function(editor) { - // FIXME - // SwellRT editor is created with .wave-editor-off - // Should use .wave-editor-on when SwellRT editor callback is available - // https://github.com/P2Pvalue/swellrt/issues/84 - var editorElement = angular.element($element.find('.swellrt-editor').children()[0]); + disableAllButtons(); - editorElement.on('focus', updateAllButtons); - editorElement.on('blur', disableAllButtons); + editor.onSelectionChanged(function(range) { + annotations = range.annotations; + updateAllButtons(); + }); + }; - $scope.pad.outline = editor.getAnnotationSet('paragraph/header'); + $scope.padReady = function(editor) { + // FIXME + // SwellRT editor is created with .wave-editor-off + // Should use .wave-editor-on when SwellRT editor callback is available + // https://github.com/P2Pvalue/swellrt/issues/84 + var editorElement = angular.element($element.find('.swellrt-editor').children()[0]); - $scope.annotate = function(btn) { - let [key, val] = annotationMap[btn].split('='); - let currentVal = annotations[key]; - if (currentVal === val) { - val = null; - } + editorElement.on('focus', updateAllButtons); + editorElement.on('blur', disableAllButtons); - annotations[key] = val; - editor.setAnnotation(key, val); - editorElement.focus(); - }; + $scope.pad.outline = editor.getAnnotationSet('paragraph/header'); - $scope.clearFormat = function() { - editor.clearAnnotation('style'); - editorElement.focus(); - }; + $scope.annotate = function(btn) { + let [key, val] = annotationMap[btn].split('='); + let currentVal = annotations[key]; + if (currentVal === val) { + val = null; + } - $scope.widget = function(type) { - if (type === 'need') { - needWidget.add(editor, $scope); - } - if (type === 'img') { - if (arguments[1] === undefined) { // First step - $scope.pad.selectingFile = true; - $timeout(() => $scope.pad.selectingFile = false); - } else { // Second step - $scope.pad.selectingFile = false; - var id = $scope.project.addAttachment(arguments[1]); - editor.addWidget('img', id); - } - } - }; - - $scope.editOn = function () { - if (editorElement.attr('class') === 'wave-editor-on') { - $scope.pad.editing = true; - SessionSvc.showSaving = true; - SharedState.turnOn('hiddenTabs'); - $timeout(); - } - }; - - $scope.editOff = function () { - if (editorElement.attr('class') === 'wave-editor-on') { - $scope.pad.editing = $scope.editingDefault; - SessionSvc.showSaving = false; - SharedState.turnOff('hiddenTabs'); - $timeout(); + annotations[key] = val; + editor.setAnnotation(key, val); + editorElement.focus(); + }; + + $scope.clearFormat = function() { + editor.clearAnnotation('style'); + editorElement.focus(); + }; + + $scope.widget = function(type) { + if (type === 'need') { + needWidget.add(editor, $scope); + } + if (type === 'img') { + if (arguments[1] === undefined) { // First step + $scope.pad.selectingFile = true; + $timeout(() => $scope.pad.selectingFile = false); + } else { // Second step + $scope.pad.selectingFile = false; + var id = $scope.project.addAttachment(arguments[1]); + editor.addWidget('img', id); } - }; + } + }; - if ($scope.editingDefault && $scope.project.isParticipant()) { + $scope.editOn = function () { + if (editorElement.attr('class') === 'wave-editor-on') { $scope.pad.editing = true; + SessionSvc.showSaving = true; + SharedState.turnOn('hiddenTabs'); + $timeout(); } + }; - // FIXME We should get the pad text directly from the editor, but - // I couldn't find the proper way - if ($scope.project.isParticipant() && $scope.project.pad.text() === '') { - $scope.pad.emptyTip = true; + $scope.editOff = function () { + if (editorElement.attr('class') === 'wave-editor-on') { + $scope.pad.editing = $scope.editingDefault; + SessionSvc.showSaving = false; + SharedState.turnOff('hiddenTabs'); + $timeout(); } - }; - $scope.$watchCollection(function() { - return SessionSvc.status; - }, function(current) { - $scope.pad.saving = !current.sync; - }); + if ($scope.editingDefault && $scope.project.isParticipant()) { + $scope.pad.editing = true; + } - $scope.closePadEmptyTip = function closePadEmptyTip() { - $scope.pad.emptyTip = false; - $timeout(() => { - angular.element(document.querySelector('.wave-editor-on')).focus(); - }); - }; + // FIXME We should get the pad text directly from the editor, but + // I couldn't find the proper way + if ($scope.project.isParticipant() && $scope.project.pad.text() === '') { + $scope.pad.emptyTip = true; + } + + }; + + $scope.$watchCollection(function() { + return SessionSvc.status; + }, function(current) { + $scope.pad.saving = !current.sync; + }); + + $scope.closePadEmptyTip = function closePadEmptyTip() { + $scope.pad.emptyTip = false; + $timeout(() => { + angular.element(document.querySelector('.wave-editor-on')).focus(); + }); + }; }], templateUrl: 'pad.html' diff --git a/src/js/services/pad/linkPreview.js b/src/js/services/pad/linkPreview.js new file mode 100644 index 00000000..1d7a0284 --- /dev/null +++ b/src/js/services/pad/linkPreview.js @@ -0,0 +1,37 @@ +(function() { + 'use strict'; + + +/** + * @module Teem + * @method linkPreview + * @param {String} url + * Returns the parsed meta data of the given link + */ + +let linkPreviewFactory = angular.module('Teem'); + + +function linkPreview($http) { + const LINK_PREVIEW_SERVER_URL = 'http://localhost:9090/fetch'; + function getMetaData(url){ + //TODO: implement a check for the URL to be correct + if(!url){ + return; + } + return $http.post(LINK_PREVIEW_SERVER_URL,{url}) + .then((res) => { + return res.data; + }) + .catch((err) => { + console.log(err); + }); + } + + return { + getMetaData + }; +} +linkPreview.$inject = ['$http']; +linkPreviewFactory.factory('linkPreview', linkPreview); +})(); \ No newline at end of file diff --git a/src/js/services/pad/need-widget.js b/src/js/services/pad/need-widget.js index e75568fa..b6e10dff 100644 --- a/src/js/services/pad/need-widget.js +++ b/src/js/services/pad/need-widget.js @@ -58,7 +58,9 @@ angular.module('Teem') editor.deleteText(selection); } - need = scope.project.addNeed(need); + need = scope.project.addNeedWithoutTrello(need); + //FIXME Doesn't work with the Trello integration, for now the ones created in pad cannot be added to Trello + // need = scope.project.addNeed(need); // To generate need added event after all the info is available diff --git a/src/js/services/projectsService.js b/src/js/services/projectsService.js index bff4ab11..5ae77ee1 100644 --- a/src/js/services/projectsService.js +++ b/src/js/services/projectsService.js @@ -2,616 +2,677 @@ angular.module('Teem') .factory('ProjectsSvc', [ - 'swellRT', '$q', '$timeout', 'base64', 'SessionSvc', 'SwellRTCommon', 'User', - '$rootScope', 'Logo', 'Url', 'Participation', - function(swellRT, $q, $timeout, base64, SessionSvc, SwellRTCommon, User, - $rootScope, Logo, Url, Participation){ - - // class that expose only read methods of the project object - class ProjectReadOnly extends aggregation(Object, Logo, Url, Participation.ReadOnly) { - - // val is a readOnly object from a SwellRT query result - constructor (val) { - // calling "this" is not allowed before super() - super(); - - if (val) { - for (var k in val.root){ - if (val.root.hasOwnProperty(k)){ - this[k] = val.root[k]; + 'swellRT', '$q', '$timeout', 'base64', 'SessionSvc', 'SwellRTCommon', 'User', + '$rootScope', 'Logo', 'Url', 'Participation', 'trelloSvc', '$location', + function (swellRT, $q, $timeout, base64, SessionSvc, SwellRTCommon, User, + $rootScope, Logo, Url, Participation, trelloSvc, $location) { + + // class that expose only read methods of the project object + class ProjectReadOnly extends aggregation(Object, Logo, Url, Participation.ReadOnly) { + + // val is a readOnly object from a SwellRT query result + constructor(val) { + // calling "this" is not allowed before super() + super(); + + if (val) { + for (var k in val.root) { + if (val.root.hasOwnProperty(k)) { + this[k] = val.root[k]; + } } - } - this._participants = val.participants; - if (val._id && val._id.$oid){ - this._creationDate = - parseInt(val._id.$oid.slice(0, 8), 16) * 1000; + this._participants = val.participants; + if (val._id && val._id.$oid) { + this._creationDate = + parseInt(val._id.$oid.slice(0, 8), 16) * 1000; } else { this._creationDate = 0; } + } } - } - get pathPrefix () { return '/teems/'; } - - getTimestampAccess () { - var access; - - if (this.lastAccesses === undefined) { - this.lastAccesses = []; + get pathPrefix() { + return '/teems/'; } - angular.forEach(this.lastAccesses, function(a) { - if (a.user === User.currentId()) { - access = a; - } - }); + getTimestampAccess() { + var access; - return access; - } + if (this.lastAccesses === undefined) { + this.lastAccesses = []; + } - // section in {'chat', 'pad', 'needs'} - lastChange (section) { - if (section === undefined) { - //cast to Date - return new Date(Math.max( - this.lastChange('chat').getTime(), - this.lastChange('pad').getTime(), - this.lastChange('needs').getTime() - )); - } else { - switch (section) { - - case 'chat': - if (this.chat && this.chat.length > 0) { - return new Date(this.chat[this.chat.length -1].time); - } else { - return new Date(0); + angular.forEach(this.lastAccesses, function (a) { + if (a.user === User.currentId()) { + access = a; } - break; - - case 'pad': - return new Date(this.pad.lastmodtime) || new Date(0); - - case 'needs': - var lastChange = new Date(0); - angular.forEach(this.needs, function(n){ - lastChange = Math.max( - lastChange, - new Date(n.time||0), - (function() { - var lastComment = new Date(0); - angular.forEach(n.comments, function(c){ - lastComment = Math.max(lastComment, new Date(c.time || 0)); - }); - return lastComment; - }()) - ); - }); - // cast to Date - return new Date(lastChange); + }); + + return access; + } - default: - return new Date(0); + // section in {'chat', 'pad', 'needs'} + lastChange(section) { + if (section === undefined) { + //cast to Date + return new Date(Math.max( + this.lastChange('chat').getTime(), + this.lastChange('pad').getTime(), + this.lastChange('needs').getTime() + )); + } else { + switch (section) { + + case 'chat': + if (this.chat && this.chat.length > 0) { + return new Date(this.chat[this.chat.length - 1].time); + } else { + return new Date(0); + } + break; + + case 'pad': + return new Date(this.pad.lastmodtime) || new Date(0); + + case 'needs': + var lastChange = new Date(0); + angular.forEach(this.needs, function (n) { + lastChange = Math.max( + lastChange, + new Date(n.time || 0), + (function () { + var lastComment = new Date(0); + angular.forEach(n.comments, function (c) { + lastComment = Math.max(lastComment, new Date(c.time || 0)); + }); + return lastComment; + }()) + ); + }); + // cast to Date + return new Date(lastChange); + + default: + return new Date(0); + } } } - } - get creationDate(){ - return this._creationDate; - } + get creationDate() { + return this._creationDate; + } + + // section in {'chat', 'pad', 'needs'} + // pos in {'last','prev'} + lastAccess(section, pos) { + var access; - // section in {'chat', 'pad', 'needs'} - // pos in {'last','prev'} - lastAccess (section, pos) { - var access; + angular.forEach(this.lastAccesses || [], function (a) { + if (a.user === User.currentId()) { + access = a; + } + }); - angular.forEach(this.lastAccesses || [], function(a) { - if (a.user === User.currentId()) { - access = a; + if (!pos) { + pos = 'last'; } - }); - if (!pos) { - pos = 'last'; + return (access && access[section] && access[section][pos] ? new Date(access[section][pos]) : new Date(0)); } - return (access && access[section] && access[section][pos] ? new Date(access[section][pos]) : new Date(0)); - } - - newMessagesCount () { - var access = this.lastAccess('chat'); + newMessagesCount() { + var access = this.lastAccess('chat'); - if (this.chat.length > 0){ - var i = this.chat.length - 1; + if (this.chat.length > 0) { + var i = this.chat.length - 1; - while (i > -1 && (new Date(this.chat[i].time) > access)) { - i --; + while (i > -1 && (new Date(this.chat[i].time) > access)) { + i--; + } + return this.chat.length - 1 - i; + } else { + return 0; } - return this.chat.length - 1 - i; - } else { - return 0; } - } - padEditionCount () { - if (this.lastAccess('pad').getTime() < this.pad.lastmodtime) { - return 1; - } else { - return 0; + padEditionCount() { + if (this.lastAccess('pad').getTime() < this.pad.lastmodtime) { + return 1; + } else { + return 0; + } } - } - isFeatured () { - return this.featured !== undefined && this.featured !== 'false'; - } + isFeatured() { + return this.featured !== undefined && this.featured !== 'false'; + } - featureDate () { - if (this.isFeatured()){ - return parseInt(this.featured)||0; - } else { - return -1; + featureDate() { + if (this.isFeatured()) { + return parseInt(this.featured) || 0; + } else { + return -1; + } } - } - isSupporter (userId = User.currentId()) { - if (!userId) { - return false; + isSupporter(userId = User.currentId()) { + if (!userId) { + return false; + } + + return this.supporters.indexOf(userId) > -1; } - return this.supporters.indexOf(userId) > -1; - } + needCompletedCount() { + var completed = 0; - needCompletedCount () { - var completed = 0; + angular.forEach(this.needs, function (need) { + if (need.completed === 'true') { + completed++; + } + }); - angular.forEach(this.needs, function(need) { - if (need.completed === 'true') { - completed++; - } - }); + return completed; + } - return completed; - } + needIncompletedCount() { + return this.needCount() - this.needCompletedCount(); + } - needIncompletedCount () { - return this.needCount() - this.needCompletedCount(); - } + needCount() { + if (this.needs === undefined) { + return 0; + } - needCount () { - if (this.needs === undefined) { - return 0; + return this.needs.length; } - return this.needs.length; - } + progressPercentage() { + var size = this.needCount(); - progressPercentage () { - var size = this.needCount(); + if (size === 0) { + return 0; + } - if (size === 0) { - return 0; + return Math.round(this.needCompletedCount() * 100 / size); } - return Math.round(this.needCompletedCount() * 100 / size); - } + // Show at least 1% + progressPercentageNotZero() { + var value = this.progressPercentage(); - // Show at least 1% - progressPercentageNotZero () { - var value = this.progressPercentage(); + if (value === 0 && this.needCount() > 0) { + return 1; + } - if (value === 0 && this.needCount() > 0) { - return 1; + return value; } - return value; - } + progressType() { + var percentage = this.progressPercentage(); - progressType () { - var percentage = this.progressPercentage(); + if (percentage < 33) { + return 'danger'; + } else if (percentage > 66) { + return 'success'; + } else { + return 'warning'; + } + } - if (percentage < 33) { - return 'danger'; - } else if (percentage > 66) { - return 'success'; - } else { - return 'warning'; + isShareMode(mode) { + return mode === this.shareMode; } } - isShareMode (mode) { - return mode === this.shareMode; - } - } + class Project extends aggregation(ProjectReadOnly, Participation.ReadWrite, SessionSvc.SynchedModel) { - class Project extends aggregation(ProjectReadOnly, Participation.ReadWrite, SessionSvc.SynchedModel) { + setShareMode(shareMode) { + this.shareMode = shareMode; + } - setShareMode (shareMode) { - this.shareMode = shareMode; - } + /* + * Record when the user had her last and previous access to one project section + */ + setTimestampAccess(section, overridePrev) { + if (!this.isParticipant()) { + return; + } - /* - * Record when the user had her last and previous access to one project section - */ - setTimestampAccess (section, overridePrev) { - if (! this.isParticipant()) { - return; - } + if (this.getTimestampAccess() === undefined) { + this.lastAccesses.push({user: User.currentId()}); + } + var timestamp = this.getTimestampAccess()[section]; - if (this.getTimestampAccess() === undefined){ - this.lastAccesses.push({user: User.currentId()}); - } - var timestamp = this.getTimestampAccess()[section]; + if (timestamp === undefined) { + timestamp = {}; + } - if (timestamp === undefined) { - timestamp = {}; - } + if (timestamp instanceof Date) { + timestamp = { + last: timestamp + }; + } - if (timestamp instanceof Date) { - timestamp = { - last: timestamp - }; - } + if (!overridePrev) { + timestamp.prev = timestamp.last || (new Date()).toJSON(); + } else { + timestamp.prev = (new Date()).toJSON(); + } - if(! overridePrev){ - timestamp.prev = timestamp.last || (new Date()).toJSON(); - } else { - timestamp.prev = (new Date()).toJSON(); - } + timestamp.last = (new Date()).toJSON(); - timestamp.last = (new Date()).toJSON(); + this.getTimestampAccess()[section] = timestamp; + } - this.getTimestampAccess()[section] = timestamp; - } + toggleSupport() { - toggleSupport () { + var userId = User.currentId(); - var userId = User.currentId(); + if (userId === undefined) { + return; + } + var index = this.supporters.indexOf(userId); - if (userId === undefined) { - return; + if (index > -1) { + this.supporters.splice(index, 1); + } else { + this.supporters.push(userId); + } } - var index = this.supporters.indexOf(userId); - if (index > -1) { - this.supporters.splice(index, 1); - } else { - this.supporters.push(userId); + addAttachment(file) { + if (!file) { + return; + } + if (typeof this.attachments === 'undefined') { + this.attachments = {}; + } + var swellFile = new swellRT.FileObject(file); + var id = Math.floor((1 + Math.random()) * 0x100000000000000).toString(16); + this.attachments[id] = { + id, + file: swellFile, + who: User.currentId(), + time: (new Date()).toJSON() + }; + return id; } - } - addAttachment (file) { - if (!file) { - return; - } - if (typeof this.attachments === 'undefined') { - this.attachments = {}; + addChatMessage(text, file) { + if (file) { + file = new swellRT.FileObject(file); + } + this.chat.push({ + text, + file, + who: User.currentId(), + time: (new Date()).toJSON() + }); + + this.setTimestampAccess('chat', true); } - var swellFile = new swellRT.FileObject(file); - var id = Math.floor((1+Math.random())*0x100000000000000).toString(16); - this.attachments[id] = { - id, - file: swellFile, - who: User.currentId(), - time: (new Date()).toJSON() - }; - return id; - } - addChatMessage (text, file) { - if (file) { - file = new swellRT.FileObject(file); + findNeed(id) { + return this.needs.filter(function (need) { + return need._id === id; + })[0]; } - this.chat.push({ - text, - file, - who: User.currentId(), - time: (new Date()).toJSON() - }); - this.setTimestampAccess('chat', true); - } + addNeed(need) { + // Quick dirty hack until SwellRT provides ids for array elements + need._id = Math.random().toString().substring(2); + need.author = SessionSvc.users.current(); + need.time = (new Date()).toJSON(); + need.completed = 'false'; - findNeed (id) { - return this.needs.filter(function (need) { - return need._id === id; - })[0]; - } + this.setTimestampAccess('needs', true); - addNeed(need) { + if (need.text === '') { + return need; + } + if (this.trello) { + if (this.trello.boardId && this.trello.listId) { + trelloSvc.addNewCard(this.trello, need).then((cardData) => { + need.trelloId = cardData.id; + this.needs.push(need); + }) + .catch(err => console.log(err)); + } + } + else { + this.needs.push(need); + } - // Quick dirty hack until SwellRT provides ids for array elements - need._id = Math.random().toString().substring(2); - need.author = SessionSvc.users.current(); - need.time = (new Date()).toJSON(); - need.completed = 'false'; + return need; + } - this.needs.push(need); - this.setTimestampAccess('needs', true); + addNeedWithoutTrello(need) { + need._id = Math.random().toString().substring(2); + need.author = SessionSvc.users.current(); + need.time = (new Date()).toJSON(); + need.completed = 'false'; + this.setTimestampAccess('needs', true); + this.needs.push(need); - return need; - } + return need; + } - toggleNeedCompleted (need) { - var newStatus; + toggleNeedCompleted(need) { + var newStatus; - if (! this.isParticipant()) { - return; - } + if (!this.isParticipant()) { + return; + } - newStatus = need.completed !== 'true'; + newStatus = need.completed !== 'true'; - need.completed = newStatus.toString(); + need.completed = newStatus.toString(); - if (newStatus) { - need.completionDate = (new Date()).toJSON(); - } else { - delete need.completionDate; + if (newStatus) { + need.completionDate = (new Date()).toJSON(); + trelloSvc.archiveCard(this.trello, need).then(data => need.closed = true) + .catch(err => console.log(err)); + } else { + trelloSvc.unarchiveCard(this.trello, need).then(data => { + need.closed = false; + }) + .catch(err => console.log(err)); + delete need.completionDate; + } } - } - toggleFeatured () { - if (! this.isParticipant()) { - return; - } + toggleFeatured() { + if (!this.isParticipant()) { + return; + } - if (this.isFeatured()){ - this.featured = 'false'; - } else { - this.featured = new Date().getTime().toString(); + if (this.isFeatured()) { + this.featured = 'false'; + } else { + this.featured = new Date().getTime().toString(); + } } - } - removeNeed (need) { - var i = this.needs.indexOf(need); + removeNeed(need) { + var i = this.needs.indexOf(need); - this.needs.splice(i,1); - } + this.needs.splice(i, 1); - addNeedComment (need, comment) { - if (!need.comments){ - need.comments = []; + trelloSvc.archiveNeed(this.trello, need).then(data => need.removed = true) + .catch(err => console.log(err)); } - need.comments.push({ - text: comment, - time: (new Date()).toJSON(), - author: User.currentId() - }); - this.setTimestampAccess('needs', true); - } - delete () { - this.type = 'deleted'; - this.communities = []; - } - } + addNeedComment(need, comment) { + if (!need.comments) { + need.comments = []; + } + if (this.trello) { + trelloSvc.addNewComment(this.trello, need, comment).then(data => console.log(data)) + .catch(err => console.log(err)); + } + need.comments.push({ + text: comment, + time: (new Date()).toJSON(), + author: User.currentId() + }); + this.setTimestampAccess('needs', true); + } - // Service functions // + delete() { + this.type = 'deleted'; + this.communities = []; + } + } - var openedProjects = {}; + // Service functions // + var openedProjects = {}; - /* - * Build options for all query - */ - function buildAllQuery(options) { - var query = { - _aggregate: [ - { - $match: { - 'root.type': 'project', - 'root.title': {$ne: ''} + /* + * Build options for all query + */ + function buildAllQuery(options) { + var query = { + _aggregate: [ + { + $match: { + 'root.type': 'project', + 'root.title': {$ne: ''} + } + }, + { + $sort: {'root.pad.lastmodtime': -1} + }, + { + $skip: options.pagination.pageIndex * options.pagination.pageSize + }, + { + $limit: options.pagination.pageSize } - }, - { - $sort : {'root.pad.lastmodtime': -1 } - }, - { - $skip: options.pagination.pageIndex * options.pagination.pageSize - }, - { - $limit: options.pagination.pageSize - } - ] - }; + ] + }; - // all method only list public and user's projects - query._aggregate[0].$match.$or = [ - // when anonymous user, Users.currentId is undefined - { 'participants': User.currentId() || 'anonymous' }, - { 'root.shareMode': 'public' } - ]; + // all method only list public and user's projects + query._aggregate[0].$match.$or = [ + // when anonymous user, Users.currentId is undefined + {'participants': User.currentId() || 'anonymous'}, + {'root.shareMode': 'public'} + ]; - if (options.contributor) { - query._aggregate[0].$match.participants = options.contributor; - } + if (options.contributor) { + query._aggregate[0].$match.participants = options.contributor; + } - if (options.community) { - query._aggregate[0].$match['root.communities'] = options.community; - } + if (options.community) { + query._aggregate[0].$match['root.communities'] = options.community; + } - if (options.localId) { - query._aggregate[0].$match['root.localId'] = options.localId; - } + if (options.localId) { + query._aggregate[0].$match['root.localId'] = options.localId; + } - if (options.shareMode) { - query._aggregate[0].$match['root.shareMode'] = 'public'; - } + if (options.shareMode) { + query._aggregate[0].$match['root.shareMode'] = 'public'; + } - if (options.titleLike) { - query._aggregate[0].$match['root.title'] = { - $regex: options.titleLike, - $options: 'i' - }; - } + if (options.titleLike) { + query._aggregate[0].$match['root.title'] = { + $regex: options.titleLike, + $options: 'i' + }; + } - if (options.featured) { - query._aggregate[0].$match['root.featured'] = {$exists: true, $ne: 'false'}; - query._aggregate[1].$sort = {'root.featured' : -1}; - } + if (options.featured) { + query._aggregate[0].$match['root.featured'] = {$exists: true, $ne: 'false'}; + query._aggregate[1].$sort = {'root.featured': -1}; + } - if (options.latest) { - query._aggregate[1].$sort = {'_id' : -1}; - } + if (options.latest) { + query._aggregate[1].$sort = {'_id': -1}; + } - if (options.projection){ - query._aggregate.push({ - $project: options.projection - }); - } + if (options.projection) { + query._aggregate.push({ + $project: options.projection + }); + } - return query; - } + return query; + } - /* - * Find all the projects that meet some condition - */ - function all(options) { + /* + * Find all the projects that meet some condition + */ + function all(options) { - if (!options.pagination) { - options.pagination = {}; - } + if (!options.pagination) { + options.pagination = {}; + } - if (!options.pagination.pageSize) { - options.pagination.pageSize = 12; - } + if (!options.pagination.pageSize) { + options.pagination.pageSize = 12; + } - if (!options.pagination.pageIndex) { - options.pagination.pageIndex = 0; - } + if (!options.pagination.pageIndex) { + options.pagination.pageIndex = 0; + } - var projects = [], - query = buildAllQuery(options); + var projects = [], + query = buildAllQuery(options); - var nextPage = function () { - // build a new options parameter for next page - var nextPageOptions = options; + var nextPage = function () { + // build a new options parameter for next page + var nextPageOptions = options; - nextPageOptions.pagination.pageIndex += 1; + nextPageOptions.pagination.pageIndex += 1; - return all(nextPageOptions); - }; + return all(nextPageOptions); + }; - var projsPromise = $q(function(resolve, reject) { - swellRT.query(query, function(result) { + var projsPromise = $q(function (resolve, reject) { + swellRT.query(query, function (result) { - if (result.length === 0) { - nextPage = undefined; - } + if (result.length === 0) { + nextPage = undefined; + } - angular.forEach(result.result, function(val){ - var v = new ProjectReadOnly(val); - projects.push(v); - }); + angular.forEach(result.result, function (val) { + var v = new ProjectReadOnly(val); + projects.push(v); + }); - resolve(projects); - }, - function(error){ - console.log(error); + resolve(projects); + }, + function (error) { + console.log(error); - reject([]); + reject([]); + }); }); - }); - projsPromise.next = nextPage; + projsPromise.next = nextPage; - return projsPromise; - } + return projsPromise; + } - function contributing (options = {}) { + function contributing(options = {}) { - if (!SessionSvc.users.loggedIn()) { - return $q(function(resolve) { - resolve([]); - }); - } + if (!SessionSvc.users.loggedIn()) { + return $q(function (resolve) { + resolve([]); + }); + } - options.contributor = User.currentId(); + options.contributor = User.currentId(); - return all(options); - } + return all(options); + } - function find(id) { - var def = $q.defer(); + function find(id) { + var def = $q.defer(); - if (!openedProjects[id]) { - openedProjects[id] = def.promise; - swellRT.openModel(id, function(model){ - $timeout(function(){ - var pr = swellRT.proxy(model, Project); - def.resolve(pr); + if (!openedProjects[id]) { + openedProjects[id] = def.promise; + swellRT.openModel(id, function (model) { + $timeout(function () { + var pr = swellRT.proxy(model, Project); + def.resolve(pr); + }); + }, function (error) { + console.log(error); + def.reject(error); }); - }, function(error){ - console.log(error); - def.reject(error); - }); + } + return openedProjects[id]; + } + + function findByUrlId(urlId) { + return find(base64.urldecode(urlId)); } - return openedProjects[id]; - } - function findByUrlId(urlId) { - return find(base64.urldecode(urlId)); - } + function create(options, callback) { + var d = $q.defer(); - function create(options, callback) { - var d = $q.defer(); + swellRT.createModel(function (model) { + openedProjects[model.id()] = d.promise; - swellRT.createModel(function(model){ - openedProjects[model.id()] = d.promise; + SwellRTCommon.makeModelPublic(model); - SwellRTCommon.makeModelPublic(model); + var proxyProj; - var proxyProj; + $timeout(function () { + proxyProj = swellRT.proxy(model, Project); + }); - $timeout(function(){ - proxyProj = swellRT.proxy(model, Project); + $timeout(function () { + proxyProj.type = 'project'; + proxyProj.communities = + (options.communityId) ? [options.communityId] : []; + proxyProj.id = model.id(); + proxyProj.title = ''; + proxyProj.chat = []; + proxyProj.pad = new swellRT.TextObject(); + proxyProj.attachments = {}; + proxyProj.needs = []; + proxyProj.lastAccesses = []; + proxyProj.promoter = User.currentId(); + proxyProj.supporters = []; + proxyProj.shareMode = 'public'; + d.resolve(proxyProj); + }); }); - $timeout(function(){ - proxyProj.type = 'project'; - proxyProj.communities = - (options.communityId) ? [ options.communityId ] : []; - proxyProj.id = model.id(); - proxyProj.title = ''; - proxyProj.chat = []; - proxyProj.pad = new swellRT.TextObject(); - proxyProj.attachments = {}; - proxyProj.needs = []; - proxyProj.lastAccesses = []; - proxyProj.promoter = User.currentId(); - proxyProj.supporters = []; - proxyProj.shareMode = 'public'; - d.resolve(proxyProj); + d.promise.then(callback); + + return d.promise; + } + + function updateTrello() { + $location.path(localStorage.getItem('projectUrl')); + let locationSplit = $location.path().split('/'); + let urlId = locationSplit[locationSplit.length - 1]; + let id = base64.urldecode(urlId); + if (!localStorage.getItem('trelloTeemToken')) return; + find(id).then((model) => { + model.trello = {}; + model.trello.token = localStorage.getItem('trelloTeemToken'); + localStorage.removeItem('trelloTeemToken'); + trelloSvc.createTrelloBoard(model).then((BoardData) => { + model.trello.boardId = BoardData.id; + trelloSvc.createNewList(model.trello).then((listData) => { + model.trello.listId = listData.id; + }) + .catch(err => console.log(err)); + }) + .catch(err => console.error(err)); }); - }); - - d.promise.then(callback); - - return d.promise; - } - - var projectListProjection = { - participants: 1, - root: { - title: 1, - image: 1, - id: 1, - _urlId: 1, - type: 1, - featured: 1, - communities: 1, - pad: { - lastmodtime: 1 - } } - }; - - return { - all, - contributing, - findByUrlId, - find, - create, - projectListProjection - }; - }]); + + var projectListProjection = { + participants: 1, + root: { + title: 1, + image: 1, + id: 1, + _urlId: 1, + type: 1, + featured: 1, + communities: 1, + pad: { + lastmodtime: 1 + } + } + }; + + return { + all, + contributing, + findByUrlId, + find, + create, + projectListProjection, + updateTrello + }; + }]); diff --git a/src/js/services/trelloService.js b/src/js/services/trelloService.js new file mode 100644 index 00000000..45210e6b --- /dev/null +++ b/src/js/services/trelloService.js @@ -0,0 +1,101 @@ +(function () { + 'use strict'; + + angular + .module('Teem') + .factory('trelloSvc', trelloSvc); + trelloSvc.$inject = ['$http', '$location', '$window', '$q']; + function trelloSvc($http, $location, $window, $q) { + + function getToken() { + localStorage.setItem('projectUrl', $location.path()); + const redirUrl = 'https://trello.com/1/authorize?expiration=never&name="Teem"&key=09e4aced60041e389dbb27b9accadd65&callback_method=fragment&scope=read%2Cwrite%2Caccount&return_url=http://localhost:8000/trello/get/'; + $window.location.href = redirUrl; + } + + function parseTokenFromUrl(urlString) { + console.log(urlString); + } + + function createTrelloBoard(prObj) { + let deferred = $q.defer(); + if (!prObj.trello.token) { + getToken(); + deferred.resolve(); + return deferred.promise; + } + console.log(prObj); + const BoardApiURL = `https://api.trello.com/1/boards/?name=${prObj.title}&key=09e4aced60041e389dbb27b9accadd65&token=${prObj.trello.token}`; + $http.post(BoardApiURL).then((result) => { + deferred.resolve(result.data); + }) + .catch((err) => { + deferred.reject(err); + }); + return deferred.promise; + } + + function createNewList(trelloObj) { + let deferred = $q.defer(); + if (!trelloObj.boardId) { + deferred.reject('No trello Board Id found!'); + } + else { + const listAPIendPoint = `https://api.trello.com/1/lists?name=Teem&idBoard=${trelloObj.boardId}&key=09e4aced60041e389dbb27b9accadd65&token=${trelloObj.token}`; + $http.post(listAPIendPoint) + .then((result) => { + deferred.resolve(result.data); + }) + .catch(err => deferred.reject(err)); + } + return deferred.promise; + } + + function addNewCard(trelloObj, need) { + let deferred = $q.defer(); + const newCardURL = `https://api.trello.com/1/cards?idList=${trelloObj.listId}&name=${need.text}&key=09e4aced60041e389dbb27b9accadd65&token=${trelloObj.token}`; + $http.post(newCardURL). + then(result => deferred.resolve(result.data)) + .catch(err => deferred.resolve(err)); + return deferred.promise; + } + + function archiveCard(trelloObj, need){ + let deferred = $q.defer(); + const archiveCardURL = `https://api.trello.com/1/cards/${need.trelloId}?closed=true&key=09e4aced60041e389dbb27b9accadd65&token=${trelloObj.token}`; + $http.put(archiveCardURL). + then(result => deferred.resolve(result.data)) + .catch(err => deferred.reject(err)); + return deferred.promise; + } + + function unarchiveCard(trelloObj, need){ + let deferred = $q.defer(); + const archiveCardURL = `https://api.trello.com/1/cards/${need.trelloId}?closed=false&key=09e4aced60041e389dbb27b9accadd65&token=${trelloObj.token}`; + $http.put(archiveCardURL). + then(result => deferred.resolve(result.data)) + .catch(err => deferred.reject(err)); + return deferred.promise; + } + + function addNewComment(trelloObj, need, comment){ + let deferred = $q.defer(); + const newCommentURL = `https://api.trello.com/1/cards/${need.trelloId}/actions/comments?key=09e4aced60041e389dbb27b9accadd65&token=${trelloObj.token}&text=${comment}`; + $http.post(newCommentURL). + then(result => deferred.resolve(result.data)) + .catch(err => deferred.reject(err)); + return deferred.promise; + } + + return { + getToken: getToken, + parseTokenFromUrl: parseTokenFromUrl, + createTrelloBoard: createTrelloBoard, + createNewList: createNewList, + addNewCard: addNewCard, + archiveCard: archiveCard, + addNewComment: addNewComment, + unarchiveCard: unarchiveCard + }; + } +})(); diff --git a/src/templates/pad.html b/src/templates/pad.html index db88fc30..83009723 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -66,3 +66,5 @@
+ + diff --git a/src/templates/projects/project/desktop.html b/src/templates/projects/project/desktop.html index 7f4d870f..cb5b60eb 100644 --- a/src/templates/projects/project/desktop.html +++ b/src/templates/projects/project/desktop.html @@ -54,8 +54,9 @@
+ +

need.nav.title

-

need.nav.title

diff --git a/swellrt/docker-compose.yml b/swellrt/docker-compose.yml index 32d8367f..65cb8dbb 100644 --- a/swellrt/docker-compose.yml +++ b/swellrt/docker-compose.yml @@ -15,6 +15,11 @@ services: mongo: image: mongo:latest restart: always + teem-link-preview: + image: krshubham/teem-link-preview:latest + restart: always + ports: + - "0.0.0.0:9090:9090"