Annotation support in Item::fromJSON()/toJSON()

And clean up embedded-image handling
This commit is contained in:
Dan Stillman 2020-09-08 04:10:18 -04:00
parent 3c6ae0e656
commit e133aab530
2 changed files with 235 additions and 96 deletions

View file

@ -2222,7 +2222,6 @@ Zotero.Item.prototype.isAttachment = function() {
return Zotero.ItemTypes.getName(this.itemTypeID) == 'attachment';
}
/**
* @return {Promise<Boolean>}
*/
@ -2240,7 +2239,6 @@ Zotero.Item.prototype.isImportedAttachment = function() {
return false;
}
/**
* @return {Promise<Boolean>}
*/
@ -2255,7 +2253,6 @@ Zotero.Item.prototype.isWebAttachment = function() {
return true;
}
/**
* @return {Boolean}
*/
@ -2266,7 +2263,6 @@ Zotero.Item.prototype.isFileAttachment = function() {
return this.attachmentLinkMode != Zotero.Attachments.LINK_MODE_LINKED_URL;
}
/**
* @return {Boolean}
*/
@ -2274,6 +2270,13 @@ Zotero.Item.prototype.isLinkedFileAttachment = function() {
return this.isAttachment() && this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE;
}
/**
* @return {Boolean}
*/
Zotero.Item.prototype.isEmbeddedImageAttachment = function() {
return this.isAttachment() && this.attachmentLinkMode == Zotero.Attachments.LINK_MODE_EMBEDDED_IMAGE;
}
/**
* Returns number of child attachments of item
@ -3513,6 +3516,10 @@ for (let name of ['type', 'text', 'comment', 'color', 'pageLabel', 'sortIndex'])
return;
}
if (name != 'type' && !this._getLatestField('annotationType')) {
throw new Error("annotationType must be set before other annotation properties");
}
switch (name) {
case 'type': {
let currentType = this._getLatestField('annotationType');
@ -4592,12 +4599,15 @@ Zotero.Item.prototype.fromJSON = function (json, options = {}) {
case 'key':
case 'version':
case 'itemType':
case 'note':
case 'noteSchemaVersion':
// Use?
case 'md5':
case 'mtime':
//
// Handled below
//
case 'note':
case 'noteSchemaVersion':
case 'collections':
case 'parentItem':
case 'deleted':
@ -4678,6 +4688,20 @@ Zotero.Item.prototype.fromJSON = function (json, options = {}) {
this.attachmentPath = val;
break;
//
// Annotation fields
//
case 'annotationType':
case 'annotationType':
case 'annotationText':
case 'annotationComment':
case 'annotationColor':
case 'annotationPageLabel':
case 'annotationSortIndex':
case 'annotationPosition':
this[field] = val;
break;
// Item fields
default:
let fieldID = Zotero.ItemFields.getID(field);
@ -4872,6 +4896,8 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
obj.version = this.version;
obj.itemType = Zotero.ItemTypes.getName(this.itemTypeID);
var embeddedImage = this.isEmbeddedImageAttachment();
// Fields
for (let i in this._itemData) {
let val = this.getField(i) + '';
@ -4896,7 +4922,9 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
obj.linkMode = Zotero.Attachments.linkModeToName(linkMode);
obj.contentType = this.attachmentContentType;
obj.charset = this.attachmentCharset;
if (!embeddedImage) {
obj.charset = this.attachmentCharset;
}
if (linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
obj.path = this.attachmentPath;
@ -4929,29 +4957,49 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
}
// Notes and embedded attachment notes
let note = this.note;
if (note !== "" || mode == 'full' || (mode == 'new' && this.isNote())) {
obj.note = note;
obj.noteSchemaVersion = this.noteSchemaVersion || 0;
if (this.isAttachment() || this.isNote()) {
let note = this.note;
if (note !== "" || mode == 'full' || (mode == 'new' && this.isNote())) {
obj.note = note;
obj.noteSchemaVersion = this.noteSchemaVersion || 0;
}
}
if (this.isAnnotation()) {
let type = this.annotationType;
obj.annotationType = type;
if (type == 'highlight') {
obj.annotationText = this.annotationText || '';
}
obj.annotationComment = this.annotationComment || '';
obj.annotationColor = this.annotationColor || '';
obj.annotationPageLabel = this.annotationPageLabel || '';
obj.annotationSortIndex = this.annotationSortIndex || '';
obj.annotationPosition = JSON.stringify(this.annotationPosition) || '';
}
}
// Tags
obj.tags = [];
var tags = this.getTags();
for (let i=0; i<tags.length; i++) {
obj.tags.push(tags[i]);
}
// Collections
if (this.isTopLevelItem()) {
obj.collections = this.getCollections().map(function (id) {
var { libraryID, key } = this.ContainerObjectsClass.getLibraryAndKeyFromID(id);
if (!key) {
throw new Error("Collection " + id + " not found for item " + this.libraryKey);
}
return key;
}.bind(this));
if (!embeddedImage) {
// Tags
obj.tags = [];
var tags = this.getTags();
for (let i=0; i<tags.length; i++) {
obj.tags.push(tags[i]);
}
// Collections
if (this.isTopLevelItem()) {
obj.collections = this.getCollections().map(function (id) {
var { libraryID, key } = this.ContainerObjectsClass.getLibraryAndKeyFromID(id);
if (!key) {
throw new Error("Collection " + id + " not found for item " + this.libraryKey);
}
return key;
}.bind(this));
}
// Relations
obj.relations = this.getRelations();
}
// My Publications
@ -4961,9 +5009,6 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
obj.inPublications = this._inPublications;
}
// Relations
obj.relations = this.getRelations();
if (obj.accessDate) obj.accessDate = Zotero.Date.sqlToISO8601(obj.accessDate);
if (this.dateAdded) {
@ -4975,6 +5020,10 @@ Zotero.Item.prototype.toJSON = function (options = {}) {
var json = this._postToJSON(env);
if (this.isAnnotation()) {
delete json.relations;
}
// TODO: Remove once we stop clearing props from the cached JSON in patch mode
if (options.skipStorageProperties) {
delete json.md5;

View file

@ -1670,82 +1670,140 @@ describe("Zotero.Item", function () {
assert.isUndefined(json.numPages);
})
it.skip("should output attachment fields from file", function* () {
var file = getTestDataDirectory();
file.append('test.png');
var item = yield Zotero.Attachments.importFromFile({ file });
describe("Attachments", function () {
it.skip("should output attachment fields from file", function* () {
var file = getTestDataDirectory();
file.append('test.png');
var item = yield Zotero.Attachments.importFromFile({ file });
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, new Date().getTime()
);
yield Zotero.Sync.Storage.Local.setSyncedHash(
item.id, 'b32e33f529942d73bea4ed112310f804'
);
});
var json = item.toJSON();
assert.equal(json.linkMode, 'imported_file');
assert.equal(json.filename, 'test.png');
assert.isUndefined(json.path);
assert.equal(json.mtime, (yield item.attachmentModificationTime));
assert.equal(json.md5, (yield item.attachmentHash));
})
yield Zotero.DB.executeTransaction(function* () {
yield Zotero.Sync.Storage.Local.setSyncedModificationTime(
item.id, new Date().getTime()
);
yield Zotero.Sync.Storage.Local.setSyncedHash(
item.id, 'b32e33f529942d73bea4ed112310f804'
);
it("should omit storage values with .skipStorageProperties", function* () {
var file = getTestDataDirectory();
file.append('test.png');
var item = yield Zotero.Attachments.importFromFile({ file });
item.attachmentSyncedModificationTime = new Date().getTime();
item.attachmentSyncedHash = 'b32e33f529942d73bea4ed112310f804';
yield item.saveTx({ skipAll: true });
var json = item.toJSON({
skipStorageProperties: true
});
assert.isUndefined(json.mtime);
assert.isUndefined(json.md5);
});
var json = item.toJSON();
assert.equal(json.linkMode, 'imported_file');
assert.equal(json.filename, 'test.png');
assert.isUndefined(json.path);
assert.equal(json.mtime, (yield item.attachmentModificationTime));
assert.equal(json.md5, (yield item.attachmentHash));
})
it("should omit storage values with .skipStorageProperties", function* () {
var file = getTestDataDirectory();
file.append('test.png');
var item = yield Zotero.Attachments.importFromFile({ file });
it("should output synced storage values with .syncedStorageProperties", function* () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'imported_file';
item.fileName = 'test.txt';
yield item.saveTx();
var mtime = new Date().getTime();
var md5 = 'b32e33f529942d73bea4ed112310f804';
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = md5;
yield item.saveTx({ skipAll: true });
var json = item.toJSON({
syncedStorageProperties: true
});
assert.equal(json.mtime, mtime);
assert.equal(json.md5, md5);
})
item.attachmentSyncedModificationTime = new Date().getTime();
item.attachmentSyncedHash = 'b32e33f529942d73bea4ed112310f804';
yield item.saveTx({ skipAll: true });
it.skip("should output unset storage properties as null", function* () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'imported_file';
item.fileName = 'test.txt';
var id = yield item.saveTx();
var json = item.toJSON();
assert.isNull(json.mtime);
assert.isNull(json.md5);
})
var json = item.toJSON({
skipStorageProperties: true
it("shouldn't include filename or path for linked_url attachments", function* () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'linked_url';
item.url = "https://www.zotero.org/";
var json = item.toJSON();
assert.notProperty(json, "filename");
assert.notProperty(json, "path");
});
it("shouldn't include various properties on embedded-image attachments", async function () {
var item = await createDataObject('item', { itemType: 'note' });
var attachment = await createEmbeddedImage(item);
var json = attachment.toJSON();
assert.notProperty(json, 'title');
assert.notProperty(json, 'url');
assert.notProperty(json, 'accessDate');
assert.notProperty(json, 'tags');
assert.notProperty(json, 'collections');
assert.notProperty(json, 'relations');
assert.notProperty(json, 'note');
assert.notProperty(json, 'charset');
assert.notProperty(json, 'path');
});
assert.isUndefined(json.mtime);
assert.isUndefined(json.md5);
});
it("should output synced storage values with .syncedStorageProperties", function* () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'imported_file';
item.fileName = 'test.txt';
yield item.saveTx();
describe("Annotations", function () {
var attachment;
var mtime = new Date().getTime();
var md5 = 'b32e33f529942d73bea4ed112310f804';
item.attachmentSyncedModificationTime = mtime;
item.attachmentSyncedHash = md5;
yield item.saveTx({ skipAll: true });
var json = item.toJSON({
syncedStorageProperties: true
before(async function () {
attachment = await importFileAttachment('test.pdf');
});
assert.equal(json.mtime, mtime);
assert.equal(json.md5, md5);
})
it.skip("should output unset storage properties as null", function* () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'imported_file';
item.fileName = 'test.txt';
var id = yield item.saveTx();
var json = item.toJSON();
assert.isNull(json.mtime);
assert.isNull(json.md5);
})
it("shouldn't include filename or path for linked_url attachments", function* () {
var item = new Zotero.Item('attachment');
item.attachmentLinkMode = 'linked_url';
item.url = "https://www.zotero.org/";
var json = item.toJSON();
assert.notProperty(json, "filename");
assert.notProperty(json, "path");
it("should output highlight annotation", async function () {
var item = createUnsavedDataObject(
'item', { itemType: 'annotation', parentKey: attachment.key }
);
item.annotationType = 'highlight';
item.annotationText = "This is an <b>extracted</b> text with rich-text\nAnd a new line";
item.annotationComment = "This is a comment with <i>rich-text</i>\nAnd a new line";
item.annotationColor = "#ffec00";
item.annotationPageLabel = "15";
item.annotationSortIndex = "00015|002431|00000.000";
item.annotationPosition = {
"pageIndex": 1,
"rects": [
[231.284, 402.126, 293.107, 410.142],
[54.222, 392.164, 293.107, 400.18],
[54.222, 382.201, 293.107, 390.217],
[54.222, 372.238, 293.107, 380.254],
[54.222, 362.276, 273.955, 370.292]
]
};
var json = item.toJSON();
Zotero.debug(json);
for (let prop of ['Type', 'Text', 'Comment', 'Color', 'PageLabel', 'SortIndex']) {
let name = 'annotation' + prop;
assert.propertyVal(json, name, item[name]);
}
assert.deepEqual(JSON.parse(json.annotationPosition), item.annotationPosition);
assert.notProperty(json, 'collections');
assert.notProperty(json, 'relations');
});
});
it("should include inPublications=true for items in My Publications", function* () {
@ -2324,14 +2382,46 @@ describe("Zotero.Item", function () {
assert.equal(item.getField("bookTitle"), "Publication Title");
});
it("should set noteSchemaField", function () {
it("should set note and noteSchemaField", function () {
var item = new Zotero.Item;
item.fromJSON({
itemType: "note",
note: "<p>Foo</p>",
noteSchemaVersion: 3
});
assert.equal(item.note, "<p>Foo</p>");
assert.equal(item.noteSchemaVersion, 3);
});
it("should import annotation fields", function () {
var item = new Zotero.Item();
var json = {
itemType: "annotation",
annotationType: 'highlight',
annotationText: "This is highlighted text.",
annotationComment: "This is a comment with <i>rich-text</i>\nAnd a new line",
annotationSortIndex: '00015|002431|00000.000',
annotationPosition: JSON.stringify({
pageIndex: 123,
rects: [
[314.4, 412.8, 556.2, 609.6]
]
}),
tags: [
{
tag: "tagA"
}
]
};
item.fromJSON(json, { strict: true });
for (let i in json) {
if (i == 'tags') {
assert.deepEqual(item.getTags(), json[i]);
}
else {
assert.equal(item[i], json[i]);
}
}
});
});
});