- Add texteditor for system description field, closed #698

This commit is contained in:
Mark Friedrich
2018-10-06 00:44:17 +02:00
parent 184ec228da
commit 0c47e6874e
22 changed files with 912 additions and 506 deletions

View File

@@ -10,6 +10,7 @@ namespace Controller\Api;
use Controller;
use Model;
use Exception;
class System extends Controller\AccessController {
@@ -79,24 +80,34 @@ class System extends Controller\AccessController {
}
if( !is_null($systemModel) ){
// set/update system custom data
$systemModel->copyfrom($systemData, ['statusId', 'locked', 'rallyUpdated', 'position', 'description']);
try{
// set/update system custom data
$systemModel->copyfrom($systemData, ['statusId', 'locked', 'rallyUpdated', 'position', 'description']);
if($systemModel->save($activeCharacter)){
// get data from "fresh" model (e.g. some relational data has changed: "statusId")
/**
* @var $newSystemModel Model\SystemModel
*/
$newSystemModel = Model\BasicModel::getNew('SystemModel');
$newSystemModel->getById( $systemModel->_id, 0);
$newSystemModel->clearCacheData();
$return->systemData = $newSystemModel->getData();
if($systemModel->save($activeCharacter)){
// get data from "fresh" model (e.g. some relational data has changed: "statusId")
/**
* @var $newSystemModel Model\SystemModel
*/
$newSystemModel = Model\BasicModel::getNew('SystemModel');
$newSystemModel->getById( $systemModel->_id, 0);
$newSystemModel->clearCacheData();
$return->systemData = $newSystemModel->getData();
// broadcast map changes
$this->broadcastMapData($newSystemModel->mapId);
}else{
$return->error = $systemModel->getErrors();
// broadcast map changes
$this->broadcastMapData($newSystemModel->mapId);
}else{
$return->error = $systemModel->getErrors();
}
}catch(Exception\ValidationException $e){
$validationError = (object) [];
$validationError->type = 'error';
$validationError->field = $e->getField();
$validationError->message = $e->getMessage();
$return->error[] = $validationError;
}
}
}

View File

@@ -94,10 +94,11 @@ class SystemModel extends AbstractMapTrackingModel {
'activity-log' => true
],
'description' => [
'type' => Schema::DT_VARCHAR512,
'type' => Schema::DT_TEXT,
'nullable' => false,
'default' => '',
'activity-log' => true
'activity-log' => true,
'validate' => true
],
'posX' => [
'type' => Schema::DT_INT,
@@ -262,6 +263,21 @@ class SystemModel extends AbstractMapTrackingModel {
return $valid;
}
/**
* @param string $key
* @param string $val
* @return bool
* @throws \Exception\ValidationException
*/
protected function validate_description(string $key, string $val): bool {
$valid = true;
if(mb_strlen($val) > 9000){
$valid = false;
$this->throwValidationException($key);
}
return $valid;
}
/**
* setter for system alias
* @param string $alias

View File

@@ -23,6 +23,7 @@
"php-64bit": ">=7.0",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-ctype": "*",
"ext-zmq": ">=1.1.3",
"react/zmq": "0.3.*",

View File

@@ -23,6 +23,7 @@
"php-64bit": ">=7.0",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-ctype": "*",
"ext-zmq": ">=1.1.3",
"react/zmq": "0.3.*",

View File

@@ -56,7 +56,9 @@ requirejs.config({
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
summernote: 'lib/summernote/summernote.min', // v0.8.10 Summernote WYSIWYG editor
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org
'summernote': 'lib/summernote/summernote.min',
// header animation
easePack: 'lib/EasePack.min',

View File

@@ -275,7 +275,8 @@ define([
// exclude some HTML Tags from watcher
if(
e.target.tagName !== 'INPUT' &&
e.target.tagName !== 'TEXTAREA'
e.target.tagName !== 'TEXTAREA' &&
!e.target.classList.contains('note-editable') // Summerstyle editor
){
let key = e.key.toUpperCase();
map[key] = true;

View File

@@ -79,7 +79,6 @@ define([
callback(newSystemData);
}
// show errors
if(
responseData.error &&
responseData.error.length > 0

187
js/app/summernote.loader.js Normal file
View File

@@ -0,0 +1,187 @@
define([
'jquery',
'app/init',
'summernote'
], ($, Init) => {
'use strict';
// all Summernote stuff is available...
let initDefaultSummernoteConfig = () => {
// "length" hint plugin ---------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
lengthField: function (context){
let self = this;
let ui = $.summernote.ui;
// add counter
context.memo('button.lengthField', () => {
return $('<kbd>', {
class: ['text-right', 'txt-color'].join(' ')
});
});
/**
* update counter element with left chars
* @param contents
*/
let updateCounter = (contents) => {
let maxTextLength = context.options.maxTextLength;
let textLength = contents.length;
let counter = context.layoutInfo.toolbar.find('kbd');
let counterLeft = maxTextLength - textLength;
counter.text(counterLeft).data('charCount', counterLeft);
counter.toggleClass('txt-color-red', maxTextLength <= textLength);
// disable "save" button
let saveBtn = context.layoutInfo.toolbar.find('.btn-save');
saveBtn.prop('disabled', maxTextLength < textLength);
};
// events
this.events = {
'summernote.init': function (we, e) {
updateCounter(context.$note.summernote('code'));
},
'summernote.change': function(we, contents){
updateCounter(contents);
}
};
}
});
// "discard" button plugin ------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
discardBtn: function (context){
let self = this;
let ui = $.summernote.ui;
// add button
context.memo('button.discardBtn', () => {
let button = ui.button({
contents: '<i class="fas fa-fw fa-ban"/>',
container: 'body',
click: function(){
// show confirmation dialog
$(this).confirmation('show');
}
});
let $button = button.render();
// show "discard" changes confirmation
let confirmationSettings = {
container: 'body',
placement: 'top',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
title: 'discard changes',
btnOkClass: 'btn btn-sm btn-warning',
btnOkLabel: 'discard',
btnOkIcon: 'fas fa-fw fa-ban',
onConfirm: (e, target) => {
// discard all changes
context.$note.summernote('reset');
context.$note.summernote('destroy');
}
};
$button.confirmation(confirmationSettings);
return $button;
});
}
});
};
/**
* init new Summernote editor
* @param element
* @param options
*/
let initSummernote = (element, options) => {
let defaultOptions = {
dialogsInBody: true,
dialogsFade: true,
//textareaAutoSync: false,
//hintDirection: 'right',
//tooltip: 'right',
//container: 'body',
styleTags: ['p', 'h2', 'h3', 'blockquote'],
linkTargetBlank: true,
tableClassName: 'table table-condensed table-bordered',
insertTableMaxSize: {
col: 5,
row: 5
},
icons: {
//'align': 'note-icon-align',
'alignCenter': 'fas fa-align-center',
'alignJustify': 'fas fa-align-justify',
'alignLeft': 'fas fa-align-left',
'alignRight': 'fas fa-align-right',
//'rowBelow': 'note-icon-row-below',
//'colBefore': 'note-icon-col-before',
//'colAfter': 'note-icon-col-after',
//'rowAbove': 'note-icon-row-above',
//'rowRemove': 'note-icon-row-remove',
//'colRemove': 'note-icon-col-remove',
'indent': 'fas fa-indent',
'outdent': 'fas fa-outdent',
'arrowsAlt': 'fas fa-expand-arrows-alt',
'bold': 'fas fa-bold',
'caret': 'fas fa-caret-down',
'circle': 'fas fa-circle',
'close': 'fas fa-time',
'code': 'fas fa-code',
'eraser': 'fas fa-eraser',
'font': 'fas fa-font',
//'frame': 'note-icon-frame',
'italic': 'fas fa-italic',
'link': 'fas fa-link',
'unlink': 'fas fa-unlink',
'magic': 'fas fa-magic',
'menuCheck': 'fas fa-check',
'minus': 'fas fa-minus',
'orderedlist': 'fas fa-list-ol',
'pencil': 'fa-pen',
'picture': 'fas fa-image',
'question': 'fas fa-question',
'redo': 'fas fa-redo',
'square': 'fas fa-square',
'strikethrough': 'fas fa-strikethrough',
'subscript': 'fas fa-subscript',
'superscript': 'fas fa-superscript',
'table': 'fas fa-table',
'textHeight': 'fas fa-text-height',
'trash': 'fas fa-trash',
'underline': 'fas fa-underline',
'undo': 'fas fa-undo',
'unorderedlist': 'fas fa-list-ul',
'video': 'fab fa-youtube'
},
colors: [
['#5cb85c', '#e28a0d', '#d9534f', '#e06fdf', '#9fa8da', '#e2ce48', '#428bca']
],
colorsName: [
['Green', 'Orange', 'Red', 'Pink', 'Indigo', 'Yellow', 'Blue']
],
};
options = $.extend({}, defaultOptions, options);
element.summernote(options);
};
initDefaultSummernoteConfig();
return {
initSummernote: initSummernote
};
});

