diff --git a/README.md b/README.md index 2fe1a976..e6af288f 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ **Lens** provides a novel way of looking at content on the web. It is designed to make life easier for researchers, reviewers, authors and readers. -- **Read the [announcement](http://elifesciences.org/elife-news/lens)** -- **Watch the [introduction video](http://vimeo.com/67254579).** -- **See Lens in [action](http://lens.elifesciences.org/00778)** +- **Read the [announcement](https://elifesciences.org/elife-news/lens)** +- **Watch the [introduction video](https://vimeo.com/67254579).** +- **See Lens in [action](https://lens.elifesciences.org/00778)** ## Using Lens @@ -29,7 +29,7 @@ However, now let's look into developing our own extensions. ### Prerequisites -For Lens development, you need to have Node.js >=0.10.x installed. +For Lens development, you need to have Node.js >=10.x installed. You need to repeat that install step whenever you updated the screwdriver repo. @@ -37,25 +37,37 @@ You need to repeat that install step whenever you updated the screwdriver repo. 1. Clone the `lens-starter` repository - ```bash - $ git clone https://github.com/elifesciences/lens-starter.git - ``` +```bash + git clone https://github.com/elifesciences/lens-starter.git + cd lens-starter +``` + +2. Configure System -2. Fetch dependencies +As stated above, you'll need version 10.x of Node installed, and you'll also need version 2.7.x of Python available. You can use [nvm](https://github.com/nvm-sh/nvm) to manage which version of node to use on a per-project basis, and [PyEnv](https://github.com/pyenv/pyenv) to do the same for Python. With both of these tools setup, you can... - ```bash - $ cd lens-starter - $ npm install - ``` +```bash +echo "lts/dubnium" > .nvmrc +nvm install +nvm use +echo "2.7.17" > .python-version +pyenv install +pvenv local +``` -3. Run the server +3. Fetch dependencies - ```bash - ~/projects/lens-starter $ node server - Lens running on port 4001 - http://127.0.0.1:4001/ - ``` +```bash +npm install +``` + +4. Run the server + +```bash +npm start +``` +Then navigate to http://127.0.0.1:4001/ in your web browser. ### Converter @@ -94,7 +106,7 @@ ElifeConverter.Prototype = function() { return [baseURL, node.url].join(''); } else { node.url = [ - "http://cdn.elifesciences.org/elife-articles/", + "https://cdn.elifesciences.org/elife-articles/", state.doc.id, "/suppl/", node.url @@ -203,7 +215,7 @@ Lens can easily be extended with a customized panel. It can be used to show addi - Pull in metrics (click count, number of articles citing that article etc.) - Retrieve related articles dynamically (e.g. important ones that reference the existing one) -For demonstration we will look at the implementation of a simple Altmetrics panel. It will pull data asynchronously from the Altmetrics API (http://api.altmetric.com/v1/doi/10.7554/eLife.00005) and render the information in Lens. +For demonstration we will look at the implementation of a simple Altmetrics panel. It will pull data asynchronously from the Altmetrics API (https://api.altmetric.com/v1/doi/10.7554/eLife.00005) and render the information in Lens. #### Panel Definition @@ -241,7 +253,7 @@ AltmetricsController.Prototype = function() { var doi = this.document.get('publication_info').doi; $.ajax({ - url: "http://api.altmetric.com/v1/doi/"+doi, + url: "https://api.altmetric.com/v1/doi/"+doi, dataType: "json", }).done(function(res) { cb(null, res); @@ -335,9 +347,9 @@ Mobile support has been removed with Lens 2.0 to reduce technical debt and itera ## Credits -Lens was developed in collaboration between [UC Berkeley](http://bioegrad.berkeley.edu/) graduate student [Ivan Grubisic](http://www.linkedin.com/pub/ivan-grubisic/26/353/739) and [eLife](http://elifesciences.org). The team of [Substance](http://substance.io) is helping with the technical execution. +Lens was developed in collaboration between [UC Berkeley](http://bioegrad.berkeley.edu/) graduate student [Ivan Grubisic](https://www.linkedin.com/pub/ivan-grubisic/26/353/739) and [eLife](https://elifesciences.org). The team of [Substance](http://substance.io) is helping with the technical execution. -Substantial contributions were made by [HighWire](highwire.org), which launched Lens for a number of science journals in fall 2014 (The Journal of Biological Chemistry, The Plant Cell, Journal of Lipid Research, mBio®, and more). [The American Mathematical Society (AMS)](http://ams.org/) made Lens ready for advanced rendering of math articles. +Substantial contributions were made by [HighWire](http://highwire.org), which launched Lens for a number of science journals in fall 2014 (The Journal of Biological Chemistry, The Plant Cell, Journal of Lipid Research, mBio®, and more). [The American Mathematical Society (AMS)](http://ams.org/) made Lens ready for advanced rendering of math articles. Thanks go to the following people, who made Lens possible: diff --git a/article/README.md b/article/README.md index 943fe3c1..1e2bec7d 100644 --- a/article/README.md +++ b/article/README.md @@ -1,7 +1,7 @@ Lens Article ===== -The Lens Article Format is an implementation the [Substance Document Model](http://github.com/substance/document) dedicated to scientific content. It features basic content types such as paragraphs, headings, and various figure types such as images, tables and videos complete with captions and cross-references. +The Lens Article Format is an implementation the [Substance Document Model](https://github.com/substance/document) dedicated to scientific content. It features basic content types such as paragraphs, headings, and various figure types such as images, tables and videos complete with captions and cross-references. The document defintions can be extended easily, so you can either create your own flavour or contribute to the Lens Article Format directly. @@ -9,4 +9,4 @@ The document defintions can be extended easily, so you can either create your ow - XML-based formats such as NML are hard to consume by webclients - Strict separation of content and style. Existing formats target print, and thus contain style information, which makes them hard to process by computer programs -- The greatest advantage of Lens Articles is that any of them can be viewed in [Lens](http://github.com/elifesciences/lens), a modern web-based interface for consuming science content. +- The greatest advantage of Lens Articles is that any of them can be viewed in [Lens](https://github.com/elifesciences/lens), a modern web-based interface for consuming science content. diff --git a/article/article_util.js b/article/article_util.js index 8b40e272..041076f3 100644 --- a/article/article_util.js +++ b/article/article_util.js @@ -19,9 +19,9 @@ util.formatDate = function (pubDate) { var parts = pubDate.split("-"); if (parts.length >= 3) { // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]]) - // Note: months are 0-based + // Note: months are 0-based, which are stripped using a regexp var localDate = new Date(parts[0], parts[1]-1, parts[2]); - return localDate.toUTCString().slice(0, 16); + return localDate.toDateString().slice(4, 16).replace(/\b0+/g, '') } else if (parts.length === 2) { var month = parts[1].replace(/^0/, ""); var year = parts[0]; diff --git a/article/nodes/_affiliation/affiliation.js b/article/nodes/_affiliation/affiliation.js index 9fc038ce..bab60604 100644 --- a/article/nodes/_affiliation/affiliation.js +++ b/article/nodes/_affiliation/affiliation.js @@ -15,7 +15,8 @@ Affiliation.type = { "country": "string", "department": "string", "institution": "string", - "label": "string" + "label": "string", + "specific_use": "string" } }; diff --git a/article/nodes/citation/citation.js b/article/nodes/citation/citation.js index 47aa1c64..60b943de 100644 --- a/article/nodes/citation/citation.js +++ b/article/nodes/citation/citation.js @@ -90,7 +90,7 @@ Citation.example = { "citation_urls": [ { "name": "PubMed", - "url": "http://www.ncbi.nlm.nih.gov/pubmed/19606141" + "url": "https://www.ncbi.nlm.nih.gov/pubmed/19606141" } ] }; diff --git a/article/nodes/composite/composite_view.js b/article/nodes/composite/composite_view.js index cb4cb8d1..9fbb9eea 100644 --- a/article/nodes/composite/composite_view.js +++ b/article/nodes/composite/composite_view.js @@ -16,10 +16,6 @@ CompositeView.Prototype = function() { // ============================= // - // Render Markup - // -------- - // - this.render = function() { NodeView.prototype.render.call(this); diff --git a/article/nodes/contributor/contributor.css b/article/nodes/contributor/contributor.css index 5ff11687..d6c66b49 100644 --- a/article/nodes/contributor/contributor.css +++ b/article/nodes/contributor/contributor.css @@ -1,5 +1,5 @@ /* -Contributor +Contributor --------------------------------------- */ .lens-article .resources .content-node.contributor .resource-header .name { @@ -9,16 +9,16 @@ Contributor } .lens-article .content-node.contributor .content { - + } .lens-article .content-node.contributor .affiliation { - margin-top: 10px; - font-size: 14px; + margin-top: 12px; + margin-bottom: 12px; } .lens-article .content-node.contributor .contributor-bio { - padding-top: 30px; + padding-top: 30px; } .lens-article .content-node.contributor .contributor-bio .bio { @@ -44,8 +44,10 @@ Contributor margin-bottom: 20px; } -.lens-article .content-node.contributor .label { - font-size: 14px; - margin-top: 20px; - color: #999; -} \ No newline at end of file +.lens-article .content-node.contributor .contrib-label { + font-weight: 600; +} + +.lens-article .content-node.contributor .contrib-data { + margin-bottom: 12px; +} diff --git a/article/nodes/contributor/contributor_view.js b/article/nodes/contributor/contributor_view.js index 51ce0da7..ed25daaf 100644 --- a/article/nodes/contributor/contributor_view.js +++ b/article/nodes/contributor/contributor_view.js @@ -35,9 +35,9 @@ ContributorView.Prototype = function() { // ------- if (this.node.role) { - this.content.appendChild($$('.role', {text: this.node.role})); + this.content.appendChild($$('.role', {text: this.node.role})); } - + // Add Affiliations // ------- @@ -57,29 +57,47 @@ ContributorView.Prototype = function() { })); - // Present Address // ------- if (this.node.present_address) { - this.content.appendChild($$('.label', {text: 'Present address'})); - this.content.appendChild($$('.contribution', {text: this.node.present_address})); + this.content.appendChild( + $$('.present-address.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'Present address: '}), + $$('span', {text: this.node.present_address}) + ] + }) + ); } // Contribution // ------- if (this.node.contribution) { - this.content.appendChild($$('.label', {text: 'Contribution'})); - this.content.appendChild($$('.contribution', {text: this.node.contribution})); + this.content.appendChild( + $$('.contribution.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'Contribution: '}), + $$('span', {text: this.node.contribution}) + ] + }) + ); } // Equal contribution // ------- + if (this.node.equal_contrib && this.node.equal_contrib.length > 0) { - this.content.appendChild($$('.label', {text: 'Contributed equally with'})); - this.content.appendChild($$('.equal-contribution', {text: this.node.equal_contrib})); + this.content.appendChild( + $$('.equal-contribution.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'Contributed equally with: '}), + $$('span', {text: this.node.equal_contrib.join(', ')}) + ] + }) + ); } @@ -87,38 +105,51 @@ ContributorView.Prototype = function() { // ------- if (this.node.emails.length > 0) { - this.content.appendChild($$('.label', {text: 'For correspondence'})); - this.content.appendChild($$('.emails', { - children: _.map(this.node.emails, function(email) { - return $$('a', {href: "mailto:"+email, text: email}); + this.content.appendChild( + $$('.emails.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'For correspondence: '}), + $$('span', { + children: _.map(this.node.emails, function(email) { + return $$('a', {href: "mailto:"+email, text: email+' '}); + }) + }) + ] }) - })); + ); } - // Funding // ------- if (this.node.fundings.length > 0) { - this.content.appendChild($$('.label', {text: 'Funding'})); - this.content.appendChild($$('.fundings', { - children: _.map(this.node.fundings, function(funding) { - return $$('.funding', {text: funding}); + this.content.appendChild( + $$('.fundings.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'Funding: '}), + $$('span', { + text: this.node.fundings.join('; ') + }) + ] }) - })); + ); } - // Competing interests // ------- - if (this.node.competing_interests.length > 0) { - this.content.appendChild($$('.label', {text: 'Competing Interests'})); - this.content.appendChild($$('.competing-interests', { - children: _.map(this.node.competing_interests, function(ci) { - return $$('.conflict', {text: ci}); + + if (this.node.competing_interests.length) { + this.content.appendChild( + $$('.competing-interests.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'Competing Interests: '}), + $$('span', { + text: this.node.competing_interests.join(', ') + }) + ] }) - })); + ); } @@ -126,24 +157,34 @@ ContributorView.Prototype = function() { // ------- if (this.node.orcid) { - this.content.appendChild($$('.label', { text: 'ORCID' })); - this.content.appendChild($$('a.orcid', { href: this.node.orcid, text: this.node.orcid })); + this.content.appendChild( + $$('.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'ORCID: '}), + $$('a.orcid', { href: this.node.orcid, text: this.node.orcid }) + ] + }) + ); } + // Group member (in case contributor is a person group) // ------- if (this.node.members.length > 0) { - this.content.appendChild($$('.label', {text: 'Group Members'})); - this.content.appendChild($$('.members', { - children: _.map(this.node.members, function(member) { - return $$('.member', {text: member}); + this.content.appendChild( + $$('.group-members.contrib-data', { + children: [ + $$('span.contrib-label', {text: 'Group Members: '}), + $$('span', { + text: this.node.members.join(', ') + }) + ] }) - })); + ); } - // Contributor Bio // ------- @@ -164,7 +205,6 @@ ContributorView.Prototype = function() { // ------- if (this.node.deceased) { - // this.content.appendChild($$('.label', {text: 'Present address'})); this.content.appendChild($$('.label', {text: "* Deceased"})); } diff --git a/article/nodes/cover/cover.css b/article/nodes/cover/cover.css index 1b0d758c..ab03b997 100644 --- a/article/nodes/cover/cover.css +++ b/article/nodes/cover/cover.css @@ -1,22 +1,5 @@ .lens-article .content-node.cover { - color: rgba(0,0,0,0.8); -} - -.lens-article .content-node.cover .breadcrumbs { - font-size: 14px; - margin-top: 30px; - margin-bottom: 20px; - - /* Prevent from nasty scrollbars that appear when eLife logo is shown */ - overflow: hidden; -} - -.lens-article .content-node.cover .breadcrumbs a { - margin-right: 20px; - display: block; - float: left; - line-height: 40px; - height: 40px; + text-align: center; } .lens-article .content-node.cover .content { @@ -30,16 +13,26 @@ padding-top: 20px; } +.lens-article .content-node.cover .subjects { + +} + .lens-article .content-node.cover .published-on { - margin-top: 50px; - margin-bottom: 20px; - color: #666; + color: #999; +} + +.lens-article .content-node.cover .published-on a { + font-weight: 600; + color: #999; +} + +.lens-article .content-node.cover .published-on a:hover { + color: #0277BD; } .lens-article .content-node.cover .doi { margin-top: 30px; margin-bottom: 20px; - color: #666; font-size: 14px; } @@ -47,6 +40,7 @@ padding-top: 30px; color: #1B6685; overflow: auto; + margin-bottom: 24px; } .lens-article .content-node.cover .content .links { @@ -61,12 +55,12 @@ /* One para per author */ .lens-article .content-node.cover .authors .text { - float: left; + display: inline-block; padding: 0px; margin: 0px; font-size: 17px; margin-right: 10px; - margin-bottom: 8px; + margin-bottom: 0px; } .lens-article .content-node.cover .authors .text.plain { @@ -74,7 +68,6 @@ padding-left: 1px; } - .lens-article .intro { font-size: 13px; background: #FFFEF5; @@ -96,4 +89,4 @@ .lens-article .intro .send-feedback:hover { color: #ff0000; -} \ No newline at end of file +} diff --git a/article/nodes/cover/cover_view.js b/article/nodes/cover/cover_view.js index 02c26d92..49ac45c3 100644 --- a/article/nodes/cover/cover_view.js +++ b/article/nodes/cover/cover_view.js @@ -30,33 +30,28 @@ CoverView.Prototype = function() { var node = this.node; var pubInfo = this.node.document.get('publication_info'); - if (node.breadcrumbs && node.breadcrumbs.length > 0) { - var breadcrumbs = $$('.breadcrumbs', { - children: _.map(node.breadcrumbs, function(bc) { - var html; - if (bc.image) { - html = ''; - } else { - html = bc.name; - } - return $$('a', {href: bc.url, html: html}); - }) - }); - this.content.appendChild(breadcrumbs); - } + // Render Subject(s) if available + // -------------- + // if (pubInfo) { - var pubDate = pubInfo.published_on; - if (pubDate) { - var items = [articleUtil.formatDate(pubDate)]; - if (pubInfo.journal && !node.breadcrumbs) { - items.push(' in '+pubInfo.journal+''); - } + var subjects = pubInfo.subjects; + if (subjects) { + var subjectsEl + if (pubInfo.subject_link) { + subjectsEl = $$('.subjects', { + children: _.map(pubInfo.getSubjectLinks(), function(subject) { + return $$('a', {href: subject.url, text: subject.name}) + }) + }) - this.content.appendChild($$('.published-on', { - html: items.join('') - })); + } else { + subjectsEl = $$('.subjects', { + html: subjects.join(' ') + }) + } + this.content.appendChild(subjectsEl); } } @@ -88,6 +83,28 @@ CoverView.Prototype = function() { this.content.appendChild(authors); + if (pubInfo) { + var pubDate = pubInfo.published_on; + var articleType = pubInfo.article_type; + if (pubDate) { + var items = [articleUtil.formatDate(pubDate)]; + + if (articleType) { + if (pubInfo.article_type_link) { + var linkData = pubInfo.getArticleTypeLink() + items.unshift(''+linkData.name+'') + } else { + items.unshift(articleType) + } + + } + + this.content.appendChild($$('.published-on', { + html: items.join(' ') + })); + } + } + // Render Links // -------------- // diff --git a/article/nodes/cross_reference/cross_reference_view.js b/article/nodes/cross_reference/cross_reference_view.js new file mode 100644 index 00000000..1ea2bf32 --- /dev/null +++ b/article/nodes/cross_reference/cross_reference_view.js @@ -0,0 +1,20 @@ +"use strict"; + +var AnnotationView = require('../annotation/annotation_view'); + +var CrossReferenceView = function(node, viewFactory) { + AnnotationView.call(this, node, viewFactory); + this.$el.addClass('cross-reference'); +}; + +CrossReferenceView.Prototype = function() { + this.createElement = function() { + var el = document.createElement('a'); + el.setAttribute('href', ''); + return el; + }; +}; +CrossReferenceView.Prototype.prototype = AnnotationView.prototype; +CrossReferenceView.prototype = new CrossReferenceView.Prototype(); + +module.exports = CrossReferenceView; diff --git a/article/nodes/cross_reference/index.js b/article/nodes/cross_reference/index.js index 66279861..733cf974 100644 --- a/article/nodes/cross_reference/index.js +++ b/article/nodes/cross_reference/index.js @@ -1,5 +1,5 @@ module.exports = { Model: require('./cross_reference.js'), - View: require('../resource_reference/resource_reference_view.js') + View: require('./cross_reference_view.js') }; diff --git a/article/nodes/custom_annotation/custom_annotation_view.js b/article/nodes/custom_annotation/custom_annotation_view.js index 64304098..e873cd9d 100644 --- a/article/nodes/custom_annotation/custom_annotation_view.js +++ b/article/nodes/custom_annotation/custom_annotation_view.js @@ -8,7 +8,7 @@ CustomAnnotationView.Prototype = function() { this.setClasses = function() { AnnotationView.prototype.setClasses.call(this); - this.$el.addClass(this.node.name); + this.$el.removeClass('custom_annotation').addClass(this.node.name); }; }; diff --git a/article/nodes/footnote/footnote.js b/article/nodes/footnote/footnote.js index 7fd4ac72..1ecd2c67 100644 --- a/article/nodes/footnote/footnote.js +++ b/article/nodes/footnote/footnote.js @@ -3,31 +3,20 @@ var Document = require('../../../substance/document'); var DocumentNode = Document.Node; var Paragraph = require('../paragraph').Model; - +var Composite = Document.Composite; var Footnote = function(node, document) { - Paragraph.call(this, node, document); + Composite.call(this, node, document); }; Footnote.type = { "id": "footnote", "parent": "paragraph", "properties": { - "label": "string" - } -}; - -// This is used for the auto-generated docs -// ----------------- -// - -Footnote.description = { - "name": "Footnote", - "remarks": [ - "A Footnote is basically a Paragraph with a label." - ], - "properties": { - "label": "A string used as label", + "footnoteType": "string", + "specificUse": "string", + "label": "string", + "children": ["array", "string"] } }; @@ -54,6 +43,6 @@ Footnote.Prototype.prototype = Paragraph.prototype; Footnote.prototype = new Footnote.Prototype(); Footnote.prototype.constructor = Footnote; -DocumentNode.defineProperties(Footnote); +DocumentNode.defineProperties(Footnote.prototype, ["children", "label", "footnoteType", "specificUse"]); module.exports = Footnote; diff --git a/article/nodes/footnote/footnote_view.js b/article/nodes/footnote/footnote_view.js index d11367a9..5f025e4e 100644 --- a/article/nodes/footnote/footnote_view.js +++ b/article/nodes/footnote/footnote_view.js @@ -1,31 +1,20 @@ "use strict"; -var ParagraphView = require("../paragraph").View; +var CompositeView = require("../composite/composite_view"); // Substance.Image.View // ========================================================================== var FootnoteView = function(node, viewFactory) { - ParagraphView.call(this, node, viewFactory); + CompositeView.call(this, node, viewFactory); }; FootnoteView.Prototype = function() { - this.render = function() { - ParagraphView.prototype.render.call(this); - - var labelEl = document.createElement('span'); - labelEl.classList.add('label'); - labelEl.innerHTML = this.node.label; - - this.el.insertBefore(labelEl, this.content); - - return this; - }; }; -FootnoteView.Prototype.prototype = ParagraphView.prototype; +FootnoteView.Prototype.prototype = CompositeView.prototype; FootnoteView.prototype = new FootnoteView.Prototype(); module.exports = FootnoteView; diff --git a/article/nodes/footnote_reference/footnote_reference.js b/article/nodes/footnote_reference/footnote_reference.js new file mode 100644 index 00000000..faef8491 --- /dev/null +++ b/article/nodes/footnote_reference/footnote_reference.js @@ -0,0 +1,28 @@ + +var Document = require('../../../substance/document'); +var Annotation = require('../annotation/annotation'); +var ResourceReference = require('../resource_reference/resource_reference'); + +var FootnoteReference = function(node, doc) { + ResourceReference.call(this, node, doc); +}; + +FootnoteReference.type = { + id: "footnote_reference", + parent: "resource_reference", + properties: { + "target": "footnote" + } +}; + +FootnoteReference.Prototype = function() {}; +FootnoteReference.Prototype.prototype = ResourceReference.prototype; +FootnoteReference.prototype = new FootnoteReference.Prototype(); +FootnoteReference.prototype.constructor = FootnoteReference; + +// Do not fragment this annotation +FootnoteReference.fragmentation = Annotation.NEVER; + +Document.Node.defineProperties(FootnoteReference); + +module.exports = FootnoteReference; diff --git a/article/nodes/footnote_reference/footnote_reference_view.js b/article/nodes/footnote_reference/footnote_reference_view.js new file mode 100644 index 00000000..e787ef36 --- /dev/null +++ b/article/nodes/footnote_reference/footnote_reference_view.js @@ -0,0 +1,52 @@ +"use strict"; + +var AnnotationView = require('../annotation/annotation_view'); +var $$ = require('../../../substance/application').$$; + +var FootnoteReferenceView = function(node, viewFactory) { + AnnotationView.call(this, node, viewFactory); + this.$el.addClass('footnote-reference'); + this._expanded = false; +}; + + +FootnoteReferenceView.Prototype = function() { + + this.render = function() { + var footnote = this._getFootnote(); + // this.el.innerHTML = formulaView.render().el.innerHTML; + this.el.innerHTML = ""; + this.toggleEl = $$('a', {href: '#', html: footnote.properties.label}); + + $(this.toggleEl).on('click', this._onToggle.bind(this)); + this.$el.append(this.toggleEl); + this.footnoteView = this._createView(footnote).render(); + // HACK: some use xref with ref-type='fn' which will produce a different view class + this.footnoteView.$el.addClass('footnote'); + if (this.node.properties.generated) { + this.$el.addClass('sm-generated'); + } + this.$el.append(this.footnoteView.el); + }; + + this._onToggle = function(e) { + e.preventDefault(); + this.$el.toggleClass('sm-expanded'); + }; + + this._createView = function(node) { + var view = this.viewFactory.createView(node); + return view; + }; + + this._getFootnote = function() { + var node = this.node.document.get(this.node.target); + return node; + }; +}; + +FootnoteReferenceView.Prototype.prototype = AnnotationView.prototype; +FootnoteReferenceView.prototype = new FootnoteReferenceView.Prototype(); + +module.exports = FootnoteReferenceView; + diff --git a/article/nodes/footnote_reference/index.js b/article/nodes/footnote_reference/index.js new file mode 100644 index 00000000..0043da5a --- /dev/null +++ b/article/nodes/footnote_reference/index.js @@ -0,0 +1,4 @@ +module.exports = { + Model: require('./footnote_reference.js'), + View: require('./footnote_reference_view.js') +}; diff --git a/article/nodes/heading/heading.css b/article/nodes/heading/heading.css index 8269f0fb..de388acc 100644 --- a/article/nodes/heading/heading.css +++ b/article/nodes/heading/heading.css @@ -14,7 +14,6 @@ .content-node.heading { } .content-node.heading .content { - color: rgba(0,0,0,0.8); font-weight: 600; line-height: 40px; } diff --git a/article/nodes/html_table/html_table.css b/article/nodes/html_table/html_table.css index 4240c5f7..ec8b8ad2 100644 --- a/article/nodes/html_table/html_table.css +++ b/article/nodes/html_table/html_table.css @@ -19,8 +19,8 @@ position: relative; border-collapse: collapse; border-spacing: 0; - margin-bottom: 20px; margin: 0 auto; + margin-bottom: 20px; } .lens-article .content-node.html-table thead tr { diff --git a/article/nodes/index.js b/article/nodes/index.js index 6d897b20..38a74a25 100644 --- a/article/nodes/index.js +++ b/article/nodes/index.js @@ -22,6 +22,7 @@ module.exports = { "citation_reference": require("./citation_reference"), "definition_reference": require("./definition_reference"), "cross_reference": require("./cross_reference"), + "footnote_reference": require("./footnote_reference"), "publication_info": require("./publication_info"), /* Annotation'ish content types */ "link": require("./link"), @@ -48,5 +49,5 @@ module.exports = { "codeblock": require("./codeblock"), "affiliation": require("./_affiliation"), "footnote": require("./footnote"), - "quote": require("./quote") + "quote": require("./quote"), }; diff --git a/article/nodes/node/node_view.js b/article/nodes/node/node_view.js index e80ea21b..2c23cab9 100644 --- a/article/nodes/node/node_view.js +++ b/article/nodes/node/node_view.js @@ -58,8 +58,7 @@ NodeView.Prototype = function() { }; this.renderAnnotatedText = function(path, el) { - var property = this.node.document.resolve(path); - var view = TextPropertyView.renderAnnotatedText(this.node.document, property, el, this.viewFactory); + var view = TextPropertyView.renderAnnotatedText(this.node.document, path, el, this.viewFactory); return view; }; diff --git a/article/nodes/paragraph/paragraph.css b/article/nodes/paragraph/paragraph.css index 6ce5306e..2677e100 100644 --- a/article/nodes/paragraph/paragraph.css +++ b/article/nodes/paragraph/paragraph.css @@ -18,7 +18,7 @@ display:block; } -.content-node.paragraph .content-node.text div { +.content-node.paragraph > .content-node.text > div { display:inline; width: auto; } diff --git a/article/nodes/publication_info/publication_info.css b/article/nodes/publication_info/publication_info.css index 13835dac..9d9ef391 100644 --- a/article/nodes/publication_info/publication_info.css +++ b/article/nodes/publication_info/publication_info.css @@ -1,8 +1,8 @@ /* Publication Info */ .lens-article .content-node.publication-info { - font-size: 14px; color: #333; + font-size: 16px; } .lens-article .content-node.publication-info table { @@ -26,30 +26,19 @@ margin-left: 140px; } -.lens-article .content-node.publication-info .dates { - /*font-size: 14px;*/ -} - .article .resources .nodes > .content-node.publication-info > .content { border: none; padding: 20px; } -.article .resources .nodes > .content-node.publication-info .content-node[data-id=articleinfo] { - font-size: 14px; -} - -.article .resources .nodes > .content-node.publication-info .content-node[data-id=articleinfo] .heading.level-3 { - padding-top: 10px; -} - .article .resources .nodes > .content-node.publication-info .content-node[data-id=articleinfo] .heading.level-3 .content { - font-size: 14px; - font-weight: normal; - font-family: "Source Sans Pro"; - color: #999; + font-size: 20px; + margin-top: 12px; + /* The 'content-node paragraph' below has a padding top making the space between the heading and text 22px rather than 12. */ + /* margin-bottom: 12px; */ } .article .resources .nodes > .content-node.publication-info .content-node[data-id=articleinfo] .content-node { padding-top: 10px; } + diff --git a/article/nodes/publication_info/publication_info.js b/article/nodes/publication_info/publication_info.js index b152804d..ab190a84 100644 --- a/article/nodes/publication_info/publication_info.js +++ b/article/nodes/publication_info/publication_info.js @@ -22,7 +22,10 @@ PublicationInfo.type = { "links": ["array", "objects"], "doi": "string", "related_article": "string", - "article_info": "paragraph" + "article_info": "paragraph", + // optional + "subject_link": "string", + "article_type_link": "string" } }; @@ -86,6 +89,22 @@ PublicationInfo.Prototype = function() { return this.document.get("articleinfo"); }; + this.getSubjectLinks = function() { + return this.subjects.map(function(subject) { + return { + name: subject, + url: this.subject_link + '/' + subject.replace(/ /g, '-').toLowerCase() + } + }.bind(this)) + } + + this.getArticleTypeLink = function() { + return { + name: this.article_type, + url: this.article_type_link + '/' + this.article_type.replace(/ /g, '-').toLowerCase() + } + } + }; PublicationInfo.Prototype.prototype = Document.Node.prototype; diff --git a/article/nodes/text/text_property_view.js b/article/nodes/text/text_property_view.js index f790d824..26742e55 100644 --- a/article/nodes/text/text_property_view.js +++ b/article/nodes/text/text_property_view.js @@ -34,7 +34,7 @@ TextPropertyView.Prototype = function() { this.render = function() { this.el.innerHTML = ""; - TextPropertyView.renderAnnotatedText(this.document, this.property, this.el, this.viewFactory); + TextPropertyView.renderAnnotatedText(this.document, this.path, this.el, this.viewFactory); return this; }; @@ -81,10 +81,10 @@ TextPropertyView.Prototype = function() { TextPropertyView.Prototype.prototype = View.prototype; TextPropertyView.prototype = new TextPropertyView.Prototype(); -TextPropertyView.renderAnnotatedText = function(doc, property, el, viewFactory) { +TextPropertyView.renderAnnotatedText = function(doc, path, el, viewFactory) { var fragment = window.document.createDocumentFragment(); - var text = property.get(); - var annotations = doc.getIndex("annotations").get(property.path); + var text = doc.get(path); + var annotations = doc.getIndex("annotations").get(path); // this splits the text and annotations into smaller pieces // which is necessary to generate proper HTML. var annotationViews = []; diff --git a/article/nodes/video/video.js b/article/nodes/video/video.js index 29775c2c..55cc16d6 100644 --- a/article/nodes/video/video.js +++ b/article/nodes/video/video.js @@ -58,10 +58,10 @@ Video.example = { "id": "video_1", "type": "video", "label": "Video 1.", - "url": "http://cdn.elifesciences.org/video/eLifeLensIntro2.mp4", - "url_webm": "http://cdn.elifesciences.org/video/eLifeLensIntro2.webm", - "url_ogv": "http://cdn.elifesciences.org/video/eLifeLensIntro2.ogv", - "poster": "http://cdn.elifesciences.org/video/eLifeLensIntro2.png", + "url": "https://cdn.elifesciences.org/video/eLifeLensIntro2.mp4", + "url_webm": "https://cdn.elifesciences.org/video/eLifeLensIntro2.webm", + "url_ogv": "https://cdn.elifesciences.org/video/eLifeLensIntro2.ogv", + "poster": "https://cdn.elifesciences.org/video/eLifeLensIntro2.png", // "doi": "http://dx.doi.org/10.7554/Fake.doi.003", "caption": "caption_25" }; diff --git a/converter/elife_converter.js b/converter/elife_converter.js index ce9e7b9f..b02e0127 100644 --- a/converter/elife_converter.js +++ b/converter/elife_converter.js @@ -3,7 +3,7 @@ var util = require("../substance/util"); var _ = require("underscore"); -var LensConverter = require('lens/converter'); +var LensConverter = require('./lens_converter'); var ElifeConverter = function(options) { LensConverter.call(this, options); @@ -13,6 +13,12 @@ ElifeConverter.Prototype = function() { var __super__ = LensConverter.prototype; + // Fix to focus on videos from left reading pane to the right figures pane + if (!("video" in __super__._refTypeMapping)) + { + __super__._refTypeMapping["video"] = "figure_reference"; + } + this.test = function(xmlDoc, documentUrl) { var publisherName = xmlDoc.querySelector("publisher-name").textContent; return publisherName === "eLife Sciences Publications, Ltd"; @@ -34,51 +40,23 @@ ElifeConverter.Prototype = function() { var doc = state.doc; var heading, body; - // Decision letter (if available) - // ----------- - - var articleCommentary = article.querySelector("#SA1"); - if (articleCommentary) { - heading = { - id: state.nextId("heading"), - type: "heading", - level: 1, - content: "Article Commentary" - }; - doc.create(heading); - nodes.push(heading); - - heading = { - id: state.nextId("heading"), - type: "heading", - level: 2, - content: "Decision letter" - }; - doc.create(heading); - nodes.push(heading); - - body = articleCommentary.querySelector("body"); - nodes = nodes.concat(this.bodyNodes(state, util.dom.getChildren(body))); - } - - // Author response + // Decision letter and author response sub articles (if available) // ----------- - - var authorResponse = article.querySelector("#SA2"); - if (authorResponse) { - - heading = { - id: state.nextId("heading"), - type: "heading", - level: 2, - content: "Author response" - }; - doc.create(heading); - nodes.push(heading); - - body = authorResponse.querySelector("body"); - nodes = nodes.concat(this.bodyNodes(state, util.dom.getChildren(body))); - } + var subArticles = article.querySelectorAll("sub-article"); + _.each(subArticles, function(subArticle) { + var subArticleTitle = subArticle.querySelector("title-group article-title").textContent; + heading = { + id: state.nextId("heading"), + type: "heading", + level: 1, + content: subArticleTitle + }; + doc.create(heading); + nodes.push(heading); + + body = subArticle.querySelector("body"); + nodes = nodes.concat(this.bodyNodes(state, util.dom.getChildren(body))); + }.bind(this)); // Show them off // ---------- @@ -88,23 +66,6 @@ ElifeConverter.Prototype = function() { } }; - this.enhanceCover = function(state, node, element) { - var category; - var dispChannel = element.querySelector("subj-group[subj-group-type=display-channel] subject").textContent; - try { - category = element.querySelector("subj-group[subj-group-type=heading] subject").textContent; - } catch(err) { - category = null; - } - - node.breadcrumbs = [ - { name: "eLife", url: "http://elifesciences.org/", image: "http://lens.elifesciences.org/lens-elife/styles/elife.png" }, - { name: dispChannel, url: "http://elifesciences.org/category/"+dispChannel.replace(/ /g, '-').toLowerCase() }, - ]; - - if (category) node.breadcrumbs.push( { name: category, url: "http://elifesciences.org/category/"+category.replace(/ /g, '-').toLowerCase() } ); - }; - // Resolves figure url // -------- // @@ -116,7 +77,7 @@ ElifeConverter.Prototype = function() { }; - // Example url to JPG: http://cdn.elifesciences.org/elife-articles/00768/svg/elife00768f001.jpg + // Example url to JPG: https://cdn.elifesciences.org/elife-articles/00768/svg/elife00768f001.jpg this.resolveURL = function(state, url) { // Use absolute URL if (url.match(/http:\/\//)) return url; @@ -135,9 +96,9 @@ ElifeConverter.Prototype = function() { } else if (!(url.match(/\.gif$/g))) { url = url+'.jpg' } - + return [ - "http://publishing-cdn.elifesciences.org/", + "https://cdn.elifesciences.org/articles/", state.doc.id, "/", url @@ -151,7 +112,7 @@ ElifeConverter.Prototype = function() { return [baseURL, node.url].join(''); } else { node.url = [ - "http://publishing-cdn.elifesciences.org/", + "https://cdn.elifesciences.org/articles/", state.doc.id, "/", node.url @@ -219,13 +180,13 @@ ElifeConverter.Prototype = function() { if (pdfURI) { var pdfLink = [ - "http://publishing-cdn.elifesciences.org/", + "https://cdn.elifesciences.org/articles/", state.doc.id, "/", pdfURI ? pdfURI.getAttribute("xlink:href") : "#" ].join(''); } - + // Version number from the PDF href, default to 1 var match = null; if (pdfURI) { @@ -247,7 +208,7 @@ ElifeConverter.Prototype = function() { } links.push({ - url: "https://s3.amazonaws.com/elife-publishing-cdn/"+state.doc.id+"/elife-"+state.doc.id+"-v"+version+".xml", + url: "https://cdn.elifesciences.org/articles/"+state.doc.id+"/elife-"+state.doc.id+"-v"+version+".xml", name: "Source XML", type: "xml" }); @@ -266,6 +227,9 @@ ElifeConverter.Prototype = function() { publicationInfo.article_type = articleType ? articleType.textContent : ""; publicationInfo.links = links; + publicationInfo.subject_link = 'https://elifesciences.org/category' + publicationInfo.article_type_link = 'https://elifesciences.org/category' + if (publicationInfo.related_article) publicationInfo.related_article = "http://dx.doi.org/" + publicationInfo.related_article; }; @@ -275,7 +239,7 @@ ElifeConverter.Prototype = function() { return [baseURL, node.url].join(''); } else { node.url = [ - "http://publishing-cdn.elifesciences.org/", + "https://cdn.elifesciences.org/articles/", state.doc.id, "/", node.url @@ -283,16 +247,70 @@ ElifeConverter.Prototype = function() { } }; + this.enhanceTable = function(state, tableNode, tableWrap) { + this.mapCitations(state); + tableNode.content = this.enhanceHTML(tableNode.content); + } + + this.mapCitations = function (state) + { + // Map citation id to their citation reference for use in HTML replacements + this.citationCitationReferenceMap = []; + var citationSourceIdMap = []; + var doc = state.doc; + _.each(doc.nodes, function(node) { + if(node.type == "citation") + { + citationSourceIdMap[node.source_id] = node.id; + } + }.bind(this)); + var annotations = state.annotations; + _.each(annotations, function(anno) { + if(anno.type == "citation_reference") + { + this.citationCitationReferenceMap[anno.target] = anno.id; + } + }.bind(this)); + } + + this.enhanceHTML = function(html) { + html = html.replace(/<(\/)?bold>/g, "<$1strong>"); + html = html.replace(/<(\/)?italic>/g, "<$1em>"); + // Table colours + html = html.replace(/]*)>/g, ""); + // named-content span + html = html.replace(/]*)>([^<]*)<\/named-content>/g, "$3"); + // Hacky way to convert references inside tables + if(this.citationCitationReferenceMap) + { + var xref_pattern = /([^<]*)<\/xref>/g; + var matches = html.match(xref_pattern); + _.each(matches, function(match) { + var rid = match.replace(xref_pattern, "$1"); + var content = match.replace(xref_pattern, "$2"); + var citation_reference = this.citationCitationReferenceMap[rid]; + var replace_tag = ''+content+''; + html = html.replace(match, replace_tag); + }.bind(this)); + } + return html; + }; + this.enhanceVideo = function(state, node, element) { + var id = element.getAttribute("id"); var href = element.getAttribute("xlink:href").split("."); var name = href[0]; - node.url = "http://api.elifesciences.org/v2/articles/"+state.doc.id+"/media/file/"+name+".mp4"; - node.url_ogv = "http://api.elifesciences.org/v2/articles/"+state.doc.id+"/media/file//"+name+".ogv"; - node.url_webm = "http://api.elifesciences.org/v2/articles/"+state.doc.id+"/media/file//"+name+".webm"; - node.poster = "http://api.elifesciences.org/v2/articles/"+state.doc.id+"/media/file/"+name+".jpg"; + + // set attributes from previously populated data in video_data + if(typeof video_data !== 'undefined' && id in video_data) { + node.url = video_data[id]['mp4_href']; + node.url_ogv = video_data[id]['ogv_href']; + node.url_webm = video_data[id]['webm_href']; + node.poster = video_data[id]['jpg_href']; + } }; - // Example url to JPG: http://cdn.elifesciences.org/elife-articles/00768/svg/elife00768f001.jpg + // Example url to JPG: https://cdn.elifesciences.org/elife-articles/00768/svg/elife00768f001.jpg this.resolveURL = function(state, url) { // Use absolute URL if (url.match(/http:\/\//)) return url; @@ -304,16 +322,16 @@ ElifeConverter.Prototype = function() { return [baseURL, url].join(''); } else { // Use special URL resolving for production articles - + // File extension support if (url.match(/\.tif$/g)) { url = url.replace(/\.tif$/g, '.jpg') } else if (!(url.match(/\.gif$/g))) { url = url+'.jpg' } - + return [ - "http://publishing-cdn.elifesciences.org/", + "https://cdn.elifesciences.org/articles/", state.doc.id, "/", url @@ -334,15 +352,29 @@ ElifeConverter.Prototype = function() { } }; + this.back = function(state, back) { + var appGroups = back.querySelectorAll('app-group'); + + if (appGroups && appGroups.length > 0) { + _.each(appGroups, function(appGroup) { + this.appGroup(state, appGroup); + }.bind(this)); + } + }; + this.showNode = function(state, node) { switch(node.type) { // Boxes go into the figures view if these conditions are met // 1. box has a label (e.g. elife 00288) + // Disable it July 2017: cross reference links do not work if box reference + // in the content panel tries to link to the figures panel + /* case "box": if (node.label) { state.doc.show("figures", node.id); } break; + */ default: __super__.showNode.apply(this, arguments); } diff --git a/converter/lens_converter.js b/converter/lens_converter.js index af23dcd4..0e1138c1 100644 --- a/converter/lens_converter.js +++ b/converter/lens_converter.js @@ -19,6 +19,9 @@ NlmToLensConverter.Prototype = function() { "sub": "subscript", "sup": "superscript", "sc": "custom_annotation", + "roman": "custom_annotation", + "sans-serif": "custom_annotation", + "styled-content": "custom_annotation", "underline": "underline", "ext-link": "link", "xref": "", @@ -28,6 +31,10 @@ NlmToLensConverter.Prototype = function() { "uri": "link" }; + this._inlineNodeTypes = { + "fn": true, + }; + // mapping from xref.refType to node type this._refTypeMapping = { "bibr": "citation_reference", @@ -36,6 +43,7 @@ NlmToLensConverter.Prototype = function() { "supplementary-material": "figure_reference", "other": "figure_reference", "list": "definition_reference", + "fn": "footnote_reference", }; // mapping of contrib type to human readable names @@ -73,6 +81,10 @@ NlmToLensConverter.Prototype = function() { return this._annotationTypes[type] !== undefined; }; + this.isInlineNode = function(type) { + return this._inlineNodeTypes[type] !== undefined; + }; + this.isParagraphish = function(node) { for (var i = 0; i < node.childNodes.length; i++) { var el = node.childNodes[i]; @@ -99,7 +111,7 @@ NlmToLensConverter.Prototype = function() { if (givenNamesEl) names.push(givenNamesEl.textContent); if (surnameEl) names.push(surnameEl.textContent); - if (suffix) return [names.join(" "), suffix.textContent].join(", "); + if (suffix && suffix.textContent.trim() !== "") return [names.join(" "), suffix.textContent].join(", "); return names.join(" "); }; @@ -176,7 +188,7 @@ NlmToLensConverter.Prototype = function() { // Overridden to create a Lens Article instance this.createDocument = function() { - + var doc = new Article(); return doc; }; @@ -225,6 +237,9 @@ NlmToLensConverter.Prototype = function() { // Article information var articleInfo = this.extractArticleInfo(state, article); + // Funding information + var fundingInfo = this.extractFundingInfo(state, article); + // Create PublicationInfo node // --------------- @@ -236,6 +251,7 @@ NlmToLensConverter.Prototype = function() { "related_article": relatedArticle ? relatedArticle.getAttribute("xlink:href") : "", "doi": articleDOI ? articleDOI.textContent : "", "article_info": articleInfo.id, + "funding_info": fundingInfo, // TODO: 'article_type' should not be optional; we need to find a good default implementation "article_type": "", // Optional fields not covered by the default implementation @@ -288,7 +304,7 @@ NlmToLensConverter.Prototype = function() { nodes = nodes.concat(this.extractAcknowledgements(state, article)); // License and Copyright nodes = nodes.concat(this.extractCopyrightAndLicense(state, article)); - // Notes (Footnotes + Author notes) + // Notes ( elements) nodes = nodes.concat(this.extractNotes(state, article)); articleInfo.children = nodes; @@ -297,6 +313,18 @@ NlmToLensConverter.Prototype = function() { return articleInfo; }; + this.extractFundingInfo = function(state, article) { + var fundingInfo = []; + var fundingStatementEls = article.querySelectorAll("funding-statement"); + if (fundingStatementEls.length > 0){ + for (var i = 0; i < fundingStatementEls.length; i++) { + fundingInfo.push(this.annotatedText(state, fundingStatementEls[i], ["publication_info", "funding_info", i])); + } + } + + return fundingInfo; + }; + // Get reviewing editor // -------------- // TODO: it is possible to have multiple editors. This does only show the first one @@ -403,6 +431,7 @@ NlmToLensConverter.Prototype = function() { "level" : 3, "content" : title ? this.capitalized(title.textContent.toLowerCase(), "all") : "Acknowledgements" }; + doc.create(header); nodes.push(header.id); @@ -410,6 +439,7 @@ NlmToLensConverter.Prototype = function() { var pars = this.bodyNodes(state, util.dom.getChildren(ack), { ignore: ["title"] }); + _.each(pars, function(par) { nodes.push(par.id); }); @@ -420,14 +450,12 @@ NlmToLensConverter.Prototype = function() { }; // - // Extracts footnotes that should be shown in article info + // Extracts notes that should be shown in article info // ------------------------------------------ // - // Needs to be overwritten in configuration - - this.extractNotes = function(/*state, article*/) { - var nodes = []; - return nodes; + this.extractNotes = function(state, article) { + /* jshint unused:false */ + return []; }; // Can be overridden by custom converter to ignore values. @@ -439,7 +467,7 @@ NlmToLensConverter.Prototype = function() { var nodeIds = []; var doc = state.doc; - var customMetaEls = article.querySelectorAll('article-meta-group custom-meta'); + var customMetaEls = article.querySelectorAll('article-meta custom-meta'); if (customMetaEls.length === 0) return nodeIds; for (var i = 0; i < customMetaEls.length; i++) { @@ -595,11 +623,19 @@ NlmToLensConverter.Prototype = function() { this.affiliation = function(state, aff) { var doc = state.doc; - var institution = aff.querySelector("institution"); + var department = aff.querySelector("institution[content-type=dept]"); + if (department) { + var institution = aff.querySelector("institution:not([content-type=dept])"); + } else { + var department = aff.querySelector("addr-line named-content[content-type=department]"); + var institution = aff.querySelector("institution"); + } var country = aff.querySelector("country"); var label = aff.querySelector("label"); - var department = aff.querySelector("addr-line named-content[content-type=department]"); + var city = aff.querySelector("addr-line named-content[content-type=city]"); + // TODO: there are a lot more elements which can have this. + var specific_use = aff.getAttribute('specific-use'); // TODO: this is a potential place for implementing a catch-bin // For that, iterate all children elements and fill into properties as needed or add content to the catch-bin @@ -612,7 +648,8 @@ NlmToLensConverter.Prototype = function() { department: department ? department.textContent : null, city: city ? city.textContent : null, institution: institution ? institution.textContent : null, - country: country ? country.textContent: null + country: country ? country.textContent: null, + specific_use: specific_use || null }; doc.create(affiliationNode); }; @@ -771,7 +808,20 @@ NlmToLensConverter.Prototype = function() { // // // and we only want to display the first text node, excluding the funder id - var fundingSourceName = fundingSource.childNodes[0].textContent; + // or this + // + // They can also look like this + // + // + // + // http://dx.doi.org/10.13039/100005156 + // Alexander von Humboldt-Stiftung + // + // + // Then we take the institution element + + var institution = fundingSource.querySelector('institution') + var fundingSourceName = institution ? institution.textContent : fundingSource.childNodes[0].textContent; contribNode.fundings.push([fundingSourceName, awardId].join('')); } else if (xref.getAttribute("ref-type") === "corresp") { var correspId = xref.getAttribute("rid"); @@ -791,7 +841,11 @@ NlmToLensConverter.Prototype = function() { var fnType = fnElem.getAttribute("fn-type"); switch (fnType) { case "con": - contribNode.contribution = fnElem.textContent; + if (fnElem.getAttribute("id").indexOf("equal-contrib")>=0) { + equalContribs = this._getEqualContribs(state, contrib, fnElem.getAttribute("id")); + } else { + contribNode.contribution = fnElem.textContent; + } break; case "conflict": compInterests.push(fnElem.textContent.trim()); @@ -949,6 +1003,15 @@ NlmToLensConverter.Prototype = function() { this.extractFigures(state, article); + // catch all unhandled foot-notes + this.extractFootNotes(state, article); + + // Extract back element, if it exists + var back = article.querySelector("back"); + if (back){ + this.back(state,back); + } + this.enhanceArticle(state, article); }; @@ -1029,8 +1092,10 @@ NlmToLensConverter.Prototype = function() { this.extractFigures = function(state, xmlDoc) { // Globally query all figure-ish content, , , , // mimetype="video" - var body = xmlDoc.querySelector("body"); - var figureElements = body.querySelectorAll("fig, table-wrap, supplementary-material, media[mimetype=video]"); + + // NOTE: We previously only considered figures within but since + // appendices can also have figures we now use a gobal selector. + var figureElements = xmlDoc.querySelectorAll("fig, table-wrap, supplementary-material, media[mimetype=video]"); var nodes = []; for (var i = 0; i < figureElements.length; i++) { var figEl = figureElements[i]; @@ -1054,6 +1119,17 @@ NlmToLensConverter.Prototype = function() { this.show(state, nodes); }; + // Catch-all implementation for footnotes that have not been + // converted yet. + this.extractFootNotes = function(state, article) { + var fnEls = article.querySelectorAll('fn'); + for (var i = 0; i < fnEls.length; i++) { + var fnEl = fnEls[i]; + if (fnEl.__converted__) continue; + this.footnote(state, fnEl); + } + }; + this.extractCitations = function(state, xmlDoc) { var refList = xmlDoc.querySelector("ref-list"); if (refList) { @@ -1128,6 +1204,8 @@ NlmToLensConverter.Prototype = function() { }, this); }; + // TODO: abstract should be a dedicated node + // as it can have some extra information in JATS, such as specific-use this.abstract = function(state, abs) { var doc = state.doc; var nodes = []; @@ -1161,14 +1239,7 @@ NlmToLensConverter.Prototype = function() { this.body = function(state, body) { var doc = state.doc; - var heading = { - id: state.nextId("heading"), - type: "heading", - level: 1, - content: "Main Text" - }; - doc.create(heading); - var nodes = [heading].concat(this.bodyNodes(state, util.dom.getChildren(body))); + var nodes = this.bodyNodes(state, util.dom.getChildren(body)); if (nodes.length > 0) { this.show(state, nodes); } @@ -1207,7 +1278,7 @@ NlmToLensConverter.Prototype = function() { node = this.ignoredNode(state, child, type); if (node) nodes.push(node); } else { - console.error("Node not yet supported as top-level node: " + type); + console.error("Node not supported as block-level element: " + type +"\n"+child.outerHTML); } } return nodes; @@ -1240,9 +1311,10 @@ NlmToLensConverter.Prototype = function() { this._bodyNodes["comment"] = function(state, child) { return this.comment(state, child); }; - this._bodyNodes["fig"] = function(state, child) { - return this.figure(state, child); - }; + // Disable fig as a body node, otherwise the order of nodes in the Figures tab can be incorrect + //this._bodyNodes["fig"] = function(state, child) { + // return this.figure(state, child); + //}; // Overwirte in specific converter this.ignoredNode = function(/*state, node, type*/) { @@ -1258,11 +1330,13 @@ NlmToLensConverter.Prototype = function() { // Assuming that there are no nested elements var childNodes = this.bodyNodes(state, util.dom.getChildren(box)); var boxId = state.nextId("box"); + // Optional heading label + var label = this.selectDirectChildren(box, "label")[0]; var boxNode = { "type": "box", "id": boxId, "source_id": box.getAttribute("id"), - "label": "", + "label": label ? label.textContent : "", "children": _.pluck(childNodes, 'id') }; doc.create(boxNode); @@ -1283,9 +1357,9 @@ NlmToLensConverter.Prototype = function() { }; doc.create(quoteNode); return quoteNode; - }; + }; - this.datasets = function(state, datasets) { + this.datasets = function(state, datasets) { var nodes = []; for (var i=0;i element @@ -1446,8 +1521,15 @@ NlmToLensConverter.Prototype = function() { // ignore some elements if (this.ignoredParagraphElements[type]) continue; + // paragraph block-types such as disp-formula + // i.e they are allowed within a paragraph, but + // we pull them out on the top level + if (this.acceptedParagraphElements[type]) { + blocks.push(_.extend({node: child}, this.acceptedParagraphElements[type])); + } // paragraph elements - if (type === "text" || this.isAnnotation(type) || this.inlineParagraphElements[type]) { + //if (type === "text" || this.isAnnotation(type) || this.inlineParagraphElements[type]) { + else { if (lastType !== "paragraph") { blocks.push({ handler: "paragraph", nodes: [] }); lastType = "paragraph"; @@ -1455,10 +1537,7 @@ NlmToLensConverter.Prototype = function() { _.last(blocks).nodes.push(child); continue; } - // other elements are treated as single blocks - else if (this.acceptedParagraphElements[type]) { - blocks.push(_.extend({node: child}, this.acceptedParagraphElements[type])); - } + lastType = type; } return blocks; @@ -1491,6 +1570,11 @@ NlmToLensConverter.Prototype = function() { return nodes; }; + // DEPRECATED: using this handler for

elements is + // deprecated, as in JATS

can contain certain block-level + // elements. Better use this.paragraphGroup in cases where you + // convert

elements. + // TODO: we should refactor this and make it a 'private' helper this.paragraph = function(state, children) { var doc = state.doc; @@ -1511,7 +1595,7 @@ NlmToLensConverter.Prototype = function() { var type = util.dom.getNodeType(child); // annotated text node - if (type === "text" || this.isAnnotation(type)) { + if (type === "text" || this.isAnnotation(type) || this.isInlineNode(type)) { var textNode = { id: state.nextId("text"), type: "text", @@ -1526,7 +1610,13 @@ NlmToLensConverter.Prototype = function() { // In that case, the iterator will still have more elements // and the loop is continued // Before descending, we reset the iterator to provide the current element again. - var annotatedText = this._annotatedText(state, iterator.back(), { offset: 0, breakOnUnknown: true }); + // TODO: We have disabled the described behavior as it seems + // worse to break automatically on unknown inline tags, + // than to render plain text, as it results in data loss. + // If you find a situation where you want to flatten structure + // found within a paragraph, use this.acceptedParagraphElements instead + // which is used in a preparation step before converting paragraphs. + var annotatedText = this._annotatedText(state, iterator.back(), { offset: 0, breakOnUnknown: false }); // Ignore empty paragraphs if (annotatedText.length > 0) { @@ -1538,7 +1628,6 @@ NlmToLensConverter.Prototype = function() { // popping the stack state.stack.pop(); } - // inline image node else if (type === "inline-graphic") { var url = child.getAttribute("xlink:href"); @@ -1599,6 +1688,8 @@ NlmToLensConverter.Prototype = function() { var listItems = list.querySelectorAll("list-item"); for (var i = 0; i < listItems.length; i++) { var listItem = listItems[i]; + // Only consider direct children + if (listItem.parentNode !== list) continue; // Note: we do not care much about what is served as items // However, we do not have complex nodes on paragraph level // They will be extract as sibling items @@ -1825,9 +1916,11 @@ NlmToLensConverter.Prototype = function() { }; // Note: using a DOM div element to create HTML - var table = tableWrap.querySelector("table"); + var table = tableWrap.querySelectorAll("table"); if (table) { - tableNode.content = this.toHtml(table); + for (var i = 0; i < table.length; i++) { + tableNode.content += this.toHtml(table[i]); + } } this.extractTableCaption(state, tableNode, tableWrap); @@ -1913,6 +2006,33 @@ NlmToLensConverter.Prototype = function() { return formulaNode; }; + this.footnote = function(state, footnoteElement) { + var doc = state.doc; + var footnote = { + type: 'footnote', + id: state.nextId('fn'), + source_id: footnoteElement.getAttribute("id"), + label: '', + children: [] + }; + var children = footnoteElement.children; + var i = 0; + if (children[i].tagName.toLowerCase() === 'label') { + footnote.label = this.annotatedText(state, children[i], [footnote.id, 'label']); + i++; + } + footnote.children = []; + for (; i 0) { + _.each(appGroups, function(appGroup) { + this.appGroup(state, appGroup); + }.bind(this)); + } else { + // HACK: We treat element as app-group, sine there + // are docs that wrongly put elements into the back + // element directly. + this.appGroup(state, back); + } + }; + + this.appGroup = function(state, appGroup) { + var apps = appGroup.querySelectorAll('app'); + var doc = state.doc; + var title = appGroup.querySelector('title'); + if (!title) { + console.error("FIXME: every app should have a title", this.toHtml(title)); + } + + var headingId =state.nextId("heading"); + // Insert top level element for Appendix + var heading = doc.create({ + "type" : "heading", + "id" : headingId, + "level" : 1, + "content" : "Appendices" + }); + + this.show(state, [heading]); + _.each(apps, function(app) { + state.sectionLevel = 2; + this.app(state, app); + }.bind(this)); + }; + + this.app = function(state, app) { + var doc = state.doc; + var nodes = []; + var title = app.querySelector('title'); + if (!title) { + console.error("FIXME: every app should have a title", this.toHtml(title)); + } + + var headingId = state.nextId("heading"); + var heading = { + "type" : "heading", + "id" : headingId, + "level" : 2, + "content": title ? this.annotatedText(state, title, [headingId, "content"]) : "" + }; + var headingNode = doc.create(heading); + nodes.push(headingNode); + + // There may be multiple paragraphs per ack element + var pars = this.bodyNodes(state, util.dom.getChildren(app), { + ignore: ["title", "label", "ref-list"] + }); + _.each(pars, function(par) { + nodes.push(par); + }); + this.show(state, nodes); }; + + // Annotations // ----------- @@ -2128,6 +2312,8 @@ NlmToLensConverter.Prototype = function() { } else if (type === 'inline-formula') { var formula = this.formula(state, el, "inline"); anno.target = formula.id; + } else if (anno.type === 'custom_annotation') { + anno.name = type; } }; @@ -2139,6 +2325,41 @@ NlmToLensConverter.Prototype = function() { if (sourceId) anno.target = sourceId; }; + this.createInlineNode = function(state, el, start) { + var inlineNode = { + type: "inline-node", + path: _.last(state.stack).path, + range: [start, start+1], + }; + + this.addInlineNodeData(state, inlineNode, el); + this.enhanceInlineNodeData(state, inlineNode, el); + + // assign an id after the type has been extracted to be able to create typed ids + inlineNode.id = state.nextId(inlineNode.type); + + state.annotations.push(inlineNode); + }; + + this.addInlineNodeData = function(state, inlineNode, el) { + /*jshint unused: false*/ + var tagName = el.tagName.toLowerCase(); + switch(tagName) { + case 'fn': + // when we hit a inline, we will create a footnote-reference + var footnote = this.footnote(state, el); + inlineNode.type = 'footnote_reference'; + inlineNode.target = footnote.id; + // We generate footnote references if we find an inline fn element + inlineNode.generated = true; + break; + } + }; + + this.enhanceInlineNodeData = function(state, inlineNode, el, tagName) { + /*jshint unused: false*/ + }; + // Parse annotated text // -------------------- // Make sure you call this method only for nodes where `this.isParagraphish(node) === true` @@ -2194,6 +2415,10 @@ NlmToLensConverter.Prototype = function() { } } } + else if (this.isInlineNode(type)) { + plainText += " "; + this.createInlineNode(state, el, charPos); + } // Unsupported... else if (!breakOnUnknown) { if (state.top().ignore.indexOf(type) < 0) { @@ -2203,7 +2428,7 @@ NlmToLensConverter.Prototype = function() { } } else { if (nested) { - console.error("Node not yet supported in annoted text: " + type); + console.error("Node not supported in annoted text: " + type +"\n"+el.outerHTML); } else { // on paragraph level other elements can break a text block @@ -2397,7 +2622,7 @@ NlmToLensConverter.State = function(converter, xmlDoc, doc) { // of processed nodes to be able to associate other things (e.g., annotations) correctly. this.stack = []; - this.sectionLevel = 1; + this.sectionLevel = 0; // Tracks all available affiliations this.affiliations = []; diff --git a/extensions/math/index.js b/extensions/math/index.js index f2a431a3..7e76711c 100644 --- a/extensions/math/index.js +++ b/extensions/math/index.js @@ -3,6 +3,5 @@ module.exports = { MathPanel: require("./math_panel"), MathNodes: require('./nodes'), ToggleFormula: require("./workflows/toggle_formula"), - ToggleMathEnvironment: require("./workflows/toggle_math_environment"), - ZoomFormula: require("./workflows/zoom_formula") + ToggleMathEnvironment: require("./workflows/toggle_math_environment") }; \ No newline at end of file diff --git a/extensions/math/math_converter.js b/extensions/math/math_converter.js index 76fcc428..0bf8973b 100644 --- a/extensions/math/math_converter.js +++ b/extensions/math/math_converter.js @@ -1,8 +1,8 @@ var _ = require('underscore'); -var util = require("lens/substance/util"); -var LensConverter = require('lens/converter'); -var LensArticle = require("lens/article"); +var util = require("../../substance/util"); +var LensConverter = require('../../converter'); +var LensArticle = require("../../article"); var MathNodeTypes = require("./nodes"); // Options: @@ -21,6 +21,14 @@ MathConverter.Prototype = function MathConverterPrototype() { this._refTypeMapping["disp-formula"] = "formula_reference"; this._refTypeMapping["statement"] = "math_environment_reference"; + this.acceptedParagraphElements = _.extend(__super__.acceptedParagraphElements, { + "def-list": { handler: 'defList' } + }); + + this._annotationTypes = _.extend(__super__._annotationTypes, { + "roman": "custom_annotation" + }); + this.test = function(xmlDoc, documentUrl) { /* jshint unused:false */ var publisherName = xmlDoc.querySelector("publisher-name").textContent; @@ -67,6 +75,7 @@ MathConverter.Prototype = function MathConverterPrototype() { return doc; }; + // TODO: the default implemenation should be plain, i.e. not adding an extra heading 'Main Text' // Instead the LensConverter should override this... // ...or we should consider adding an option (if the eLife way to do it is more often applicable...) @@ -96,7 +105,7 @@ MathConverter.Prototype = function MathConverterPrototype() { } }; - this._bodyNodes['def-list'] = function(state, defList) { + this._bodyNodes['def-list'] = this.defList = function(state, defList) { var enumerationNode = { type: 'enumeration', id: state.nextId('enumeration'), @@ -105,14 +114,36 @@ MathConverter.Prototype = function MathConverterPrototype() { var defItems = this.selectDirectChildren(defList, 'def-item'); for (var i = 0; i < defItems.length; i++) { var defItem = defItems[i]; + var term = defItem.querySelector('term'); + var termId = term.id; + var def = defItem.querySelector('def'); var enumItemNode = { type: 'enumeration-item', + // TODO: enabling the correct id makes warnings disappear + // which are given when seeing references to this def + // However, to work properly, we would need nesting support + // for definition references + // so we leave it for now + // id: termId || state.nextId('enumeration-item'), id: state.nextId('enumeration-item'), + children: [] }; - var term = defItem.querySelector('term'); + // convert label enumItemNode.label = this.annotatedText(state, term, [enumItemNode.id, 'label']); - var content = defItem.querySelector('def p'); - enumItemNode.children = _.pluck(this.paragraphGroup(state, content), "id"); + // convert content + // TODO: is the assumption correct that def-item content is always wrapped in a p element? + var pEls = this.selectDirectChildren(def, 'p'); + for (var j = 0; j < pEls.length; j++) { + var p = pEls[j]; + var children = this.paragraphGroup(state, p); + var pgroup = { + type: 'paragraph', + id: state.nextId('pgroup'), + children: _.pluck(children, 'id') + }; + state.doc.create(pgroup); + enumItemNode.children.push(pgroup.id); + } state.doc.create(enumItemNode); enumerationNode.items.push(enumItemNode.id); } @@ -120,6 +151,10 @@ MathConverter.Prototype = function MathConverterPrototype() { return enumerationNode; }; + // HACK: There is content that has nested elements, which is not allowed + // we just treat them as sections + this._bodyNodes['app'] = this._bodyNodes['sec']; + this.extractDefinitions = function(/*state, article*/) { // We don't want to show a definitions (glossary) panel // TODO: we should consider making this a static configuration for lens-converter @@ -276,7 +311,7 @@ MathConverter.Prototype = function MathConverterPrototype() { this._getFormulaData = function(state, formulaElement, formulaId, inline) { var result = []; - var labels = {'tex' : {}, 'svg': {}, 'math': {}}; + var labels = {'tex' : {}, 'svg': {}, 'html': {}, 'math': {}}; var el = formulaElement; var alternatives = el.querySelector('alternatives'); if (alternatives) el = alternatives; @@ -297,6 +332,13 @@ MathConverter.Prototype = function MathConverterPrototype() { data: this.toHtml(child) }); break; + case "textual-form": + labels.html = this._extractLabels(child); + result.push({ + format: "html", + data: $(child).text() + }); + break; case "mml:math": case "math": // HACK: make sure that mml in display-formulas has set display="block" @@ -342,6 +384,7 @@ MathConverter.Prototype = function MathConverterPrototype() { state.labelsForFormula[formulaId] = labels; return result; }; + this.formula = function(state, formulaElement, inline) { var doc = state.doc; var id = state.nextId("formula"); @@ -496,6 +539,7 @@ MathConverter.Prototype = function MathConverterPrototype() { if (type === 'label' || !el.textContent) continue; institutionText += el.textContent; } + var specific_use = aff.getAttribute('specific-use'); // TODO: we might add a property to the affiliation node that collects // data which is not handled here @@ -505,7 +549,8 @@ MathConverter.Prototype = function MathConverterPrototype() { type: "affiliation", source_id: aff.getAttribute("id"), label: label ? label.textContent : null, - institution: institutionText + institution: institutionText, + specific_use: specific_use || null }; state.affiliations.push(affiliationNode.id); @@ -539,7 +584,7 @@ MathConverter.Prototype = function MathConverterPrototype() { // lipid droplet // anti-bacterial // - var keyWords = articleMeta.querySelectorAll("kwd-group kwd"); + var keywordEls = articleMeta.querySelectorAll("kwd-group kwd"); // Extract subjects // ------------ @@ -551,7 +596,7 @@ MathConverter.Prototype = function MathConverterPrototype() { // Microbiology and infectious disease // - var subjects = articleMeta.querySelectorAll("subj-group[subj-group-type=heading] subject"); + var subjectEls = articleMeta.querySelectorAll("subj-group[subj-group-type=heading] subject"); // Article Type // @@ -596,8 +641,18 @@ MathConverter.Prototype = function MathConverterPrototype() { publicationInfo.raw_formats = rawFormats; - publicationInfo.keywords = _.pluck(keyWords, "textContent"); - publicationInfo.subjects = _.pluck(subjects, "textContent"); + var keywords = []; + for (var i = 0; i < keywordEls.length; i++) { + keywords.push(this.annotatedText(state, keywordEls[i], ["publication_info", "keywords", i])); + } + publicationInfo.keywords = keywords; + + var subjects = []; + for (var i = 0; i < subjectEls.length; i++) { + subjects.push(this.annotatedText(state, subjectEls[i], ["publication_info", "subjects", i])); + } + publicationInfo.subjects = subjects; + publicationInfo.article_type = articleType ? articleType.textContent : ""; publicationInfo.links = links; }; @@ -672,14 +727,17 @@ MathConverter.Prototype = function MathConverterPrototype() { anno.target = targetNode.id; } else { console.log("Could not lookup math environment for reference", anno); + continue; } referencedMath[targetNode.id] = true; } else { targetNode = state.doc.getNodeBySourceId(anno.target) || state.doc.get(anno.target); if (targetNode) { anno.target = targetNode.id; + targetNode.isReferenced = true; } else { console.log("Could not lookup targetNode for annotation", anno); + continue; } } } @@ -696,58 +754,91 @@ MathConverter.Prototype = function MathConverterPrototype() { state.referencedMath = referencedMath; }; + function _showFigure(state, node) { + // show all figures in the figures panel + state.doc.show('figures', node.id); + // show unreferenced and anchored figures in the main content + if (!node.isReferenced || node.position === 'anchor') { + state.doc.show('content', node.id); + } + } + + function _showFormulaOrEnvironment(state, node, nested) { + var referencedMath = state.referencedMath; + var info = state.nodeInfo[node.id]; + // only show formulas and environments in the math panel + // - if they are referenced + // - or have specificUse='resource' set explicitly + if (referencedMath[node.id] || + (info && info.specificUse === "resource")) { + doc.show(MATH_PANEL, node.id); + } + if (!nested) { + doc.show('content', node.id); + } + // a math environment can have nested content + // such as figures or environments which need + // to be processed recursively + if (node.type === 'math_environment') { + _showNestedContent(state, node.body); + } + } + + function _showNestedContent(state, nodeIds) { + var referencedMath = state.referencedMath; + for (var i = 0; i < nodeIds.length; i++) { + var nodeId = nodeIds[i] + var node = state.doc.get(nodeId); + var info = state.nodeInfo[nodeId]; + switch (node.type) { + case 'figure': + // show all figures in the figures panel + state.doc.show('figures', nodeId); + // hide referenced and unanchored figures from the environment + if (node.isReferenced && node.position !== 'anchor') { + nodeIds.splice(i, 1); + i--; + } + break; + case 'formula': + case 'math_environment': + _showFormulaOrEnvironment(state, node, 'nested'); + break; + default: + // nothing + } + } + } + + function _showProof(state, node) { + // proofs are always shown only in the content + state.doc.show('content', node.id); + _showNestedContent(state, node.children); + } + this.populatePanels = function(state) { var doc = state.doc; var referencedMath = state.referencedMath; var node, child, info; for (var i = 0; i < state.shownNodes.length; i++) { - node = state.shownNodes[i]; + node = doc.get(state.shownNodes[i].id); switch (node.type) { case 'figure': - // show figures without captions are only in-flow - if (!node.caption) { - state.doc.show('content', node.id); - } - // all others are shown in the figures panel - else { - state.doc.show('figures', node.id); - // in addition a figure can be shown in-flow using position='anchor' - if (node.position === 'anchor') { - state.doc.show('content', node.id); - } - } + _showFigure(state, node); break; case 'formula': case 'math_environment': - info = state.nodeInfo[node.id]; - // only environments or formulas go into the math panel - // that ar referenced or forced using `specific-use='resource'` - if (referencedMath[node.id] || - (info && info.specificUse === "resource")) { - doc.show(MATH_PANEL, node.id); - } - doc.show('content', node.id); + _showFormulaOrEnvironment(state, node); break; - // Special treatment for proofs as they may contain equations - // which when referenced should be displayed in the resource panel case 'proof': - LensConverter.prototype.showNode.call(this, state, node); - for (var j = 0; j < node.children.length; j++) { - child = doc.get(node.children[j]); - if (child.type === 'formula' || child.type === 'math_environment') { - info = state.nodeInfo[child.id]; - if (referencedMath[child.id] || - (info && info.specificUse === "resource")) { - doc.show(MATH_PANEL, child.id); - } - } - } + _showProof(state, node); break; default: LensConverter.prototype.showNode.call(this, state, node); } } }; + }; MathConverter.Prototype.prototype = LensConverter.prototype; diff --git a/extensions/math/math_panel.js b/extensions/math/math_panel.js index 9ec31814..72b8a240 100644 --- a/extensions/math/math_panel.js +++ b/extensions/math/math_panel.js @@ -1,6 +1,6 @@ "use strict"; -var Lens = require('lens/reader'); +var Lens = require('../../reader'); var ContainerPanel = Lens.ContainerPanel; var ContainerPanelController = Lens.ContainerPanelController; var ContainerPanelView = Lens.ContainerPanelView; diff --git a/extensions/math/nodes/citation/citation_view.js b/extensions/math/nodes/citation/citation_view.js index 5d376fa1..f9b88e5a 100644 --- a/extensions/math/nodes/citation/citation_view.js +++ b/extensions/math/nodes/citation/citation_view.js @@ -1,6 +1,6 @@ "use strict"; -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../article/nodes'); var LensCitationView = LensNodes['citation'].View; diff --git a/extensions/math/nodes/citation/index.js b/extensions/math/nodes/citation/index.js index 8fca51c3..e9672eca 100644 --- a/extensions/math/nodes/citation/index.js +++ b/extensions/math/nodes/citation/index.js @@ -1,4 +1,4 @@ -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../../../article/nodes'); module.exports = { Model: LensNodes['citation'].Model, diff --git a/extensions/math/nodes/enumeration/enumeration.js b/extensions/math/nodes/enumeration/enumeration.js index caa5dbd3..ca56d1c0 100644 --- a/extensions/math/nodes/enumeration/enumeration.js +++ b/extensions/math/nodes/enumeration/enumeration.js @@ -1,7 +1,7 @@ "use strict"; var _ = require("underscore"); -var Document = require("lens/substance/document"); +var Document = require("../../../../substance/document"); var DocumentNode = Document.Node; var Composite = Document.Composite; diff --git a/extensions/math/nodes/enumeration/enumeration_view.js b/extensions/math/nodes/enumeration/enumeration_view.js index 57d680fe..0403be47 100644 --- a/extensions/math/nodes/enumeration/enumeration_view.js +++ b/extensions/math/nodes/enumeration/enumeration_view.js @@ -1,6 +1,6 @@ "use strict"; -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../../../article/nodes'); var NodeView = LensNodes['node'].View; var CompositeView = LensNodes['composite'].View; diff --git a/extensions/math/nodes/enumeration_item/enumeration_item.js b/extensions/math/nodes/enumeration_item/enumeration_item.js index 092c7c72..03a1e69d 100644 --- a/extensions/math/nodes/enumeration_item/enumeration_item.js +++ b/extensions/math/nodes/enumeration_item/enumeration_item.js @@ -1,7 +1,7 @@ "use strict"; var _ = require('underscore'); -var Document = require('lens/substance/document'); +var Document = require('../../../../substance/document'); var EnumerationItem = function(node, doc) { Document.Composite.call(this, node, doc); diff --git a/extensions/math/nodes/enumeration_item/enumeration_item_view.js b/extensions/math/nodes/enumeration_item/enumeration_item_view.js index f9d84f92..d2de789d 100644 --- a/extensions/math/nodes/enumeration_item/enumeration_item_view.js +++ b/extensions/math/nodes/enumeration_item/enumeration_item_view.js @@ -1,7 +1,7 @@ "use strict"; -var LensNodes = require("lens/article/nodes"); -var $$ = require("lens/substance/application").$$; +var LensNodes = require("../../../../article/nodes"); +var $$ = require("../../../../substance/application").$$; var NodeView = LensNodes["node"].View; var CompositeView = LensNodes["composite"].View; diff --git a/extensions/math/nodes/formula/formula.js b/extensions/math/nodes/formula/formula.js index 7c723803..8e20c2d3 100644 --- a/extensions/math/nodes/formula/formula.js +++ b/extensions/math/nodes/formula/formula.js @@ -1,6 +1,6 @@ "use strict"; -var Document = require('lens/substance/document'); +var Document = require('../../../../substance/document'); // Formula // ----------------- diff --git a/extensions/math/nodes/formula/formula_view.js b/extensions/math/nodes/formula/formula_view.js index ef2ac480..ffb2ec0d 100644 --- a/extensions/math/nodes/formula/formula_view.js +++ b/extensions/math/nodes/formula/formula_view.js @@ -1,12 +1,12 @@ "use strict"; var _ = require('underscore'); -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../../../article/nodes'); var NodeView = LensNodes["node"].View; -var LensArticle = require('lens/article'); +var LensArticle = require('../../../../article'); var ResourceView = LensArticle.ResourceView; -var $$ = require('lens/substance/application').$$; +var $$ = require('../../../../substance/application').$$; // FormulaView // =========== @@ -153,6 +153,14 @@ FormulaView.Prototype = function() { hasPreview = true; } break; + case "html": + // add only if no preview + if (!hasPreview) { + // don't use a preview element + this.$content.append($(data)); + hasPreview = true; + } + break; default: console.error("Unknown formula format:", format); } diff --git a/extensions/math/nodes/formula_reference/formula_reference.js b/extensions/math/nodes/formula_reference/formula_reference.js index 859762c4..aab49483 100644 --- a/extensions/math/nodes/formula_reference/formula_reference.js +++ b/extensions/math/nodes/formula_reference/formula_reference.js @@ -1,7 +1,7 @@ "use strict"; -var Document = require('lens/substance/document'); -var LensNodes = require('lens/article/nodes'); +var Document = require('../../../../substance/document'); +var LensNodes = require('../../../../article/nodes'); var Annotation = LensNodes['annotation'].Model; var ResourceReference = LensNodes['resource_reference'].Model; diff --git a/extensions/math/nodes/formula_reference/index.js b/extensions/math/nodes/formula_reference/index.js index 07a78d82..f6a72b7e 100644 --- a/extensions/math/nodes/formula_reference/index.js +++ b/extensions/math/nodes/formula_reference/index.js @@ -1,4 +1,4 @@ -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../../../article/nodes'); module.exports = { Model: require('./formula_reference.js'), diff --git a/extensions/math/nodes/math_environment/math_environment.js b/extensions/math/nodes/math_environment/math_environment.js index dfed8829..93a6a704 100644 --- a/extensions/math/nodes/math_environment/math_environment.js +++ b/extensions/math/nodes/math_environment/math_environment.js @@ -1,6 +1,6 @@ "use strict"; -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../../../article/nodes'); var DocumentNode = LensNodes['node'].Model; var MathEnvironment = function(node, document) { @@ -8,6 +8,7 @@ var MathEnvironment = function(node, document) { }; MathEnvironment.type = { + "id": "math_environment", "parent": "content", "properties": { "source_id": "string", diff --git a/extensions/math/nodes/math_environment/math_environment_view.js b/extensions/math/nodes/math_environment/math_environment_view.js index 9b93e380..31257507 100644 --- a/extensions/math/nodes/math_environment/math_environment_view.js +++ b/extensions/math/nodes/math_environment/math_environment_view.js @@ -1,12 +1,12 @@ "use strict"; var _ = require('underscore'); -var LensArticle = require('lens/article'); -var LensNodes = require('lens/article/nodes'); +var LensArticle = require('../../../../article'); +var LensNodes = require('../../../../article/nodes'); var NodeView = LensNodes["node"].View; var ResourceView = LensArticle.ResourceView; -var $$ = require('lens/substance/application').$$; +var $$ = require('../../../../substance/application').$$; // Lens.MathEnvironment.View // ========================================================================== diff --git a/extensions/math/nodes/math_environment_reference/index.js b/extensions/math/nodes/math_environment_reference/index.js index 7299bae3..e693d54c 100644 --- a/extensions/math/nodes/math_environment_reference/index.js +++ b/extensions/math/nodes/math_environment_reference/index.js @@ -1,4 +1,4 @@ -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../../../article/nodes'); module.exports = { Model: require('./math_environment_reference.js'), diff --git a/extensions/math/nodes/math_environment_reference/math_environment_reference.js b/extensions/math/nodes/math_environment_reference/math_environment_reference.js index cadfaf09..c1224b10 100644 --- a/extensions/math/nodes/math_environment_reference/math_environment_reference.js +++ b/extensions/math/nodes/math_environment_reference/math_environment_reference.js @@ -1,7 +1,7 @@ "use strict"; -var Document = require('lens/substance/document'); -var LensNodes = require('lens/article/nodes'); +var Document = require('../../../../substance/document'); +var LensNodes = require('../../../../article/nodes'); var Annotation = LensNodes['annotation'].Model; var ResourceReference = LensNodes['resource_reference'].Model; diff --git a/extensions/math/nodes/plain_citation/plain_citation.js b/extensions/math/nodes/plain_citation/plain_citation.js index d6544f64..affad7ec 100644 --- a/extensions/math/nodes/plain_citation/plain_citation.js +++ b/extensions/math/nodes/plain_citation/plain_citation.js @@ -1,6 +1,6 @@ "use strict"; -var Document = require('lens/substance/document'); +var Document = require('../../../../substance/document'); var PlainCitation = function(node, doc) { Document.Node.call(this, node, doc); diff --git a/extensions/math/nodes/plain_citation/plain_citation_view.js b/extensions/math/nodes/plain_citation/plain_citation_view.js index faed8b2d..bd638eb9 100644 --- a/extensions/math/nodes/plain_citation/plain_citation_view.js +++ b/extensions/math/nodes/plain_citation/plain_citation_view.js @@ -6,11 +6,11 @@ var LABELS = { }; var _ = require('underscore'); -var LensArticle = require('lens/article'); -var LensNodes = require('lens/article/nodes'); +var LensArticle = require('../../../../article'); +var LensNodes = require('../../../../article/nodes'); var NodeView = LensNodes['node'].View; var ResourceView = LensArticle.ResourceView; -var $$ = require("lens/substance/application").$$; +var $$ = require("../../../../substance/application").$$; // Lens.Citation.View // ========================================================================== diff --git a/extensions/math/nodes/proof/proof.js b/extensions/math/nodes/proof/proof.js index b91e0e7d..0adc25bd 100644 --- a/extensions/math/nodes/proof/proof.js +++ b/extensions/math/nodes/proof/proof.js @@ -1,6 +1,6 @@ "use strict"; -var Document = require('lens/substance/document'); +var Document = require('../../../../substance/document'); var Composite = Document.Composite; // Lens.Proof diff --git a/extensions/math/nodes/proof/proof_view.js b/extensions/math/nodes/proof/proof_view.js index bf415745..8186afcd 100644 --- a/extensions/math/nodes/proof/proof_view.js +++ b/extensions/math/nodes/proof/proof_view.js @@ -1,9 +1,9 @@ "use strict"; -var LensNodes = require('lens/article/nodes'); +var LensNodes = require('../../../../article/nodes'); var NodeView = LensNodes["node"].View; var CompositeView = LensNodes["composite"].View; -var $$ = require("lens/substance/application").$$; +var $$ = require("../../../../substance/application").$$; // Lens.Proof.View diff --git a/extensions/math/workflows/toggle_formula.js b/extensions/math/workflows/toggle_formula.js index cd813065..3508f031 100644 --- a/extensions/math/workflows/toggle_formula.js +++ b/extensions/math/workflows/toggle_formula.js @@ -1,5 +1,5 @@ var _ = require('underscore'); -var Lens = require('lens/reader'); +var Lens = require('../../../reader'); var Workflow = Lens.Workflow; var ToggleFormula = function() { diff --git a/extensions/math/workflows/toggle_math_environment.js b/extensions/math/workflows/toggle_math_environment.js index 84d5afc0..57896e36 100644 --- a/extensions/math/workflows/toggle_math_environment.js +++ b/extensions/math/workflows/toggle_math_environment.js @@ -1,5 +1,5 @@ var _ = require('underscore'); -var Lens = require('lens/reader'); +var Lens = require('../../../reader'); var Workflow = Lens.Workflow; var ToggleMathEnvironment = function() { diff --git a/extensions/math/workflows/zoom_formula.js b/extensions/math/workflows/zoom_formula.js deleted file mode 100644 index a1e6e46f..00000000 --- a/extensions/math/workflows/zoom_formula.js +++ /dev/null @@ -1,159 +0,0 @@ -var _ = require('underscore'); -var Lens = require('lens/reader'); -var Workflow = Lens.Workflow; - -var ZoomFormula = function() { - Workflow.call(this); - - this.formulaWidths = {}; - this.formulaHeights = {}; - this.formulaIsZoomed = {}; - - this._handleFormulasChange = function() { - this.fitFormulas(); - }.bind(this); - -}; - -ZoomFormula.Prototype = function() { - - this.registerHandlers = function() { - var self = this; - - // Listen for clicks on formulas to toggle scale/scroll - // ------------------ - // - // this way to handle the event is a hack! lens/substance infrastructure should be used here! - - $(this.readerView.$el).on("click",".formula .content .MathJax_Display.zoomable", - function(e) { self.toggleFormulaScaling(e,this); } - ); - - // Attach a lazy/debounced handler for resize events - // ------------------ - // - - $(window).resize(_.debounce(_.bind(function() { - this.fitFormulas(); - }, this), 1)); - - // Recompute zoom factors after MathJax has finished processing. - // ------------------ - // - - var self = this; - MathJax.Hub.Register.MessageHook("End Process", function (message) { - self.fitFormulas(); - }); - - this.readerView.doc.on('app:formulas:changed', this._handleFormulasChange); - }; - - this.unRegisterHandlers = function() { - $(this.readerView.$el).off("click",".formula .content .MathJax_Display"); - this.readerView.doc.off('app:formulas:changed', this._handleFormulasChange); - }; - - this.fitFormula = function(nodeId,formulaNode) { - var mathjaxContainer = $(formulaNode).find('.MathJax_Display')[0]; - var mathEl = $(formulaNode).find('.math')[0]; - if (!mathEl) return; - - if(this.getFormulaIsZoomed(nodeId)) { // Zoomed - - mathEl.style["-webkit-transform-origin"] = ""; - mathEl.style["-moz-transform-origin"] = ""; - mathEl.style["-ms-transform-origin"] = ""; - mathEl.style.transformOrigin = ""; - mathEl.style["-webkit-transform"] = ""; - mathEl.style["-moz-transform"] = ""; - mathEl.style["-ms-transform"] = ""; - mathEl.style.transform = ""; - - mathjaxContainer.style.height = ""; - mathjaxContainer.style.overflowX = "auto"; - - } else { // Scaled - var containerWidth = $(mathjaxContainer).width(); - var INDENT = 3.0; - var style; - - if (!this.formulaWidths[nodeId]) { - var spanElement = $(mathjaxContainer).find(".math")[0]; - if (spanElement) { - style = window.getComputedStyle(spanElement); - this.formulaWidths[nodeId] = parseFloat(style.fontSize) * (spanElement.bbox.w + INDENT); - } - } - - if (!this.formulaHeights[nodeId]) { - style = window.getComputedStyle(mathjaxContainer); - this.formulaHeights[nodeId] = parseFloat(style.height); - } - var CORRECTION_FACTOR = 0.92; - var ratio = Math.min(containerWidth / this.formulaWidths[nodeId]*CORRECTION_FACTOR,1.0); - // mathEl.style.cursor = (ratio < 0.9 ? "zoom-in" : ""); //simple zoom UI - if (ratio < 0.9) { - mathjaxContainer.classList.add("zoomable"); - } else { - mathjaxContainer.classList.remove("zoomable"); - } - - mathEl.style["-webkit-transform-origin"] = "top left"; - mathEl.style["-moz-transform-origin"] = "top left"; - mathEl.style["-ms-transform-origin"] = "top left"; - mathEl.style.transformOrigin = "top left"; - mathEl.style["-webkit-transform"] = "scale("+ratio+")"; - mathEl.style["-moz-transform"] = "scale("+ratio+")"; - mathEl.style["-ms-transform"] = "scale("+ratio+")"; - mathEl.style.transform = "scale("+ratio+")"; - - mathjaxContainer.style.height = "" + (this.formulaHeights[nodeId] * ratio) + "px"; - mathjaxContainer.style.overflowX = "visible"; - } - }; - - // Trigger fitting all formulas in the document - // ----------------- - - this.fitFormulas = function() { - var self = this; - - $('.content-node.formula').each(function() { - var nodeId = $(this).find('.MathJax_Display .MathJax').attr("id"); - self.fitFormula(nodeId,this); - }); - }; - - // these methods handle the toggling logic - // ----------------- - - this.getFormulaIsZoomed = function(nodeId) { - if(this.formulaIsZoomed[nodeId] === undefined) { - return false; - } else { - return this.formulaIsZoomed[nodeId]; - } - }; - - this.setFormulaIsZoomed = function(nodeId,value) { - this.formulaIsZoomed[nodeId] = value; - }; - - this.toggleFormulaIsZoomed = function(nodeId) { - this.setFormulaIsZoomed(nodeId, !this.getFormulaIsZoomed(nodeId)); - }; - - this.toggleFormulaScaling = function(e, node) { - // var nodeId = $(e.currentTarget).find('.MathJax').attr("id"); - var nodeId = $(node).find('.MathJax').attr("id"); - var formulaNode = $(node).parents('.content-node.formula')[0]; - this.toggleFormulaIsZoomed(nodeId); - this.fitFormula(nodeId, formulaNode); - }; - -}; -ZoomFormula.Prototype.prototype = Workflow.prototype; -ZoomFormula.prototype = new ZoomFormula.Prototype(); - -module.exports = ZoomFormula; diff --git a/maintainers.txt b/maintainers.txt new file mode 100644 index 00000000..39a7775b --- /dev/null +++ b/maintainers.txt @@ -0,0 +1 @@ +gnott diff --git a/package.json b/package.json index 0ed13374..cd59ada3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lens", "version": "2.1.0", "description": "A novel way of seeing content.", - "url": "http://github.com/elifesciences/lens", + "url": "https://github.com/elifesciences/lens", "keywords": [ "digital documents", "linked-data", @@ -26,7 +26,7 @@ "url": "https://github.com/elifesciences/lens.git" }, "dependencies": { - "underscore": "1.8.3" + "underscore": "1.12.1" }, "engines": { diff --git a/reader/lens.js b/reader/lens.js index 7d8a74bf..a15cd22f 100644 --- a/reader/lens.js +++ b/reader/lens.js @@ -1,9 +1,9 @@ "use strict"; -var Application = require("../substance/application"); var LensController = require("./lens_controller"); -var LensConverter = require("lens/converter"); -var LensArticle = require("lens/article"); +var LensConverter = require("../converter"); +var Application = require("../substance/application"); +var LensArticle = require("../article"); var ResourcePanelViewFactory = require("./panels/resource_panel_viewfactory"); var ReaderController = require('./reader_controller'); var ReaderView = require('./reader_view'); diff --git a/reader/lens_controller.js b/reader/lens_controller.js index f385e34c..7f5222ec 100644 --- a/reader/lens_controller.js +++ b/reader/lens_controller.js @@ -5,8 +5,8 @@ var util = require("../substance/util"); var Controller = require('../substance/application').Controller; var LensView = require("./lens_view"); var ReaderController = require('./reader_controller'); -var LensArticle = require('lens/article'); -var NLMConverter = require('lens/converter'); +var LensArticle = require('../article'); +var NLMConverter = require('../converter'); // Lens.Controller diff --git a/reader/panels/container_panel_view.js b/reader/panels/container_panel_view.js index 43baa6e8..b98d1467 100644 --- a/reader/panels/container_panel_view.js +++ b/reader/panels/container_panel_view.js @@ -4,8 +4,7 @@ var _ = require("underscore"); var Scrollbar = require("./surface_scrollbar"); var Surface = require("../lens_surface"); var PanelView = require("./panel_view"); - -var MENU_BAR_HEIGHT = 40; +var getRelativeBoundingRect = require('../../substance/util/getRelativeBoundingRect'); // TODO: try to get rid of DocumentController and use the Container node instead var ContainerPanelView = function( panelCtrl, viewFactory, config ) { @@ -34,12 +33,12 @@ ContainerPanelView.Prototype = function() { this.render = function() { // Hide the whole tab if there is no content - if (this.getContainer().getLength() === 0) { - this.hideToggle(); - this.hide(); - } else { + if (this.getContainer().hasContent(this.config.type)) { this.surface.render(); this.scrollbar.render(); + } else { + this.hideToggle(); + this.hide(); } return this; }; @@ -59,29 +58,21 @@ ContainerPanelView.Prototype = function() { this.scrollTo = function(nodeId) { var n = this.findNodeView(nodeId); if (n) { - var $n = $(n); - - var windowHeight = $(window).height(); var panelHeight = this.surface.$el.height(); - var scrollTop; + var screenTop = this.surface.$el.scrollTop(); + var screenBottom = screenTop + panelHeight; + var elRect = getRelativeBoundingRect([n], this.surface.$nodes[0]); + var elHeight = elRect.height; + + var upperBound = elRect.top; // top-offset of upper bound to relative parent + var lowerBound = upperBound+elRect.height; // top-offset of lower bound to relative parent - scrollTop = this.surface.$el.scrollTop(); - var elTop = $n.offset().top; - var elHeight = $n.height(); - var topOffset; // Do not scroll if the element is fully visible - if ((elTop > 0 && elTop + elHeight < panelHeight) || (elTop >= 0 && elTop < panelHeight)) { - // everything fine + if (upperBound>=screenTop && lowerBound <= screenBottom) { return; } - // In all other cases scroll to the top of the element - else { - // HACK: we subtract the height of the menu bar to the scroll position, - // because elTop does not consider the offset - topOffset = scrollTop + elTop - MENU_BAR_HEIGHT; - } - this.surface.$el.scrollTop(topOffset); + this.surface.$el.scrollTop(upperBound); this.scrollbar.update(); } else { console.info("ContainerPanelView.scrollTo(): Unknown resource '%s'", nodeId); diff --git a/reader/panels/content/content_panel_view.js b/reader/panels/content/content_panel_view.js index 65a405ae..7877486e 100644 --- a/reader/panels/content/content_panel_view.js +++ b/reader/panels/content/content_panel_view.js @@ -59,8 +59,7 @@ ContentPanelView.Prototype = function() { this.onTocItemSelected = function(nodeId) { var n = this.findNodeView(nodeId); if (n) { - var topOffset = $(n).position().top+CORRECTION; - this.surface.$el.scrollTop(topOffset); + n.scrollIntoView(); } }; @@ -136,4 +135,4 @@ ContentPanelView.Prototype.prototype = ContainerPanelView.prototype; ContentPanelView.prototype = new ContentPanelView.Prototype(); ContentPanelView.prototype.constructor = ContentPanelView; -module.exports = ContentPanelView; \ No newline at end of file +module.exports = ContentPanelView; diff --git a/reader/panels/panel_view.js b/reader/panels/panel_view.js index 32edfab0..3bf5aade 100644 --- a/reader/panels/panel_view.js +++ b/reader/panels/panel_view.js @@ -127,7 +127,6 @@ PanelView.Prototype = function() { return this.el.querySelector('*[data-id='+nodeId+']'); }; - // Event handling // -------- // diff --git a/reader/reader_view.js b/reader/reader_view.js index 986924ce..40b18954 100644 --- a/reader/reader_view.js +++ b/reader/reader_view.js @@ -31,8 +31,6 @@ var ReaderView = function(readerCtrl) { // Note: ATM, it is not possible to override the content panel + toc via panelSpecification this.contentView = readerCtrl.panelCtrls.content.createView(); this.tocView = this.contentView.getTocView(); - - this.panelViews = {}; // mapping to associate reference types to panels // NB, in Lens each resource type has one dedicated panel; @@ -78,6 +76,7 @@ var ReaderView = function(readerCtrl) { this.listenTo(panelView, "toggle-resource-reference", this.onToggleResourceReference); this.listenTo(panelView, "toggle-fullscreen", this.onToggleFullscreen); }, this); + // TODO: treat content panel as panelView and delegate to tocView where necessary this.listenTo(this.contentView, "toggle", this._onTogglePanel); this.listenTo(this.contentView, "toggle-resource", this.onToggleResource); @@ -165,11 +164,13 @@ ReaderView.Prototype = function() { var self = this; // MathJax requires the processed elements to be in the DOM - window.MathJax.Hub.Queue(["Typeset", window.MathJax.Hub]); - window.MathJax.Hub.Queue(function () { - // HACK: using updateState() instead of updateScrollbars() as it also knows how to scroll - self.updateState(); - }); + if (window.MathJax){ + window.MathJax.Hub.Queue(["Typeset", window.MathJax.Hub]); + window.MathJax.Hub.Queue(function () { + // HACK: using updateState() instead of updateScrollbars() as it also knows how to scroll + self.updateState(); + }); + } }, this), 1); return this; @@ -248,6 +249,7 @@ ReaderView.Prototype = function() { // HACK: abusing addHighlight for adding the fullscreen class // instead I would prefer to handle such focussing explicitely in a workflow if (state.fullscreen) classes.push("fullscreen"); + this.contentView.addHighlight(state.focussedNode, classes.concat('main-occurrence').join(' ')); currentPanelView.addHighlight(state.focussedNode, classes.join(' ')); currentPanelView.scrollTo(state.focussedNode); } @@ -314,7 +316,7 @@ ReaderView.Prototype = function() { self.updateScrollbars(); _.delay(function() { - self.updateScrollbars(); + self.updateScrollbars(); }, 2000); }; diff --git a/styles/default.scss b/styles/default.scss new file mode 100644 index 00000000..90472dc1 --- /dev/null +++ b/styles/default.scss @@ -0,0 +1,306 @@ +/* Default style +=============================== */ + +$interface-font: "Avenir Next", "Helvetica", arial, sans-serif; +$prose-font: "PT Serif", "Georgia", serif; +$default-text-color: #212121; + +/* Used as a mixin */ +.contextual-label { + font-family: $interface-font; + font-weight: 600; + font-size: 11px; + text-transform: uppercase; + letter-spacing: .5px; +} + +html { + -webkit-font-smoothing: inherit; +} +body { + font-family: $interface-font; + font-weight: 500; + color: $default-text-color; +} + +/* Links */ +a { + color: #0277BD; + text-decoration: none; +} + +a:hover { + color: #0277BD; +} + +/* Line height overrides */ +.article .resources { + line-height: 24px; +} + +/* Bread crumbs */ +.lens-article .content-node.cover .breadcrumbs a { + @extend .contextual-label; +} + +/* Article title */ +.lens-article .content-node.cover .title { + font-size: 36px; + line-height: 48px; + font-weight: 600; +} + +/* Published date */ +.lens-article .content-node.cover .published-on { + @extend .contextual-label; +} + +.lens-article .content-node.cover .subjects { + @extend .contextual-label; +} + +.lens-article .content-node.cover .subjects a { + font-weight: 600; +} + +/* Authors on cover page */ +.lens-article .content-node.cover .doi { + @extend .contextual-label; + border-top: 1px solid #e0e0e0; + border-bottom: 1px solid #e0e0e0; + margin: 24px 0; + padding: 11px 0; + + a { + text-transform: none; + color: #212121; + font-weight: 600; + } +} + +.lens-article .content-node.cover .authors .text a { + font-weight: 600; + font-size: 16px; + line-height: 24px; + margin: 0 0 12px; + color: $default-text-color; +} + +.lens-article .content-node.cover .authors .text a:hover { + background: none; + // border-bottom: 1px solid #212121; + color: #0277BD; +} + +.lens-article .content-node.cover .authors .contributor_reference.highlighted { + border-bottom: 1px solid #0277BD; + background: none; + color: #0277BD; +} + +/* Links on cover page (PDF, Source XML, Lens JSON) */ +.lens-article .content-node.cover .content .links { + display: none; // hide links to PDF, Source XML, Lens JSON +} +.lens-article .content-node.cover .content .links a { + font-weight: 600; + @extend .contextual-label; +} + +/* Paragraph (only in main content) */ +.surface.content .content-node.paragraph { + font-family: $prose-font; + line-height: 1.5; + font-size: 16px; +} + +/* Headings */ +.content-node.heading.level-1 .content { + font-size: 26px; + line-height: 24px; + padding-top: 24px; + font-weight: 600; +} + +.content-node.heading.level-2 .content { + font-size: 22px; + line-height: 24px; + padding-top: 12px; + font-weight: 600; +} +.surface.content .nodes > .content-node.heading.level-2 { + padding-bottom: 0px; +} + +.content-node.heading.level-3 .content { + font-size: 20px; + line-height: 24px; + padding-top: 12px; + font-weight: 600; +} +.surface.content .nodes > .content-node.heading.level-3 { + padding-bottom: 0px; +} + +.content-node.heading.level-4 .content { + font-size: 18px; + line-height: 24px; + padding-top: 12px; + font-weight: 600; +} +.surface.content .nodes > .content-node.heading.level-4 { + padding-bottom: 0px; +} + +.content-node.heading.level-5 .content { + font-size: 16px; + line-height: 24px; + padding-top: 12px; + font-weight: 600; +} +.surface.content .nodes > .content-node.heading.level-5 { + padding-bottom: 0px; +} + +/* Headings in TOC */ +.resource-view.toc .heading-ref { + color: inherit; // reset color + padding: 0; + margin-bottom: 12px; + border: none; +} + +.resource-view.toc .heading-ref.active { + color: inherit; + border: none; + span { + text-decoration: underline; + } +} + +.resource-view.toc .heading-ref { + font-size: 16px; +} + +.resource-view.toc .heading-ref.level-2 { + font-weight: 500; + font-size: 16px; + padding-left: 24px; +} + +.resource-view.toc .heading-ref.level-3 { + font-weight: 500; + font-size: 16px; + padding-left: 48px; +} + +.resource-view.toc .heading-ref.level-4 { + padding-left: 48px; +} + +/* Reference card */ +.lens-article .resources .content-node.citation .resource-header .name { + font-weight: 600; +} + +.lens-article .resources .content-node.contributor .resource-header .name { + font-weight: 600; + font-size: 14px; +} + +/* Scrollbar default highlight color */ +.node.highlighted { + background: #666; +} + +/* Highlighted resource default style (e.g. contributor) */ +.article .resources .info .content-node.highlighted { + border-left: 3px solid #212121; +} + +/* Citation resource */ +.lens-article .resources .content-node.citation .resource-header .name { + @extend .contextual-label; +} + +.lens-article .resources .content-node.citation { + font-family: $prose-font; +} + +.lens-article .content-node.citation .content .authors { + font-family: $interface-font; + font-weight: 600; + font-size: 14px; +} + +.lens-article .content-node.citation .content .source, +.lens-article .content-node.citation .content .doi { + font-size: 11px; +} + +/* Change font-size for all resource nodes */ +.lens-article .resources .figures .nodes > .content-node { + font-size: 13px; +} + +/* Caption title (e.g. figure, table) */ +.lens-article .content-node.caption > .content > .content-node.caption-title { + font-size: 13px; + padding-bottom: 0px; +} + +/* Label for resources */ +.article .resources .resource-header .name { + font-size: 13px; +} + +/* HACK: Make sure always the interface font is in toggle buttons (focus, fullscreen) */ +.article .resources .content-node .resource-header .toggles { + font-family: $interface-font; +} + +/* Resource view */ + +.panel.info { + font-family: $prose-font; +} + +.lens-article .content-node.publication-info .label { + font-family: $interface-font; +} + +.article .resources .nodes > .content-node.publication-info .content-node[data-id=articleinfo] .heading .content { + font-weight: 600; +} + +.lens-article .content-node.contributor .label { + font-family: $interface-font; + color: $default-text-color; + font-weight: 600; +} + + +.article .resources .nodes > .content-node.publication-info .content-node[data-id=articleinfo] .heading.level-3 .content { + font-family: $interface-font; + font-weight: 600; +} + +.lens-article .resources .content-node.contributor .resource-header .name { + @extend .contextual-label; + font-size: 11px; +} + +.lens-article .content-node.publication-info .label { + color: $default-text-color; + font-weight: 600; + font-size: 14px; +} + +.lens-article .content-node.contributor .contributor-name { + font-family: $interface-font; + font-weight: 600; + font-size: 20px; + line-height: 24px; + margin-top: 12px; + margin-bottom: 12px; +} diff --git a/styles/lens.css b/styles/lens.css index 198d3f24..a49f28d6 100644 --- a/styles/lens.css +++ b/styles/lens.css @@ -11,11 +11,10 @@ body { margin: 0; background-color: white; -/* -moz-transition: background-color 200ms linear; + /* -moz-transition: background-color 200ms linear; -o-transition: background-color 200ms linear; -webkit-transition: background-color 200ms linear; transition: background-color 200ms linear;*/ - } body.loading { @@ -43,7 +42,7 @@ body.reader { } /* -General Layout +General Layout --------------------------------------- */ #container { @@ -67,7 +66,7 @@ a { color: #1B6685; font-weight: normal; text-decoration: none; - + /* -moz-transition: background-color 100ms linear, color 100ms linear, opacity 100ms linear; -o-transition: background-color 100ms linear, color 100ms linear, opacity 100ms linear; -webkit-transition: background-color 100ms linear, color 100ms linear, opacity 100ms linear; @@ -86,7 +85,7 @@ img { strong { font-weight: 700; } -h1, h2, h3 { +h1, h2, h3 { font-weight: 700; } @@ -94,16 +93,16 @@ h1 a { color: white; } h1 a:hover { color: white; } h2 { - font-size: 1.75em; + font-size: 1.75em; padding-bottom: 20px; } - + h3, h4, h5, h6 { margin-bottom: 20px; font-size: 1em; font-weight: 700; } - + p { padding-bottom: 20px; } @@ -154,6 +153,54 @@ p:last-child { padding-bottom: 0; } color: gold; } +.author-callout-style-a1 { + color: rgb(54, 107, 251); // Blue +} + +.author-callout-style-a2 { + color: rgb(156, 39, 176); // Purple +} + +.author-callout-style-a3 { + color: rgb(213, 0, 0); // Red +} + +.author-callout-style-b1 { + background-color: rgb(144, 202, 249); // Blue +} + +.author-callout-style-b2 { + background-color: rgb(197, 225, 165); // Green +} + +.author-callout-style-b3 { + background-color: rgb(255, 183, 77); // Orange +} + +.author-callout-style-b4 { + background-color: rgb(255, 241, 118); // Yellow +} + +.author-callout-style-b5 { + background-color: rgb(158, 134, 201); // Purple +} + +.author-callout-style-b6 { + background-color: rgb(229, 115, 115); // Red +} + +.author-callout-style-b7 { + background-color: rgb(244, 143, 177); // Pink +} + +.author-callout-style-b8 { + background-color: rgb(230, 230, 230); // Grey +} + +.lens-article .content-node.cover .subjects a:not(:last-child):after { + content: ', ' +} + /* main --------------------------------------- */ @@ -195,7 +242,7 @@ body.loading .spinner-wrapper { .spinner-wrapper .spinner { width: 40px; height: 40px; - margin: 0 auto; + margin: 0 auto; background: #444; -webkit-animation: rotateplane 1.2s infinite ease-in-out; animation: rotateplane 1.2s infinite ease-in-out; diff --git a/styles/reader.css b/styles/reader.css index 251425e7..7a3a6af7 100644 --- a/styles/reader.css +++ b/styles/reader.css @@ -146,7 +146,7 @@ out (on iOS 5.1), or flicker (on iOS 6). */ position: relative; } -/* It's not exactly good to have the overflow-y: scroll for the container AND the surface. +/* It's not exactly good to have the overflow-y: scroll for the container AND the surface. There should be just one overflowing container, if possible */ @@ -169,7 +169,6 @@ out (on iOS 5.1), or flicker (on iOS 6). */ right: 0px; overflow-y: scroll; overflow-x: hidden; - font-size: 14px; -webkit-overflow-scrolling: touch; } @@ -184,7 +183,6 @@ out (on iOS 5.1), or flicker (on iOS 6). */ } .article .resources .nodes > .content-node { - color: #505050; position: relative; background: #fff; border-bottom: 1px solid #ddd; @@ -237,8 +235,6 @@ out (on iOS 5.1), or flicker (on iOS 6). */ .article .resources .resource-header .name { display: block; min-height: 30px; - - color: #444; font-size: 16px; line-height: 21px; padding: 0px 20px; @@ -404,7 +400,7 @@ span.annotation.formula_reference, span.publication_reference { .content-node .question { background-color: rgba(16, 167, 222, 0.3); } .content-node .error { background-color: rgba(237, 96, 48, 0.3); } -.content-node .link { color: #1B6685; font-weight: bold; } +.content-node .link { font-weight: bold; } .content-node .link:hover, .content-node .link.highlighted { color: rgba(11, 157, 217, 1); } /* Inline Code Annotations */ @@ -755,8 +751,11 @@ TOC /* Colors for scroll-bar overlays */ .article .resources .figures .surface-scrollbar .highlighted, -.article .content .surface-scrollbar .highlighted.figure_reference +.article .content .surface-scrollbar .highlighted.figure_reference, +.article .content .surface-scrollbar .highlighted.figure { background-color: rgba(145, 187, 4, 1); } +.article .content .surface-scrollbar .highlighted.figure.main-occurrence + { border-right: 3px solid #5C6148; } .article .resources .citations .surface-scrollbar .highlighted, .article .content .surface-scrollbar .highlighted.citation_reference @@ -783,4 +782,40 @@ TOC .panel.document .nodes > .content-node > .focus-handle:hover { border-left: 3px solid #bbb; -} \ No newline at end of file +} + + +/* Footnote +=============================== */ + +.content-node.footnote { + font-size: 13px; + display: block !important; + margin: 20px 0; + border: 1px solid #ddd; + border-radius: 5px; + padding: 20px; +} + +.footnote-reference > a { + vertical-align: super; + padding: 0 2px; + border: 1px solid #ddd; + font-size: 13px; +} + +.footnote-reference > a:hover { + background: #eee; +} + +.footnote-reference.sm-expanded > a { + background: #ddd; +} + +.footnote-reference > .footnote { + display: none !important; +} + +.footnote-reference.sm-expanded >.footnote { + display: block !important; +} diff --git a/substance/document/container.js b/substance/document/container.js index c139a3c4..8510e9f3 100644 --- a/substance/document/container.js +++ b/substance/document/container.js @@ -136,6 +136,10 @@ Container.Prototype = function() { return this.listView.length; }; + this.hasContent = function(panelType) { + return this.listView.length > 0 || (panelType === 'resource' && this.treeView.length > 0); + }; + // Returns true if there is another node after a given position. // -------- // diff --git a/substance/util/getRelativeBoundingRect.js b/substance/util/getRelativeBoundingRect.js new file mode 100644 index 00000000..3a8d4186 --- /dev/null +++ b/substance/util/getRelativeBoundingRect.js @@ -0,0 +1,95 @@ +'use strict'; + +var _ = require("underscore"); +var map = _.map; +var forEach = _.each; + +/* + Calculate a bounding rectangle for a set of rectangles. + + Note: Here, `bounds.right` and `bounds.bottom` are relative to + the left top of the viewport. +*/ +function _getBoundingRect(rects) { + var bounds = { + left: Number.POSITIVE_INFINITY, + top: Number.POSITIVE_INFINITY, + right: Number.NEGATIVE_INFINITY, + bottom: Number.NEGATIVE_INFINITY, + width: Number.NaN, + height: Number.NaN + }; + + forEach(rects, function(rect) { + if (rect.left < bounds.left) { + bounds.left = rect.left; + } + if (rect.top < bounds.top) { + bounds.top = rect.top; + } + if (rect.left + rect.width > bounds.right) { + bounds.right = rect.left + rect.width; + } + if (rect.top + rect.height > bounds.bottom) { + bounds.bottom = rect.top + rect.height; + } + }); + bounds.width = bounds.right - bounds.left; + bounds.height = bounds.bottom - bounds.top; + return bounds; +} + +/* + Calculate the bounding rect of a single element relative to a parent. + + The rectangle dimensions are calculated as the union of the given elements + clientRects. A selection fragment, for example, may appear as a multi-line span + element that consists of a single client rect per line of text in variable widths. +*/ +function _getBoundingOffsetsRect(el, relativeParentEl) { + var relativeParentElRect = relativeParentEl.getBoundingClientRect(); + var elRect = _getBoundingRect(el.getClientRects()); + + var left = elRect.left - relativeParentElRect.left; + var top = elRect.top - relativeParentElRect.top; + return { + left: left, + top: top, + right: relativeParentElRect.width - left - elRect.width, + bottom: relativeParentElRect.height - top - elRect.height, + width: elRect.width, + height: elRect.height + }; +} + +/** + Get bounding rectangle relative to a given parent element. Allows multiple + elements being passed (we need this for selections that consist of multiple + selection fragments). Takes a relative parent element that is used as a + reference point, instead of the browser's viewport. + + @param {Array} els elements to compute the bounding rectangle for + @param {DOMElement} containerEl relative parent used as a reference point + @return {object} rectangle description with left, top, right, bottom, width and height +*/ +function getRelativeBoundingRect(els, containerEl) { + if (els.length === undefined) { + els = [els]; + } + var elRects = map(els, function(el) { + return _getBoundingOffsetsRect(el, containerEl); + }); + + var elsRect = _getBoundingRect(elRects); + var containerElRect = containerEl.getBoundingClientRect(); + return { + left: elsRect.left, + top: elsRect.top, + right: containerElRect.width - elsRect.left - elsRect.width, + bottom: containerElRect.height - elsRect.top - elsRect.height, + width: elsRect.width, + height: elsRect.height + }; +} + +module.exports = getRelativeBoundingRect; \ No newline at end of file