diff --git a/gulpfile.js b/gulpfile.js index fdd99d4f..b067b565 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -825,4 +825,4 @@ gulp.task('default', function(done){ tasks.push('watch'); seq('build', tasks, done); -}); +}); \ No newline at end of file diff --git a/src/js/directives/pad.js b/src/js/directives/pad.js index 8133d0f1..42ff45dc 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,100 @@ 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; + //these styles need to be present here for positioning purposes + div.style.top = clientRect.top + 35 + 'px'; + div.style.left = clientRect.left + 'px'; + 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 +142,13 @@ angular.module('Teem') // cannot use spinner template directly here parentElement.innerHTML = ` -
-
- - - -
-
`; +
+
+ + + +
+
`; $scope.project.attachments[state].file.getUrl().then(url => { parentElement.innerHTML = ``; @@ -71,25 +165,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 +210,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 +221,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 +237,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 +256,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 +274,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 +286,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 +337,9 @@ angular.module('Teem') }; - $scope.$watchCollection(function() { + $scope.$watchCollection(function () { return SessionSvc.status; - }, function(current) { + }, function (current) { $scope.pad.saving = !current.sync; }); @@ -248,7 +350,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/colors.sass b/src/sass/colors.sass index baa69613..583c916c 100644 --- a/src/sass/colors.sass +++ b/src/sass/colors.sass @@ -6,7 +6,8 @@ $teal: #00bfa0 /* Teem-green */ $blue: #4A87E0 $aubergine: #B2409E $yellow: #D8AB32 - +$black: #000 +$seashell: #f1f1f1 $twilight-blue: #0C5464 $pale-blue: #4E828F diff --git a/src/sass/pad.sass b/src/sass/pad.sass index 8d1817e2..8ff4d309 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,70 @@ font-weight: bold color: $pad-empty-tip-modal-description margin-top: 20px + +#popover + max-width: 330px + max-height: 270px + margin: 0 5px + border-radius: 6px + +div + &.popover-link-image + max-width: 330px + max-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 + width: 345px + height: 300px + position: absolute + border: 1px solid $grey + z-index: 3 + background-color: darken(#fff, 5%) + padding-top: 5px + &: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"