View File

@@ -6,8 +6,7 @@ define([
'jquery',
'app/init',
'app/util',
'app/map/util',
'summernote'
'app/map/util'
], ($, Init, Util, MapUtil) => {
'use strict';
@@ -38,7 +37,7 @@ define([
systemInfoWormholeClass: 'pf-system-info-wormhole-', // class prefix for static wormhole element
// description field
descriptionArea: 'pf-system-info-description-area', // class for "description" area
descriptionAreaClass: 'pf-system-info-description-area', // class for "description" area
addDescriptionButtonClass: 'pf-system-info-description-button', // class for "add description" button
descriptionTextareaElementClass: 'pf-system-info-description', // class for "description" textarea element (xEditable)
@@ -50,213 +49,44 @@ define([
};
// max character length for system description
let maxDescriptionLength = 512;
let maxDescriptionLength = 9000;
/**
* save system (description)
* @param requestData
* @param context
* @param callback
*/
let saveSystem = (requestData, context, callback) => {
context.descriptionArea.showLoadingAnimation();
let initTextEditor = (element, options) => {
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
// "length" hint plugin ---------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
lengthField: function (context){
let self = this;
let ui = $.summernote.ui;
// add counter
context.memo('button.lengthField', () => {
return $('<kbd>', {
class: ['text-right', 'txt-color'].join(' ')
});
});
/**
* update counter element with left chars
* @param contents
*/
let updateCounter = (contents) => {
let maxTextLength = context.options.maxTextLength;
let textLength = contents.length;
let counter = context.layoutInfo.toolbar.find('kbd');
let counterLeft = maxTextLength - textLength;
counter.text(counterLeft).data('charCount', counterLeft);
counter.toggleClass('txt-color-red', maxTextLength <= textLength);
// disable "save" button
let saveBtn = context.layoutInfo.toolbar.find('.btn-save');
saveBtn.prop('disabled', maxTextLength < textLength);
};
// events
this.events = {
'summernote.init': function (we, e) {
updateCounter(context.$note.summernote('code'));
},
'summernote.change': function(we, contents){
updateCounter(contents);
}
};
if( !$.isEmptyObject(newSystemData) ){
callback(newSystemData);
}
});
// "discard" button plugin ------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
discardBtn: function (context){
let self = this;
let ui = $.summernote.ui;
// add button
context.memo('button.discardBtn', () => {
let button = ui.button({
contents: '<i class="fas fw fa-times"/>',
container: 'body',
click: function(){
// show confirmation dialog
$(this).confirmation('show');
}
});
let $button = button.render();
// show "discard" changes confirmation
let confirmationSettings = {
container: 'body',
placement: 'top',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'discard changes',
btnOkClass: 'btn btn-sm btn-warning',
btnOkLabel: 'discard',
btnOkIcon: 'fas fa-fw fa-times',
onConfirm: (e, target) => {
// discard all changes
context.$note.summernote('reset');
context.$note.summernote('destroy');
}
};
$button.confirmation(confirmationSettings);
return $button;
});
}
});
// "save button -----------------------------------------------------------------------------------------------
let saveBtn = context => {
let ui = $.summernote.ui;
let button = ui.button({
contents: '<i class="fas fw fa-check"/>',
container: 'body',
className: ['btn-success', 'btn-save'],
click: e => {
// save changes
if( !context.$note.summernote('isEmpty') ){
// get current code
let description = context.$note.summernote('code');
console.log('code to save: ', description)
}
context.$note.summernote('destroy');
if(
responseData.error &&
responseData.error.length > 0
){
for(let error of responseData.error){
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
});
return button.render();
};
let defaultOptions = {
height: 68, // set editor height
minHeight: 68, // set minimum height of editor
maxHeight: 500, // set maximum height of editor
dialogsInBody: true,
dialogsFade: true,
//textareaAutoSync: false,
//hintDirection: 'right',
//tooltip: 'right',
//container: 'body',
toolbar: [
['style', ['style']],
['font', ['bold', 'underline', 'strikethrough', 'clear']],
['color', ['color']],
//['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'hr']],
//['view', ['codeview', 'help']],
['misc', ['undo', 'redo']],
['lengthField'],
['customBtn', [ 'discardBtn', 'saveBtn']]
],
buttons: {
saveBtn: saveBtn
},
insertTableMaxSize: {
col: 4,
row: 4
},
icons: {
//'align': 'note-icon-align',
'alignCenter': 'fas fa-align-center',
'alignJustify': 'fas fa-align-justify',
'alignLeft': 'fas fa-align-left',
'alignRight': 'fas fa-align-right',
//'rowBelow': 'note-icon-row-below',
//'colBefore': 'note-icon-col-before',
//'colAfter': 'note-icon-col-after',
//'rowAbove': 'note-icon-row-above',
//'rowRemove': 'note-icon-row-remove',
//'colRemove': 'note-icon-col-remove',
'indent': 'fas fa-indent',
'outdent': 'fas fa-outdent',
'arrowsAlt': 'fas fa-expand-arrows-alt',
'bold': 'fas fa-bold',
'caret': 'fas fa-caret-down',
'circle': 'fas fa-circle',
'close': 'fas fa-time',
'code': 'fas fa-code',
'eraser': 'fas fa-eraser',
'font': 'fas fa-font',
//'frame': 'note-icon-frame',
'italic': 'fas fa-italic',
'link': 'fas fa-link',
'unlink': 'fas fa-unlink',
'magic': 'fas fa-magic',
'menuCheck': 'fas fa-check',
'minus': 'fas fa-minus',
'orderedlist': 'fas fa-list-ol',
'pencil': 'fa-pen',
'picture': 'fas fa-image',
'question': 'fas fa-question',
'redo': 'fas fa-redo',
'square': 'fas fa-square',
'strikethrough': 'fas fa-strikethrough',
'subscript': 'fas fa-subscript',
'superscript': 'fas fa-superscript',
'table': 'fas fa-table',
'textHeight': 'fas fa-text-height',
'trash': 'fas fa-trash',
'underline': 'fas fa-underline',
'undo': 'fas fa-undo',
'unorderedlist': 'fas fa-list-ul',
'video': 'fab fa-youtube'
},
colors: [
['#5cb85c', '#e28a0d', '#d9534f', '#e06fdf', '#9fa8da', '#e2ce48', '#428bca']
],
colorsName: [
['Green', 'Orange', 'Red', 'Pink', 'Indigo', 'Yellow', 'Blue']
],
};
options = $.extend({}, defaultOptions, options);
element.summernote(options);
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
}).always(function(){
this.descriptionArea.hideLoadingAnimation();
});
};
/**
@@ -267,8 +97,24 @@ define([
*/
let updateModule = (moduleElement, systemData) => {
let systemId = moduleElement.data('id');
let updated = moduleElement.data('updated');
if(
systemId === systemData.id &&
updated !== systemData.updated.updated
){
let setUpdated = true;
// created/updated tooltip --------------------------------------------------------------------------------
let nameRowElement = moduleElement.find('.' + config.systemInfoNameClass);
let tooltipData = {
created: systemData.created,
updated: systemData.updated
};
nameRowElement.addCharacterInfoTooltip( tooltipData );
if(systemId === systemData.id){
// update system status -----------------------------------------------------------------------------------
let systemStatusLabelElement = moduleElement.find('.' + config.systemInfoStatusLabelClass);
let systemStatusId = parseInt( systemStatusLabelElement.attr( config.systemInfoStatusAttributeName ) );
@@ -286,22 +132,33 @@ define([
// update description textarea ----------------------------------------------------------------------------
let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass);
if(typeof descriptionTextareaElement.data().summernote === 'object'){
// "Summernote" editor is currently open
console.log('Open');
}else{
// not open
console.log('NOT open');
if(descriptionTextareaElement.length){
let description = descriptionTextareaElement.html();
console.log(description);
console.log('update: ', description === systemData.description);
if(description !== systemData.description){
descriptionTextareaElement.html(systemData.description);
// description has changed
if(typeof descriptionTextareaElement.data().summernote === 'object'){
// "Summernote" editor is currently open
setUpdated = false;
}else{
// not open
let newDescription = systemData.description;
if( !Util.isValidHtml(newDescription) ){
// try to convert raw text into valid html
newDescription = newDescription.replace(/(\r\n|\n|\r)/g, '<br>');
newDescription = '<p>' + newDescription + '</p>';
}
descriptionTextareaElement.html(newDescription);
}
}
}
if(setUpdated){
moduleElement.data('updated', systemData.updated.updated);
}
}
moduleElement.find('.' + config.descriptionArea).hideLoadingAnimation();
moduleElement.find('.' + config.descriptionAreaClass).hideLoadingAnimation();
};
/**
@@ -355,6 +212,7 @@ define([
trueSecClass: Util.getTrueSecClassForSystem( systemData.trueSec ),
effectName: effectName,
effectClass: effectClass,
descriptionAreaClass: config.descriptionAreaClass,
descriptionButtonClass: config.addDescriptionButtonClass,
descriptionTextareaClass: config.descriptionTextareaElementClass,
systemNameClass: () => {
@@ -377,36 +235,126 @@ define([
systemUrlLinkClass: config.urlLinkClass
};
requirejs(['text!templates/modules/system_info.html', 'mustache'], (template, Mustache) => {
requirejs(['text!templates/modules/system_info.html', 'mustache', 'summernote.loader'], (template, Mustache, Summernote) => {
let content = Mustache.render(template, data);
moduleElement.append(content);
// lock "description" field until first update
moduleElement.find('.' + config.descriptionArea).showLoadingAnimation();
// WYSIWYG init on button click ---------------------------------------------------------------------------
let descriptionArea = moduleElement.find('.' + config.descriptionAreaClass);
let descriptionButton = moduleElement.find('.' + config.addDescriptionButtonClass);
let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass);
// lock "description" field until first update
descriptionArea.showLoadingAnimation();
// WYSIWYG init on button click ---------------------------------------------------------------------------
descriptionButton.on('click', function(e){
e.stopPropagation();
let descriptionButton = $(this);
// hide edit button
descriptionButton.hide();
initTextEditor(descriptionTextareaElement, {
// content has changed
let descriptionChanged = false;
Summernote.initSummernote(descriptionTextareaElement, {
height: 68, // set editor height
minHeight: 68, // set minimum height of editor
maxHeight: 500, // set maximum height of editor
focus: true,
placeholder: false,
maxTextLength: maxDescriptionLength,
disableDragAndDrop: true,
shortcuts: false,
toolbar: [
['style', ['style']],
['font', ['underline', 'strikethrough', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'hr']],
//['view', ['codeview', 'help']],
['misc', ['undo', 'redo']],
['lengthField'],
['customBtn', ['discardBtn', 'saveBtn']]
],
buttons: {
saveBtn: context => {
let ui = $.summernote.ui;
let button = ui.button({
contents: '<i class="fas fa-fw fa-check"/>',
container: 'body',
className: ['btn-success', 'btn-save'],
click: e => {
context.layoutInfo.editable.removeClass('has-error');
// save changes
if(descriptionChanged){
let validDescription = true;
let description = '';
if( context.$note.summernote('isEmpty') ){
// ... isEmpty -> clear empty default tags as well
context.$note.summernote('code', '');
}else{
description = context.$note.summernote('code');
if( !Util.isValidHtml(description) ){
// ... not valid HTML
validDescription = false;
context.layoutInfo.editable.addClass('has-error');
Util.showNotify({title: 'Validation failed', text: 'HTML not valid', type: 'error'});
}
}
if(validDescription){
// ... valid -> save()
saveSystem({
mapData: {
id: mapId
},
systemData: {
id: systemData.id,
description: description
}
}, {
descriptionArea: descriptionArea
}, (systemData) => {
// .. save callback
context.$note.summernote('destroy');
updateModule(moduleElement, systemData);
});
}
}else{
// ... no changes -> no save()
context.$note.summernote('destroy');
}
}
});
return button.render();
}
},
callbacks: {
onInit: function(context){
// make editable field a big larger
context.editable.css('height', '150px');
// set default background color
// -> could not figure out how to set by API as default color
context.toolbar.find('.note-current-color-button').attr('data-backcolor', config.defaultBgColor)
.find('.note-recent-color').css('background-color', config.defaultBgColor);
},
onChange: function(contents){
descriptionChanged = true;
},
onPaste: function (e) {
let bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');
e.preventDefault();
// Firefox fix
setTimeout(() => {
document.execCommand('insertText', false, bufferText);
}, 10);
},
onDestroy: function(context){
descriptionButton.show();
}
@@ -436,16 +384,6 @@ define([
});
});
// created/updated tooltip --------------------------------------------------------------------------------
let nameRowElement = moduleElement.find('.' + config.systemInfoNameClass);
let tooltipData = {
created: systemData.created,
updated: systemData.updated
};
nameRowElement.addCharacterInfoTooltip( tooltipData );
// constellation popover ----------------------------------------------------------------------------------
moduleElement.find('a.popup-ajax').popover({
html: true,

View File

@@ -2821,6 +2821,17 @@ define([
*/
let htmlDecode = value => $('<div>').html(value).text();
/**
* checks if html is valid
* -> see https://stackoverflow.com/a/15458968/4329969
* @param html
* @returns {boolean}
*/
let isValidHtml = html => {
let doc = new DOMParser().parseFromString(html, 'text/html');
return Array.from(doc.body.childNodes).some(node => node.nodeType === 1);
};
/**
* get deep json object value if exists
* -> e.g. key = 'first.last.third' string
@@ -3021,6 +3032,7 @@ define([
getDataTableInstance: getDataTableInstance,
htmlEncode: htmlEncode,
htmlDecode: htmlDecode,
isValidHtml: isValidHtml,
getObjVal: getObjVal,
redirect: redirect,
logout: logout,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -56,7 +56,9 @@ requirejs.config({
bootstrapToggle: 'lib/bootstrap-toggle.min', // v2.2.0 Bootstrap Toggle (Checkbox) - http://www.bootstraptoggle.com
lazyload: 'lib/jquery.lazyload.min', // v1.9.5 LazyLoader images - http://www.appelsiini.net/projects/lazyload
sortable: 'lib/sortable.min', // v1.6.0 Sortable - drag&drop reorder - https://github.com/rubaxa/Sortable
summernote: 'lib/summernote/summernote.min', // v0.8.10 Summernote WYSIWYG editor
'summernote.loader': './app/summernote.loader', // v0.8.10 Summernote WYSIWYG editor -https://summernote.org
'summernote': 'lib/summernote/summernote.min',
// header animation
easePack: 'lib/EasePack.min',

View File

@@ -275,7 +275,8 @@ define([
// exclude some HTML Tags from watcher
if(
e.target.tagName !== 'INPUT' &&
e.target.tagName !== 'TEXTAREA'
e.target.tagName !== 'TEXTAREA' &&
!e.target.classList.contains('note-editable') // Summerstyle editor
){
let key = e.key.toUpperCase();
map[key] = true;

View File

@@ -79,7 +79,6 @@ define([
callback(newSystemData);
}
// show errors
if(
responseData.error &&
responseData.error.length > 0

View File

@@ -0,0 +1,187 @@
define([
'jquery',
'app/init',
'summernote'
], ($, Init) => {
'use strict';
// all Summernote stuff is available...
let initDefaultSummernoteConfig = () => {
// "length" hint plugin ---------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
lengthField: function (context){
let self = this;
let ui = $.summernote.ui;
// add counter
context.memo('button.lengthField', () => {
return $('<kbd>', {
class: ['text-right', 'txt-color'].join(' ')
});
});
/**
* update counter element with left chars
* @param contents
*/
let updateCounter = (contents) => {
let maxTextLength = context.options.maxTextLength;
let textLength = contents.length;
let counter = context.layoutInfo.toolbar.find('kbd');
let counterLeft = maxTextLength - textLength;
counter.text(counterLeft).data('charCount', counterLeft);
counter.toggleClass('txt-color-red', maxTextLength <= textLength);
// disable "save" button
let saveBtn = context.layoutInfo.toolbar.find('.btn-save');
saveBtn.prop('disabled', maxTextLength < textLength);
};
// events
this.events = {
'summernote.init': function (we, e) {
updateCounter(context.$note.summernote('code'));
},
'summernote.change': function(we, contents){
updateCounter(contents);
}
};
}
});
// "discard" button plugin ------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
discardBtn: function (context){
let self = this;
let ui = $.summernote.ui;
// add button
context.memo('button.discardBtn', () => {
let button = ui.button({
contents: '<i class="fas fa-fw fa-ban"/>',
container: 'body',
click: function(){
// show confirmation dialog
$(this).confirmation('show');
}
});
let $button = button.render();
// show "discard" changes confirmation
let confirmationSettings = {
container: 'body',
placement: 'top',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
title: 'discard changes',
btnOkClass: 'btn btn-sm btn-warning',
btnOkLabel: 'discard',
btnOkIcon: 'fas fa-fw fa-ban',
onConfirm: (e, target) => {
// discard all changes
context.$note.summernote('reset');
context.$note.summernote('destroy');
}
};
$button.confirmation(confirmationSettings);
return $button;
});
}
});
};
/**
* init new Summernote editor
* @param element
* @param options
*/
let initSummernote = (element, options) => {
let defaultOptions = {
dialogsInBody: true,
dialogsFade: true,
//textareaAutoSync: false,
//hintDirection: 'right',
//tooltip: 'right',
//container: 'body',
styleTags: ['p', 'h2', 'h3', 'blockquote'],
linkTargetBlank: true,
tableClassName: 'table table-condensed table-bordered',
insertTableMaxSize: {
col: 5,
row: 5
},
icons: {
//'align': 'note-icon-align',
'alignCenter': 'fas fa-align-center',
'alignJustify': 'fas fa-align-justify',
'alignLeft': 'fas fa-align-left',
'alignRight': 'fas fa-align-right',
//'rowBelow': 'note-icon-row-below',
//'colBefore': 'note-icon-col-before',
//'colAfter': 'note-icon-col-after',
//'rowAbove': 'note-icon-row-above',
//'rowRemove': 'note-icon-row-remove',
//'colRemove': 'note-icon-col-remove',
'indent': 'fas fa-indent',
'outdent': 'fas fa-outdent',
'arrowsAlt': 'fas fa-expand-arrows-alt',
'bold': 'fas fa-bold',
'caret': 'fas fa-caret-down',
'circle': 'fas fa-circle',
'close': 'fas fa-time',
'code': 'fas fa-code',
'eraser': 'fas fa-eraser',
'font': 'fas fa-font',
//'frame': 'note-icon-frame',
'italic': 'fas fa-italic',
'link': 'fas fa-link',
'unlink': 'fas fa-unlink',
'magic': 'fas fa-magic',
'menuCheck': 'fas fa-check',
'minus': 'fas fa-minus',
'orderedlist': 'fas fa-list-ol',
'pencil': 'fa-pen',
'picture': 'fas fa-image',
'question': 'fas fa-question',
'redo': 'fas fa-redo',
'square': 'fas fa-square',
'strikethrough': 'fas fa-strikethrough',
'subscript': 'fas fa-subscript',
'superscript': 'fas fa-superscript',
'table': 'fas fa-table',
'textHeight': 'fas fa-text-height',
'trash': 'fas fa-trash',
'underline': 'fas fa-underline',
'undo': 'fas fa-undo',
'unorderedlist': 'fas fa-list-ul',
'video': 'fab fa-youtube'
},
colors: [
['#5cb85c', '#e28a0d', '#d9534f', '#e06fdf', '#9fa8da', '#e2ce48', '#428bca']
],
colorsName: [
['Green', 'Orange', 'Red', 'Pink', 'Indigo', 'Yellow', 'Blue']
],
};
options = $.extend({}, defaultOptions, options);
element.summernote(options);
};
initDefaultSummernoteConfig();
return {
initSummernote: initSummernote
};
});

