mirror of
https://bitbucket.org/wisemapping/wisemapping-frontend.git
synced 2024-11-25 15:47:55 +01:00
Clean up multitext editor
This commit is contained in:
parent
9b45ec7b41
commit
2a43f3310f
@ -29,7 +29,7 @@ class LocalStorageManager extends PersistenceManager {
|
|||||||
this.forceLoad = forceLoad;
|
this.forceLoad = forceLoad;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveMapXml(mapId: string, mapDoc: Document, pref = null, events = null): void {
|
saveMapXml(mapId: string, mapDoc: Document): void {
|
||||||
const mapXml = new XMLSerializer().serializeToString(mapDoc);
|
const mapXml = new XMLSerializer().serializeToString(mapDoc);
|
||||||
localStorage.setItem(`${mapId}-xml`, mapXml);
|
localStorage.setItem(`${mapId}-xml`, mapXml);
|
||||||
}
|
}
|
||||||
|
@ -21,17 +21,21 @@ import $ from 'jquery';
|
|||||||
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
|
import initHotKeyPluggin from '../../../../libraries/jquery.hotkeys';
|
||||||
import Events from './Events';
|
import Events from './Events';
|
||||||
import ActionDispatcher from './ActionDispatcher';
|
import ActionDispatcher from './ActionDispatcher';
|
||||||
|
import Topic from './Topic';
|
||||||
|
|
||||||
initHotKeyPluggin($);
|
initHotKeyPluggin($);
|
||||||
|
|
||||||
class MultilineTextEditor extends Events {
|
class MultilineTextEditor extends Events {
|
||||||
|
private _topic: Topic;
|
||||||
|
|
||||||
|
private _containerElem: JQuery;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this._topic = null;
|
this._topic = null;
|
||||||
this._timeoutId = -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static _buildEditor() {
|
private static _buildEditor() {
|
||||||
const result = $('<div></div>')
|
const result = $('<div></div>')
|
||||||
.attr('id', 'textContainer')
|
.attr('id', 'textContainer')
|
||||||
.css({
|
.css({
|
||||||
@ -54,25 +58,19 @@ class MultilineTextEditor extends Events {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
_registerEvents(containerElem) {
|
private _registerEvents(containerElem: JQuery) {
|
||||||
const textareaElem = this._getTextareaElem();
|
const textareaElem = this._getTextareaElem();
|
||||||
const me = this;
|
textareaElem.on('keydown', (event) => {
|
||||||
let start;
|
const j: any = $;
|
||||||
let end;
|
switch (j.hotkeys.specialKeys[event.keyCode]) {
|
||||||
textareaElem.on('keydown', function keydown(event) {
|
|
||||||
switch ($.hotkeys.specialKeys[event.keyCode]) {
|
|
||||||
case 'esc':
|
case 'esc':
|
||||||
me.close(false);
|
this.close(false);
|
||||||
break;
|
break;
|
||||||
case 'enter':
|
case 'enter': {
|
||||||
if (event.metaKey || event.ctrlKey) {
|
if (event.metaKey || event.ctrlKey) {
|
||||||
// Add return ...
|
// Add return ...
|
||||||
const text = textareaElem.val();
|
const text = this._getTextAreaText();
|
||||||
let cursorPosition = text.length;
|
const cursorPosition = text.length;
|
||||||
if (textareaElem.selectionStart) {
|
|
||||||
cursorPosition = textareaElem.selectionStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
const head = text.substring(0, cursorPosition);
|
const head = text.substring(0, cursorPosition);
|
||||||
let tail = '';
|
let tail = '';
|
||||||
if (cursorPosition < text.length) {
|
if (cursorPosition < text.length) {
|
||||||
@ -80,31 +78,12 @@ class MultilineTextEditor extends Events {
|
|||||||
}
|
}
|
||||||
textareaElem.val(`${head}\n${tail}`);
|
textareaElem.val(`${head}\n${tail}`);
|
||||||
|
|
||||||
// Position cursor ...
|
textareaElem.focus();
|
||||||
if (textareaElem[0].setSelectionRange) {
|
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
|
||||||
textareaElem.focus();
|
|
||||||
textareaElem[0].setSelectionRange(cursorPosition + 1, cursorPosition + 1);
|
|
||||||
} else if (textareaElem.createTextRange) {
|
|
||||||
const range = textareaElem.createTextRange();
|
|
||||||
range.moveStart('character', cursorPosition + 1);
|
|
||||||
range.select();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
me.close(true);
|
this.close(true);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'tab': {
|
|
||||||
event.preventDefault();
|
|
||||||
start = $(this).get(0).selectionStart;
|
|
||||||
end = $(this).get(0).selectionEnd;
|
|
||||||
|
|
||||||
// set textarea value to: text before caret + tab + text after caret
|
|
||||||
$(this).val(`${$(this).val().substring(0, start)}\t${$(this).val().substring(end)}`);
|
|
||||||
|
|
||||||
// put caret at right position again
|
|
||||||
$(this).get(0).selectionEnd = start + 1;
|
|
||||||
$(this).get(0).selectionStart = $(this).get(0).selectionEnd;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// No actions...
|
// No actions...
|
||||||
@ -118,9 +97,9 @@ class MultilineTextEditor extends Events {
|
|||||||
});
|
});
|
||||||
|
|
||||||
textareaElem.on('keyup', (event) => {
|
textareaElem.on('keyup', (event) => {
|
||||||
const text = me._getTextareaElem().val();
|
const text = this._getTextareaElem().val();
|
||||||
me.fireEvent('input', [event, text]);
|
this.fireEvent('input', [event, text]);
|
||||||
me._adjustEditorSize();
|
this._adjustEditorSize();
|
||||||
});
|
});
|
||||||
|
|
||||||
// If the user clicks on the input, all event must be ignored ...
|
// If the user clicks on the input, all event must be ignored ...
|
||||||
@ -135,14 +114,16 @@ class MultilineTextEditor extends Events {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_adjustEditorSize() {
|
private _adjustEditorSize() {
|
||||||
if (this.isVisible()) {
|
if (this.isVisible()) {
|
||||||
const textElem = this._getTextareaElem();
|
const textElem = this._getTextareaElem();
|
||||||
|
|
||||||
const lines = textElem.val().split('\n');
|
const lines = this._getTextAreaText().split('\n');
|
||||||
let maxLineLength = 1;
|
let maxLineLength = 1;
|
||||||
lines.forEach((line) => {
|
lines.forEach((line) => {
|
||||||
if (maxLineLength < line.length) maxLineLength = line.length;
|
if (maxLineLength < line.length) {
|
||||||
|
maxLineLength = line.length;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
textElem.attr('cols', maxLineLength);
|
textElem.attr('cols', maxLineLength);
|
||||||
@ -155,13 +136,13 @@ class MultilineTextEditor extends Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isVisible() {
|
isVisible(): boolean {
|
||||||
return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
|
return $defined(this._containerElem) && this._containerElem.css('display') === 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateModel() {
|
private _updateModel() {
|
||||||
if (this._topic.getText() !== this._getText()) {
|
if (this._topic.getText() !== this._getTextAreaText()) {
|
||||||
const text = this._getText();
|
const text = this._getTextAreaText();
|
||||||
const topicId = this._topic.getId();
|
const topicId = this._topic.getId();
|
||||||
|
|
||||||
const actionDispatcher = ActionDispatcher.getInstance();
|
const actionDispatcher = ActionDispatcher.getInstance();
|
||||||
@ -169,7 +150,7 @@ class MultilineTextEditor extends Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
show(topic, text) {
|
show(topic: Topic, text: string): void {
|
||||||
// Close a previous node editor if it's opened ...
|
// Close a previous node editor if it's opened ...
|
||||||
if (this._topic) {
|
if (this._topic) {
|
||||||
this.close(false);
|
this.close(false);
|
||||||
@ -187,7 +168,7 @@ class MultilineTextEditor extends Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_showEditor(defaultText) {
|
private _showEditor(defaultText: string) {
|
||||||
const topic = this._topic;
|
const topic = this._topic;
|
||||||
|
|
||||||
// Hide topic text ...
|
// Hide topic text ...
|
||||||
@ -199,32 +180,26 @@ class MultilineTextEditor extends Events {
|
|||||||
fontStyle.size = nodeText.getHtmlFontSize();
|
fontStyle.size = nodeText.getHtmlFontSize();
|
||||||
fontStyle.color = nodeText.getColor();
|
fontStyle.color = nodeText.getColor();
|
||||||
this._setStyle(fontStyle);
|
this._setStyle(fontStyle);
|
||||||
const me = this;
|
|
||||||
|
|
||||||
// Set editor's initial size
|
// Set editor's initial size
|
||||||
const displayFunc = function displayFunc() {
|
// Position the editor and set the size...
|
||||||
// Position the editor and set the size...
|
const textShape = topic.getTextShape();
|
||||||
const textShape = topic.getTextShape();
|
|
||||||
|
|
||||||
me._containerElem.css('display', 'block');
|
this._containerElem.css('display', 'block');
|
||||||
|
|
||||||
// FIXME: Im not sure if this is best way...
|
const shapePosition = textShape.getNativePosition();
|
||||||
const shapePosition = textShape.getNativePosition();
|
this._containerElem.offset(shapePosition);
|
||||||
me._containerElem.offset(shapePosition);
|
|
||||||
|
|
||||||
// Set editor's initial text ...
|
// Set editor's initial text ...
|
||||||
const text = $defined(defaultText) ? defaultText : topic.getText();
|
const text = $defined(defaultText) ? defaultText : topic.getText();
|
||||||
me._setText(text);
|
this._setText(text);
|
||||||
|
|
||||||
// Set the element focus and select the current text ...
|
// Set the element focus and select the current text ...
|
||||||
const inputElem = me._getTextareaElem();
|
const inputElem = this._getTextareaElem();
|
||||||
me._positionCursor(inputElem, !$defined(defaultText));
|
this._positionCursor(inputElem, !$defined(defaultText));
|
||||||
};
|
|
||||||
|
|
||||||
this._timeoutId = setTimeout(() => displayFunc(), 10);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_setStyle(fontStyle) {
|
private _setStyle(fontStyle) {
|
||||||
const inputField = this._getTextareaElem();
|
const inputField = this._getTextareaElem();
|
||||||
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
@ -252,51 +227,33 @@ class MultilineTextEditor extends Events {
|
|||||||
this._containerElem.css(style);
|
this._containerElem.css(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
_setText(text) {
|
private _setText(text: string) {
|
||||||
const textareaElem = this._getTextareaElem();
|
const textareaElem = this._getTextareaElem();
|
||||||
textareaElem.val(text);
|
textareaElem.val(text);
|
||||||
this._adjustEditorSize();
|
this._adjustEditorSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
_getText() {
|
private _getTextAreaText(): string {
|
||||||
return this._getTextareaElem().val();
|
return this._getTextareaElem().val() as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTextareaElem() {
|
private _getTextareaElem(): JQuery<HTMLTextAreaElement> {
|
||||||
return this._containerElem.find('textarea');
|
return this._containerElem.find('textarea');
|
||||||
}
|
}
|
||||||
|
|
||||||
_positionCursor(textareaElem, selectText) {
|
private _positionCursor(textareaElem: JQuery<HTMLTextAreaElement>, selectText: boolean) {
|
||||||
textareaElem.focus();
|
textareaElem.focus();
|
||||||
const lengh = textareaElem.val().length;
|
const lengh = this._getTextAreaText().length;
|
||||||
if (selectText) {
|
if (selectText) {
|
||||||
// Mark text as selected ...
|
// Mark text as selected ...
|
||||||
if (textareaElem.createTextRange) {
|
textareaElem[0].setSelectionRange(0, lengh);
|
||||||
const rang = textareaElem.createTextRange();
|
|
||||||
rang.select();
|
|
||||||
rang.move('character', lengh);
|
|
||||||
} else {
|
|
||||||
textareaElem[0].setSelectionRange(0, lengh);
|
|
||||||
}
|
|
||||||
} else if (textareaElem.createTextRange) {
|
|
||||||
const range = textareaElem.createTextRange();
|
|
||||||
range.move('character', lengh);
|
|
||||||
} else {
|
} else {
|
||||||
// allowed param reassign to avoid risks of existing code relying in this side-effect
|
|
||||||
/* eslint-disable no-param-reassign */
|
|
||||||
textareaElem.selectionStart = lengh;
|
|
||||||
textareaElem.selectionEnd = lengh;
|
|
||||||
/* eslint-enable no-param-reassign */
|
|
||||||
|
|
||||||
textareaElem.focus();
|
textareaElem.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(update) {
|
close(update: boolean): void {
|
||||||
if (this.isVisible() && this._topic) {
|
if (this.isVisible() && this._topic) {
|
||||||
// Update changes ...
|
|
||||||
clearTimeout(this._timeoutId);
|
|
||||||
|
|
||||||
if (!$defined(update) || update) {
|
if (!$defined(update) || update) {
|
||||||
this._updateModel();
|
this._updateModel();
|
||||||
}
|
}
|
||||||
@ -307,7 +264,6 @@ class MultilineTextEditor extends Events {
|
|||||||
// Remove it form the screen ...
|
// Remove it form the screen ...
|
||||||
this._containerElem.remove();
|
this._containerElem.remove();
|
||||||
this._containerElem = null;
|
this._containerElem = null;
|
||||||
this._timeoutId = -1;
|
|
||||||
}
|
}
|
||||||
this._topic = null;
|
this._topic = null;
|
||||||
}
|
}
|
@ -1268,7 +1268,7 @@ abstract class Topic extends NodeGraph {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If a drag node is create for it, let's hide the editor.
|
// If a drag node is create for it, let's hide the editor.
|
||||||
this._getTopicEventDispatcher().close();
|
this._getTopicEventDispatcher().close(false);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { $assert } from '@wisemapping/core-js';
|
|||||||
import Events from './Events';
|
import Events from './Events';
|
||||||
import MultilineTextEditor from './MultilineTextEditor';
|
import MultilineTextEditor from './MultilineTextEditor';
|
||||||
import { TopicShape } from './model/INodeModel';
|
import { TopicShape } from './model/INodeModel';
|
||||||
|
import Topic from './Topic';
|
||||||
|
|
||||||
const TopicEvent = {
|
const TopicEvent = {
|
||||||
EDIT: 'editnode',
|
EDIT: 'editnode',
|
||||||
@ -26,30 +27,39 @@ const TopicEvent = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
class TopicEventDispatcher extends Events {
|
class TopicEventDispatcher extends Events {
|
||||||
constructor(readOnly) {
|
private _readOnly: boolean;
|
||||||
|
|
||||||
|
private _activeEditor: MultilineTextEditor;
|
||||||
|
|
||||||
|
private _multilineEditor: MultilineTextEditor;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-use-before-define
|
||||||
|
static _instance: TopicEventDispatcher;
|
||||||
|
|
||||||
|
constructor(readOnly: boolean) {
|
||||||
super();
|
super();
|
||||||
this._readOnly = readOnly;
|
this._readOnly = readOnly;
|
||||||
this._activeEditor = null;
|
this._activeEditor = null;
|
||||||
this._multilineEditor = new MultilineTextEditor();
|
this._multilineEditor = new MultilineTextEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
close(update) {
|
close(update: boolean): void {
|
||||||
if (this.isVisible()) {
|
if (this.isVisible()) {
|
||||||
this._activeEditor.close(update);
|
this._activeEditor.close(update);
|
||||||
this._activeEditor = null;
|
this._activeEditor = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
show(topic, options) {
|
show(topic: Topic, options?): void {
|
||||||
this.process(TopicEvent.EDIT, topic, options);
|
this.process(TopicEvent.EDIT, topic, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
process(eventType, topic, options) {
|
process(eventType: string, topic: Topic, options?): void {
|
||||||
$assert(eventType, 'eventType can not be null');
|
$assert(eventType, 'eventType can not be null');
|
||||||
|
|
||||||
// Close all previous open editor ....
|
// Close all previous open editor ....
|
||||||
if (this.isVisible()) {
|
if (this.isVisible()) {
|
||||||
this.close();
|
this.close(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the new editor ...
|
// Open the new editor ...
|
||||||
@ -66,20 +76,18 @@ class TopicEventDispatcher extends Events {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isVisible() {
|
isVisible(): boolean {
|
||||||
return this._activeEditor != null && this._activeEditor.isVisible();
|
return this._activeEditor != null && this._activeEditor.isVisible();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static configure(readOnly: boolean): void {
|
||||||
|
this._instance = new TopicEventDispatcher(readOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getInstance(): TopicEventDispatcher {
|
||||||
|
return this._instance;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TopicEventDispatcher._instance = null;
|
|
||||||
|
|
||||||
TopicEventDispatcher.configure = function configure(readOnly) {
|
|
||||||
this._instance = new TopicEventDispatcher(readOnly);
|
|
||||||
};
|
|
||||||
|
|
||||||
TopicEventDispatcher.getInstance = function getInstance() {
|
|
||||||
return this._instance;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { TopicEvent };
|
export { TopicEvent };
|
||||||
export default TopicEventDispatcher;
|
export default TopicEventDispatcher;
|
Loading…
Reference in New Issue
Block a user