ingenta-lens
Version:
A novel way of seeing content.
364 lines (297 loc) • 8.17 kB
JavaScript
"use strict";
var _ = require("underscore");
var util = require("../substance/util");
var Document = require("../substance/document");
// Lens.Article
// -----------------
var Article = function(options) {
options = Article.prepareOptions(options);
Document.call(this, options);
// Index for easy mapping from NLM sourceIds to generated nodeIds
// Needed for resolving figrefs / citationrefs etc.
this.bySourceId = this.addIndex("by_source_id", {
property: "source_id"
});
this.nodeTypes = options.nodeTypes;
// Seed the doc
// --------
if (options.seed === undefined) {
this.create({
id: "document",
type: "document",
guid: options.id, // external global document id
creator: options.creator,
created_at: options.created_at,
views: Article.views, // is views really needed on the instance level
title: "",
abstract: "",
authors: []
});
// Create views on the doc
_.each(Article.views, function(view) {
this.create({
id: view,
"type": "view",
nodes: []
});
}, this);
}
};
Article.Prototype = function() {
this.fromSnapshot = function(data, options) {
return Article.fromSnapshot(data, options);
};
// For a given NLM source id, returns the corresponding node in the document graph
// --------
this.getNodeBySourceId = function(sourceId) {
var nodes = this.bySourceId.get(sourceId);
var nodeId = Object.keys(nodes)[0];
var node = nodes[nodeId];
return node;
};
// Get all headings of the content view
// --------
this.getHeadings = function() {
var headings = _.filter(this.get('content').getNodes(), function(node) {
return node.type === "heading";
});
return headings;
};
this.getTocNodes = function() {
var nodes = _.filter(this.get('content').getNodes(), function(node) {
return node.includeInToc();
});
return nodes;
};
};
Article.prepareOptions = function(options) {
// prepare configuration for
options = options || {};
options.nodeTypes = _.extend(Article.nodeTypes, options.nodeTypes);
options.schema = Article.getSchema(options.nodeTypes);
return options;
};
Article.getSchema = function(nodeTypes) {
var schema = util.deepclone(Document.schema);
schema.id = "lens-article";
schema.version = "2.0.0";
_.each(nodeTypes, function(nodeSpec, key) {
schema.types[key] = nodeSpec.Model.type;
});
return schema;
};
// Factory method
// --------
//
// TODO: Ensure the snapshot doesn't get chronicled
Article.fromSnapshot = function(data, options) {
options = options || {};
options.seed = data;
return new Article(options);
};
// Define available views
// --------
Article.views = ["content", "figures", "citations", "definitions", "info"];
// Register node types
// --------
Article.nodeTypes = require("./nodes");
Article.ViewFactory = require('./view_factory');
// HACK: ResourceView is only used as a mixin for resource view implementations
// There is no specific model for it, thus can not be registered in nodeTypes
Article.ResourceView = require('./resource_view');
// From article definitions generate a nice reference document
// --------
//
var ARTICLE_DOC_SEED = {
"id": "lens_article",
"nodes": {
"document": {
"type": "document",
"id": "document",
"views": [
"content"
],
"title": "The Anatomy of a Lens Article",
"authors": ["contributor_1", "contributor_2", "contributor_3"],
"guid": "lens_article"
},
"content": {
"type": "view",
"id": "content",
"nodes": [
"cover",
]
},
"cover": {
"id": "cover",
"type": "cover"
},
"contributor_1": {
"id": "contributor_1",
"type": "contributor",
"name": "Michael Aufreiter"
},
"contributor_2": {
"id": "contributor_2",
"type": "contributor",
"name": "Ivan Grubisic"
},
"contributor_3": {
"id": "contributor_3",
"type": "contributor",
"name": "Rebecca Close"
}
}
};
Article.describe = function() {
var doc = new Article({seed: ARTICLE_DOC_SEED});
var id = 0;
_.each(Article.nodeTypes, function(nodeType) {
nodeType = nodeType.Model;
// Create a heading for each node type
var headingId = "heading_"+nodeType.type.id;
doc.create({
id: headingId,
type: "heading",
content: nodeType.description.name,
level: 1
});
// Turn remarks and description into an introduction paragraph
var introText = nodeType.description.remarks.join(' ');
var introId = "text_"+nodeType.type.id+"_intro";
doc.create({
id: introId,
type: "text",
content: introText,
});
// Show it in the content view
doc.show("content", [headingId, introId], -1);
// Include property description
// --------
//
doc.create({
id: headingId+"_properties",
type: "text",
content: nodeType.description.name+ " uses the following properties:"
});
doc.show("content", [headingId+"_properties"], -1);
var items = [];
_.each(nodeType.description.properties, function(propertyDescr, key) {
var listItemId = "text_" + (++id);
doc.create({
id: listItemId,
type: "text",
content: key +": " + propertyDescr
});
// Create code annotation for the propertyName
doc.create({
"id": id+"_annotation",
"type": "code",
"path": [listItemId, "content"],
"range":[0, key.length]
});
items.push(listItemId);
});
// Create list
doc.create({
id: headingId+"_property_list",
type: "list",
items: items,
ordered: false
});
// And show it
doc.show("content", [headingId+"_property_list"], -1);
// Include example
// --------
//
doc.create({
id: headingId+"_example",
type: "text",
content: "Here's an example:"
});
doc.create({
id: headingId+"_example_codeblock",
type: "codeblock",
content: JSON.stringify(nodeType.example, null, ' '),
});
doc.show("content", [headingId+"_example", headingId+"_example_codeblock"], -1);
});
return doc;
};
Article.Prototype.prototype = Document.prototype;
Article.prototype = new Article.Prototype();
Article.prototype.constructor = Article;
// Add convenience accessors for builtin document attributes
Object.defineProperties(Article.prototype, {
id: {
get: function () {
return this.get("document").guid;
},
set: function(id) {
this.get("document").guid = id;
}
},
creator: {
get: function () {
return this.get("document").creator;
},
set: function(creator) {
this.get("document").creator = creator;
}
},
created_at: {
get: function () {
return this.get("document").created_at;
},
set: function(created_at) {
this.get("document").created_at = created_at;
}
},
title: {
get: function () {
return this.get("document").title;
},
set: function(title) {
this.get("document").title = title;
}
},
abstract: {
get: function () {
return this.get("document").abstract;
},
set: function(abstract) {
this.get("document").abstract = abstract;
}
},
on_behalf_of: {
get: function () {
return this.get("document").on_behalf_of;
},
set: function(on_behalf_of) {
this.get("document").on_behalf_of = on_behalf_of;
}
},
authors: {
get: function () {
var docNode = this.get("document");
if (docNode.authors) {
return _.map(docNode.authors, function(contributorId) {
return this.get(contributorId);
}, this);
} else {
return "";
}
},
set: function(val) {
var docNode = this.get("document");
docNode.authors = _.clone(val);
}
},
views: {
get: function () {
// Note: returing a copy to avoid inadvertent changes
return this.get("document").views.slice(0);
}
},
});
module.exports = Article;