View File

@@ -6,8 +6,7 @@ define([
'jquery',
'app/init',
'app/util',
'app/map/util',
'summernote'
'app/map/util'
], ($, Init, Util, MapUtil) => {
'use strict';
@@ -38,7 +37,7 @@ define([
systemInfoWormholeClass: 'pf-system-info-wormhole-', // class prefix for static wormhole element
// description field
descriptionArea: 'pf-system-info-description-area', // class for "description" area
descriptionAreaClass: 'pf-system-info-description-area', // class for "description" area
addDescriptionButtonClass: 'pf-system-info-description-button', // class for "add description" button
descriptionTextareaElementClass: 'pf-system-info-description', // class for "description" textarea element (xEditable)
@@ -50,213 +49,44 @@ define([
};
// max character length for system description
let maxDescriptionLength = 512;
let maxDescriptionLength = 9000;
/**
* save system (description)
* @param requestData
* @param context
* @param callback
*/
let saveSystem = (requestData, context, callback) => {
context.descriptionArea.showLoadingAnimation();
let initTextEditor = (element, options) => {
$.ajax({
type: 'POST',
url: Init.path.saveSystem,
data: requestData,
dataType: 'json',
context: context
}).done(function(responseData){
let newSystemData = responseData.systemData;
// "length" hint plugin ---------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
lengthField: function (context){
let self = this;
let ui = $.summernote.ui;
// add counter
context.memo('button.lengthField', () => {
return $('<kbd>', {
class: ['text-right', 'txt-color'].join(' ')
});
});
/**
* update counter element with left chars
* @param contents
*/
let updateCounter = (contents) => {
let maxTextLength = context.options.maxTextLength;
let textLength = contents.length;
let counter = context.layoutInfo.toolbar.find('kbd');
let counterLeft = maxTextLength - textLength;
counter.text(counterLeft).data('charCount', counterLeft);
counter.toggleClass('txt-color-red', maxTextLength <= textLength);
// disable "save" button
let saveBtn = context.layoutInfo.toolbar.find('.btn-save');
saveBtn.prop('disabled', maxTextLength < textLength);
};
// events
this.events = {
'summernote.init': function (we, e) {
updateCounter(context.$note.summernote('code'));
},
'summernote.change': function(we, contents){
updateCounter(contents);
}
};
if( !$.isEmptyObject(newSystemData) ){
callback(newSystemData);
}
});
// "discard" button plugin ------------------------------------------------------------------------------------
$.extend($.summernote.plugins, {
/**
* @param {Object} context - context object has status of editor.
*/
discardBtn: function (context){
let self = this;
let ui = $.summernote.ui;
// add button
context.memo('button.discardBtn', () => {
let button = ui.button({
contents: '<i class="fas fw fa-times"/>',
container: 'body',
click: function(){
// show confirmation dialog
$(this).confirmation('show');
}
});
let $button = button.render();
// show "discard" changes confirmation
let confirmationSettings = {
container: 'body',
placement: 'top',
btnCancelClass: 'btn btn-sm btn-default',
btnCancelLabel: 'cancel',
btnCancelIcon: 'fas fa-fw fa-ban',
title: 'discard changes',
btnOkClass: 'btn btn-sm btn-warning',
btnOkLabel: 'discard',
btnOkIcon: 'fas fa-fw fa-times',
onConfirm: (e, target) => {
// discard all changes
context.$note.summernote('reset');
context.$note.summernote('destroy');
}
};
$button.confirmation(confirmationSettings);
return $button;
});
}
});
// "save button -----------------------------------------------------------------------------------------------
let saveBtn = context => {
let ui = $.summernote.ui;
let button = ui.button({
contents: '<i class="fas fw fa-check"/>',
container: 'body',
className: ['btn-success', 'btn-save'],
click: e => {
// save changes
if( !context.$note.summernote('isEmpty') ){
// get current code
let description = context.$note.summernote('code');
console.log('code to save: ', description)
}
context.$note.summernote('destroy');
if(
responseData.error &&
responseData.error.length > 0
){
for(let error of responseData.error){
Util.showNotify({title: error.field + ' error', text: 'System: ' + error.message, type: error.type});
}
});
return button.render();
};
let defaultOptions = {
height: 68, // set editor height
minHeight: 68, // set minimum height of editor
maxHeight: 500, // set maximum height of editor
dialogsInBody: true,
dialogsFade: true,
//textareaAutoSync: false,
//hintDirection: 'right',
//tooltip: 'right',
//container: 'body',
toolbar: [
['style', ['style']],
['font', ['bold', 'underline', 'strikethrough', 'clear']],
['color', ['color']],
//['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'hr']],
//['view', ['codeview', 'help']],
['misc', ['undo', 'redo']],
['lengthField'],
['customBtn', [ 'discardBtn', 'saveBtn']]
],
buttons: {
saveBtn: saveBtn
},
insertTableMaxSize: {
col: 4,
row: 4
},
icons: {
//'align': 'note-icon-align',
'alignCenter': 'fas fa-align-center',
'alignJustify': 'fas fa-align-justify',
'alignLeft': 'fas fa-align-left',
'alignRight': 'fas fa-align-right',
//'rowBelow': 'note-icon-row-below',
//'colBefore': 'note-icon-col-before',
//'colAfter': 'note-icon-col-after',
//'rowAbove': 'note-icon-row-above',
//'rowRemove': 'note-icon-row-remove',
//'colRemove': 'note-icon-col-remove',
'indent': 'fas fa-indent',
'outdent': 'fas fa-outdent',
'arrowsAlt': 'fas fa-expand-arrows-alt',
'bold': 'fas fa-bold',
'caret': 'fas fa-caret-down',
'circle': 'fas fa-circle',
'close': 'fas fa-time',
'code': 'fas fa-code',
'eraser': 'fas fa-eraser',
'font': 'fas fa-font',
//'frame': 'note-icon-frame',
'italic': 'fas fa-italic',
'link': 'fas fa-link',
'unlink': 'fas fa-unlink',
'magic': 'fas fa-magic',
'menuCheck': 'fas fa-check',
'minus': 'fas fa-minus',
'orderedlist': 'fas fa-list-ol',
'pencil': 'fa-pen',
'picture': 'fas fa-image',
'question': 'fas fa-question',
'redo': 'fas fa-redo',
'square': 'fas fa-square',
'strikethrough': 'fas fa-strikethrough',
'subscript': 'fas fa-subscript',
'superscript': 'fas fa-superscript',
'table': 'fas fa-table',
'textHeight': 'fas fa-text-height',
'trash': 'fas fa-trash',
'underline': 'fas fa-underline',
'undo': 'fas fa-undo',
'unorderedlist': 'fas fa-list-ul',
'video': 'fab fa-youtube'
},
colors: [
['#5cb85c', '#e28a0d', '#d9534f', '#e06fdf', '#9fa8da', '#e2ce48', '#428bca']
],
colorsName: [
['Green', 'Orange', 'Red', 'Pink', 'Indigo', 'Yellow', 'Blue']
],
};
options = $.extend({}, defaultOptions, options);
element.summernote(options);
}
}).fail(function(jqXHR, status, error){
let reason = status + ' ' + error;
Util.showNotify({title: jqXHR.status + ': saveSystem', text: reason, type: 'warning'});
}).always(function(){
this.descriptionArea.hideLoadingAnimation();
});
};
/**
@@ -267,8 +97,24 @@ define([
*/
let updateModule = (moduleElement, systemData) => {
let systemId = moduleElement.data('id');
let updated = moduleElement.data('updated');
if(
systemId === systemData.id &&
updated !== systemData.updated.updated
){
let setUpdated = true;
// created/updated tooltip --------------------------------------------------------------------------------
let nameRowElement = moduleElement.find('.' + config.systemInfoNameClass);
let tooltipData = {
created: systemData.created,
updated: systemData.updated
};
nameRowElement.addCharacterInfoTooltip( tooltipData );
if(systemId === systemData.id){
// update system status -----------------------------------------------------------------------------------
let systemStatusLabelElement = moduleElement.find('.' + config.systemInfoStatusLabelClass);
let systemStatusId = parseInt( systemStatusLabelElement.attr( config.systemInfoStatusAttributeName ) );
@@ -286,22 +132,33 @@ define([
// update description textarea ----------------------------------------------------------------------------
let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass);
if(typeof descriptionTextareaElement.data().summernote === 'object'){
// "Summernote" editor is currently open
console.log('Open');
}else{
// not open
console.log('NOT open');
if(descriptionTextareaElement.length){
let description = descriptionTextareaElement.html();
console.log(description);
console.log('update: ', description === systemData.description);
if(description !== systemData.description){
descriptionTextareaElement.html(systemData.description);
// description has changed
if(typeof descriptionTextareaElement.data().summernote === 'object'){
// "Summernote" editor is currently open
setUpdated = false;
}else{
// not open
let newDescription = systemData.description;
if( !Util.isValidHtml(newDescription) ){
// try to convert raw text into valid html
newDescription = newDescription.replace(/(\r\n|\n|\r)/g, '<br>');
newDescription = '<p>' + newDescription + '</p>';
}
descriptionTextareaElement.html(newDescription);
}
}
}
if(setUpdated){
moduleElement.data('updated', systemData.updated.updated);
}
}
moduleElement.find('.' + config.descriptionArea).hideLoadingAnimation();
moduleElement.find('.' + config.descriptionAreaClass).hideLoadingAnimation();
};
/**
@@ -355,6 +212,7 @@ define([
trueSecClass: Util.getTrueSecClassForSystem( systemData.trueSec ),
effectName: effectName,
effectClass: effectClass,
descriptionAreaClass: config.descriptionAreaClass,
descriptionButtonClass: config.addDescriptionButtonClass,
descriptionTextareaClass: config.descriptionTextareaElementClass,
systemNameClass: () => {
@@ -377,36 +235,126 @@ define([
systemUrlLinkClass: config.urlLinkClass
};
requirejs(['text!templates/modules/system_info.html', 'mustache'], (template, Mustache) => {
requirejs(['text!templates/modules/system_info.html', 'mustache', 'summernote.loader'], (template, Mustache, Summernote) => {
let content = Mustache.render(template, data);
moduleElement.append(content);
// lock "description" field until first update
moduleElement.find('.' + config.descriptionArea).showLoadingAnimation();
// WYSIWYG init on button click ---------------------------------------------------------------------------
let descriptionArea = moduleElement.find('.' + config.descriptionAreaClass);
let descriptionButton = moduleElement.find('.' + config.addDescriptionButtonClass);
let descriptionTextareaElement = moduleElement.find('.' + config.descriptionTextareaElementClass);
// lock "description" field until first update
descriptionArea.showLoadingAnimation();
// WYSIWYG init on button click ---------------------------------------------------------------------------
descriptionButton.on('click', function(e){
e.stopPropagation();
let descriptionButton = $(this);
// hide edit button
descriptionButton.hide();
initTextEditor(descriptionTextareaElement, {
// content has changed
let descriptionChanged = false;
Summernote.initSummernote(descriptionTextareaElement, {
height: 68, // set editor height
minHeight: 68, // set minimum height of editor
maxHeight: 500, // set maximum height of editor
focus: true,
placeholder: false,
maxTextLength: maxDescriptionLength,
disableDragAndDrop: true,
shortcuts: false,
toolbar: [
['style', ['style']],
['font', ['underline', 'strikethrough', 'clear']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['insert', ['link', 'hr']],
//['view', ['codeview', 'help']],
['misc', ['undo', 'redo']],
['lengthField'],
['customBtn', ['discardBtn', 'saveBtn']]
],
buttons: {
saveBtn: context => {
let ui = $.summernote.ui;
let button = ui.button({
contents: '<i class="fas fa-fw fa-check"/>',
container: 'body',
className: ['btn-success', 'btn-save'],
click: e => {
context.layoutInfo.editable.removeClass('has-error');
// save changes
if(descriptionChanged){
let validDescription = true;
let description = '';
if( context.$note.summernote('isEmpty') ){
// ... isEmpty -> clear empty default tags as well
context.$note.summernote('code', '');
}else{
description = context.$note.summernote('code');
if( !Util.isValidHtml(description) ){
// ... not valid HTML
validDescription = false;
context.layoutInfo.editable.addClass('has-error');
Util.showNotify({title: 'Validation failed', text: 'HTML not valid', type: 'error'});
}
}
if(validDescription){
// ... valid -> save()
saveSystem({
mapData: {
id: mapId
},
systemData: {
id: systemData.id,
description: description
}
}, {
descriptionArea: descriptionArea
}, (systemData) => {
// .. save callback
context.$note.summernote('destroy');
updateModule(moduleElement, systemData);
});
}
}else{
// ... no changes -> no save()
context.$note.summernote('destroy');
}
}
});
return button.render();
}
},
callbacks: {
onInit: function(context){
// make editable field a big larger
context.editable.css('height', '150px');
// set default background color
// -> could not figure out how to set by API as default color
context.toolbar.find('.note-current-color-button').attr('data-backcolor', config.defaultBgColor)
.find('.note-recent-color').css('background-color', config.defaultBgColor);
},
onChange: function(contents){
descriptionChanged = true;
},
onPaste: function (e) {
let bufferText = ((e.originalEvent || e).clipboardData || window.clipboardData).getData('Text');
e.preventDefault();
// Firefox fix
setTimeout(() => {
document.execCommand('insertText', false, bufferText);
}, 10);
},
onDestroy: function(context){
descriptionButton.show();
}
@@ -436,16 +384,6 @@ define([
});
});
// created/updated tooltip --------------------------------------------------------------------------------
let nameRowElement = moduleElement.find('.' + config.systemInfoNameClass);
let tooltipData = {
created: systemData.created,
updated: systemData.updated
};
nameRowElement.addCharacterInfoTooltip( tooltipData );
// constellation popover ----------------------------------------------------------------------------------
moduleElement.find('a.popup-ajax').popover({
html: true,

View File

@@ -2821,6 +2821,17 @@ define([
*/
let htmlDecode = value => $('<div>').html(value).text();
/**
* checks if html is valid
* -> see https://stackoverflow.com/a/15458968/4329969
* @param html
* @returns {boolean}
*/
let isValidHtml = html => {
let doc = new DOMParser().parseFromString(html, 'text/html');
return Array.from(doc.body.childNodes).some(node => node.nodeType === 1);
};
/**
* get deep json object value if exists
* -> e.g. key = 'first.last.third' string
@@ -3021,6 +3032,7 @@ define([
getDataTableInstance: getDataTableInstance,
htmlEncode: htmlEncode,
htmlDecode: htmlDecode,
isValidHtml: isValidHtml,
getObjVal: getObjVal,
redirect: redirect,
logout: logout,

View File

@@ -36,15 +36,15 @@
<div class="row">
{{! info text ================================================================================================ }}
<div class="col-xs-12 col-sm-8">
<div class="pf-dynamic-area pf-system-info-description-area">
<div class="col-xs-12 col-sm-9">
<div class="pf-dynamic-area {{descriptionAreaClass}}">
<div class="{{descriptionTextareaClass}}"></div>
<i class="fas fa-fw fa-lg fa-pen pull-right {{moduleHeadlineIconClass}} {{descriptionButtonClass}}" data-toggle="tooltip" title="edit description"></i>
</div>
</div>
{{! info table ================================================================================================ }}
<div class="col-xs-12 col-sm-4">
<div class="col-xs-12 col-sm-3">
<span data-toggle="tooltip" title="status" data-status="{{systemStatusId}}" class="label center-block {{statusInfoClass}} {{systemStatusClass}}">
{{systemStatusLabel}}
@@ -56,7 +56,7 @@
<thead>
<tr>
<th>Name</th>
<th class="text-right">
<th class="text-right pf-system-info-name-cell">
{{#system.shattered}}
<i class="fas fa-fw fa-skull {{shatteredClass}}" data-toggle="tooltip" title="shattered"></i>&nbsp;
{{/system.shattered}}

View File

@@ -2,7 +2,17 @@
.panel-heading{
&.note-toolbar{
background-color: $gray-dark
background-color: $gray-dark;
.dropdown-toggle>i+span{
margin-left: 3px; // some more spacing (sugar)
}
.dropdown-menu{
> .note-btn-group:first-child{
margin-bottom: 3px; // some more spacing (sugar)
}
}
}
}
@@ -16,6 +26,12 @@
.note-editable{
color: $gray-light;
background-color: transparent;
will-change: height;
border: 1px solid transparent; // overwrite form-control default
&.has-error{
border-color: $red;
}
}
}

View File

@@ -6,16 +6,20 @@
text-transform: capitalize;
}
// system "name" cell should truncate on long names
.pf-system-info-name-cell{
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
user-select: all;
}
// dynamic area specific for the description field
.pf-system-info-description-area{
min-height: 124px;
padding: 0; // overwrite default "pf-dynamic-area"
// custom WYSIWYG style
hr{
border-top: 1px solid $gray-dark;
}
.pf-system-info-description-button{
position: absolute;
right: 10px;
@@ -24,11 +28,12 @@
.pf-system-info-description{
padding: 10px;
user-select: text;
}
.note-toolbar{
kbd{
padding: 3px 5px;
padding: 3px 4px;
}
.note-customBtn{
@@ -38,6 +43,76 @@
@include clearfix(); // because of "floating" right button group
}
}
// custom wysiwyg editor styles
.pf-system-info-description, .note-editable{
&>h2:first-child, &>h3:first-child{
margin-top: 0 !important; // no margin if first element
}
h2, h3{
&:before{
font-family: "Font Awesome 5 Free";
content: "\f105";
font-weight: bold;
display: inline-block;
margin-right: 3px;
}
}
h2{
font-size: 16px;
margin: 15px 0;
}
h3{
font-size: 14px;
margin: 10px 0;
}
a{
&:after{
font-family: "Font Awesome 5 Free";
content: "\f35d";
font-size: 70%;
vertical-align: top;
font-weight: bold;
display: inline-block;
margin-left: 2px;
}
}
// custom WYSIWYG style
hr{
border-top: 1px solid $gray-dark;
}
blockquote {
padding: 5px 10px;
margin: 0 0 10px;
font-size: 13px;
border-left: 3px solid $teal;
}
ol, ul{
padding-left: 20px;
}
ul{
list-style: disc;
}
ol{
list-style: decimal;
}
}
}
.modal.link-dialog{
.checkbox{
display: none; // hide checkbox for "target" link attribute -> always _blank
}
}
// system signature module ==================================================

View File

@@ -367,9 +367,10 @@
.note-editor.note-frame .note-status-output:empty {
height: 0;
border-top: 0 solid transparent
border-top: 0 solid transparent;
padding-top: 0;
}
/*
.note-editor.note-frame .note-status-output .pull-right {
float: right !important
}
@@ -429,7 +430,7 @@
color: #a94442 !important;
background-color: #f2dede !important
}
*/
.note-editor.note-frame .note-statusbar {
background-color: #f5f5f5;
border-top: 1px solid #ddd;
@@ -472,7 +473,8 @@
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle
vertical-align: middle;
margin-top: 5px; // horizontal align link preview text in popover
}
.note-popover.popover .arrow {
@@ -517,6 +519,7 @@
z-index: 1;
width: 5em;
height: 5em;
opacity: 0.75;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASAgMAAAAroGbEAAAACVBMVEUAAIj4+Pjp6ekKlAqjAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfYAR0BKhmnaJzPAAAAG0lEQVQI12NgAAOtVatWMTCohoaGUY+EmIkEAEruEzK2J7tvAAAAAElFTkSuQmCC') repeat
}
@@ -561,7 +564,7 @@
margin: 2px 7px;
font-size: 12px;
text-align: center;
border-bottom: 1px solid #eee
border-bottom: 1px solid #63676a
}
.note-popover .popover-content .note-color .dropdown-menu .note-palette .note-color-reset, .panel-heading.note-toolbar .note-color .dropdown-menu .note-palette .note-color-reset {
@@ -593,7 +596,11 @@
}
.note-popover .popover-content .dropdown-menu, .panel-heading.note-toolbar .dropdown-menu {
min-width: 90px
min-width: 90px;
&:not(.dropdown-style){
color: #adadad;
background-color: #3c3f41;
}
}
.note-popover .popover-content .dropdown-menu.right, .panel-heading.note-toolbar .dropdown-menu.right {
@@ -633,11 +640,12 @@
height: 20px;
padding: 0;
margin: 0;
border: 1px solid #fff
border: 1px solid #63676a;
cursor: pointer;
}
.note-popover .popover-content .note-color-palette div .note-color-btn:hover, .panel-heading.note-toolbar .note-color-palette div .note-color-btn:hover {
border: 1px solid #000
border: 1px solid #3c3f41
}
.note-dialog > div {