diff --git a/.travis/before_script.sh b/.travis/before_script.sh index 1d62dad7..a7d85c78 100755 --- a/.travis/before_script.sh +++ b/.travis/before_script.sh @@ -9,7 +9,7 @@ fi if [ $TRAVIS_BRANCH = "master" ] || [ $TRAVIS_BRANCH = "staging" ]; then echo "Using config.js for branch $TRAVIS_BRANCH" - openssl aes-256-cbc -K $encrypted_f03f2d3a9637_key -iv $encrypted_f03f2d3a9637_iv -in .travis/secrets.tar.enc -out .travis/secrets.tar -d + openssl aes-256-cbc -K $encrypted_249e297d6459_key -iv $encrypted_249e297d6459_iv -in .travis/secrets.tar.enc -out .travis/secrets.tar -d tar xvf .travis/secrets.tar --directory .travis diff --git a/.travis/secrets.tar.enc b/.travis/secrets.tar.enc index e24ba777..a192d49a 100644 Binary files a/.travis/secrets.tar.enc and b/.travis/secrets.tar.enc differ 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/directives/pad.js b/src/js/directives/pad.js index 8133d0f1..60b97a38 100644 --- a/src/js/directives/pad.js +++ b/src/js/directives/pad.js @@ -7,23 +7,23 @@ * # Chat Ctrl * Show Pad for a given project */ - +let timer; angular.module('Teem') - .directive('pad', function() { + .directive('pad', function () { return { scope: true, - link: function($scope, elem, attrs) { + link: function ($scope, elem, attrs) { $scope.editingDefault = attrs.editingDefault; }, controller: [ 'SessionSvc', '$rootScope', '$scope', '$route', '$location', - '$timeout', 'SharedState', 'needWidget', '$element', - function(SessionSvc, $rootScope, $scope, $route, $location, - $timeout, SharedState, needWidget, $element) { + '$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']; + 'format_align_left', 'format_align_center', 'format_align_right', + 'format_list_bulleted', 'format_list_numbered']; var annotationMap = { 'text_fields': 'paragraph/header=h3', @@ -39,6 +39,106 @@ angular.module('Teem') var annotations = {}; + function openLinkPopover(event, range) { + timer = $timeout(() => { + event.stopPropagation(); + let div = document.createElement('div'); + let btn = event.target; + //cannot inject the spinner HTML directly here + let inHTML = ` + +
+
+ + + +
+
+ `; + linkPreview.getMetaData(btn.href) + .then((meta) => { + if (!meta) { + div.style.display = 'none'; + return; + } + let urlImage = meta.image, + urlAuthor = meta.author, + urlTitle = meta.title, + urlDescription = meta.description; + let innerEle = document.createElement('div'); + if (urlImage && urlDescription) { + innerEle.innerHTML = document.getElementById('urlImage-and-urlDescription').innerHTML; + innerEle.querySelector('#popoverLinkTitle').innerHTML = urlTitle; + innerEle.querySelector('#popoverLinkImage').src = urlImage; + innerEle.querySelector('#popoverLinkDescription').innerHTML = urlDescription; + innerEle.querySelector('#popoverLinkUrl').innerHTML = btn.href; + inHTML = innerEle.innerHTML; + } + else if (urlDescription && !urlImage) { + div.style.height = '110px'; + innerEle.innerHTML = document.getElementById('description-and-no-image').innerHTML; + innerEle.querySelector('#popoverLinkTitle').innerHTML = urlTitle; + innerEle.querySelector('#popoverLinkDescription').innerHTML = urlDescription; + innerEle.querySelector('#popoverLinkUrl').innerHTML = btn.href; + inHTML = innerEle.innerHTML; + } + else { + if (!urlTitle) { + div.style.height = '110px'; + innerEle.innerHTML = document.getElementById('no-title-in-url').innerHTML; + innerEle.querySelector('#popoverLinkDescription').innerHTML = urlDescription; + innerEle.querySelector('#popoverLinkUrl').innerHTML = btn.href; + inHTML = innerEle.innerHTML; + } + else { + div.style.height = '110px'; + innerEle.innerHTML = document.getElementById('no-title-in-url').innerHTML; + innerEle.querySelector('#popoverLinkTitle').innerHTML = urlTitle; + innerEle.querySelector('#popoverLinkDescription').innerHTML = urlDescription; + innerEle.querySelector('#popoverLinkUrl').innerHTML = btn.href; + inHTML = innerEle.innerHTML; + } + } + 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')); + } + }, delay); + } + } + function imgWidget(parentElement, before, state) { state = state || before; @@ -48,13 +148,13 @@ angular.module('Teem') // cannot use spinner template directly here parentElement.innerHTML = ` -
-
- - - -
-
`; +
+
+ + + +
+
`; $scope.project.attachments[state].file.getUrl().then(url => { parentElement.innerHTML = ``; @@ -71,25 +171,33 @@ angular.module('Teem') $scope.padAnnotations = { 'paragraph/header': { - onAdd: function() { + onAdd: function () { $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); $timeout(); }, - onChange: function() { + onChange: function () { $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); $timeout(); }, - onRemove: function() { + onRemove: function () { $scope.pad.outline = this.editor.getAnnotationSet('paragraph/header'); $timeout(); } }, 'link': { - onEvent: function(range, event) { + onEvent: function (range, event) { if (event.type === 'click') { event.stopPropagation(); + closeLinkPopover(0); $scope.linkModal.open(range); } + else if (event.type === 'mouseover') { + openLinkPopover(event, range); + console.log(range); + } + else if (event.type === 'mouseout') { + closeLinkPopover(500); + } } } }; @@ -108,10 +216,10 @@ angular.module('Teem') $timeout(); } - $scope.padCreate = function(editor) { + $scope.padCreate = function (editor) { $scope.linkModal = { - add: function(event) { + add: function (event) { event.stopPropagation(); let range = editor.getSelection(); if (range.text) { @@ -119,7 +227,7 @@ angular.module('Teem') } $scope.linkModal.open(range); }, - open: function(range) { + open: function (range) { let annotation = editor.getAnnotationInRange(range, 'link'); $scope.linkModal.range = range; @@ -135,17 +243,17 @@ angular.module('Teem') $scope.linkModal.link = annotation ? annotation.value : ''; $scope.linkModal.show = true; - let emptyInput = !range.text ? 'text': 'link'; + let emptyInput = !range.text ? 'text' : 'link'; let autofocus = document.querySelector('#link-modal [ng-model="linkModal.' + emptyInput + '"]'); $timeout(() => autofocus && autofocus.focus()); }, - change: function() { + 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() { + clear: function () { editor.clearAnnotationInRange($scope.linkModal.range, 'link'); $scope.linkModal.show = false; $scope.linkModal.edit = false; @@ -154,13 +262,13 @@ angular.module('Teem') disableAllButtons(); - editor.onSelectionChanged(function(range) { + editor.onSelectionChanged(function (range) { annotations = range.annotations; updateAllButtons(); }); }; - $scope.padReady = function(editor) { + $scope.padReady = function (editor) { // FIXME // SwellRT editor is created with .wave-editor-off // Should use .wave-editor-on when SwellRT editor callback is available @@ -172,7 +280,7 @@ angular.module('Teem') $scope.pad.outline = editor.getAnnotationSet('paragraph/header'); - $scope.annotate = function(btn) { + $scope.annotate = function (btn) { let [key, val] = annotationMap[btn].split('='); let currentVal = annotations[key]; if (currentVal === val) { @@ -184,12 +292,12 @@ angular.module('Teem') editorElement.focus(); }; - $scope.clearFormat = function() { + $scope.clearFormat = function () { editor.clearAnnotation('style'); editorElement.focus(); }; - $scope.widget = function(type) { + $scope.widget = function (type) { if (type === 'need') { needWidget.add(editor, $scope); } @@ -235,9 +343,9 @@ angular.module('Teem') }; - $scope.$watchCollection(function() { + $scope.$watchCollection(function () { return SessionSvc.status; - }, function(current) { + }, function (current) { $scope.pad.saving = !current.sync; }); @@ -248,7 +356,7 @@ angular.module('Teem') }); }; - }], + }], 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/sass/pad.sass b/src/sass/pad.sass index 8d1817e2..c0dc6f39 100644 --- a/src/sass/pad.sass +++ b/src/sass/pad.sass @@ -77,7 +77,7 @@ .project-desktop .project-pad-editing - margin-top: -61px + margin-top: -61px .swellrt-placeholder:empty color: $placeholder-color font-family: "Lato", sans-serif, "Material Icons" @@ -183,3 +183,66 @@ font-weight: bold color: $pad-empty-tip-modal-description margin-top: 20px + +$black: #000 +$seashell: #f1f1f1 + +#popover + width: 330px + height: 270px + margin: 0 5px + border-radius: 6px + +div + &.popover-link-image + width: 330px + height: 190px + margin: 5px auto + + &.popover-link-description + width: 320px + height: auto + max-height: 50px + margin: 0 auto + overflow: hidden + word-wrap: break-word + + +.popover-link-title + word-wrap: break-word + text-overflow: ellpsis + overflow: hidden + white-space: nowrap + font-weight: 600 + margin-left: 5px + +.popover-link-address + color: $black + margin-left: 5px + overflow: hidden + word-wrap: break-word + text-overflow: ellipsis + white-space: nowrap + +#popover-container + &:after + content: "" + position: absolute + bottom: -25px + left: 175px + border-style: solid + visibility: hidden + width: 0 + z-index: 1 + + &:before + content: "" + position: absolute + top: -11px + left: -1px + border-style: solid + border-width: 0 10px 10px + border-color: $seashell transparent + display: block + width: 0 + z-index: 0 diff --git a/src/templates/pad.html b/src/templates/pad.html index db88fc30..42c50b35 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -1,6 +1,6 @@
+ ngf-drop="widget('img', $file)" ngf-fix-orientation="true">
+ +
+ +
+
+ + + + +
+
+ + +
+
+ + + +
+
+ +
+
+ + +
+
+ +
+
+ + + +
+
+
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"