pad.libre-service.eu-etherpad/src/static/js/AttributeManager.js

205 lines
6.8 KiB
JavaScript
Raw Normal View History

2012-04-05 00:50:04 +02:00
var Changeset = require('./Changeset');
var ChangesetUtils = require('./ChangesetUtils');
var _ = require('./underscore');
var lineMarkerAttribute = 'lmkr';
// If one of these attributes are set to the first character of a
// line it is considered as a line attribute marker i.e. attributes
// set on this marker are applied to the whole line.
// The list attribute is only maintained for compatibility reasons
var lineAttributes = [lineMarkerAttribute,'list'];
/*
The Attribute manager builds changesets based on a document
representation for setting and removing range or line-based attributes.
@param rep the document representation to be used
@param applyChangesetCallback this callback will be called
once a changeset has been built.
2012-04-08 21:21:05 +02:00
A document representation contains
- an array `alines` containing 1 attributes string for each line
- an Attribute pool `apool`
- a SkipList `lines` containing the text lines of the document.
*/
2012-04-05 00:50:04 +02:00
var AttributeManager = function(rep, applyChangesetCallback)
{
this.rep = rep;
this.applyChangesetCallback = applyChangesetCallback;
this.author = '';
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
};
AttributeManager.lineAttributes = lineAttributes;
2012-04-05 00:50:04 +02:00
AttributeManager.prototype = _(AttributeManager.prototype).extend({
applyChangeset: function(changeset){
if(!this.applyChangesetCallback) return changeset;
2012-04-05 00:50:04 +02:00
var cs = changeset.toString();
if (!Changeset.isIdentity(cs))
{
this.applyChangesetCallback(cs);
}
return changeset;
2012-04-05 00:50:04 +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
@param attribute: an array of attributes
*/
setAttributesOnRange: function(start, end, attribs)
{
2012-04-05 22:26:39 +02:00
var builder = Changeset.builder(this.rep.lines.totalWidth());
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, start);
ChangesetUtils.buildKeepRange(this.rep, builder, start, end, attribs, this.rep.apool);
return this.applyChangeset(builder);
},
/*
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;
return _.find(lineAttributes, function(attribute){
2012-04-05 01:07:47 +02:00
return that.getAttributeOnLine(lineNum, attribute) != '';
}) !== undefined;
2012-04-05 00:50:04 +02:00
},
/*
Gets 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 get, e.g. list
*/
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 '';
},
/*
Gets all attributes on a line
@param lineNum: the number of the line to set the attribute for
*/
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 []
Changeset.eachAttribNumber(op.attribs, function(n) {
attributes.push([this.rep.apool.getAttribKey(n), this.rep.apool.getAttribValue(n)])
}.bind(this))
return attributes;
}
}
return [];
},
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)
*/
setAttributeOnLine: function(lineNum, attributeName, attributeValue){
var loc = [0,0];
var builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum);
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);
}else{
// add a line marker
builder.insert('*', [
['author', this.author],
['insertorder', 'first'],
[lineMarkerAttribute, '1'],
2012-04-05 00:50:04 +02:00
[attributeName, attributeValue]
], this.rep.apool);
}
return this.applyChangeset(builder);
},
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
*/
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
2015-03-06 23:02:31 +01:00
var builder = Changeset.builder(this.rep.lines.totalWidth());
var hasMarker = this.lineHasMarker(lineNum);
var found = false;
2015-03-06 23:02:31 +01:00
var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){
found = true;
return [attributeName, ''];
}
return attrib;
});
2015-03-06 23:02:31 +01:00
if (!found) {
return;
}
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
var list = _.chain(attribs).filter(function(a){return !!a[1];}).map(function(a){return a[0];}).value();
//if we have marker and any of attributes don't need to have marker. we need delete it
if(hasMarker && !_.intersection(lineAttributes,list)){
2015-03-22 23:14:17 +01:00
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 1], [lineNum, 2]);
2015-03-06 23:02:31 +01:00
}else{
2015-03-22 23:14:17 +01:00
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 1], [lineNum, 2], attribs, this.rep.apool);
2015-03-06 23:02:31 +01:00
}
2012-04-05 00:50:04 +02:00
return this.applyChangeset(builder);
},
/*
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)
*/
toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
return this.getAttributeOnLine(attributeName) ?
this.removeAttributeOnLine(lineNum, attributeName) :
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
}
});
2015-03-06 23:02:31 +01:00
module.exports = AttributeManager;