Annotation support in Item::fromJSON()/toJSON()
And clean up embedded-image handling
This commit is contained in:
parent
3c6ae0e656
commit
e133aab530
2 changed files with 235 additions and 96 deletions
|
@ -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]);
|
||||
}
|
||||
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));
|
||||
// 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;
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
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));
|
||||
})
|
||||
|
||||
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 output synced storage values with .syncedStorageProperties", function* () {
|
||||
var item = new Zotero.Item('attachment');
|
||||
item.attachmentLinkMode = 'imported_file';
|
||||
item.fileName = 'test.txt';
|
||||
yield item.saveTx();
|
||||
|
||||
it("should omit storage values with .skipStorageProperties", function* () {
|
||||
var file = getTestDataDirectory();
|
||||
file.append('test.png');
|
||||
var item = yield Zotero.Attachments.importFromFile({ file });
|
||||
var mtime = new Date().getTime();
|
||||
var md5 = 'b32e33f529942d73bea4ed112310f804';
|
||||
|
||||
item.attachmentSyncedModificationTime = new Date().getTime();
|
||||
item.attachmentSyncedHash = 'b32e33f529942d73bea4ed112310f804';
|
||||
yield item.saveTx({ skipAll: true });
|
||||
item.attachmentSyncedModificationTime = mtime;
|
||||
item.attachmentSyncedHash = md5;
|
||||
yield item.saveTx({ skipAll: true });
|
||||
|
||||
var json = item.toJSON({
|
||||
skipStorageProperties: true
|
||||
var json = item.toJSON({
|
||||
syncedStorageProperties: true
|
||||
});
|
||||
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("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();
|
||||
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();
|
||||
|
||||
assert.isNull(json.mtime);
|
||||
assert.isNull(json.md5);
|
||||
})
|
||||
Zotero.debug(json);
|
||||
|
||||
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");
|
||||
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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue