devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
372 lines (371 loc) • 14.4 kB
JavaScript
/**
* DevExtreme (esm/ui/html_editor/modules/mentions.js)
* Version: 21.1.4
* Build date: Mon Jun 21 2021
*
* Copyright (c) 2012 - 2021 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import $ from "../../../core/renderer";
import Quill from "devextreme-quill";
import {
compileGetter
} from "../../../core/utils/data";
import {
isString
} from "../../../core/utils/type";
import {
extend
} from "../../../core/utils/extend";
import {
getPublicElement
} from "../../../core/element";
import eventsEngine from "../../../events/core/events_engine";
import BaseModule from "./base";
import PopupModule from "./popup";
import Mention from "../formats/mention";
var MentionModule = BaseModule;
if (Quill) {
var USER_ACTION = "user";
var SILENT_ACTION = "silent";
var DEFAULT_MARKER = "@";
var KEYS = {
ARROW_UP: "upArrow",
ARROW_DOWN: "downArrow",
ARROW_LEFT: "leftArrow",
ARROW_RIGHT: "rightArrow",
ENTER: "enter",
ESCAPE: "escape",
SPACE: "space",
PAGE_UP: "pageUp",
PAGE_DOWN: "pageDown",
END: "end",
HOME: "home"
};
var NAVIGATION_KEYS = [KEYS.ARROW_LEFT, KEYS.ARROW_RIGHT, KEYS.PAGE_UP, KEYS.PAGE_DOWN, KEYS.END, KEYS.HOME];
var ALLOWED_PREFIX_CHARS = [" ", "\n"];
var DISABLED_STATE_CLASS = "dx-state-disabled";
Quill.register({
"formats/mention": Mention
}, true);
MentionModule = class extends PopupModule {
_getDefaultOptions() {
var baseConfig = super._getDefaultOptions();
return extend(baseConfig, {
itemTemplate: "item",
valueExpr: "this",
displayExpr: "this",
template: null,
searchExpr: null,
searchTimeout: 500,
minSearchLength: 0
})
}
constructor(quill, options) {
super(quill, options);
this._mentions = {};
options.mentions.forEach(item => {
var marker = item.marker;
if (!marker) {
item.marker = marker = DEFAULT_MARKER
}
var template = item.template;
if (template) {
var preparedTemplate = this.editorInstance._getTemplate(template);
preparedTemplate && Mention.addTemplate(marker, preparedTemplate)
}
this._mentions[marker] = extend({}, this._getDefaultOptions(), item)
});
this._attachKeyboardHandlers();
this.addCleanCallback(this.clean.bind(this));
this.quill.on("text-change", this.onTextChange.bind(this))
}
_attachKeyboardHandlers() {
this.quill.keyboard.addBinding({
key: KEYS.ARROW_UP
}, this._moveToItem.bind(this, "prev"));
this.quill.keyboard.addBinding({
key: KEYS.ARROW_DOWN
}, this._moveToItem.bind(this, "next"));
this.quill.keyboard.addBinding({
key: [KEYS.ENTER, KEYS.SPACE]
}, this._selectItemHandler.bind(this));
var enterBindings = this.quill.keyboard.bindings[KEYS.ENTER];
enterBindings.unshift(enterBindings.pop());
this.quill.keyboard.addBinding({
key: KEYS.ESCAPE
}, this._escapeKeyHandler.bind(this));
this.quill.keyboard.addBinding({
key: [KEYS.ARROW_LEFT, KEYS.ARROW_RIGHT],
shiftKey: true
}, this._ignoreKeyHandler.bind(this));
this.quill.keyboard.addBinding({
key: NAVIGATION_KEYS
}, this._ignoreKeyHandler.bind(this))
}
_moveToItem(direction) {
var dataSource = this._list.getDataSource();
if (this._isMentionActive && !dataSource.isLoading()) {
var $focusedItem = $(this._list.option("focusedElement"));
var defaultItemPosition = "next" === direction ? "first" : "last";
var $nextItem = $focusedItem[direction]();
$nextItem = $nextItem.length ? $nextItem : this._activeListItems[defaultItemPosition]();
this._list.option("focusedElement", getPublicElement($nextItem));
this._list.scrollToItem($nextItem)
}
return !this._isMentionActive
}
_ignoreKeyHandler() {
return !this._isMentionActive
}
_fitIntoRange(value, start, end) {
if (value > end) {
return start
}
if (value < start) {
return end
}
return value
}
_selectItemHandler() {
if (this._isMentionActive) {
this._list.selectItem(this._list.option("focusedElement"))
}
return !this._isMentionActive
}
_escapeKeyHandler() {
if (this._isMentionActive) {
this._popup.hide()
}
return !this._isMentionActive
}
renderList($container, options) {
this.compileGetters(this.options);
super.renderList($container, options)
}
compileGetters(_ref) {
var {
displayExpr: displayExpr,
valueExpr: valueExpr
} = _ref;
this._valueGetter = compileGetter(displayExpr);
this._idGetter = compileGetter(valueExpr)
}
_getListConfig(options) {
var baseConfig = super._getListConfig(options);
return extend(baseConfig, {
itemTemplate: this.options.itemTemplate,
onContentReady: () => {
if (this._hasSearch) {
this._popup.repaint();
this._focusFirstElement();
this._hasSearch = false
}
}
})
}
insertEmbedContent() {
var markerLength = this._activeMentionConfig.marker.length;
var textLength = markerLength + this._searchValue.length;
var caretPosition = this.getPosition();
var startIndex = Math.max(0, caretPosition - markerLength);
var selectedItem = this._list.option("selectedItem");
var value = {
value: this._valueGetter(selectedItem),
id: this._idGetter(selectedItem),
marker: this._activeMentionConfig.marker
};
setTimeout(function() {
this.quill.insertText(startIndex, " ", SILENT_ACTION);
this.quill.deleteText(startIndex + 1, textLength, SILENT_ACTION);
this.quill.insertEmbed(startIndex, "mention", value);
this.quill.setSelection(startIndex + 2)
}.bind(this))
}
_getLastInsertOperation(ops) {
var lastOperation = ops[ops.length - 1];
var isLastOperationInsert = "insert" in lastOperation;
if (isLastOperationInsert) {
return lastOperation
}
var isLastOperationDelete = "delete" in lastOperation;
if (isLastOperationDelete && ops.length >= 2) {
var penultOperation = ops[ops.length - 2];
var isPenultOperationInsert = "insert" in penultOperation;
var isSelectionReplacing = isLastOperationDelete && isPenultOperationInsert;
if (isSelectionReplacing) {
return penultOperation
}
}
return null
}
onTextChange(newDelta, oldDelta, source) {
if (source === USER_ACTION) {
var lastOperation = newDelta.ops[newDelta.ops.length - 1];
if (this._isMentionActive && this._isPopupVisible) {
this._processSearchValue(lastOperation) && this._filterList(this._searchValue)
} else {
var {
ops: ops
} = newDelta;
var lastInsertOperation = this._getLastInsertOperation(ops);
if (lastInsertOperation) {
this.checkMentionRequest(lastInsertOperation, ops)
}
}
}
}
get _isPopupVisible() {
var _this$_popup;
return null === (_this$_popup = this._popup) || void 0 === _this$_popup ? void 0 : _this$_popup.option("visible")
}
_processSearchValue(operation) {
var isInsertOperation = "insert" in operation;
if (isInsertOperation) {
this._searchValue += operation.insert
} else if (!this._searchValue.length || operation.delete > 1) {
this._popup.hide();
return false
} else {
this._searchValue = this._searchValue.slice(0, -1)
}
return true
}
checkMentionRequest(_ref2, ops) {
var {
insert: insert
} = _ref2;
var caret = this.quill.getSelection();
if (!insert || !isString(insert) || !caret || this._isMarkerPartOfText(ops[0].retain)) {
return
}
this._activeMentionConfig = this._mentions[insert];
if (this._activeMentionConfig) {
this._updateList(this._activeMentionConfig);
this.savePosition(caret.index);
this._popup.option("position", this._popupPosition);
this._searchValue = "";
this._popup.show()
}
}
_isMarkerPartOfText(retain) {
if (!retain || -1 !== ALLOWED_PREFIX_CHARS.indexOf(this._getCharByIndex(retain - 1))) {
return false
}
return true
}
_getCharByIndex(index) {
return this.quill.getContents(index, 1).ops[0].insert
}
_updateList(_ref3) {
var {
dataSource: dataSource,
displayExpr: displayExpr,
valueExpr: valueExpr,
itemTemplate: itemTemplate,
searchExpr: searchExpr
} = _ref3;
this.compileGetters({
displayExpr: displayExpr,
valueExpr: valueExpr
});
this._list.unselectAll();
this._list.option({
dataSource: dataSource,
displayExpr: displayExpr,
itemTemplate: itemTemplate,
searchExpr: searchExpr
})
}
_filterList(searchValue) {
if (!this._isMinSearchLengthExceeded(searchValue)) {
this._resetFilter();
return
}
var searchTimeout = this._activeMentionConfig.searchTimeout;
if (searchTimeout) {
clearTimeout(this._searchTimer);
this._searchTimer = setTimeout(() => {
this._search(searchValue)
}, searchTimeout)
} else {
this._search(searchValue)
}
}
_isMinSearchLengthExceeded(searchValue) {
return searchValue.length >= this._activeMentionConfig.minSearchLength
}
_resetFilter() {
clearTimeout(this._searchTimer);
this._search(null)
}
_search(searchValue) {
this._hasSearch = true;
this._list.option("searchValue", searchValue)
}
_focusFirstElement() {
if (!this._list) {
return
}
var $firstItem = this._activeListItems.first();
this._list.option("focusedElement", getPublicElement($firstItem));
this._list.scrollToItem($firstItem)
}
get _popupPosition() {
var position = this.getPosition();
var {
left: mentionLeft,
top: mentionTop,
height: mentionHeight
} = this.quill.getBounds(position ? position - 1 : position);
var {
left: leftOffset,
top: topOffset
} = $(this.quill.root).offset();
var positionEvent = eventsEngine.Event("positionEvent", {
pageX: leftOffset + mentionLeft,
pageY: topOffset + mentionTop
});
return {
of: positionEvent,
offset: {
v: mentionHeight
},
my: "top left",
at: "top left",
collision: {
y: "flip",
x: "flipfit"
}
}
}
_getPopupConfig() {
return extend(super._getPopupConfig(), {
closeOnTargetScroll: false,
onShown: () => {
this._isMentionActive = true;
this._hasSearch = false;
this._focusFirstElement()
},
onHidden: () => {
this._list.unselectAll();
this._list.option("focusedElement", null);
this._isMentionActive = false;
this._search(null)
},
focusStateEnabled: false
})
}
get _activeListItems() {
return this._list.itemElements().filter(":not(.".concat(DISABLED_STATE_CLASS, ")"))
}
clean() {
Object.keys(this._mentions).forEach(marker => {
if (this._mentions[marker].template) {
Mention.removeTemplate(marker)
}
})
}
}
}
export default MentionModule;