Skip to content

Commit f1eb7a2

Browse files
committed
Changeset: Migrate to the new attribute API
1 parent f40d285 commit f1eb7a2

File tree

15 files changed

+175
-210
lines changed

15 files changed

+175
-210
lines changed

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88
(low-level API) and `ep_etherpad-lite/static/js/AttributeMap` (high-level
99
API).
1010

11+
### Compatibility changes
12+
13+
#### For plugin authors
14+
15+
* Changes to the `src/static/js/Changeset.js` library:
16+
* The following attribute processing functions are deprecated (use the new
17+
attribute APIs instead):
18+
* `attribsAttributeValue()`
19+
* `eachAttribNumber()`
20+
* `makeAttribsString()`
21+
* `opAttributeValue()`
22+
1123
# 1.8.15
1224

1325
### Security fixes

doc/api/hooks_server-side.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -665,14 +665,15 @@ Context properties:
665665
Example:
666666

667667
```javascript
668+
const AttributeMap = require('ep_etherpad-lite/static/js/AttributeMap');
668669
const Changeset = require('ep_etherpad-lite/static/js/Changeset');
669670

670671
exports.getLineHTMLForExport = async (hookName, context) => {
671672
if (!context.attribLine) return;
672673
const opIter = Changeset.opIterator(context.attribLine);
673674
if (!opIter.hasNext()) return;
674675
const op = opIter.next();
675-
const heading = Changeset.opAttributeValue(op, 'heading', apool);
676+
const heading = AttributeMap.fromString(op.attribs, context.apool).get('heading');
676677
if (!heading) return;
677678
context.lineContent = `<${heading}>${context.lineContent}</${heading}>`;
678679
};

src/node/handler/PadMessageHandler.js

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* limitations under the License.
2020
*/
2121

22+
const AttributeMap = require('../../static/js/AttributeMap');
2223
const padManager = require('../db/PadManager');
2324
const Changeset = require('../../static/js/Changeset');
2425
const ChatMessage = require('../../static/js/ChatMessage');
@@ -32,7 +33,6 @@ const plugins = require('../../static/js/pluginfw/plugin_defs.js');
3233
const log4js = require('log4js');
3334
const messageLogger = log4js.getLogger('message');
3435
const accessLogger = log4js.getLogger('access');
35-
const _ = require('underscore');
3636
const hooks = require('../../static/js/pluginfw/hooks.js');
3737
const stats = require('../stats');
3838
const assert = require('assert').strict;
@@ -585,14 +585,6 @@ const handleUserChanges = async (socket, message) => {
585585
// Verify that the changeset has valid syntax and is in canonical form
586586
Changeset.checkRep(changeset);
587587

588-
// Verify that the attribute indexes used in the changeset are all
589-
// defined in the accompanying attribute pool.
590-
Changeset.eachAttribNumber(changeset, (n) => {
591-
if (!wireApool.getAttrib(n)) {
592-
throw new Error(`Attribute pool is missing attribute ${n} for changeset ${changeset}`);
593-
}
594-
});
595-
596588
// Validate all added 'author' attribs to be the same value as the current user
597589
const iterator = Changeset.opIterator(Changeset.unpack(changeset).ops);
598590
let op;
@@ -605,19 +597,14 @@ const handleUserChanges = async (socket, message) => {
605597
// - can have attribs, but they are discarded and don't show up in the attribs -
606598
// but do show up in the pool
607599

608-
op.attribs.split('*').forEach((attr) => {
609-
if (!attr) return;
610-
611-
attr = wireApool.getAttrib(Changeset.parseNum(attr));
612-
if (!attr) return;
613-
614-
// the empty author is used in the clearAuthorship functionality so this
615-
// should be the only exception
616-
if ('author' === attr[0] && (attr[1] !== thisSession.author && attr[1] !== '')) {
617-
throw new Error(`Author ${thisSession.author} tried to submit changes as author ` +
618-
`${attr[1]} in changeset ${changeset}`);
619-
}
620-
});
600+
// Besides verifying the author attribute, this serves a second purpose:
601+
// AttributeMap.fromString() ensures that all attribute numbers are valid (it will throw if
602+
// an attribute number isn't in the pool).
603+
const opAuthorId = AttributeMap.fromString(op.attribs, wireApool).get('author');
604+
if (opAuthorId && opAuthorId !== thisSession.author) {
605+
throw new Error(`Author ${thisSession.author} tried to submit changes as author ` +
606+
`${opAuthorId} in changeset ${changeset}`);
607+
}
621608
}
622609

623610
// ex. adoptChangesetAttribs
@@ -758,11 +745,8 @@ const _correctMarkersInPad = (atext, apool) => {
758745
let offset = 0;
759746
while (iter.hasNext()) {
760747
const op = iter.next();
761-
762-
const hasMarker = _.find(
763-
AttributeManager.lineAttributes,
764-
(attribute) => Changeset.opAttributeValue(op, attribute, apool)) !== undefined;
765-
748+
const attribs = AttributeMap.fromString(op.attribs, apool);
749+
const hasMarker = AttributeManager.lineAttributes.some((a) => attribs.has(a));
766750
if (hasMarker) {
767751
for (let i = 0; i < op.chars; i++) {
768752
if (offset > 0 && text.charAt(offset - 1) !== '\n') {

src/node/utils/ExportHelper.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
* limitations under the License.
2020
*/
2121

22+
const AttributeMap = require('../../static/js/AttributeMap');
2223
const Changeset = require('../../static/js/Changeset');
2324

2425
exports.getPadPlainText = (pad, revNum) => {
@@ -54,7 +55,8 @@ exports._analyzeLine = (text, aline, apool) => {
5455
const opIter = Changeset.opIterator(aline);
5556
if (opIter.hasNext()) {
5657
const op = opIter.next();
57-
let listType = Changeset.opAttributeValue(op, 'list', apool);
58+
const attribs = AttributeMap.fromString(op.attribs, apool);
59+
let listType = attribs.get('list');
5860
if (listType) {
5961
lineMarker = 1;
6062
listType = /([a-z]+)([0-9]+)/.exec(listType);
@@ -63,7 +65,7 @@ exports._analyzeLine = (text, aline, apool) => {
6365
line.listLevel = Number(listType[2]);
6466
}
6567
}
66-
const start = Changeset.opAttributeValue(op, 'start', apool);
68+
const start = attribs.get('start');
6769
if (start) {
6870
line.start = start;
6971
}

src/node/utils/ExportHtml.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
const Changeset = require('../../static/js/Changeset');
19+
const attributes = require('../../static/js/attributes');
1920
const padManager = require('../db/PadManager');
2021
const _ = require('underscore');
2122
const Security = require('../../static/js/security');
@@ -206,11 +207,11 @@ const getHTMLFromAtext = async (pad, atext, authorColors) => {
206207
const usedAttribs = [];
207208

208209
// mark all attribs as used
209-
Changeset.eachAttribNumber(o.attribs, (a) => {
210+
for (const a of attributes.decodeAttribString(o.attribs)) {
210211
if (a in anumMap) {
211212
usedAttribs.push(anumMap[a]); // i = 0 => bold, etc.
212213
}
213-
});
214+
}
214215
let outermostTag = -1;
215216
// find the outer most open tag that is no longer used
216217
for (let i = openTags.length - 1; i >= 0; i--) {

src/node/utils/ExportTxt.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121

2222
const Changeset = require('../../static/js/Changeset');
23+
const attributes = require('../../static/js/attributes');
2324
const padManager = require('../db/PadManager');
2425
const _analyzeLine = require('./ExportHelper')._analyzeLine;
2526

@@ -82,7 +83,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
8283
const o = iter.next();
8384
let propChanged = false;
8485

85-
Changeset.eachAttribNumber(o.attribs, (a) => {
86+
for (const a of attributes.decodeAttribString(o.attribs)) {
8687
if (a in anumMap) {
8788
const i = anumMap[a]; // i = 0 => bold, etc.
8889

@@ -93,7 +94,7 @@ const getTXTFromAtext = (pad, atext, authorColors) => {
9394
propVals[i] = STAY;
9495
}
9596
}
96-
});
97+
}
9798

9899
for (let i = 0; i < propVals.length; i++) {
99100
if (propVals[i] === true) {

src/node/utils/padDiff.js

Lines changed: 16 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
'use strict';
2+
3+
const AttributeMap = require('../../static/js/AttributeMap');
24
const Changeset = require('../../static/js/Changeset');
5+
const attributes = require('../../static/js/attributes');
36
const exportHtml = require('./ExportHtml');
47

58
function PadDiff(pad, fromRev, toRev) {
@@ -54,17 +57,11 @@ PadDiff.prototype._isClearAuthorship = function (changeset) {
5457
return false;
5558
}
5659

57-
const attributes = [];
58-
Changeset.eachAttribNumber(changeset, (attrNum) => {
59-
attributes.push(attrNum);
60-
});
61-
62-
// check that this changeset uses only one attribute
63-
if (attributes.length !== 1) {
64-
return false;
65-
}
60+
const [appliedAttribute, anotherAttribute] =
61+
attributes.attribsFromString(clearOperator.attribs, this._pad.pool);
6662

67-
const appliedAttribute = this._pad.pool.getAttrib(attributes[0]);
63+
// Check that the operation has exactly one attribute.
64+
if (appliedAttribute == null || anotherAttribute != null) return false;
6865

6966
// check if the applied attribute is an anonymous author attribute
7067
if (appliedAttribute[0] !== 'author' || appliedAttribute[1] !== '') {
@@ -376,27 +373,19 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
376373
// If the text this operator applies to is only a star,
377374
// than this is a false positive and should be ignored
378375
if (csOp.attribs && textBank !== '*') {
379-
const deletedAttrib = apool.putAttrib(['removed', true]);
380-
let authorAttrib = apool.putAttrib(['author', '']);
381-
const attribs = [];
382-
Changeset.eachAttribNumber(csOp.attribs, (n) => {
383-
const attrib = apool.getAttrib(n);
384-
attribs.push(attrib);
385-
if (attrib[0] === 'author') authorAttrib = n;
386-
});
376+
const attribs = AttributeMap.fromString(csOp.attribs, apool);
387377
const undoBackToAttribs = cachedStrFunc((oldAttribsStr) => {
388-
const backAttribs = [];
378+
const oldAttribs = AttributeMap.fromString(oldAttribsStr, apool);
379+
const backAttribs = new AttributeMap(apool)
380+
.set('author', '')
381+
.set('removed', 'true');
389382
for (const [key, value] of attribs) {
390-
const oldValue = Changeset.attribsAttributeValue(oldAttribsStr, key, apool);
391-
if (oldValue !== value) backAttribs.push([key, oldValue])
383+
const oldValue = oldAttribs.get(key);
384+
if (oldValue !== value) backAttribs.set(key, oldValue);
392385
}
393-
394-
return Changeset.makeAttribsString('=', backAttribs, apool);
386+
return backAttribs.toString();
395387
});
396388

397-
const oldAttribsAddition =
398-
`*${Changeset.numToString(deletedAttrib)}*${Changeset.numToString(authorAttrib)}`;
399-
400389
let textLeftToProcess = textBank;
401390

402391
while (textLeftToProcess.length > 0) {
@@ -429,7 +418,7 @@ PadDiff.prototype._createDeletionChangeset = function (cs, startAText, apool) {
429418
let textBankIndex = 0;
430419
consumeAttribRuns(lengthToProcess, (len, attribs, endsLine) => {
431420
// get the old attributes back
432-
const oldAttribs = (undoBackToAttribs(attribs) || '') + oldAttribsAddition;
421+
const oldAttribs = undoBackToAttribs(attribs);
433422

434423
builder.insert(processText.substr(textBankIndex, len), oldAttribs);
435424
textBankIndex += len;

src/static/js/AttributeManager.js

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
'use strict';
22

3+
const AttributeMap = require('./AttributeMap');
34
const Changeset = require('./Changeset');
45
const ChangesetUtils = require('./ChangesetUtils');
6+
const attributes = require('./attributes');
57
const _ = require('./underscore');
68

79
const lineMarkerAttribute = 'lmkr';
@@ -150,7 +152,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
150152
if (!aline) return '';
151153
const opIter = Changeset.opIterator(aline);
152154
if (!opIter.hasNext()) return '';
153-
return Changeset.opAttributeValue(opIter.next(), attributeName, this.rep.apool) || '';
155+
return AttributeMap.fromString(opIter.next().attribs, this.rep.apool).get(attributeName) || '';
154156
},
155157

156158
/*
@@ -164,10 +166,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
164166
const opIter = Changeset.opIterator(aline);
165167
if (!opIter.hasNext()) return [];
166168
const op = opIter.next();
167-
if (!op.attribs) return [];
168-
const attributes = [];
169-
Changeset.eachAttribNumber(op.attribs, (n) => attributes.push(this.rep.apool.getAttrib(n)));
170-
return attributes;
169+
return [...attributes.attribsFromString(op.attribs, this.rep.apool)];
171170
},
172171

173172
/*
@@ -191,9 +190,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
191190
}
192191
}
193192

194-
const withIt = Changeset.makeAttribsString('+', [
195-
[attributeName, 'true'],
196-
], rep.apool);
193+
const withIt = new AttributeMap(rep.apool).set(attributeName, 'true').toString();
197194
const withItRegex = new RegExp(`${withIt.replace(/\*/g, '\\*')}(\\*|$)`);
198195
const hasIt = (attribs) => withItRegex.test(attribs);
199196

@@ -274,10 +271,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
274271
currentOperation = opIter.next();
275272
currentPointer += currentOperation.chars;
276273
if (currentPointer <= column) continue;
277-
const attributes = [];
278-
Changeset.eachAttribNumber(
279-
currentOperation.attribs, (n) => attributes.push(this.rep.apool.getAttrib(n)));
280-
return attributes;
274+
return [...attributes.attribsFromString(currentOperation.attribs, this.rep.apool)];
281275
}
282276
return [];
283277
},

0 commit comments

Comments
 (0)