Add annotationIsExternal property to annotations

This commit is contained in:
Dan Stillman 2021-01-19 22:38:49 -05:00
parent 5266734ac9
commit ebc0ca2462
9 changed files with 117 additions and 7 deletions

View file

@ -75,6 +75,7 @@ Zotero.Item = function(itemTypeOrID) {
this._annotationPageLabel = null;
this._annotationSortIndex = null;
this._annotationPosition = null;
this._annotationIsExternal = null;
this._tags = [];
this._collections = [];
@ -1795,10 +1796,11 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
let pageLabel = this._getLatestField('annotationPageLabel');
let sortIndex = this._getLatestField('annotationSortIndex');
let position = this._getLatestField('annotationPosition');
let isExternal = this._getLatestField('annotationIsExternal');
let sql = "REPLACE INTO itemAnnotations "
+ "(itemID, parentItemID, type, text, comment, color, pageLabel, sortIndex, position) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
+ "(itemID, parentItemID, type, text, comment, color, pageLabel, sortIndex, position, isExternal) "
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
yield Zotero.DB.queryAsync(
sql,
[
@ -1810,7 +1812,8 @@ Zotero.Item.prototype._saveData = Zotero.Promise.coroutine(function* (env) {
color || null,
pageLabel || null,
sortIndex,
position
position,
isExternal ? 1 : 0
]
);
@ -3547,7 +3550,7 @@ Zotero.Item.prototype.clearBestAttachmentState = function () {
////////////////////////////////////////////////////////
// Main annotation properties (required for items list display)
for (let name of ['type', 'text', 'comment', 'color', 'pageLabel', 'sortIndex']) {
for (let name of ['type', 'text', 'comment', 'color', 'pageLabel', 'sortIndex', 'isExternal']) {
let field = 'annotation' + name[0].toUpperCase() + name.substr(1);
Zotero.defineProperty(Zotero.Item.prototype, field, {
get: function () {
@ -3587,6 +3590,16 @@ for (let name of ['type', 'text', 'comment', 'color', 'pageLabel', 'sortIndex'])
throw new Error(`Invalid sortIndex '${value}`);
}
break;
case 'isExternal':
if (typeof value != 'boolean') {
throw new Error('annotationIsExternal must be a boolean');
}
let currentValue = this._getLatestField('annotationIsExternal');
if (currentValue !== null && currentValue !== value) {
throw new Error("Cannot change annotationIsExternal");
}
break;
}
this._markFieldChange(field, value);

View file

@ -495,7 +495,8 @@ Zotero.Items = function() {
this._loadAnnotations = async function (libraryID, ids, idSQL) {
var sql = "SELECT itemID, IA.parentItemID, IA.type, IA.text, IA.comment, IA.color, IA.sortIndex "
var sql = "SELECT itemID, IA.parentItemID, IA.type, IA.text, IA.comment, IA.color, "
+ "IA.sortIndex, IA.isExternal "
+ "FROM items JOIN itemAnnotations IA USING (itemID) "
+ "WHERE libraryID=?" + idSQL;
var params = [libraryID];
@ -536,6 +537,7 @@ Zotero.Items = function() {
item._annotationComment = row.getResultByIndex(4);
item._annotationColor = row.getResultByIndex(5);
item._annotationSortIndex = row.getResultByIndex(6);
item._annotationIsExternal = !!row.getResultByIndex(7);
item._loaded.annotation = true;
item._clearChanged('annotation');

View file

@ -3236,7 +3236,7 @@ Zotero.Schema = new function(){
yield Zotero.DB.queryAsync("DROP TABLE IF EXISTS users");
yield Zotero.DB.queryAsync("CREATE TABLE users (\n userID INTEGER PRIMARY KEY,\n name TEXT NOT NULL\n)");
yield Zotero.DB.queryAsync("CREATE TABLE itemAnnotations (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT NOT NULL,\n type INTEGER NOT NULL,\n text TEXT,\n comment TEXT,\n color TEXT,\n pageLabel TEXT,\n sortIndex TEXT NOT NULL,\n position TEXT NOT NULL,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("CREATE TABLE itemAnnotations (\n itemID INTEGER PRIMARY KEY,\n parentItemID INT NOT NULL,\n type INTEGER NOT NULL,\n text TEXT,\n comment TEXT,\n color TEXT,\n pageLabel TEXT,\n sortIndex TEXT NOT NULL,\n position TEXT NOT NULL,\n isExternal INT NOT NULL,\n FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,\n FOREIGN KEY (parentItemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE\n)");
yield Zotero.DB.queryAsync("CREATE INDEX itemAnnotations_parentItemID ON itemAnnotations(parentItemID)");
yield Zotero.DB.queryAsync("ALTER TABLE itemAttachments ADD COLUMN lastProcessedModificationTime INT");

View file

@ -543,9 +543,13 @@ Zotero.Sync.Data.Local = {
var sql = "SELECT O." + objectsClass.idColumn + " FROM " + objectsClass.table + " O";
if (objectType == 'item') {
sql += " LEFT JOIN itemAttachments IA USING (itemID) "
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) ";
+ "LEFT JOIN itemNotes INo ON (O.itemID=INo.itemID) "
+ "LEFT JOIN itemAnnotations IAn ON (O.itemID=IAn.itemID)";
}
sql += " WHERE libraryID=? AND synced=0";
if (objectType == 'item') {
sql += " AND (IAn.isExternal IS NULL OR IAN.isExternal=0)";
}
var ids = yield Zotero.DB.columnQueryAsync(sql, [libraryID]);

View file

@ -126,6 +126,7 @@ CREATE TABLE itemAnnotations (
pageLabel TEXT,
sortIndex TEXT NOT NULL,
position TEXT NOT NULL,
isExternal INT NOT NULL,
FOREIGN KEY (itemID) REFERENCES items(itemID) ON DELETE CASCADE,
FOREIGN KEY (parentItemID) REFERENCES itemAttachments(itemID) ON DELETE CASCADE
);

View file

@ -952,6 +952,9 @@ async function createAnnotation(type, parentItem, options = {}) {
[314.4, 412.8, 556.2, 609.6]
]
});
if (options.isExternal) {
annotation.annotationIsExternal = options.isExternal;
}
if (options.tags) {
annotation.setTags(options.tags);
}

View file

@ -1834,6 +1834,26 @@ describe("Zotero.Item", function () {
assert.deepEqual(json.annotationPosition, item.annotationPosition);
assert.notProperty(json, 'collections');
assert.notProperty(json, 'relations');
assert.notProperty(json, 'annotationIsExternal');
});
describe("#annotationIsExternal", function () {
it("should be false if not set", async function () {
var item = await createAnnotation('highlight', attachment);
assert.isFalse(item.annotationIsExternal);
});
it("should be true if set", async function () {
var item = await createAnnotation('highlight', attachment, { isExternal: true });
assert.isTrue(item.annotationIsExternal);
});
it("should prevent changing of annotationIsExternal on existing item", async function () {
var item = await createAnnotation('highlight', attachment);
assert.throws(() => {
item.annotationIsExternal = true;
}, "Cannot change annotationIsExternal");
});
});
});

View file

@ -1386,6 +1386,62 @@ describe("Zotero.Sync.Data.Engine", function () {
assert.equal(json.data.md5, md5);
})
// See also: "shouldn't include external annotations" in syncLocalTest.js
it("shouldn't upload external annotations", async function () {
({ engine, client, caller } = await setup());
var library = Zotero.Libraries.userLibrary;
var libraryID = library.id;
var lastLibraryVersion = 5;
library.libraryVersion = lastLibraryVersion;
await library.saveTx();
var nextLibraryVersion = lastLibraryVersion + 1;
var attachment = await importFileAttachment('test.pdf');
var annotation1 = await createAnnotation('highlight', attachment);
var annotation2 = await createAnnotation('highlight', attachment, { isExternal: true });
var item1ResponseJSON = attachment.toResponseJSON();
item1ResponseJSON.version = item1ResponseJSON.data.version = nextLibraryVersion;
var item2ResponseJSON = annotation1.toResponseJSON();
item2ResponseJSON.version = item2ResponseJSON.data.version = nextLibraryVersion;
server.respond(function (req) {
if (req.method == "POST") {
assert.equal(
req.requestHeaders["If-Unmodified-Since-Version"], lastLibraryVersion
);
if (req.url == baseURL + "users/1/items") {
let json = JSON.parse(req.requestBody);
assert.lengthOf(json, 2);
let keys = [json[0].key, json[1].key];
assert.include(keys, attachment.key);
assert.include(keys, annotation1.key);
req.respond(
200,
{
"Content-Type": "application/json",
"Last-Modified-Version": nextLibraryVersion
},
JSON.stringify({
successful: {
"0": item1ResponseJSON,
"1": item2ResponseJSON
},
unchanged: {},
failed: {}
})
);
return;
}
}
})
await engine.start();
});
it("should update local objects with remotely saved version after uploading if necessary", function* () {
({ engine, client, caller } = yield setup());

View file

@ -468,6 +468,17 @@ describe("Zotero.Sync.Data.Local", function() {
describe("#getUnsynced()", function () {
// See also: "shouldn't upload external annotations" in syncEngineTest.js
it("shouldn't include external annotations", async function () {
var attachment = await importFileAttachment('test.pdf');
var annotation1 = await createAnnotation('highlight', attachment);
var annotation2 = await createAnnotation('highlight', attachment, { isExternal: true });
var ids = await Zotero.Sync.Data.Local.getUnsynced('item', Zotero.Libraries.userLibraryID);
assert.include(ids, attachment.id);
assert.include(ids, annotation1.id);
});
it("should correct incorrectly nested collections", async function () {
var c1 = await createDataObject('collection');
var c2 = await createDataObject('collection');