upfront-editable
Version:
Friendly contenteditable API
416 lines (339 loc) • 12.1 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');
var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');
var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);
var _inherits2 = require('babel-runtime/helpers/inherits');
var _inherits3 = _interopRequireDefault(_inherits2);
require('rangy/lib/rangy-textrange');
var _jquery = require('jquery');
var _jquery2 = _interopRequireDefault(_jquery);
var _cursor = require('./cursor');
var _cursor2 = _interopRequireDefault(_cursor);
var _content = require('./content');
var content = _interopRequireWildcard(_content);
var _parser = require('./parser');
var parser = _interopRequireWildcard(_parser);
var _config = require('./config');
var config = _interopRequireWildcard(_config);
var _highlightSupport = require('./highlight-support');
var _highlightSupport2 = _interopRequireDefault(_highlightSupport);
var _highlightText = require('./highlight-text');
var _highlightText2 = _interopRequireDefault(_highlightText);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* The Selection module provides a cross-browser abstraction layer for range
* and selection.
*
* @module core
* @submodule selection
*/
/**
* Class that represents a selection and provides functionality to access or
* modify the selection.
*
* @class Selection
* @constructor
*/
var Selection = function (_Cursor) {
(0, _inherits3.default)(Selection, _Cursor);
function Selection() {
(0, _classCallCheck3.default)(this, Selection);
var _this = (0, _possibleConstructorReturn3.default)(this, (Selection.__proto__ || (0, _getPrototypeOf2.default)(Selection)).apply(this, arguments));
delete _this.isCursor;
_this.isSelection = true;
return _this;
}
// Get the text inside the selection.
(0, _createClass3.default)(Selection, [{
key: 'text',
value: function text() {
return this.range.toString();
}
// Get the html inside the selection.
}, {
key: 'html',
value: function html() {
return this.range.toHtml();
}
}, {
key: 'isAllSelected',
value: function isAllSelected() {
return parser.isBeginningOfHost(this.host, this.range.startContainer, this.range.startOffset) && parser.isTextEndOfHost(this.host, this.range.endContainer, this.range.endOffset);
}
}, {
key: 'getTextRange',
value: function getTextRange() {
return this.range.toCharacterRange(this.host);
}
// Get the ClientRects of this selection.
// Use this if you want more precision than getBoundingClientRect can give.
}, {
key: 'getRects',
value: function getRects() {
// consider: translate into absolute positions
// just like Cursor#getCoordinates()
return this.range.nativeRange.getClientRects();
}
}, {
key: 'link',
value: function link(href) {
var attrs = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var $link = (0, _jquery2.default)(this.createElement(config.linkMarkup.name, config.linkMarkup.attribs));
if (href) attrs.href = href;
$link.attr(attrs);
this.forceWrap($link[0]);
}
}, {
key: 'unlink',
value: function unlink() {
this.removeFormatting(config.linkMarkup.name);
}
}, {
key: 'toggleLink',
value: function toggleLink(href, attrs) {
var links = this.getTagsByName(config.linkMarkup.name);
if (links.length >= 1) {
var firstLink = links[0];
if (this.isExactSelection(firstLink, 'visible')) {
this.unlink();
} else {
this.expandTo(firstLink);
}
} else {
this.link(href, attrs);
}
}
// Manually add a highlight
// Note: the current code does not work with newlines (LP)
}, {
key: 'highlight',
value: function highlight(_ref) {
var highlightId = _ref.highlightId;
var textBefore = this.textBefore();
var currentTextContent = this.text();
var marker = '<span class="highlight-comment"></span>';
var markerNode = _highlightSupport2.default.createMarkerNode(marker, this.win);
markerNode.setAttribute('data-match', currentTextContent);
var match = {
startIndex: textBefore.length,
endIndex: textBefore.length + currentTextContent.length,
match: currentTextContent,
marker: markerNode
// Note: highlighting won't retain the selection
};_highlightText2.default.highlightMatches(this.host, [match]);
}
// toggle('<em>')
}, {
key: 'toggle',
value: function toggle(elem) {
elem = this.adoptElement(elem);
this.range = content.toggleTag(this.host, this.range, elem);
this.setSelection();
}
}, {
key: 'toggleCustom',
value: function toggleCustom(_ref2) {
var tagName = _ref2.tagName,
attributes = _ref2.attributes;
var customElem = this.createElement(tagName, attributes);
this.toggle(customElem);
}
}, {
key: 'makeCustom',
value: function makeCustom(_ref3) {
var tagName = _ref3.tagName,
attributes = _ref3.attributes;
var customElem = this.createElement(tagName, attributes);
this.forceWrap(customElem);
}
}, {
key: 'makeBold',
value: function makeBold() {
var bold = this.createElement(config.boldMarkup.name, config.boldMarkup.attribs);
this.forceWrap(bold);
}
}, {
key: 'toggleBold',
value: function toggleBold() {
var bold = this.createElement(config.boldMarkup.name, config.boldMarkup.attribs);
this.toggle(bold);
}
}, {
key: 'giveEmphasis',
value: function giveEmphasis() {
var em = this.createElement(config.italicMarkup.name, config.italicMarkup.attribs);
this.forceWrap(em);
}
}, {
key: 'toggleEmphasis',
value: function toggleEmphasis() {
var em = this.createElement(config.italicMarkup.name, config.italicMarkup.attribs);
this.toggle(em);
}
}, {
key: 'makeUnderline',
value: function makeUnderline() {
var u = this.createElement(config.underlineMarkup.name, config.underlineMarkup.attribs);
this.forceWrap(u);
}
}, {
key: 'toggleUnderline',
value: function toggleUnderline() {
var u = this.createElement(config.underlineMarkup.name, config.underlineMarkup.attribs);
this.toggle(u);
}
}, {
key: 'insertCharacter',
value: function insertCharacter(character) {
var cursor = this.deleteContent();
var textNode = cursor.createTextNode(character);
cursor.insertBefore(textNode);
cursor.setSelection();
return cursor;
}
// Surround the selection with characters like quotes.
//
// @param {String} E.g. '«'
// @param {String} E.g. '»'
}, {
key: 'surround',
value: function surround(startCharacter, endCharacter) {
this.range = content.surround(this.host, this.range, startCharacter, endCharacter);
this.setSelection();
}
}, {
key: 'removeSurround',
value: function removeSurround(startCharacter, endCharacter) {
this.range = content.deleteCharacter(this.host, this.range, startCharacter);
this.range = content.deleteCharacter(this.host, this.range, endCharacter);
this.setSelection();
}
}, {
key: 'toggleSurround',
value: function toggleSurround(startCharacter, endCharacter) {
if (this.containsString(startCharacter) && this.containsString(endCharacter)) {
this.removeSurround(startCharacter, endCharacter);
} else {
this.surround(startCharacter, endCharacter);
}
}
// @param {String} tagName. E.g. 'a' to remove all links.
}, {
key: 'removeFormatting',
value: function removeFormatting(tagName) {
this.range = content.removeFormatting(this.host, this.range, tagName);
this.setSelection();
}
// Delete the contents inside the range. After that the selection will be a
// cursor.
//
// @return Cursor instance
}, {
key: 'deleteContent',
value: function deleteContent() {
this.range.deleteContents();
return new _cursor2.default(this.host, this.range);
}
// Expand the current selection.
//
// @param {DOM Node}
}, {
key: 'expandTo',
value: function expandTo(elem) {
this.range = content.expandTo(this.host, this.range, elem);
this.setSelection();
}
// Collapse the selection at the beginning of the selection
//
// @return Cursor instance
}, {
key: 'collapseAtBeginning',
value: function collapseAtBeginning(elem) {
this.range.collapse(true);
this.setSelection();
return new _cursor2.default(this.host, this.range);
}
// Collapse the selection at the end of the selection
//
// @return Cursor instance
}, {
key: 'collapseAtEnd',
value: function collapseAtEnd(elem) {
this.range.collapse(false);
this.setSelection();
return new _cursor2.default(this.host, this.range);
}
// Wrap the selection with the specified tag. If any other tag with
// the same tagName is affecting the selection this tag will be
// remove first.
}, {
key: 'forceWrap',
value: function forceWrap(elem) {
elem = this.adoptElement(elem);
this.range = content.forceWrap(this.host, this.range, elem);
this.setSelection();
}
// Get all tags that affect the current selection. Optionally pass a
// method to filter the returned elements.
//
// @param {Function filter(node)} [Optional] Method to filter the returned
// DOM Nodes.
// @return {Array of DOM Nodes}
}, {
key: 'getTags',
value: function getTags(filterFunc) {
return content.getTags(this.host, this.range, filterFunc);
}
// Get all tags of the specified type that affect the current selection.
//
// @method getTagsByName
// @param {String} tagName. E.g. 'a' to get all links.
// @return {Array of DOM Nodes}
}, {
key: 'getTagsByName',
value: function getTagsByName(tagName) {
return content.getTagsByName(this.host, this.range, tagName);
}
// Check if the selection is the same as the elements contents.
//
// @method isExactSelection
// @param {DOM Node}
// @param {flag: undefined or 'visible'} if 'visible' is passed
// whitespaces at the beginning or end of the selection will
// be ignored.
// @return {Boolean}
}, {
key: 'isExactSelection',
value: function isExactSelection(elem, onlyVisible) {
return content.isExactSelection(this.range, elem, onlyVisible);
}
// Check if the selection contains the passed string.
//
// @method containsString
// @return {Boolean}
}, {
key: 'containsString',
value: function containsString(str) {
return content.containsString(this.range, str);
}
// Delete all occurences of the specified character from the
// selection.
}, {
key: 'deleteCharacter',
value: function deleteCharacter(character) {
this.range = content.deleteCharacter(this.host, this.range, character);
this.setSelection();
}
}]);
return Selection;
}(_cursor2.default);
exports.default = Selection;