- Add texteditor for system description field, closed #698
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.*",
|
||||
|
||||
@@ -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.*",
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -79,7 +79,6 @@ define([
|
||||
callback(newSystemData);
|
||||
}
|
||||
|
||||
// show errors
|
||||
if(
|
||||
responseData.error &&
|
||||
responseData.error.length > 0
|
||||
|
||||
187
js/app/summernote.loader.js
Normal file
187
js/app/summernote.loader.js
Normal 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
|
||||
};
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -79,7 +79,6 @@ define([
|
||||
callback(newSystemData);
|
||||
}
|
||||
|
||||
// show errors
|
||||
if(
|
||||
responseData.error &&
|
||||
responseData.error.length > 0
|
||||
|
||||
187
public/js/v1.4.2/app/summernote.loader.js
Normal file
187
public/js/v1.4.2/app/summernote.loader.js
Normal 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
|
||||
};
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
{{/system.shattered}}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 ==================================================
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user