From e2548ef6b1a79b5885abd8927803d5b861bc3830 Mon Sep 17 00:00:00 2001 From: Benjamin Gamard Date: Sat, 24 Mar 2018 21:42:34 +0100 Subject: [PATCH] Closes #190: lightweight text editor on description field --- docs-web/src/main/webapp/Gruntfile.js | 4 +- .../webapp/src/app/docs/directive/Pell.js | 28 +++ .../webapp/src/app/docs/filter/Newline.js | 13 - .../webapp/src/app/share/filter/Newline.js | 13 - docs-web/src/main/webapp/src/index.html | 4 +- docs-web/src/main/webapp/src/lib/pell.js | 226 ++++++++++++++++++ .../src/partial/docs/document.edit.html | 5 +- .../partial/docs/document.view.content.html | 2 +- .../main/webapp/src/partial/share/share.html | 2 +- docs-web/src/main/webapp/src/share.html | 1 - docs-web/src/main/webapp/src/style/pell.css | 26 ++ 11 files changed, 290 insertions(+), 34 deletions(-) create mode 100644 docs-web/src/main/webapp/src/app/docs/directive/Pell.js delete mode 100644 docs-web/src/main/webapp/src/app/docs/filter/Newline.js delete mode 100644 docs-web/src/main/webapp/src/app/share/filter/Newline.js create mode 100644 docs-web/src/main/webapp/src/lib/pell.js create mode 100644 docs-web/src/main/webapp/src/style/pell.css diff --git a/docs-web/src/main/webapp/Gruntfile.js b/docs-web/src/main/webapp/Gruntfile.js index 6225d832..cc3c18c9 100644 --- a/docs-web/src/main/webapp/Gruntfile.js +++ b/docs-web/src/main/webapp/Gruntfile.js @@ -25,7 +25,7 @@ module.exports = function(grunt) { options: { separator: ';' }, - src: ['src/lib/jquery.js','src/lib/jquery.ui.js','src/lib/underscore.js','src/lib/colorpicker.js', 'src/lib/angular.js', 'src/lib/angular.*.js', + src: ['src/lib/jquery.js','src/lib/jquery.ui.js','src/lib/underscore.js','src/lib/colorpicker.js', 'src/lib/pell.js', 'src/lib/angular.js', 'src/lib/angular.*.js', 'dist/app/docs/app.js', 'dist/app/docs/controller/**/*.js', 'dist/app/docs/directive/*.js', 'dist/app/docs/filter/*.js', 'dist/app/docs/service/*.js'], dest: 'dist/docs.js' }, @@ -33,7 +33,7 @@ module.exports = function(grunt) { options: { separator: ';' }, - src: ['src/lib/jquery.js','src/lib/jquery.ui.js','src/lib/underscore.js','src/lib/colorpicker.js', 'src/lib/angular.js', 'src/lib/angular.*.js', + src: ['src/lib/jquery.js','src/lib/jquery.ui.js','src/lib/underscore.js','src/lib/colorpicker.js', 'src/lib/pell.js', 'src/lib/angular.js', 'src/lib/angular.*.js', 'dist/app/share/app.js', 'dist/app/share/controller/*.js', 'dist/app/share/directive/*.js', 'dist/app/share/filter/*.js', 'dist/app/share/service/*.js'], dest: 'dist/share.js' }, diff --git a/docs-web/src/main/webapp/src/app/docs/directive/Pell.js b/docs-web/src/main/webapp/src/app/docs/directive/Pell.js new file mode 100644 index 00000000..a1201d95 --- /dev/null +++ b/docs-web/src/main/webapp/src/app/docs/directive/Pell.js @@ -0,0 +1,28 @@ +'use strict'; + +/** + * Pell directive. + */ +angular.module('docs').directive('pellEditor', function ($timeout) { + return { + restrict: 'E', + template: '
', + require: 'ngModel', + replace: true, + link: function (scope, element, attrs, ngModelCtrl) { + var editor = pell.init({ + element: element[0], + defaultParagraphSeparator: 'p', + onChange: function (html) { + $timeout(function () { + ngModelCtrl.$setViewValue(html); + }); + } + }); + + ngModelCtrl.$render = function() { + editor.content.innerHTML = ngModelCtrl.$viewValue || ''; + }; + } + }; +}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/docs/filter/Newline.js b/docs-web/src/main/webapp/src/app/docs/filter/Newline.js deleted file mode 100644 index e56e6bac..00000000 --- a/docs-web/src/main/webapp/src/app/docs/filter/Newline.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Filter converting new lines in
. - */ -angular.module('docs').filter('newline', function() { - return function(text) { - if (!text) { - return ''; - } - return text.replace(/\n/g, '
'); - } -}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/app/share/filter/Newline.js b/docs-web/src/main/webapp/src/app/share/filter/Newline.js deleted file mode 100644 index 5e5c44f8..00000000 --- a/docs-web/src/main/webapp/src/app/share/filter/Newline.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict'; - -/** - * Filter converting new lines in
. - */ -angular.module('share').filter('newline', function() { - return function(text) { - if (!text) { - return ''; - } - return text.replace(/\n/g, '
'); - } -}); \ No newline at end of file diff --git a/docs-web/src/main/webapp/src/index.html b/docs-web/src/main/webapp/src/index.html index fba724a4..223a7a6a 100644 --- a/docs-web/src/main/webapp/src/index.html +++ b/docs-web/src/main/webapp/src/index.html @@ -12,6 +12,7 @@ + @@ -32,6 +33,7 @@ + @@ -92,7 +94,6 @@ - @@ -100,6 +101,7 @@ + diff --git a/docs-web/src/main/webapp/src/lib/pell.js b/docs-web/src/main/webapp/src/lib/pell.js new file mode 100644 index 00000000..62ddf0ca --- /dev/null +++ b/docs-web/src/main/webapp/src/lib/pell.js @@ -0,0 +1,226 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : + typeof define === 'function' && define.amd ? define(['exports'], factory) : + (factory((global.pell = {}))); +}(this, (function (exports) { 'use strict'; + +var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; + +var defaultParagraphSeparatorString = 'defaultParagraphSeparator'; +var formatBlock = 'formatBlock'; +var addEventListener = function addEventListener(parent, type, listener) { + return parent.addEventListener(type, listener); +}; +var appendChild = function appendChild(parent, child) { + return parent.appendChild(child); +}; +var createElement = function createElement(tag) { + return document.createElement(tag); +}; +var queryCommandState = function queryCommandState(command) { + return document.queryCommandState(command); +}; +var queryCommandValue = function queryCommandValue(command) { + return document.queryCommandValue(command); +}; + +var exec = function exec(command) { + var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; + return document.execCommand(command, false, value); +}; + +var defaultActions = { + bold: { + icon: 'B', + title: 'Bold', + state: function state() { + return queryCommandState('bold'); + }, + result: function result() { + return exec('bold'); + } + }, + italic: { + icon: 'I', + title: 'Italic', + state: function state() { + return queryCommandState('italic'); + }, + result: function result() { + return exec('italic'); + } + }, + underline: { + icon: 'U', + title: 'Underline', + state: function state() { + return queryCommandState('underline'); + }, + result: function result() { + return exec('underline'); + } + }, + strikethrough: { + icon: 'S', + title: 'Strike-through', + state: function state() { + return queryCommandState('strikeThrough'); + }, + result: function result() { + return exec('strikeThrough'); + } + }, + heading1: { + icon: 'H1', + title: 'Heading 1', + result: function result() { + return exec(formatBlock, '

'); + } + }, + heading2: { + icon: 'H2', + title: 'Heading 2', + result: function result() { + return exec(formatBlock, '

'); + } + }, + paragraph: { + icon: '¶', + title: 'Paragraph', + result: function result() { + return exec(formatBlock, '

'); + } + }, + quote: { + icon: '“ ”', + title: 'Quote', + result: function result() { + return exec(formatBlock, '

'); + } + }, + olist: { + icon: '#', + title: 'Ordered List', + result: function result() { + return exec('insertOrderedList'); + } + }, + ulist: { + icon: '•', + title: 'Unordered List', + result: function result() { + return exec('insertUnorderedList'); + } + }, + code: { + icon: '</>', + title: 'Code', + result: function result() { + return exec(formatBlock, '
');
+    }
+  },
+  line: {
+    icon: '―',
+    title: 'Horizontal Line',
+    result: function result() {
+      return exec('insertHorizontalRule');
+    }
+  },
+  link: {
+    icon: '🔗',
+    title: 'Link',
+    result: function result() {
+      var url = window.prompt('Enter the link URL');
+      if (url) exec('createLink', url);
+    }
+  },
+  image: {
+    icon: '📷',
+    title: 'Image',
+    result: function result() {
+      var url = window.prompt('Enter the image URL');
+      if (url) exec('insertImage', url);
+    }
+  }
+};
+
+var defaultClasses = {
+  actionbar: 'pell-actionbar',
+  button: 'pell-button',
+  content: 'pell-content',
+  selected: 'pell-button-selected'
+};
+
+var init = function init(settings) {
+  var actions = settings.actions ? settings.actions.map(function (action) {
+    if (typeof action === 'string') return defaultActions[action];else if (defaultActions[action.name]) return _extends({}, defaultActions[action.name], action);
+    return action;
+  }) : Object.keys(defaultActions).map(function (action) {
+    return defaultActions[action];
+  });
+
+  var classes = _extends({}, defaultClasses, settings.classes);
+
+  var defaultParagraphSeparator = settings[defaultParagraphSeparatorString] || 'div';
+
+  var actionbar = createElement('div');
+  actionbar.className = classes.actionbar;
+  appendChild(settings.element, actionbar);
+
+  var content = settings.element.content = createElement('div');
+  content.contentEditable = true;
+  content.className = classes.content;
+  content.oninput = function (_ref) {
+    var firstChild = _ref.target.firstChild;
+
+    if (firstChild && firstChild.nodeType === 3) exec(formatBlock, '<' + defaultParagraphSeparator + '>');else if (content.innerHTML === '
') content.innerHTML = ''; + settings.onChange(content.innerHTML); + }; + content.onkeydown = function (event) { + if (event.key === 'Tab') { + event.preventDefault(); + } else if (event.key === 'Enter' && queryCommandValue(formatBlock) === 'blockquote') { + setTimeout(function () { + return exec(formatBlock, '<' + defaultParagraphSeparator + '>'); + }, 0); + } + }; + appendChild(settings.element, content); + + actions.forEach(function (action) { + var button = createElement('button'); + button.className = classes.button; + button.innerHTML = action.icon; + button.title = action.title; + button.setAttribute('type', 'button'); + button.onclick = function () { + return action.result() && content.focus(); + }; + + if (action.state) { + var handler = function handler() { + return button.classList[action.state() ? 'add' : 'remove'](classes.selected); + }; + addEventListener(content, 'keyup', handler); + addEventListener(content, 'mouseup', handler); + addEventListener(button, 'click', handler); + } + + appendChild(actionbar, button); + }); + + if (settings.styleWithCSS) exec('styleWithCSS'); + exec(defaultParagraphSeparatorString, defaultParagraphSeparator); + + return settings.element; +}; + +var pell = { exec: exec, init: init }; + +exports.exec = exec; +exports.init = init; +exports['default'] = pell; + +Object.defineProperty(exports, '__esModule', { value: true }); + +}))); diff --git a/docs-web/src/main/webapp/src/partial/docs/document.edit.html b/docs-web/src/main/webapp/src/partial/docs/document.edit.html index f493c5e9..7363dc99 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.edit.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.edit.html @@ -31,8 +31,9 @@
- + +

{{ 'validation.too_long' | translate }}

diff --git a/docs-web/src/main/webapp/src/partial/docs/document.view.content.html b/docs-web/src/main/webapp/src/partial/docs/document.view.content.html index c6dc3b95..dd1f2a81 100644 --- a/docs-web/src/main/webapp/src/partial/docs/document.view.content.html +++ b/docs-web/src/main/webapp/src/partial/docs/document.view.content.html @@ -1,4 +1,4 @@ -

+

{{ 'document.subject' | translate }}
{{ document.subject }}
diff --git a/docs-web/src/main/webapp/src/partial/share/share.html b/docs-web/src/main/webapp/src/partial/share/share.html index fe9badd9..5e87cf92 100644 --- a/docs-web/src/main/webapp/src/partial/share/share.html +++ b/docs-web/src/main/webapp/src/partial/share/share.html @@ -35,7 +35,7 @@
-

+

{{ 'document.subject' | translate }}
{{ document.subject }}
diff --git a/docs-web/src/main/webapp/src/share.html b/docs-web/src/main/webapp/src/share.html index 5b60071c..a2e4b2b8 100644 --- a/docs-web/src/main/webapp/src/share.html +++ b/docs-web/src/main/webapp/src/share.html @@ -43,7 +43,6 @@ - diff --git a/docs-web/src/main/webapp/src/style/pell.css b/docs-web/src/main/webapp/src/style/pell.css new file mode 100644 index 00000000..a0e6a1cf --- /dev/null +++ b/docs-web/src/main/webapp/src/style/pell.css @@ -0,0 +1,26 @@ +.pell { + border: 1px solid rgba(10, 10, 10, 0.1); + box-sizing: border-box; } + +.pell-content { + box-sizing: border-box; + height: 300px; + outline: 0; + overflow-y: auto; + padding: 10px; } + +.pell-actionbar { + background-color: #FFF; + border-bottom: 1px solid rgba(10, 10, 10, 0.1); } + +.pell-button { + background-color: transparent; + border: none; + cursor: pointer; + height: 30px; + outline: 0; + width: 30px; + vertical-align: bottom; } + +.pell-button-selected { + background-color: #F0F0F0; }