2012-04-05 00:50:04 +02:00
|
|
|
var Changeset = require('./Changeset');
|
|
|
|
var ChangesetUtils = require('./ChangesetUtils');
|
|
|
|
var _ = require('./underscore');
|
|
|
|
|
2012-04-05 15:20:48 +02:00
|
|
|
var lineMarkerAttribute = 'lmkr';
|
|
|
|
|
2018-07-09 22:44:38 +02:00
|
|
|
// Some of these attributes are kept for compatibility purposes.
|
|
|
|
// Not sure if we need all of them
|
|
|
|
var DEFAULT_LINE_ATTRIBUTES = ['author', 'lmkr', 'insertorder', 'start'];
|
|
|
|
|
2015-09-08 16:55:36 +02:00
|
|
|
// If one of these attributes are set to the first character of a
|
2012-04-05 15:20:48 +02:00
|
|
|
// line it is considered as a line attribute marker i.e. attributes
|
2015-09-08 16:55:36 +02:00
|
|
|
// set on this marker are applied to the whole line.
|
2012-04-05 15:20:48 +02:00
|
|
|
// The list attribute is only maintained for compatibility reasons
|
|
|
|
var lineAttributes = [lineMarkerAttribute,'list'];
|
|
|
|
|
|
|
|
/*
|
2015-09-08 16:55:36 +02:00
|
|
|
The Attribute manager builds changesets based on a document
|
2012-04-06 17:44:34 +02:00
|
|
|
representation for setting and removing range or line-based attributes.
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-06 17:44:34 +02:00
|
|
|
@param rep the document representation to be used
|
2015-09-08 16:55:36 +02:00
|
|
|
@param applyChangesetCallback this callback will be called
|
2012-04-05 15:20:48 +02:00
|
|
|
once a changeset has been built.
|
2015-09-08 16:55:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
A document representation contains
|
|
|
|
- an array `alines` containing 1 attributes string for each line
|
2012-04-08 21:21:05 +02:00
|
|
|
- an Attribute pool `apool`
|
|
|
|
- a SkipList `lines` containing the text lines of the document.
|
2012-04-05 15:20:48 +02:00
|
|
|
*/
|
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
var AttributeManager = function(rep, applyChangesetCallback) {
|
2012-04-05 00:50:04 +02:00
|
|
|
this.rep = rep;
|
|
|
|
this.applyChangesetCallback = applyChangesetCallback;
|
|
|
|
this.author = '';
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 01:07:47 +02:00
|
|
|
// If the first char in a line has one of the following attributes
|
|
|
|
// it will be considered as a line marker
|
2012-04-05 00:50:04 +02:00
|
|
|
};
|
|
|
|
|
2018-07-09 22:44:38 +02:00
|
|
|
AttributeManager.DEFAULT_LINE_ATTRIBUTES = DEFAULT_LINE_ATTRIBUTES;
|
2012-04-07 01:05:25 +02:00
|
|
|
AttributeManager.lineAttributes = lineAttributes;
|
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
applyChangeset: function(changeset){
|
2012-04-05 15:20:48 +02:00
|
|
|
if(!this.applyChangesetCallback) return changeset;
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
var cs = changeset.toString();
|
|
|
|
if (!Changeset.isIdentity(cs))
|
|
|
|
{
|
|
|
|
this.applyChangesetCallback(cs);
|
|
|
|
}
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 15:20:48 +02:00
|
|
|
return changeset;
|
2012-04-05 00:50:04 +02:00
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 15:22:22 +02:00
|
|
|
/*
|
|
|
|
Sets attributes on a range
|
|
|
|
@param start [row, col] tuple pointing to the start of the range
|
|
|
|
@param end [row, col] tuple pointing to the end of the range
|
2015-09-08 16:55:36 +02:00
|
|
|
@param attribs: an array of attributes
|
2012-04-05 15:22:22 +02:00
|
|
|
*/
|
2020-11-21 19:37:57 +01:00
|
|
|
setAttributesOnRange: function(start, end, attribs) {
|
2015-09-08 16:55:36 +02:00
|
|
|
// instead of applying the attributes to the whole range at once, we need to apply them
|
|
|
|
// line by line, to be able to disregard the "*" used as line marker. For more details,
|
|
|
|
// see https://github.com/ether/etherpad-lite/issues/2772
|
|
|
|
var allChangesets;
|
|
|
|
for(var row = start[0]; row <= end[0]; row++) {
|
|
|
|
var rowRange = this._findRowRange(row, start, end);
|
|
|
|
var startCol = rowRange[0];
|
|
|
|
var endCol = rowRange[1];
|
|
|
|
|
|
|
|
var rowChangeset = this._setAttributesOnRangeByLine(row, startCol, endCol, attribs);
|
|
|
|
|
|
|
|
// compose changesets of all rows into a single changeset, as the range might not be continuous
|
|
|
|
// due to the presence of line markers on the rows
|
|
|
|
if (allChangesets) {
|
|
|
|
allChangesets = Changeset.compose(allChangesets.toString(), rowChangeset.toString(), this.rep.apool);
|
|
|
|
} else {
|
|
|
|
allChangesets = rowChangeset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.applyChangeset(allChangesets);
|
|
|
|
},
|
|
|
|
|
2020-11-21 19:37:57 +01:00
|
|
|
_findRowRange: function(row, start, end) {
|
2015-09-08 16:55:36 +02:00
|
|
|
var startCol, endCol;
|
|
|
|
|
|
|
|
var startLineOffset = this.rep.lines.offsetOfIndex(row);
|
|
|
|
var endLineOffset = this.rep.lines.offsetOfIndex(row+1);
|
|
|
|
var lineLength = endLineOffset - startLineOffset;
|
|
|
|
|
|
|
|
// find column where range on this row starts
|
|
|
|
if (row === start[0]) { // are we on the first row of range?
|
|
|
|
startCol = start[1];
|
|
|
|
} else {
|
|
|
|
startCol = this.lineHasMarker(row) ? 1 : 0; // remove "*" used as line marker
|
|
|
|
}
|
|
|
|
|
|
|
|
// find column where range on this row ends
|
|
|
|
if (row === end[0]) { // are we on the last row of range?
|
|
|
|
endCol = end[1]; // if so, get the end of range, not end of row
|
|
|
|
} else {
|
|
|
|
endCol = lineLength - 1; // remove "\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
return [startCol, endCol];
|
|
|
|
},
|
|
|
|
|
|
|
|
/*
|
|
|
|
Sets attributes on a range, by line
|
|
|
|
@param row the row where range is
|
|
|
|
@param startCol column where range starts
|
|
|
|
@param endCol column where range ends
|
|
|
|
@param attribs: an array of attributes
|
|
|
|
*/
|
2020-11-21 19:37:57 +01:00
|
|
|
_setAttributesOnRangeByLine: function(row, startCol, endCol, attribs) {
|
2012-04-05 22:26:39 +02:00
|
|
|
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
2015-09-08 16:55:36 +02:00
|
|
|
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [row, startCol]);
|
|
|
|
ChangesetUtils.buildKeepRange(this.rep, builder, [row, startCol], [row, endCol], attribs, this.rep.apool);
|
|
|
|
return builder;
|
2012-04-05 15:22:22 +02:00
|
|
|
},
|
|
|
|
|
2015-09-08 16:55:36 +02:00
|
|
|
/*
|
2012-04-05 15:20:48 +02:00
|
|
|
Returns if the line already has a line marker
|
|
|
|
@param lineNum: the number of the line
|
|
|
|
*/
|
2012-04-05 00:50:04 +02:00
|
|
|
lineHasMarker: function(lineNum){
|
2012-04-05 01:07:47 +02:00
|
|
|
var that = this;
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 15:20:48 +02:00
|
|
|
return _.find(lineAttributes, function(attribute){
|
2015-09-08 16:55:36 +02:00
|
|
|
return that.getAttributeOnLine(lineNum, attribute) != '';
|
2012-04-05 01:07:47 +02:00
|
|
|
}) !== undefined;
|
2012-04-05 00:50:04 +02:00
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 15:20:48 +02:00
|
|
|
/*
|
|
|
|
Gets a specified attribute on a line
|
|
|
|
@param lineNum: the number of the line to set the attribute for
|
2015-09-08 16:55:36 +02:00
|
|
|
@param attributeKey: the name of the attribute to get, e.g. list
|
2012-04-05 15:20:48 +02:00
|
|
|
*/
|
2012-04-05 00:50:04 +02:00
|
|
|
getAttributeOnLine: function(lineNum, attributeName){
|
|
|
|
// get `attributeName` attribute of first char of line
|
|
|
|
var aline = this.rep.alines[lineNum];
|
|
|
|
if (aline)
|
|
|
|
{
|
|
|
|
var opIter = Changeset.opIterator(aline);
|
|
|
|
if (opIter.hasNext())
|
|
|
|
{
|
|
|
|
return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return '';
|
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2014-12-27 16:15:20 +01:00
|
|
|
/*
|
|
|
|
Gets all attributes on a line
|
2015-09-08 16:55:36 +02:00
|
|
|
@param lineNum: the number of the line to get the attribute for
|
2014-12-27 16:15:20 +01:00
|
|
|
*/
|
|
|
|
getAttributesOnLine: function(lineNum){
|
|
|
|
// get attributes of first char of line
|
|
|
|
var aline = this.rep.alines[lineNum];
|
|
|
|
var attributes = []
|
|
|
|
if (aline)
|
|
|
|
{
|
|
|
|
var opIter = Changeset.opIterator(aline)
|
|
|
|
, op
|
|
|
|
if (opIter.hasNext())
|
|
|
|
{
|
|
|
|
op = opIter.next()
|
|
|
|
if(!op.attribs) return []
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2014-12-27 16:15:20 +01:00
|
|
|
Changeset.eachAttribNumber(op.attribs, function(n) {
|
|
|
|
attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)])
|
|
|
|
}.bind(this))
|
|
|
|
return attributes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2016-03-26 15:00:34 +01:00
|
|
|
/*
|
|
|
|
Gets a given attribute on a selection
|
|
|
|
@param attributeName
|
|
|
|
@param prevChar
|
|
|
|
returns true or false if an attribute is visible in range
|
|
|
|
*/
|
|
|
|
getAttributeOnSelection: function(attributeName, prevChar){
|
|
|
|
var rep = this.rep;
|
|
|
|
if (!(rep.selStart && rep.selEnd)) return
|
|
|
|
// If we're looking for the caret attribute not the selection
|
|
|
|
// has the user already got a selection or is this purely a caret location?
|
|
|
|
var isNotSelection = (rep.selStart[0] == rep.selEnd[0] && rep.selEnd[1] === rep.selStart[1]);
|
|
|
|
if(isNotSelection){
|
|
|
|
if(prevChar){
|
|
|
|
// If it's not the start of the line
|
|
|
|
if(rep.selStart[1] !== 0){
|
|
|
|
rep.selStart[1]--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var withIt = Changeset.makeAttribsString('+', [
|
|
|
|
[attributeName, 'true']
|
|
|
|
], rep.apool);
|
|
|
|
var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)");
|
2020-11-21 19:37:57 +01:00
|
|
|
function hasIt(attribs) {
|
2016-03-26 15:00:34 +01:00
|
|
|
return withItRegex.test(attribs);
|
|
|
|
}
|
|
|
|
|
|
|
|
return rangeHasAttrib(rep.selStart, rep.selEnd)
|
|
|
|
|
|
|
|
function rangeHasAttrib(selStart, selEnd) {
|
|
|
|
// if range is collapsed -> no attribs in range
|
|
|
|
if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false
|
|
|
|
|
|
|
|
if(selStart[0] != selEnd[0]) { // -> More than one line selected
|
|
|
|
var hasAttrib = true
|
|
|
|
|
|
|
|
// from selStart to the end of the first line
|
|
|
|
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length])
|
|
|
|
|
|
|
|
// for all lines in between
|
|
|
|
for(var n=selStart[0]+1; n < selEnd[0]; n++) {
|
|
|
|
hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length])
|
|
|
|
}
|
|
|
|
|
|
|
|
// for the last, potentially partial, line
|
|
|
|
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]])
|
|
|
|
|
|
|
|
return hasAttrib
|
|
|
|
}
|
|
|
|
|
|
|
|
// Logic tells us we now have a range on a single line
|
|
|
|
|
|
|
|
var lineNum = selStart[0]
|
|
|
|
, start = selStart[1]
|
|
|
|
, end = selEnd[1]
|
|
|
|
, hasAttrib = true
|
|
|
|
|
|
|
|
// Iterate over attribs on this line
|
|
|
|
|
|
|
|
var opIter = Changeset.opIterator(rep.alines[lineNum])
|
|
|
|
, indexIntoLine = 0
|
|
|
|
|
|
|
|
while (opIter.hasNext()) {
|
|
|
|
var op = opIter.next();
|
|
|
|
var opStartInLine = indexIntoLine;
|
|
|
|
var opEndInLine = opStartInLine + op.chars;
|
|
|
|
if (!hasIt(op.attribs)) {
|
|
|
|
// does op overlap selection?
|
|
|
|
if (!(opEndInLine <= start || opStartInLine >= end)) {
|
|
|
|
hasAttrib = false; // since it's overlapping but hasn't got the attrib -> range hasn't got it
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
indexIntoLine = opEndInLine;
|
|
|
|
}
|
|
|
|
|
|
|
|
return hasAttrib
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
/*
|
|
|
|
Gets all attributes at a position containing line number and column
|
|
|
|
@param lineNumber starting with zero
|
|
|
|
@param column starting with zero
|
2015-09-08 16:55:36 +02:00
|
|
|
returns a list of attributes in the format
|
2015-03-25 12:04:10 +01:00
|
|
|
[ ["key","value"], ["key","value"], ... ]
|
|
|
|
*/
|
|
|
|
getAttributesOnPosition: function(lineNumber, column){
|
|
|
|
// get all attributes of the line
|
|
|
|
var aline = this.rep.alines[lineNumber];
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
if (!aline) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
// iterate through all operations of a line
|
|
|
|
var opIter = Changeset.opIterator(aline);
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
// we need to sum up how much characters each operations take until the wanted position
|
|
|
|
var currentPointer = 0;
|
2015-09-08 16:55:36 +02:00
|
|
|
var attributes = [];
|
2015-03-25 12:04:10 +01:00
|
|
|
var currentOperation;
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
while (opIter.hasNext()) {
|
|
|
|
currentOperation = opIter.next();
|
2015-09-08 16:55:36 +02:00
|
|
|
currentPointer = currentPointer + currentOperation.chars;
|
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
if (currentPointer > column) {
|
|
|
|
// we got the operation of the wanted position, now collect all its attributes
|
|
|
|
Changeset.eachAttribNumber(currentOperation.attribs, function (n) {
|
|
|
|
attributes.push([
|
|
|
|
this.rep.apool.getAttribKey(n),
|
|
|
|
this.rep.apool.getAttribValue(n)
|
|
|
|
]);
|
|
|
|
}.bind(this));
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
// skip the loop
|
|
|
|
return attributes;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return attributes;
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2015-03-25 12:04:10 +01:00
|
|
|
/*
|
2015-09-08 16:55:36 +02:00
|
|
|
Gets all attributes at caret position
|
2015-03-25 13:29:03 +01:00
|
|
|
if the user selected a range, the start of the selection is taken
|
2015-09-08 16:55:36 +02:00
|
|
|
returns a list of attributes in the format
|
2015-03-31 10:58:47 +02:00
|
|
|
[ ["key","value"], ["key","value"], ... ]
|
2015-03-25 12:04:10 +01:00
|
|
|
*/
|
|
|
|
getAttributesOnCaret: function(){
|
|
|
|
return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]);
|
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
/*
|
|
|
|
Sets a specified attribute on a line
|
|
|
|
@param lineNum: the number of the line to set the attribute for
|
|
|
|
@param attributeKey: the name of the attribute to set, e.g. list
|
|
|
|
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
*/
|
|
|
|
setAttributeOnLine: function(lineNum, attributeName, attributeValue){
|
|
|
|
var loc = [0,0];
|
|
|
|
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
|
|
|
var hasMarker = this.lineHasMarker(lineNum);
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
|
|
|
|
|
|
|
|
if(hasMarker){
|
|
|
|
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), [
|
|
|
|
[attributeName, attributeValue]
|
|
|
|
], this.rep.apool);
|
2015-09-08 16:55:36 +02:00
|
|
|
}else{
|
2012-04-05 00:50:04 +02:00
|
|
|
// add a line marker
|
|
|
|
builder.insert('*', [
|
|
|
|
['author', this.author],
|
|
|
|
['insertorder', 'first'],
|
2012-04-05 15:20:48 +02:00
|
|
|
[lineMarkerAttribute, '1'],
|
2012-04-05 00:50:04 +02:00
|
|
|
[attributeName, attributeValue]
|
|
|
|
], this.rep.apool);
|
|
|
|
}
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
return this.applyChangeset(builder);
|
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2015-03-06 23:02:31 +01:00
|
|
|
/**
|
|
|
|
* Removes a specified attribute on a line
|
|
|
|
* @param lineNum the number of the affected line
|
|
|
|
* @param attributeName the name of the attribute to remove, e.g. list
|
|
|
|
* @param attributeValue if given only attributes with equal value will be removed
|
2012-04-05 00:50:04 +02:00
|
|
|
*/
|
2015-03-24 20:04:28 +01:00
|
|
|
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
|
|
|
|
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
|
|
|
var hasMarker = this.lineHasMarker(lineNum);
|
|
|
|
var found = false;
|
2014-12-30 17:45:26 +01:00
|
|
|
|
2015-03-24 20:04:28 +01:00
|
|
|
var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
|
|
|
|
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){
|
|
|
|
found = true;
|
|
|
|
return [attributeName, ''];
|
2016-03-15 19:44:00 +01:00
|
|
|
}else if (attrib[0] === 'author'){
|
|
|
|
// update last author to make changes to line attributes on this line
|
|
|
|
return [attributeName, this.author];
|
2015-01-05 18:38:34 +01:00
|
|
|
}
|
2015-03-24 20:04:28 +01:00
|
|
|
return attrib;
|
|
|
|
});
|
2015-01-05 18:38:34 +01:00
|
|
|
|
2015-03-24 20:04:28 +01:00
|
|
|
if (!found) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
|
|
|
|
|
|
|
|
var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];})
|
2018-07-09 22:44:38 +02:00
|
|
|
.map(function(a){return a[0];}).difference(DEFAULT_LINE_ATTRIBUTES).size().value();
|
2015-03-24 20:04:28 +01:00
|
|
|
|
|
|
|
//if we have marker and any of attributes don't need to have marker. we need delete it
|
|
|
|
if(hasMarker && !countAttribsWithMarker){
|
|
|
|
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
|
|
|
|
}else{
|
|
|
|
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.applyChangeset(builder);
|
|
|
|
},
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2012-04-05 00:50:04 +02:00
|
|
|
/*
|
2015-03-20 11:58:56 +01:00
|
|
|
Toggles a line attribute for the specified line number
|
|
|
|
If a line attribute with the specified name exists with any value it will be removed
|
|
|
|
Otherwise it will be set to the given value
|
|
|
|
@param lineNum: the number of the line to toggle the attribute for
|
|
|
|
@param attributeKey: the name of the attribute to toggle, e.g. list
|
|
|
|
@param attributeValue: the value to pass to the attribute (e.g. indention level)
|
2012-04-05 00:50:04 +02:00
|
|
|
*/
|
|
|
|
toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
|
2015-03-20 11:58:56 +01:00
|
|
|
return this.getAttributeOnLine(lineNum, attributeName) ?
|
2012-04-05 00:50:04 +02:00
|
|
|
this.removeAttributeOnLine(lineNum, attributeName) :
|
|
|
|
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
|
2015-09-08 16:55:36 +02:00
|
|
|
|
2018-01-04 15:28:00 +01:00
|
|
|
},
|
|
|
|
|
|
|
|
hasAttributeOnSelectionOrCaretPosition: function(attributeName) {
|
|
|
|
var hasSelection = ((this.rep.selStart[0] !== this.rep.selEnd[0]) || (this.rep.selEnd[1] !== this.rep.selStart[1]));
|
|
|
|
var hasAttrib;
|
|
|
|
if (hasSelection) {
|
|
|
|
hasAttrib = this.getAttributeOnSelection(attributeName);
|
|
|
|
}else {
|
|
|
|
var attributesOnCaretPosition = this.getAttributesOnCaret();
|
|
|
|
hasAttrib = _.contains(_.flatten(attributesOnCaretPosition), attributeName);
|
|
|
|
}
|
|
|
|
return hasAttrib;
|
|
|
|
},
|
2012-04-05 00:50:04 +02:00
|
|
|
});
|
|
|
|
|
2015-03-06 23:02:31 +01:00
|
|
|
module.exports = AttributeManager;
|