├── .gitignore ├── .jshintrc ├── LICENSE ├── LensController.js ├── LensReader.js ├── LensWriter.js ├── README.md ├── app ├── app.js ├── app.scss ├── assets │ ├── fonts │ │ ├── font-awesome-4.4.0 │ │ │ ├── HELP-US-OUT.txt │ │ │ ├── css │ │ │ │ ├── font-awesome.css │ │ │ │ └── font-awesome.min.css │ │ │ ├── fonts │ │ │ │ ├── FontAwesome.otf │ │ │ │ ├── fontawesome-webfont.eot │ │ │ │ ├── fontawesome-webfont.svg │ │ │ │ ├── fontawesome-webfont.ttf │ │ │ │ ├── fontawesome-webfont.woff │ │ │ │ └── fontawesome-webfont.woff2 │ │ │ ├── less │ │ │ │ ├── animated.less │ │ │ │ ├── bordered-pulled.less │ │ │ │ ├── core.less │ │ │ │ ├── fixed-width.less │ │ │ │ ├── font-awesome.less │ │ │ │ ├── icons.less │ │ │ │ ├── larger.less │ │ │ │ ├── list.less │ │ │ │ ├── mixins.less │ │ │ │ ├── path.less │ │ │ │ ├── rotated-flipped.less │ │ │ │ ├── stacked.less │ │ │ │ └── variables.less │ │ │ └── scss │ │ │ │ ├── _animated.scss │ │ │ │ ├── _bordered-pulled.scss │ │ │ │ ├── _core.scss │ │ │ │ ├── _fixed-width.scss │ │ │ │ ├── _icons.scss │ │ │ │ ├── _larger.scss │ │ │ │ ├── _list.scss │ │ │ │ ├── _mixins.scss │ │ │ │ ├── _path.scss │ │ │ │ ├── _rotated-flipped.scss │ │ │ │ ├── _stacked.scss │ │ │ │ ├── _variables.scss │ │ │ │ └── font-awesome.scss │ │ └── lato │ │ │ ├── lato-bold-italic-latin-ext.woff2 │ │ │ ├── lato-bold-italic-latin.woff2 │ │ │ ├── lato-bold-latin-ext.woff2 │ │ │ ├── lato-bold-latin.woff2 │ │ │ ├── lato-italic-latin-ext.woff2 │ │ │ ├── lato-italic-latin.woff2 │ │ │ ├── lato-regular-latin-ext.woff2 │ │ │ ├── lato-regular-latin.woff2 │ │ │ └── lato.scss │ └── index.html └── backend.js ├── data ├── example-doc-old.xml ├── example-doc.xml └── small-example.xml ├── gulpfile.js ├── i18n ├── de.js └── en.js ├── legacy ├── cite_tool.js ├── export_tool.js ├── insert_figure_tool.js └── insert_table_tool.js ├── lens.sublime-project ├── model ├── Collection.js ├── LensArticle.js ├── LensArticleExporter.js ├── LensArticleImporter.js ├── articleSchema.js └── defaultLensArticle.js ├── package.json ├── packages ├── bibliography │ ├── AddBibItemsPanel.js │ ├── BibItem.js │ ├── BibItemCitation.js │ ├── BibItemCitationCommand.js │ ├── BibItemCitationTool.js │ ├── BibItemCitationXMLConverter.js │ ├── BibItemComponent.js │ ├── BibItemEntry.js │ ├── BibItemXMLConverter.js │ ├── BibItemsPanel.js │ ├── Bibliography.js │ ├── BibliographyComponent.js │ ├── BibliographySummary.js │ ├── CiteprocCompiler.js │ ├── CiteprocDefaultConfig.js │ ├── CrossrefSearch.js │ └── citeproc │ │ ├── citeproc.js │ │ ├── csl_jquery.js │ │ ├── csl_nodejs_jsdom.js │ │ └── xmldom.js ├── citations │ ├── Citation.js │ ├── CitationCommand.js │ ├── CitationComponent.js │ ├── CitationXMLConverter.js │ ├── CitePanel.js │ └── citation.scss ├── figures │ ├── ImageFigure.js │ ├── ImageFigureCitation.js │ ├── ImageFigureCitationCommand.js │ ├── ImageFigureCitationTool.js │ ├── ImageFigureCitationXMLConverter.js │ ├── ImageFigureEntry.js │ ├── ImageFigureXMLConverter.js │ └── ManageCollectionComponent.js ├── metadata │ ├── ArticleMeta.js │ ├── Author.js │ └── MetadataXMLConverter.js ├── reader │ └── Cover.js └── writer │ ├── CoverEditor.js │ └── WriterTools.js ├── server.js └── styles ├── _lens.scss ├── _wild_overrides.scss ├── components ├── _add-bib-items-panel.scss ├── _bib-item.scss ├── _bib-items-panel.scss └── _cite-panel.scss ├── lens-reader.scss └── lens-writer.scss /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Bundles 6 | dist 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # Commenting this out is preferred by some people, see 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 28 | node_modules 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | 33 | 34 | *.sqlite 35 | *.sqlite3 -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "node": true, 4 | "devel": true, 5 | "latedef": true, 6 | "undef": true, 7 | "unused": true, 8 | "sub": true, 9 | "predef": [ 10 | "localStorage", 11 | "window", 12 | "document", 13 | "i18n" 14 | ] 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Oliver Buchtala, Michael Aufreiter 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /LensController.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var extend = require('lodash/object/extend'); 4 | var $ = require('substance/util/jquery'); 5 | var TwoPanelController = require('substance/ui/TwoPanelController'); 6 | var CrossrefSearch = require('./packages/bibliography/CrossrefSearch'); 7 | var CiteprocCompiler = require('./packages/bibliography/CiteprocCompiler'); 8 | 9 | var I18n = require('substance/ui/i18n'); 10 | I18n.instance.load(require('./i18n/en')); 11 | 12 | function LensController() { 13 | LensController.super.apply(this, arguments); 14 | 15 | this.handleActions({ 16 | 'toggleBibItem': this.toggleBibItem, 17 | }); 18 | } 19 | 20 | LensController.Prototype = function() { 21 | 22 | var _super = Object.getPrototypeOf(this); 23 | 24 | this.getContentPanel = function() { 25 | return this.refs.contentPanel; 26 | }; 27 | 28 | this.didMount = function() { 29 | _super.didMount.call(this); 30 | var doc = this.props.documentSession.getDocument(); 31 | doc.citeprocCompiler = new CiteprocCompiler(); 32 | }; 33 | 34 | // Action used by BibItemComponent when clicked on focus 35 | this.toggleBibItem = function(bibItem) { 36 | if (this.state.bibItemId === bibItem.id) { 37 | this.setState({ 38 | contextId: 'bib-items' 39 | }); 40 | } else { 41 | this.setState({ 42 | contextId: 'bib-items', 43 | bibItemId: bibItem.id 44 | }); 45 | } 46 | }; 47 | 48 | // Some things should go into controller 49 | this.getChildContext = function() { 50 | var childContext = _super.getChildContext.call(this); 51 | return extend(childContext, { 52 | bibSearchEngines: [new CrossrefSearch()], 53 | // Used for turning embed urls to HTML content 54 | embedResolver: function(srcUrl, cb) { 55 | $.get('http://iframe.ly/api/iframely?url='+encodeURIComponent(srcUrl)+'&api_key=712fe98e864c79e054e2da') 56 | // $.get('http://iframely.coko.foundation/iframely?url='+encodeURIComponent(srcUrl)+'') 57 | .success(function(res) { 58 | cb(null, res.html); 59 | }) 60 | .error(function(err) { 61 | cb(err); 62 | }); 63 | } 64 | }); 65 | }; 66 | 67 | 68 | // Action handlers 69 | // --------------- 70 | 71 | 72 | // Hande Writer state change updates 73 | // -------------- 74 | // 75 | // Here we update highlights 76 | 77 | this.handleStateUpdate = function(newState) { 78 | var doc = this.getDocument(); 79 | 80 | function getFigureHighlights(state) { 81 | if (state.citationId) { 82 | var citation = doc.get(state.citationId); 83 | if (citation && ['image-figure', 'table-figure'].indexOf(citation.getItemType()) >= 0) { 84 | return [ state.citationId ].concat(citation.targets); 85 | } 86 | } 87 | return []; 88 | } 89 | 90 | function getBibItemHighlights(state) { 91 | if (state.bibItemId) { 92 | // Get citations for a given target 93 | var citations = Object.keys(doc.citationsIndex.get(state.bibItemId)); 94 | return citations; 95 | } else if (state.citationId) { 96 | var citation = doc.get(state.citationId); 97 | if (citation && citation.getItemType() === 'bib-item') { 98 | return [ state.citationId ].concat(citation.targets); 99 | } 100 | } 101 | return []; 102 | } 103 | 104 | var bibItemHighlights = getBibItemHighlights(newState); 105 | var figureHighlights = getFigureHighlights(newState); 106 | 107 | // HACK: updates the highlights when state 108 | // transition has finished 109 | setTimeout(function() { 110 | this.contentHighlights.set({ 111 | 'bib-item': bibItemHighlights, 112 | 'figure': figureHighlights 113 | }); 114 | }.bind(this), 0); 115 | }; 116 | }; 117 | 118 | TwoPanelController.extend(LensController); 119 | 120 | module.exports = LensController; 121 | -------------------------------------------------------------------------------- /LensReader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var LensController = require('./LensController'); 4 | var BibliographyComponent = require('./packages/bibliography/BibliographyComponent'); 5 | var ContainerAnnotator = require('substance/ui/ContainerAnnotator'); 6 | var SplitPane = require("substance/ui/SplitPane"); 7 | var ScrollPane = require("substance/ui/ScrollPane"); 8 | var Cover = require('./packages/reader/Cover'); 9 | var Component = require('substance/ui/Component'); 10 | var $$ = Component.$$; 11 | 12 | var CONFIG = { 13 | controller: { 14 | commands: [ 15 | require('substance/ui/UndoCommand'), 16 | require('substance/ui/RedoCommand'), 17 | require('substance/ui/SaveCommand') 18 | ], 19 | components: { 20 | "paragraph": require('substance/packages/paragraph/ParagraphComponent'), 21 | "heading": require('substance/packages/heading/HeadingComponent'), 22 | "blockquote": require('substance/packages/blockquote/BlockquoteComponent'), 23 | "codeblock": require('substance/packages/codeblock/CodeblockComponent'), 24 | "embed": require('substance/packages/embed/EmbedComponent'), 25 | "image": require('substance/packages/image/ImageComponent'), 26 | // "table": require('substance/packages/table/TableComponent'), 27 | 28 | "image-figure": require('substance/packages/figure/FigureComponent'), 29 | // "table-figure": require('substance/packages/figure/FigureComponent'), 30 | 31 | "bib-item-citation": require('./packages/citations/CitationComponent'), 32 | "image-figure-citation": require('./packages/citations/CitationComponent'), 33 | // "table-figure-citation": require('./packages/citations/CitationComponent'), 34 | 35 | // Panels 36 | "toc": require('substance/ui/TOCPanel'), 37 | "cite": require('./packages/citations/CitePanel'), 38 | "bib-items": require('./packages/bibliography/BibItemsPanel'), 39 | 40 | // We use different states for the same panel, so we can distinguish 41 | // the citation type based on state.contextId 42 | "cite-bib-item": require('./packages/citations/CitePanel'), 43 | "cite-image-figure": require('./packages/citations/CitePanel'), 44 | // "cite-table-figure": require('./packages/citations/CitePanel'), 45 | 46 | "bib-item-entry": require('./packages/bibliography/BibItemEntry'), 47 | "image-figure-entry": require('./packages/figures/ImageFigureEntry'), 48 | }, 49 | }, 50 | main: { 51 | commands: [], 52 | }, 53 | cover: { 54 | commands: [] 55 | }, 56 | panels: { 57 | 'toc': { 58 | }, 59 | 'bib-items': { 60 | } 61 | }, 62 | tabOrder: ['toc', 'bib-items'], 63 | containerId: 'main', 64 | isEditable: false 65 | }; 66 | 67 | function LensReader() { 68 | LensReader.super.apply(this, arguments); 69 | 70 | this.connect(this, { 71 | 'citation:selected': this.onCitationSelected 72 | }); 73 | } 74 | 75 | LensReader.Prototype = function() { 76 | 77 | var _super = Object.getPrototypeOf(this); 78 | 79 | this.dispose = function() { 80 | LensController.prototype.dispose.call(this); 81 | this.disconnect(this); 82 | }; 83 | 84 | this.render = function() { 85 | return _super.render.call(this) 86 | .addClass('sc-lens sc-lens-reader sc-controller'); 87 | }; 88 | 89 | this._renderMainSection = function() { 90 | var config = this.getConfig(); 91 | 92 | return $$('div').ref('main').addClass('se-main-section').append( 93 | // Content Panel below 94 | $$(ScrollPane, { 95 | scrollbarType: 'substance', 96 | scrollbarPosition: 'left', 97 | toc: this.toc, 98 | highlights: this.contentHighlights 99 | }).ref('contentPanel').append( 100 | $$(Cover, { 101 | name: 'cover', 102 | commands: config.cover.commands 103 | }).ref('coverView'), 104 | // The full fledged document (ContainerEditor) 105 | $$("div").ref('main').addClass('document-content').append( 106 | $$(ContainerAnnotator, { 107 | name: 'main', 108 | editable: false, 109 | containerId: config.containerId, 110 | commands: config.main.commands 111 | }).ref('mainEditor') 112 | ), 113 | $$(BibliographyComponent).ref('bib') 114 | ) 115 | ); 116 | }; 117 | 118 | this.onCitationSelected = function(citation) { 119 | if (this.state.citationId === citation.id) { 120 | this.setState({ 121 | contextId: 'toc' 122 | }); 123 | return; 124 | } 125 | if (citation.type === 'bib-item-citation') { 126 | this.setState({ 127 | contextId: 'bib-items', 128 | citationId: citation.id 129 | }); 130 | } else { 131 | this.setState({ 132 | contextId: 'toc', 133 | citationId: citation.id 134 | }); 135 | } 136 | }; 137 | 138 | }; 139 | 140 | LensController.extend(LensReader); 141 | 142 | LensReader.static.config = CONFIG; 143 | 144 | module.exports = LensReader; 145 | -------------------------------------------------------------------------------- /LensWriter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var LensController = require('./LensController'); 4 | var SplitPane = require('substance/ui/SplitPane'); 5 | var ScrollPane = require('substance/ui/ScrollPane'); 6 | var BibliographyComponent = require('./packages/bibliography/BibliographyComponent'); 7 | var CoverEditor = require('./packages/writer/CoverEditor'); 8 | var Toolbar = require('substance/ui/Toolbar'); 9 | var WriterTools = require('./packages/writer/WriterTools'); 10 | var ContainerEditor = require('substance/ui/ContainerEditor'); 11 | var docHelpers = require('substance/model/documentHelpers'); 12 | var Component = require('substance/ui/Component'); 13 | var $$ = Component.$$; 14 | 15 | var CONFIG = { 16 | controller: { 17 | commands: [ 18 | require('substance/ui/UndoCommand'), 19 | require('substance/ui/RedoCommand'), 20 | require('substance/ui/SaveCommand') 21 | ], 22 | components: { 23 | "paragraph": require('substance/packages/paragraph/ParagraphComponent'), 24 | "heading": require('substance/packages/heading/HeadingComponent'), 25 | "blockquote": require('substance/packages/blockquote/BlockquoteComponent'), 26 | "codeblock": require('substance/packages/codeblock/CodeblockComponent'), 27 | "embed": require('substance/packages/embed/EmbedComponent'), 28 | "image": require('substance/packages/image/ImageComponent'), 29 | // "table": require('substance/packages/table/TableComponent'), 30 | 31 | "image-figure": require('substance/packages/figure/FigureComponent'), 32 | // "table-figure": require('substance/packages/figure/FigureComponent'), 33 | 34 | "bib-item-citation": require('./packages/citations/CitationComponent'), 35 | "image-figure-citation": require('./packages/citations/CitationComponent'), 36 | // "table-figure-citation": require('./packages/citations/CitationComponent'), 37 | 38 | // Panels 39 | "toc": require('substance/ui/TOCPanel'), 40 | "cite": require('./packages/citations/CitePanel'), 41 | "bib-items": require('./packages/bibliography/BibItemsPanel'), 42 | "add-bib-items": require('./packages/bibliography/AddBibItemsPanel'), 43 | 44 | // We use different states for the same panel, so we can distinguish 45 | // the citation type based on state.contextId 46 | "cite-bib-item": require('./packages/citations/CitePanel'), 47 | "cite-image-figure": require('./packages/citations/CitePanel'), 48 | // "cite-table-figure": require('./packages/citations/CitePanel'), 49 | 50 | "bib-item-entry": require('./packages/bibliography/BibItemEntry'), 51 | "image-figure-entry": require('./packages/figures/ImageFigureEntry'), 52 | }, 53 | }, 54 | main: { 55 | commands: [ 56 | // Special commands 57 | require('substance/packages/embed/EmbedCommand'), 58 | 59 | require('substance/packages/text/SwitchTextTypeCommand'), 60 | require('substance/packages/strong/StrongCommand'), 61 | require('substance/packages/emphasis/EmphasisCommand'), 62 | require('substance/packages/link/LinkCommand'), 63 | require('substance/packages/subscript/SubscriptCommand'), 64 | require('substance/packages/superscript/SuperscriptCommand'), 65 | require('substance/packages/code/CodeCommand'), 66 | 67 | // Insert figure 68 | require('substance/packages/figure/InsertFigureCommand'), 69 | require('./packages/bibliography/BibItemCitationCommand'), 70 | require('./packages/figures/ImageFigureCitationCommand'), 71 | ], 72 | textTypes: [ 73 | {name: 'paragraph', data: {type: 'paragraph'}}, 74 | {name: 'heading1', data: {type: 'heading', level: 1}}, 75 | {name: 'heading2', data: {type: 'heading', level: 2}}, 76 | {name: 'heading3', data: {type: 'heading', level: 3}}, 77 | {name: 'codeblock', data: {type: 'codeblock'}}, 78 | {name: 'blockquote', data: {type: 'blockquote'}} 79 | ] 80 | }, 81 | title: { 82 | commands: [ 83 | require('substance/packages/emphasis/EmphasisCommand'), 84 | require('substance/packages/text/SwitchTextTypeCommand'), 85 | require('substance/packages/subscript/SubscriptCommand'), 86 | require('substance/packages/superscript/SuperscriptCommand') 87 | ] 88 | }, 89 | abstract: { 90 | commands: [ 91 | require('substance/packages/text/SwitchTextTypeCommand'), 92 | require('substance/packages/emphasis/EmphasisCommand'), 93 | require('substance/packages/strong/StrongCommand'), 94 | require('substance/packages/subscript/SubscriptCommand'), 95 | require('substance/packages/superscript/SuperscriptCommand'), 96 | require('substance/packages/link/LinkCommand') 97 | ] 98 | }, 99 | panels: { 100 | 'toc': { 101 | }, 102 | 'bib-items': { 103 | }, 104 | 'cite-bib-item': { 105 | isDialog: true 106 | }, 107 | 'cite-image-figure': { 108 | idDialog: true 109 | }, 110 | 'add-bib-items': { 111 | isDialog: true 112 | } 113 | }, 114 | tabOrder: ['toc', 'bib-items'], 115 | containerId: 'main', 116 | isEditable: true 117 | }; 118 | 119 | function LensWriter() { 120 | LensWriter.super.apply(this, arguments); 121 | } 122 | 123 | LensWriter.Prototype = function() { 124 | 125 | var _super = Object.getPrototypeOf(this); 126 | 127 | this.render = function() { 128 | return _super.render.call(this) 129 | .addClass('sc-lens sc-lens-writer'); 130 | }; 131 | 132 | this._renderMainSection = function() { 133 | var config = this.getConfig(); 134 | 135 | return $$('div').ref('main').addClass('se-main-section').append( 136 | $$(SplitPane, {splitType: 'horizontal'}).append( 137 | // Menu Pane on top 138 | $$(Toolbar).ref('toolbar').append($$(WriterTools)), 139 | // Content Panel below 140 | $$(ScrollPane, { 141 | scrollbarType: 'substance', 142 | scrollbarPosition: 'left', 143 | toc: this.toc, 144 | highlights: this.contentHighlights 145 | }).ref('contentPanel').append( 146 | $$(CoverEditor).ref('coverEditor'), 147 | // The full fledged document (ContainerEditor) 148 | $$("div").ref('main').addClass('document-content').append( 149 | $$(ContainerEditor, { 150 | name: 'main', 151 | containerId: config.containerId, 152 | commands: config.main.commands, 153 | textTypes: config.main.textTypes 154 | }).ref('mainEditor') 155 | ), 156 | $$(BibliographyComponent).ref('bib') 157 | ) 158 | ).ref('mainSectionSplitPane') 159 | ); 160 | }; 161 | 162 | this.onSelectionChanged = function(sel, surface) { 163 | var doc; 164 | function getActiveAnno(type) { 165 | return docHelpers.getAnnotationsForSelection(doc, sel, type, 'main')[0]; 166 | } 167 | 168 | if (surface.name !== "main") return; 169 | if (sel.isNull() || !sel.isPropertySelection()) { 170 | return; 171 | } 172 | if (sel.equals(this.prevSelection)) { 173 | return; 174 | } 175 | 176 | this.prevSelection = sel; 177 | doc = surface.getDocument(); 178 | var citation = getActiveAnno('citation'); 179 | 180 | if (citation && citation.getSelection().equals(sel)) { 181 | // Trigger state change 182 | var citationType = citation.type.replace('-citation', '').replace('_', '-'); 183 | 184 | this.setState({ 185 | contextId: "cite-"+citationType, 186 | citationType: citationType, 187 | citationId: citation.id 188 | }); 189 | } else { 190 | if (this.state.contextId !== 'toc') { 191 | this.setState({ 192 | contextId: "toc" 193 | }); 194 | } 195 | } 196 | }; 197 | }; 198 | 199 | LensController.extend(LensWriter); 200 | LensWriter.static.config = CONFIG; 201 | 202 | module.exports = LensWriter; 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **No longer maintained. Use http://github.com/substance/texture instead.** 2 | 3 | # Lens (editable edition) 4 | 5 | This is an evolution of [eLife Lens](http://github.com/elifesciences/lens), developed by [Substance](http://substance.io). It comes with a Writer component for web-based authoring and a new Reader component for displaying of scientific content. 6 | 7 | Read about the backgrounds of this project: 8 | 9 | - https://medium.com/@_mql/self-host-a-scientific-journal-with-elife-lens-f420afb678aa 10 | - https://medium.com/@_mql/produce-a-scientific-paper-with-lens-writer-d0fc75d11919 11 | 12 | *Important note: This project is at an experimental state. It is also not compatible with JATS/NLM at this stage, as it reads a simplified custom XML format. We will add support for JATS import + export at a later time.* 13 | 14 | ## Install dev version 15 | 16 | Clone the repository. 17 | 18 | ```bash 19 | $ git clone https://github.com/substance/lens.git 20 | ``` 21 | 22 | Navigate to the source directory. 23 | 24 | ```bash 25 | $ cd lens 26 | ``` 27 | 28 | Install dependencies via npm 29 | 30 | ```bash 31 | $ npm install 32 | ``` 33 | 34 | Start the dev server 35 | 36 | ```bash 37 | $ npm run start 38 | ``` 39 | 40 | And navigate to [http://localhost:5000](http://localhost:5000) 41 | 42 | To create a new demo bundle do this: 43 | 44 | ```bash 45 | $ npm run bundle 46 | ``` 47 | 48 | ## Usage 49 | 50 | To embed Lens Reader: 51 | 52 | ```js 53 | var LensReader = require('lens/LensReader'); 54 | var LensArticle = require('lens/model/LensArticle'); 55 | var Component = require('substance/ui/component'); 56 | var $$ = Component.$$; 57 | 58 | var doc = LensArticle.fromXml(LENS_XML); 59 | 60 | Component.mount($$(LensReader, { 61 | doc: doc 62 | }), document.body); 63 | ``` 64 | 65 | To embed Lens Writer: 66 | 67 | ```js 68 | var LensWriter = require('lens/LensWriter'); 69 | var LensArticle = require('lens/model/LensArticle'); 70 | var Component = require('substance/ui/component'); 71 | var $$ = Component.$$; 72 | 73 | var doc = LensArticle.fromXml(LENS_XML); 74 | 75 | Component.mount($$(LensWriter, { 76 | doc: doc, 77 | onUploadFile: function(file, cb) { 78 | console.log('custom file upload handler in action...'); 79 | var fileUrl = window.URL.createObjectURL(file); 80 | cb(null, fileUrl); 81 | }, 82 | onSave: function(doc, changes, cb) { 83 | console.log('custom save handler in action...', doc.toXml()); 84 | cb(null); 85 | } 86 | }), document.body); 87 | ``` 88 | 89 | Make sure to also include the stylesheets into your app. We provide entry points at `styles/lens-writer.sass` and `styles/lens-reader.sass`. Lens requires a module bundler, such as Browserify or Webpack. 90 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Component = require('substance/ui/Component'); 4 | var $$ = Component.$$; 5 | var Backend = require("./backend"); 6 | var DocumentSession = require('substance/model/DocumentSession'); 7 | var $ = window.$ = require('substance/util/jquery'); 8 | var LensWriter = require('../LensWriter'); 9 | var LensReader = require('../LensReader'); 10 | var Router = require('substance/ui/Router'); 11 | var LensArticleExporter = require('../model/LensArticleExporter'); 12 | var exporter = new LensArticleExporter(); 13 | 14 | function App() { 15 | Component.apply(this, arguments); 16 | this.backend = new Backend(); 17 | } 18 | 19 | App.Prototype = function() { 20 | 21 | this.getInitialContext = function() { 22 | return { 23 | router: new Router(this) 24 | }; 25 | }; 26 | 27 | this.getInitialState = function() { 28 | return { 29 | mode: 'write' 30 | }; 31 | }; 32 | 33 | this.openReader = function() { 34 | this.extendState({ 35 | mode: 'read' 36 | }); 37 | }; 38 | 39 | this.openWriter = function() { 40 | this.extendState({ 41 | mode: 'write' 42 | }); 43 | }; 44 | 45 | this.render = function() { 46 | var el = $$('div').addClass('app'); 47 | 48 | el.append( 49 | $$('div').addClass('menu').append( 50 | $$('button') 51 | .addClass(this.state.mode ==='write' ? 'active': '') 52 | .on('click', this.openWriter) 53 | .append('Write'), 54 | $$('button') 55 | .addClass(this.state.mode ==='read' ? 'active': '') 56 | .on('click', this.openReader) 57 | .append('Read') 58 | ) 59 | ); 60 | 61 | if (this.documentSession) { 62 | var lensEl; 63 | if (this.state.mode === 'write') { 64 | lensEl = $$(LensWriter, { 65 | documentSession: this.documentSession, 66 | onUploadFile: function(file, cb) { 67 | console.log('custom file upload handler in action...'); 68 | var fileUrl = window.URL.createObjectURL(file); 69 | cb(null, fileUrl); 70 | }, 71 | onSave: function(doc, changes, cb) { 72 | var xml = exporter.exportDocument(doc); 73 | console.log('XML', xml); 74 | cb(null); 75 | } 76 | }).ref('writer').route(); 77 | } else { 78 | lensEl = $$(LensReader, { 79 | documentSession: this.documentSession 80 | }).ref('reader').route(); 81 | } 82 | el.append($$('div').addClass('context').append(lensEl)); 83 | } 84 | return el; 85 | }; 86 | 87 | this.didMount = function() { 88 | this.backend.getDocument('sample', function(err, doc) { 89 | this.documentSession = new DocumentSession(doc); 90 | // Expose to window for debugging 91 | window.documentSession = this.documentSession; 92 | this.rerender(); 93 | }.bind(this)); 94 | }; 95 | }; 96 | 97 | Component.extend(App); 98 | 99 | $(function() { 100 | window.app = Component.mount(App, $('#container')); 101 | }); 102 | -------------------------------------------------------------------------------- /app/app.scss: -------------------------------------------------------------------------------- 1 | // Lens styles 2 | // ------------------- 3 | 4 | @import '../styles/lens-writer'; 5 | @import '../styles/lens-reader'; 6 | 7 | /* Integration styles */ 8 | body,html { 9 | overflow: hidden; 10 | } 11 | 12 | .menu { 13 | position: absolute; 14 | top: 0px; 15 | left: 0px; 16 | right: 0px; 17 | height: 40px; 18 | background: #5c6570; 19 | 20 | button { 21 | color: rgba(255,255,255,0.6); 22 | display: block; 23 | float: left; 24 | height: 40px; 25 | line-height: 40px; 26 | padding: 0px 20px; 27 | 28 | &.active { 29 | background: rgba(0,0,0,0.1); 30 | color: rgba(255,255,255,1); 31 | } 32 | 33 | &:hover { 34 | color: rgba(255,255,255,1); 35 | } 36 | } 37 | } 38 | 39 | .context { 40 | position: absolute; 41 | bottom: 0px; 42 | left: 0px; 43 | right: 0px; 44 | top: 40px; 45 | } 46 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/HELP-US-OUT.txt: -------------------------------------------------------------------------------- 1 | I hope you love Font Awesome. If you've found it useful, please do me a favor and check out my latest project, 2 | Fonticons (https://fonticons.com). It makes it easy to put the perfect icons on your website. Choose from our awesome, 3 | comprehensive icon sets or copy and paste your own. 4 | 5 | Please. Check it out. 6 | 7 | -Dave Gandy 8 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/font-awesome-4.4.0/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/font-awesome-4.4.0/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/animated.less: -------------------------------------------------------------------------------- 1 | // Animated Icons 2 | // -------------------------- 3 | 4 | .@{fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .@{fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/bordered-pulled.less: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em @fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .@{fa-css-prefix}-pull-left { float: left; } 11 | .@{fa-css-prefix}-pull-right { float: right; } 12 | 13 | .@{fa-css-prefix} { 14 | &.@{fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.@{fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .@{fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/core.less: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/fixed-width.less: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .@{fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/font-awesome.less: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables.less"; 7 | @import "mixins.less"; 8 | @import "path.less"; 9 | @import "core.less"; 10 | @import "larger.less"; 11 | @import "fixed-width.less"; 12 | @import "list.less"; 13 | @import "bordered-pulled.less"; 14 | @import "animated.less"; 15 | @import "rotated-flipped.less"; 16 | @import "stacked.less"; 17 | @import "icons.less"; 18 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/larger.less: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .@{fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .@{fa-css-prefix}-2x { font-size: 2em; } 11 | .@{fa-css-prefix}-3x { font-size: 3em; } 12 | .@{fa-css-prefix}-4x { font-size: 4em; } 13 | .@{fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/list.less: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: @fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .@{fa-css-prefix}-li { 11 | position: absolute; 12 | left: -@fa-li-width; 13 | width: @fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.@{fa-css-prefix}-lg { 17 | left: (-@fa-li-width + (4em / 14)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/mixins.less: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | .fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal @fa-font-size-base/@fa-line-height-base FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | .fa-icon-rotate(@degrees, @rotation) { 15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); 16 | -webkit-transform: rotate(@degrees); 17 | -ms-transform: rotate(@degrees); 18 | transform: rotate(@degrees); 19 | } 20 | 21 | .fa-icon-flip(@horiz, @vert, @rotation) { 22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); 23 | -webkit-transform: scale(@horiz, @vert); 24 | -ms-transform: scale(@horiz, @vert); 25 | transform: scale(@horiz, @vert); 26 | } 27 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/path.less: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('@{fa-font-path}/fontawesome-webfont.eot?v=@{fa-version}'); 7 | src: url('@{fa-font-path}/fontawesome-webfont.eot?#iefix&v=@{fa-version}') format('embedded-opentype'), 8 | url('@{fa-font-path}/fontawesome-webfont.woff2?v=@{fa-version}') format('woff2'), 9 | url('@{fa-font-path}/fontawesome-webfont.woff?v=@{fa-version}') format('woff'), 10 | url('@{fa-font-path}/fontawesome-webfont.ttf?v=@{fa-version}') format('truetype'), 11 | url('@{fa-font-path}/fontawesome-webfont.svg?v=@{fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('@{fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/rotated-flipped.less: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-rotate-90 { .fa-icon-rotate(90deg, 1); } 5 | .@{fa-css-prefix}-rotate-180 { .fa-icon-rotate(180deg, 2); } 6 | .@{fa-css-prefix}-rotate-270 { .fa-icon-rotate(270deg, 3); } 7 | 8 | .@{fa-css-prefix}-flip-horizontal { .fa-icon-flip(-1, 1, 0); } 9 | .@{fa-css-prefix}-flip-vertical { .fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .@{fa-css-prefix}-rotate-90, 15 | :root .@{fa-css-prefix}-rotate-180, 16 | :root .@{fa-css-prefix}-rotate-270, 17 | :root .@{fa-css-prefix}-flip-horizontal, 18 | :root .@{fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/less/stacked.less: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .@{fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .@{fa-css-prefix}-stack-1x, .@{fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .@{fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .@{fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .@{fa-css-prefix}-inverse { color: @fa-inverse; } 21 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | 12 | } 13 | 14 | @mixin fa-icon-rotate($degrees, $rotation) { 15 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 16 | -webkit-transform: rotate($degrees); 17 | -ms-transform: rotate($degrees); 18 | transform: rotate($degrees); 19 | } 20 | 21 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 22 | filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); 23 | -webkit-transform: scale($horiz, $vert); 24 | -ms-transform: scale($horiz, $vert); 25 | transform: scale($horiz, $vert); 26 | } 27 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?v=#{$fa-version}'); 7 | src: url('#{$fa-font-path}/fontawesome-webfont.eot?#iefix&v=#{$fa-version}') format('embedded-opentype'), 8 | url('#{$fa-font-path}/fontawesome-webfont.woff2?v=#{$fa-version}') format('woff2'), 9 | url('#{$fa-font-path}/fontawesome-webfont.woff?v=#{$fa-version}') format('woff'), 10 | url('#{$fa-font-path}/fontawesome-webfont.ttf?v=#{$fa-version}') format('truetype'), 11 | url('#{$fa-font-path}/fontawesome-webfont.svg?v=#{$fa-version}#fontawesomeregular') format('svg'); 12 | // src: url('#{$fa-font-path}/FontAwesome.otf') format('opentype'); // used when developing fonts 13 | font-weight: normal; 14 | font-style: normal; 15 | } 16 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome-4.4.0/scss/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @import "variables"; 7 | @import "mixins"; 8 | @import "path"; 9 | @import "core"; 10 | @import "larger"; 11 | @import "fixed-width"; 12 | @import "list"; 13 | @import "bordered-pulled"; 14 | @import "animated"; 15 | @import "rotated-flipped"; 16 | @import "stacked"; 17 | @import "icons"; 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-bold-italic-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-bold-italic-latin-ext.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-bold-italic-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-bold-italic-latin.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-bold-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-bold-latin-ext.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-bold-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-bold-latin.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-italic-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-italic-latin-ext.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-italic-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-italic-latin.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-regular-latin-ext.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-regular-latin-ext.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato-regular-latin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/substance/lens/51e213d316642e15899642a0416b8a472466bfc4/app/assets/fonts/lato/lato-regular-latin.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/lato/lato.scss: -------------------------------------------------------------------------------- 1 | /* latin-ext */ 2 | @font-face { 3 | font-family: 'Lato'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Lato Regular'), local('Lato-Regular'), url(/fonts/lato/lato-regular-latin-ext.woff2) format('woff2'); 7 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 8 | } 9 | /* latin */ 10 | @font-face { 11 | font-family: 'Lato'; 12 | font-style: normal; 13 | font-weight: 400; 14 | src: local('Lato Regular'), local('Lato-Regular'), url(/fonts/lato/lato-regular-latin.woff2) format('woff2'); 15 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 16 | } 17 | /* latin-ext */ 18 | @font-face { 19 | font-family: 'Lato'; 20 | font-style: normal; 21 | font-weight: 700; 22 | src: local('Lato Bold'), local('Lato-Bold'), url(/fonts/lato/lato-bold-latin-ext.woff2) format('woff2'); 23 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 24 | } 25 | /* latin */ 26 | @font-face { 27 | font-family: 'Lato'; 28 | font-style: normal; 29 | font-weight: 700; 30 | src: local('Lato Bold'), local('Lato-Bold'), url(/fonts/lato/lato-bold-latin.woff2) format('woff2'); 31 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 32 | } 33 | /* latin-ext */ 34 | @font-face { 35 | font-family: 'Lato'; 36 | font-style: italic; 37 | font-weight: 400; 38 | src: local('Lato Italic'), local('Lato-Italic'), url(/fonts/lato/lato-italic-latin-ext.woff2) format('woff2'); 39 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 40 | } 41 | /* latin */ 42 | @font-face { 43 | font-family: 'Lato'; 44 | font-style: italic; 45 | font-weight: 400; 46 | src: local('Lato Italic'), local('Lato-Italic'), url(/fonts/lato/lato-italic-latin-ext.woff2) format('woff2'); 47 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 48 | } 49 | /* latin-ext */ 50 | @font-face { 51 | font-family: 'Lato'; 52 | font-style: italic; 53 | font-weight: 700; 54 | src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url(/fonts/lato/lato-bold-italic-latin-ext.woff2) format('woff2'); 55 | unicode-range: U+0100-024F, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF; 56 | } 57 | /* latin */ 58 | @font-face { 59 | font-family: 'Lato'; 60 | font-style: italic; 61 | font-weight: 700; 62 | src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url(/fonts/lato/lato-bold-italic-latin.woff2) format('woff2'); 63 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 64 | } -------------------------------------------------------------------------------- /app/assets/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Lens - Regulation of food intake by mechanosensory ion channels in enteric neurons 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /app/backend.js: -------------------------------------------------------------------------------- 1 | // var LensArticle = require('../model/LensArticle'); 2 | var oo = require('substance/util/oo'); 3 | var $ = require('substance/util/jquery'); 4 | var LensArticleImporter = require('../model/LensArticleImporter'); 5 | var importer = new LensArticleImporter(); 6 | 7 | var Backend = function() { 8 | 9 | }; 10 | 11 | Backend.Prototype = function() { 12 | 13 | // A generic request method 14 | // ------------------- 15 | // 16 | // Deals with sending the authentication header, encoding etc. 17 | 18 | this._request = function(method, url, data, cb) { 19 | var ajaxOpts = { 20 | type: method, 21 | url: url, 22 | contentType: "application/json; charset=UTF-8", 23 | dataType: "text", 24 | success: function(data) { 25 | cb(null, data); 26 | }, 27 | error: function(err) { 28 | console.error(err); 29 | cb(err.responseText); 30 | } 31 | }; 32 | 33 | if (data) { 34 | ajaxOpts.data = JSON.stringify(data); 35 | } 36 | 37 | $.ajax(ajaxOpts); 38 | }; 39 | 40 | // Document 41 | // ------------------ 42 | 43 | this.getDocument = function(documentId, cb) { 44 | this._request('GET', 'data/example-doc.xml', null, function(err, xml) { 45 | if (err) { console.error(err); cb(err); } 46 | 47 | var doc = importer.importDocument(xml); 48 | window.doc = doc; 49 | 50 | // Initial update of collections 51 | doc.updateCollections(); 52 | cb(null, doc); 53 | }); 54 | }; 55 | 56 | this.saveDocument = function(doc, cb) { 57 | cb('Not supported in dev version'); 58 | }; 59 | 60 | // Figure related 61 | // ------------------ 62 | 63 | this.uploadFigure = function(file, cb) { 64 | // This is a fake implementation 65 | var objectURL = window.URL.createObjectURL(file); 66 | cb(null, objectURL); 67 | }; 68 | }; 69 | 70 | oo.initClass(Backend); 71 | 72 | module.exports = Backend; -------------------------------------------------------------------------------- /data/small-example.xml: -------------------------------------------------------------------------------- 1 |
2 | 3 | 10.7554/eLife.06351 4 | Molecular architecture of human polycomb repressive complex 2 5 | 6 | Dopamine neurons in the midbrain have a central role in generating cycles of biological activity with periods as short as 4 hours and as long as 100 hours. 7 | 8 | 9 | 10 | 11 | Reconstitution of the human <em>PRC2-AEBP2 Complex</em>. 12 | TODO: support alt tags 13 | (A) Schematic representation of the components of the human PRC2 Complex and AEBP2. (B) Size-exclusion chromatography of recombinant PRC2-AEBP2 complex and corresponding SDS-PAGE separation stained with Coomassie brilliant blue. The molecular mass of the recombinant complex is ∼275 kDa. Arrows and numbers indicate elution markers in the size-exclusion chromatography experiments and their molecular masses (in kilodaltons), respectively (a.u.: arbitrary unit). (C) Negative-stain EM of the recombinant PRC2-AEBP2 complex. Individual particles have an elongated shape with a length of ∼16 nm and a thickness of ∼7 nm. Bar: 200 nm. (D) Comparison between representative reference-free 2D class averages from non cross-linked (0%) and mildly cross-linked (0.015% glutaraldehyde) particles of the PRC2-AEBP2 complex. 14 | 15 | 16 | 17 | Ab initio random conical tilt reconstruction of the human PRC2-AEBP2 complex. 18 | TODO: support alt tags 19 | (A) Representative untilted and 60° tilt-pair micrographs (80,000× magnification). Corresponding particles pairs indicated by yellow circles. (B) RCT Volumes aligned to each of the 20 corresponding reference free class averages (from 6075 particles in the 0° micrographs, each class containing between 150 and 800 particles, as indicated in parentheses). (C) Alignment of the RCT volumes with respect to each other. 20 | 21 | 22 | 23 | Ab initio random conical tilt reconstruction of the human PRC2-AEBP2 complex. 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 |
ABCDEF
123456
78910112
131415161718
192021222324
252627282930
36 | (A) Representative untilted and 60° tilt-pair micrographs (80,000× magnification). Corresponding particles pairs indicated by yellow circles. (B) RCT Volumes aligned to each of the 20 corresponding reference free class averages (from 6075 particles in the 0° micrographs, each class containing between 150 and 800 particles, as indicated in parentheses). (C) Alignment of the RCT volumes with respect to each other. 37 |
38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 |
54 | 55 |

Content from: http://lens.elifesciences.org/00005

56 |

Abstract

57 |

Polycomb Repressive Complex 2 (PRC2) is essential for gene silencing, establishing transcriptional repression of specific genes by tri-methylating Lysine 27 of histone H3, a process mediated by cofactors such as AEBP2. In spite of its biological importance, little is known about PRC2 architecture and subunit organization. Here, we present the first three-dimensional electron microscopy structure of the human PRC2 complex bound to its cofactor AEBP2. Using a novel internal protein tagging-method, in combination with isotopic chemical cross-linking and mass spectrometry, we have localized all the PRC2 subunits and their functional domains and generated a detailed map of interactions. The position and stabilization effect of AEBP2 suggests an allosteric role of this cofactor in regulating gene silencing. Regions in PRC2 that interact with modified histone tails are localized near the methyltransferase site, suggesting a molecular mechanism for the chromatin-based regulation of PRC2 activity.

58 | 59 | 60 | 61 |

Introduction

62 |

Some annotated content (see Doe, 2010 and Figure 1 and Table 1).

63 | 64 |

Reconstitution of the human PRC2-AEBP2 complex

65 |

In preliminary experiments, we reconstituted the tetrameric PRC2 complex (Ezh2/EED/Suz12/RbAp48) in an insect cell expression system, following previous protocols established to reconstitute a functional PRC2 complex (Pasini et al., 2004; Ketel et al., 2005; Margueron et al., 2008). When analyzed by SDS page the purified complex appeared to be stoichiometric and biochemically homogeneous (data not shown). However, further analysis to generate reference-free 2D class averages and a 3D reconstruction were limited in resolution and lacked clear structural details (data not shown), indicating the presence of extreme conformational flexibility and hampering further structural studies of this tetrameric PRC2.

66 | 67 | 68 | 69 | 70 |

Section 2.1

71 |

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

72 | 73 | 74 | 75 | 76 |

End of file

77 | 78 |
79 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var sass = require('gulp-sass'); 5 | var browserify = require('browserify'); 6 | var uglify = require('gulp-uglify'); 7 | var through2 = require('through2'); 8 | 9 | gulp.task('sass', function () { 10 | gulp.src('./app/app.scss') 11 | .pipe(sass().on('error', sass.logError)) 12 | .pipe(gulp.dest('./dist')); 13 | }); 14 | 15 | gulp.task('assets', function () { 16 | gulp.src('./app/assets/**/*', {base:"./app/assets"}) 17 | .pipe(gulp.dest('dist')); 18 | gulp.src('node_modules/font-awesome/fonts/*') 19 | .pipe(gulp.dest('./dist/fonts')); 20 | }); 21 | 22 | gulp.task('data', function () { 23 | gulp.src('./data/*') 24 | .pipe(gulp.dest('./dist/data')); 25 | }); 26 | 27 | gulp.task('bundle', function () { 28 | return gulp.src('./app/app.js') 29 | .pipe(through2.obj(function (file, enc, next) { 30 | browserify(file.path) 31 | .bundle(function (err, res) { 32 | if (err) { return next(err); } 33 | file.contents = res; 34 | next(null, file); 35 | }); 36 | })) 37 | .on('error', function (error) { 38 | console.log(error.stack); 39 | this.emit('end'); 40 | }) 41 | .pipe(uglify()) 42 | .pipe(gulp.dest('./dist')); 43 | }); 44 | 45 | gulp.task('default', ['assets', 'data', 'sass', 'bundle']); -------------------------------------------------------------------------------- /i18n/de.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'enter_search_term': 'Geben Sie einen Suchbegriff ein', 3 | 'choose_referenced_items': 'Referenzen auswählen', 4 | 'no_items_found': 'Keine Ergebnisse gefunden.', 5 | 'add_references': 'Referenzen hinzufügen', 6 | 'figure': 'Abbildung', 7 | 'insert': 'Einfügen', 8 | 'cite': 'Zitieren', 9 | 'bib_item': 'Referenz', 10 | 'embed-src': 'Embed URL', 11 | 'paragraph': 'Paragraph', 12 | 'heading1': 'Überschrift 1', 13 | 'heading2': 'Überschrift 2', 14 | 'heading3': 'Überschrift 3', 15 | 'codeblock': 'Codeblock', 16 | 'blockquote': 'Zitat', 17 | 'article-meta.title': 'Titel', 18 | 'article-meta.abstract': 'Zusammenfassung', 19 | 'image-figure.title': 'Titel', 20 | 'image-figure.caption': 'Beschreibung', 21 | 'container-selection': 'Mehrere Elemente', 22 | 'add-bib-entries': 'Referenz hinzufügen', 23 | 'bibItemCitation': 'Bibliographischer Verweis', 24 | 'imageFigureCitation': 'Bildverweis', 25 | }; 26 | -------------------------------------------------------------------------------- /i18n/en.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'enter_search_term': 'Enter search term', 3 | 'choose_referenced_items': 'Choose referenced items', 4 | 'no_items_found': 'No items found.', 5 | 'add_references': 'Add references', 6 | 'figure': 'Figure', 7 | 'insert': 'Insert', 8 | 'cite': 'Cite', 9 | 'focus': 'Focus', 10 | 'bib_item': 'Reference', 11 | 'manage': 'Manage', 12 | 'add_reference': 'Add References', 13 | 'bib-items': 'References', 14 | 'embed-src': 'Embed URL', 15 | 'paragraph': 'Paragraph', 16 | 'heading1': 'Heading 1', 17 | 'heading2': 'Heading 2', 18 | 'heading3': 'Heading 3', 19 | 'codeblock': 'Codeblock', 20 | 'blockquote': 'Blockquote', 21 | 'article-meta.title': 'Title', 22 | 'article-meta.abstract': 'Abstract', 23 | 'image-figure.title': 'Figure Title', 24 | 'image-figure.caption': 'Figure Caption', 25 | 'container-selection': 'Multiple Elements', 26 | 'add-bib-entries': 'Add new references', 27 | 'bibItemCitation': 'Bibliographic Citation', 28 | 'imageFigureCitation': 'Figure Citation', 29 | }; 30 | -------------------------------------------------------------------------------- /legacy/cite_tool.js: -------------------------------------------------------------------------------- 1 | var Substance = require('substance'); 2 | var Tool = Substance.Surface.Tool; 3 | var _ = require("substance/helpers"); 4 | 5 | 6 | var CiteTool = Tool.extend({ 7 | name: "cite", 8 | update: function(surface, sel) { 9 | this.surface = surface; // IMPORTANT! 10 | // Set disabled when not a property selection 11 | if (!surface.isEnabled() || sel.isNull() || !sel.isPropertySelection()) { 12 | return this.setDisabled(); 13 | } 14 | var newState = { 15 | surface: surface, 16 | sel: sel, 17 | disabled: false 18 | }; 19 | this.setToolState(newState); 20 | }, 21 | 22 | // Needs app context in order to request a state switch 23 | 24 | createCitation: function(citationTargetType) { 25 | var citation; 26 | 27 | var doc = this.context.doc; 28 | var citationType = doc.getSchema().getNodeClass(citationTargetType).static.citationType; 29 | var surface = this.surface; 30 | var editor = surface.getEditor(); 31 | 32 | console.log('citationType', citationType); 33 | surface.transaction(function(tx, args) { 34 | var selection = args.selection; 35 | var path = selection.start.path; 36 | var startOffset = selection.start.offset; 37 | if (!selection.isCollapsed) { 38 | var out = editor.delete(tx, args); 39 | args.selection = out.selection; 40 | } 41 | args.text = '$'; 42 | editor.insertText(tx, args); 43 | citation = tx.create({ 44 | id: Substance.uuid(citationType), 45 | "type": citationType, 46 | "targets": [], 47 | "path": path, 48 | "startOffset": startOffset, 49 | "endOffset": startOffset + 1, 50 | }); 51 | citation.label = "???"; 52 | args.selection = citation.getSelection(); 53 | return args; 54 | }); 55 | return citation; 56 | }, 57 | 58 | toggleTarget: function(citationId, targetId) { 59 | var doc = this.context.doc; 60 | var citation = doc.get(citationId); 61 | var newTargets = citation.targets.slice(); 62 | if (_.includes(newTargets, targetId)) { 63 | newTargets = _.without(newTargets, targetId); 64 | } else { 65 | newTargets.push(targetId); 66 | } 67 | this.surface.transaction(function(tx, args) { 68 | tx.set([citation.id, "targets"], newTargets); 69 | return args; 70 | }); 71 | } 72 | }); 73 | 74 | module.exports = CiteTool; 75 | -------------------------------------------------------------------------------- /legacy/export_tool.js: -------------------------------------------------------------------------------- 1 | var Substance = require('substance'); 2 | var Tool = Substance.Surface.Tool; 3 | 4 | function slug(str) { 5 | str = str.replace(/^\s+|\s+$/g, ''); // trim 6 | str = str.toLowerCase(); 7 | 8 | // remove accents, swap ñ for n, etc 9 | var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;"; 10 | var to = "aaaaeeeeiiiioooouuuunc------"; 11 | for (var i=0, l=from.length ; i'); 36 | $surface.append($inputEl); 37 | $inputEl.click(); 38 | 39 | $inputEl.on('change', function(e) { 40 | var file = $inputEl[0].files[0]; 41 | 42 | // We no longer need the file input 43 | $inputEl.remove(); 44 | 45 | this.context.backend.uploadFigure(file, function(err, figureUrl) { 46 | // NOTE: we are providing a custom beforeState, to make sure 47 | // thate the correct initial selection is used. 48 | var beforeState = { selection: state.sel }; 49 | 50 | surface.transaction(beforeState, function(tx, args) { 51 | var newImage = tx.create({ 52 | id: Substance.uuid("image"), 53 | type: "image", 54 | src: figureUrl, 55 | previewSrc: figureUrl, 56 | }); 57 | 58 | var newFigure = tx.create({ 59 | id: Substance.uuid("image_figure"), 60 | type: "image_figure", 61 | content: newImage.id, 62 | title: "Enter title", 63 | caption: "Enter caption" 64 | }); 65 | 66 | var newInclude = { 67 | id: Substance.uuid("include"), 68 | type: "include", 69 | nodeId: newFigure.id 70 | }; 71 | var editor = surface.getEditor(); 72 | // Note: returning the result which will contain an updated selection 73 | return editor.insertNode(tx, { selection: args.selection, node: newInclude }); 74 | }); 75 | }); 76 | }.bind(this)); 77 | 78 | } 79 | }); 80 | 81 | module.exports = InsertFigureTool; 82 | -------------------------------------------------------------------------------- /legacy/insert_table_tool.js: -------------------------------------------------------------------------------- 1 | var Substance = require('substance'); 2 | var Tool = Substance.Surface.Tool; 3 | var Article = require('../../lib/article'); 4 | 5 | var TABLE = [ 6 | '', 7 | 'Enter title.', 8 | '', 9 | '', 10 | '', 11 | '', 12 | '', 13 | '', 14 | '', 15 | '', 16 | '', 17 | '', 18 | '', 19 | '
ABCDEF
123456
78910112
131415161718
192021222324
252627282930
', 20 | 'Enter caption', 21 | '
' 22 | ].join(''); 23 | 24 | var InsertTableTool = Tool.extend({ 25 | name: "insert_table", 26 | update: function(surface, sel) { 27 | this.surface = surface; // IMPORTANT! 28 | 29 | // Set disabled when not a property selection 30 | if (!surface.isEnabled() || sel.isNull() || !sel.isPropertySelection()) { 31 | return this.setDisabled(); 32 | } 33 | 34 | var newState = { 35 | surface: surface, 36 | sel: sel, 37 | disabled: false 38 | }; 39 | this.setToolState(newState); 40 | }, 41 | 42 | // Needs app context in order to request a state switch 43 | performAction: function(app) { 44 | var state = this.getToolState(); 45 | var sel = app.getSelection(); 46 | if (state.disabled) { 47 | return; 48 | } 49 | var surface = this.surface; 50 | var editor = surface.getEditor(); 51 | 52 | // var $table = $(TABLE); 53 | // We need some more XML aware parsing here 54 | // TODO: put parsing code into a module somewhere 55 | var parser = new DOMParser(); 56 | var xmlDoc = parser.parseFromString(TABLE, "text/xml"); 57 | var $table = $(xmlDoc).find('table-figure'); 58 | 59 | surface.transaction({ selection: sel }, function(tx, args) { 60 | var htmlImporter = new Article.ArticleHtmlImporter(); 61 | htmlImporter.initialize(tx, $table); 62 | var tableNode = htmlImporter.convertElement($table); 63 | // Note: returning the result which will contain an updated selection 64 | return editor.insertNode(tx, { selection: args.selection, node: tableNode }); 65 | }); 66 | } 67 | }); 68 | 69 | module.exports = InsertTableTool; 70 | -------------------------------------------------------------------------------- /lens.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "./", 6 | "folder_exclude_patterns": ["node_modules"] 7 | }, 8 | { 9 | "path": "./node_modules/substance", 10 | "folder_exclude_patterns": ["node_modules"] 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /model/Collection.js: -------------------------------------------------------------------------------- 1 | // Generic dynamic collection of figures and tables 2 | // ----------------- 3 | // 4 | // Takes care of generating labels and keeping them up to date 5 | 6 | var _ = require('substance/util/helpers'); 7 | var pluck = require('substance/util/pluck'); 8 | var oo = require('substance/util/oo'); 9 | 10 | function Collection(doc, containerId, itemType, labelPrefix) { 11 | this.doc = doc; 12 | this.containerId = containerId; 13 | this.itemType = itemType; 14 | this.labelPrefix = labelPrefix; 15 | this.doc.connect(this, { 16 | 'document:changed': this.onDocumentChanged 17 | }); 18 | } 19 | 20 | Collection.Prototype = function() { 21 | 22 | this.dispose = function() { 23 | this.doc.disconnect(this); 24 | }; 25 | 26 | this.getDocument = function() { 27 | return this.doc; 28 | }; 29 | 30 | // Determines item order by checking their occurence in the container 31 | this.determineItems = function() { 32 | var doc = this.doc; 33 | var container = doc.get(this.containerId); 34 | var items = doc.getIndex('type').get(this.itemType); 35 | // Map itemIds (figures/tables) to container positions 36 | var _items = []; 37 | _.each(items, function(item) { 38 | var pos = container.getPosition(item.id); 39 | var isShown; 40 | if (pos >= 0) { 41 | isShown = true; 42 | } else { 43 | isShown = false; 44 | pos = Number.MAX_VALUE; 45 | } 46 | _items.push({ 47 | pos: pos, 48 | item: item, 49 | isShown: isShown 50 | }); 51 | }); 52 | _items = _.sortBy(_items, 'pos'); 53 | var counter = 0; 54 | _.each(_items, function(_item) { 55 | if (_item.isShown) { 56 | _item.index = counter++; 57 | } else { 58 | _item.index = -1; 59 | } 60 | }); 61 | // console.log('Sorted Items', sortedItems); 62 | return _items; 63 | }; 64 | 65 | this.createItemLabel = function(_item) { 66 | if (_item.index < 0) { 67 | return this.labelPrefix + " ?"; 68 | } else { 69 | return [this.labelPrefix, _item.index + 1].join(' '); 70 | } 71 | }; 72 | 73 | this.createCitationLabel = function(_citation) { 74 | var citation = _citation.citation; 75 | var targets = citation.targets; 76 | var targetPositions = []; 77 | var doc = this.doc; 78 | var err; 79 | // find the positions of the cited items 80 | _.each(targets, function(targetId) { 81 | var target = doc.get(targetId); 82 | var itemIndex = this.items.indexOf(target); 83 | if (itemIndex < 0) { 84 | console.error("citation target not found: ", targetId); 85 | err = targetId; 86 | return; 87 | } 88 | var _item = this._items[itemIndex]; 89 | if(_item.index<0) { 90 | console.error("citation target does not have a label: ", targetId); 91 | err = targetId; 92 | return; 93 | } 94 | var targetPos = _item.index + 1; 95 | targetPositions.push(targetPos); 96 | }.bind(this)); 97 | targetPositions.sort(); 98 | // generate a label by concatenating numbers 99 | // and provide a special label for empty citations. 100 | var label; 101 | if (targetPositions.length === 0) { 102 | label = this.labelPrefix+ " ???"; 103 | } else { 104 | label = [this.labelPrefix, targetPositions.join(", ")].join(' '); 105 | } 106 | // console.log('generated citation label', label); 107 | return label; 108 | }; 109 | 110 | this.updateItemLabels = function() { 111 | _.each(this._items, function(_item) { 112 | var label = this.createItemLabel(_item); 113 | // console.log('generated item label', label); 114 | _item.item.setLabel(label); 115 | }.bind(this)); 116 | }; 117 | 118 | this.updateCitationLabels = function() { 119 | // console.log('citations', this.citations); 120 | _.each(this._citations, function(_citation) { 121 | var label = this.createCitationLabel(_citation); 122 | _citation.citation.setLabel(label); 123 | }.bind(this)); 124 | }; 125 | 126 | // get citation nodes sorted by occurence in container. 127 | this.determineCitations = function() { 128 | var doc = this.doc; 129 | var citations = doc.getIndex('type').get(this.itemType+'-citation'); 130 | var container = doc.get(this.containerId); 131 | // generate information for sorting 132 | var _citations = _.map(citations, function(citation) { 133 | var address = container.getAddress(citation.path); 134 | return { 135 | citation: citation, 136 | address: address 137 | }; 138 | }); 139 | // sort citation by occurrence in the container 140 | _citations.sort(function(a, b) { 141 | if (a < b) { 142 | return -1; 143 | } else if (a > b) { 144 | return 1; 145 | } else { 146 | return a.citation.startOffset - b.citation.startOffset; 147 | } 148 | }); 149 | return _citations; 150 | }; 151 | 152 | this.update = function() { 153 | // items are augmented with information necessary for compilation 154 | // and are sorted to reflect the order they should appear in the collection 155 | // ATTENTION: there is a convention here to use `_item` for 156 | // an augmented item and `item` for the document node. 157 | this._items = this.determineItems(); 158 | // Note: doing this right away allows us to easily find the 159 | // position of an item 160 | this.items = pluck(this._items, 'item'); 161 | 162 | this._citations = this.determineCitations(); 163 | this.citations = pluck(this._citations, 'citation'); 164 | 165 | // compile labels 166 | this.updateItemLabels(); 167 | this.updateCitationLabels(); 168 | }; 169 | 170 | this.getItems = function() { 171 | return this.items; 172 | }; 173 | 174 | // HACK: Lots of hard coded things here, we need to improve this along with 175 | // removing the redudancy with Bibliography.js 176 | this.onDocumentChanged = function(change) { 177 | var doc = this.doc; 178 | var needsUpdate = false; 179 | var node, deletedNode; 180 | 181 | _.each(change.ops, function(op) { 182 | 183 | // Figure citation has been created/changed/delete 184 | // ----------------- 185 | // 186 | 187 | if (op.isCreate() || op.isSet() || op.isUpdate()) { 188 | var nodeId = op.path[0]; 189 | node = doc.get(nodeId); 190 | if (!node) return; 191 | 192 | if (op.isCreate()) { 193 | // Create 194 | if (node.type === 'image-figure-citation' || node.type === 'table-figure-citation') { 195 | needsUpdate = true; 196 | } 197 | } else { 198 | // Update/Set 199 | if (node.type === 'image-figure-citation' || node.type === 'table-figure-citation') { 200 | if (op.path[1] === 'targets') { 201 | needsUpdate = true; 202 | } 203 | } 204 | } 205 | } else if (op.isDelete()) { 206 | // Delete 207 | deletedNode = op.val; 208 | if (deletedNode.type === 'image-figure-citation' || deletedNode.type === 'table-figure-citation') { 209 | needsUpdate = true; 210 | } 211 | } 212 | 213 | 214 | // New Figure has been inserted/moved or deleted 215 | // ---------------- 216 | // 217 | // Figure insert or move case (when container is updated) 218 | if (!needsUpdate && op.path[0] === this.containerId) { 219 | if (op.type === "set") { 220 | needsUpdate = true; 221 | } 222 | // Note: updates on the container nodes are always ArrayOperations 223 | // which have the inserted or removed value as `val`. 224 | if (op.type === "update") { 225 | var id = op.diff.val; 226 | node = doc.get(id); 227 | // ATTENTION: as these are intermediate ops 228 | // it may happen that the node itself has been 229 | // deleted by a later op in this change 230 | // So this guard can be considered ok 231 | if (!node) return; 232 | // look for item type or an include pointing to an item type 233 | if (node.type === this.itemType) { 234 | needsUpdate = true; 235 | } 236 | } 237 | } 238 | 239 | // When node of this.itemType has been deleted 240 | if (op.isDelete()) { 241 | deletedNode = op.val; 242 | if (deletedNode.type === this.itemType) { 243 | needsUpdate = true; 244 | } 245 | } 246 | }.bind(this)); 247 | 248 | if (needsUpdate) { 249 | // console.log('Collection', this.itemType, 'is being updated'); 250 | this.update(); 251 | } 252 | }; 253 | 254 | }; 255 | 256 | oo.initClass(Collection); 257 | module.exports = Collection; -------------------------------------------------------------------------------- /model/LensArticle.js: -------------------------------------------------------------------------------- 1 | var schema = require('./articleSchema'); 2 | var _ = require('substance/util/helpers'); 3 | var Document = require('substance/model/Document'); 4 | var DocumentIndex = require('substance/model/DocumentIndex'); 5 | 6 | var Bibliography = require('../packages/bibliography/Bibliography'); 7 | var Collection = require('./Collection'); 8 | 9 | var without = require('lodash/array/without'); 10 | 11 | var LensArticle = function() { 12 | // HACK: at the moment we need to seed this way 13 | // as containers get registered on construction 14 | // it would be better if we created the containers dynamically 15 | LensArticle.super.call(this, schema); 16 | 17 | this.create({ 18 | type: "container", 19 | id: "main", 20 | nodes: [] 21 | }); 22 | 23 | this.collections = { 24 | "bib-item": new Bibliography(this, 'main'), 25 | "image-figure": new Collection(this, 'main', 'image-figure', 'Figure'), 26 | "table-figure": new Collection(this, 'main', 'table-figure', 'Table'), 27 | }; 28 | 29 | this.includesIndex = this.addIndex('includes', DocumentIndex.create({ 30 | type: "include", 31 | property: "nodeId" 32 | })); 33 | 34 | this.citationsIndex = this.addIndex('citations', DocumentIndex.create({ 35 | type: "citation", 36 | property: "targets" 37 | })); 38 | 39 | this.connect(this, { 40 | 'document:changed': this.onDocumentChanged 41 | }); 42 | }; 43 | 44 | LensArticle.Prototype = function() { 45 | // HACK: We ensure referential integrity by just patching the targets property 46 | // of citation nodes until we have a better solution: 47 | // See: https://github.com/substance/substance/issues/295 48 | this.onDocumentChanged = function(change) { 49 | _.each(change.ops, function(op) { 50 | if (op.isDelete()) { 51 | var deletedNode = op.val; 52 | 53 | // Check if deleted node is a citeable node 54 | if (deletedNode.type === 'image-figure' || deletedNode.type === 'table-figure' || deletedNode.type === 'bib-item') { 55 | var citations = this.getIndex('citations').get(deletedNode.id); 56 | _.each(citations, function(citation) { 57 | citation.targets = without(citation.targets, deletedNode.id); 58 | }); 59 | } 60 | } 61 | }.bind(this)); 62 | }; 63 | 64 | this.updateCollections = function() { 65 | _.each(this.collections, function(c) { 66 | c.update(); 67 | }); 68 | }; 69 | 70 | this.getDocumentMeta = function() { 71 | return this.get('article-meta'); 72 | }; 73 | 74 | this.getCiteprocCompiler = function() { 75 | return this.citeprocCompiler; 76 | }; 77 | 78 | this.getBibliography = function() { 79 | return this.collections["bib-item"]; 80 | }; 81 | 82 | // Legacy delete 83 | this.getFiguresCollection = function() { 84 | return this.collections["image-figure"]; 85 | }; 86 | 87 | this.getTablesCollection = function() { 88 | return this.collections["table-figure"]; 89 | }; 90 | 91 | this.getCollection = function(itemType) { 92 | return this.collections[itemType]; 93 | }; 94 | 95 | // Document title 96 | this.getTitle = function() { 97 | return this.get('article-meta').title; 98 | }; 99 | 100 | // Document manipulation 101 | // ------------------- 102 | 103 | // TODO: delete all figure references 104 | // 105 | this.deleteFigure = function(figureId) { 106 | this.transaction(function(tx, args) { 107 | var figureCitations = _.map(this.citationsIndex.get(figureId)); 108 | // Delete references 109 | _.each(figureCitations, function(citation) { 110 | // TODO: inspect figRef.figures if there is more than one entry 111 | // If so only remove that entry for the reference 112 | tx.delete(citation.id); 113 | }); 114 | var figIncludes = _.map(tx.getIndex('includes').get(figureId)); 115 | // Remove figure includes from container first 116 | _.each(figIncludes, function(figInc) { 117 | tx.get('main').hide(figInc.id); 118 | tx.delete(figInc.id); 119 | }); 120 | tx.delete(figureId); 121 | return args; 122 | }); 123 | }; 124 | 125 | this.deleteBibItem = function(bibItemId) { 126 | this.transaction(function(tx, args) { 127 | var bibItemCitations = _.map(tx.getIndex('citations').get(bibItemId)); 128 | // Delete references 129 | _.each(bibItemCitations, function(citation) { 130 | // TODO: inspect bibItemRef.bibItems if there is more than one entry 131 | // If so only remove that entry for the reference 132 | tx.delete(citation.id); 133 | }); 134 | tx.delete(bibItemId); 135 | return args; 136 | }); 137 | }; 138 | }; 139 | 140 | Document.extend(LensArticle); 141 | 142 | LensArticle.XML_TEMPLATE = [ 143 | '
', 144 | '', 145 | 'Enter title', 146 | 'Enter abstract', 147 | '', 148 | '', 149 | '', 150 | '

Enter your article here.

', 151 | '', 152 | '
' 153 | ].join(''); 154 | 155 | module.exports = LensArticle; -------------------------------------------------------------------------------- /model/LensArticleExporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var XMLExporter = require('substance/model/XMLExporter'); 4 | var converters = require('./LensArticleImporter').converters; 5 | var each = require('lodash/collection/each'); 6 | 7 | function LensArticleExporter() { 8 | LensArticleExporter.super.call(this, { 9 | converters: converters, 10 | containerId: 'main' 11 | }); 12 | } 13 | 14 | LensArticleExporter.Prototype = function() { 15 | this.exportDocument = function(doc) { 16 | this.state.doc = doc; 17 | var $$ = this.$$; 18 | var articleEl = $$('article'); 19 | 20 | // Export ArticleMeta 21 | var metaEl = this.convertNode(doc.get('article-meta')); 22 | articleEl.append(metaEl); 23 | 24 | // Export resources (e.g. bib items) 25 | var resourceEl = $$('resources'); 26 | var bibItems = doc.getIndex('type').get('bib-item'); 27 | each(bibItems, function(bibItem) { 28 | var bibItemEl = this.convertNode(bibItem); 29 | resourceEl.append(bibItemEl); 30 | }, this); 31 | articleEl.append(resourceEl); 32 | 33 | // Export article body 34 | var bodyElements = this.convertContainer(doc.get('main')); 35 | articleEl.append( 36 | $$('body').append(bodyElements) 37 | ); 38 | return articleEl.outerHTML; 39 | }; 40 | 41 | }; 42 | 43 | XMLExporter.extend(LensArticleExporter); 44 | 45 | module.exports = LensArticleExporter; 46 | -------------------------------------------------------------------------------- /model/LensArticleImporter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var XMLImporter = require('substance/model/XMLImporter'); 4 | var articleSchema = require('./articleSchema'); 5 | var LensArticle = require('./LensArticle'); 6 | 7 | var converters = [ 8 | require('substance/packages/paragraph/ParagraphHTMLConverter'), 9 | require('substance/packages/blockquote/BlockquoteHTMLConverter'), 10 | require('substance/packages/codeblock/CodeblockHTMLConverter'), 11 | require('substance/packages/heading/HeadingHTMLConverter'), 12 | require('substance/packages/image/ImageXMLConverter'), 13 | require('substance/packages/strong/StrongHTMLConverter'), 14 | require('substance/packages/emphasis/EmphasisHTMLConverter'), 15 | require('substance/packages/link/LinkHTMLConverter'), 16 | 17 | // Lens-specific converters 18 | require('../packages/metadata/MetadataXMLConverter'), 19 | require('../packages/bibliography/BibItemXMLConverter'), 20 | require('../packages/figures/ImageFigureXMLConverter'), 21 | 22 | require('../packages/figures/ImageFigureCitationXMLConverter'), 23 | require('../packages/bibliography/BibItemCitationXMLConverter'), 24 | ]; 25 | 26 | function LensArticleImporter() { 27 | XMLImporter.call(this, { 28 | schema: articleSchema, 29 | converters: converters, 30 | DocumentClass: LensArticle 31 | }); 32 | } 33 | 34 | LensArticleImporter.Prototype = function() { 35 | 36 | // XML import 37 | //
38 | // ... 39 | // ... 40 | // ... 41 | //
42 | this.convertDocument = function(articleElement) { 43 | // Import meta node 44 | var metaElement = articleElement.find('meta'); 45 | this.convertElement(metaElement); 46 | 47 | // Import resources 48 | var resources = articleElement.find('resources'); 49 | resources.children.forEach(function(resource) { 50 | this.convertElement(resource); 51 | }.bind(this)); 52 | 53 | // Import main container 54 | var bodyNodes = articleElement.find('body').children; 55 | this.convertContainer(bodyNodes, 'main'); 56 | }; 57 | }; 58 | 59 | // Expose converters so we can reuse them in NoteHtmlExporter 60 | LensArticleImporter.converters = converters; 61 | 62 | XMLImporter.extend(LensArticleImporter); 63 | 64 | module.exports = LensArticleImporter; 65 | -------------------------------------------------------------------------------- /model/articleSchema.js: -------------------------------------------------------------------------------- 1 | var DocumentSchema = require('substance/model/DocumentSchema'); 2 | 3 | // Substance Node Types 4 | // ---------------------- 5 | 6 | var Paragraph = require('substance/packages/paragraph/Paragraph'); 7 | var Heading = require('substance/packages/heading/Heading'); 8 | var Codeblock = require('substance/packages/codeblock/Codeblock'); 9 | var Blockquote = require('substance/packages/blockquote/Blockquote'); 10 | var Embed = require('substance/packages/embed/Embed'); 11 | var Image = require('substance/packages/image/Image'); 12 | var List = require('substance/packages/list/List'); 13 | var ListItem = require('substance/packages/list/ListItem'); 14 | var Table = require('substance/packages/table/Table'); 15 | var TableSection = require('substance/packages/table/TableSection'); 16 | var TableRow = require('substance/packages/table/TableRow'); 17 | var TableCell = require('substance/packages/table/TableCell'); 18 | var Emphasis = require('substance/packages/emphasis/Emphasis'); 19 | var Strong = require('substance/packages/strong/Strong'); 20 | var Subscript = require('substance/packages/subscript/Subscript'); 21 | var Superscript = require('substance/packages/superscript/Superscript'); 22 | var Code = require('substance/packages/code/Code'); 23 | var Link = require('substance/packages/link/Link'); 24 | 25 | 26 | // Lens-specific Node Types 27 | // ---------------------- 28 | 29 | // Figures 30 | var Figure = require('substance/packages/figure/Figure'); 31 | var ImageFigure = require('../packages/figures/ImageFigure'); 32 | var ImageFigureCitation = require('../packages/figures/ImageFigureCitation'); 33 | 34 | // Bibliography 35 | var BibItem = require('../packages/bibliography/BibItem'); 36 | var BibItemCitation = require('../packages/bibliography/BibItemCitation'); 37 | 38 | // Metadata 39 | var Author = require('../packages/metadata/Author'); 40 | var ArticleMeta = require('../packages/metadata/ArticleMeta'); 41 | var schema = new DocumentSchema("lens-article", "3.0.0"); 42 | 43 | schema.getDefaultTextType = function() { 44 | return 'paragraph'; 45 | }; 46 | 47 | schema.addNodes([ 48 | ArticleMeta, 49 | Paragraph, Heading, 50 | Codeblock, 51 | Blockquote, 52 | Embed, 53 | Code, Emphasis, Strong, Subscript, Superscript, 54 | Link, 55 | Image, 56 | Author, 57 | Figure, // abstract type (!) 58 | ImageFigure, 59 | Table, TableSection, TableRow, TableCell, 60 | ImageFigureCitation, BibItemCitation, 61 | List, ListItem, 62 | BibItem, 63 | ]); 64 | 65 | module.exports = schema; 66 | -------------------------------------------------------------------------------- /model/defaultLensArticle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var createDocumentFactory = require('substance/model/createDocumentFactory'); 4 | var LensArticle = require('./LensArticle'); 5 | 6 | module.exports = createDocumentFactory(LensArticle, function(tx) { 7 | var main = tx.get('main'); 8 | 9 | tx.create({ 10 | id: 'article-meta', 11 | type: 'article-meta', 12 | title: 'Untitled', 13 | abstract: 'Enter abstract' 14 | }); 15 | 16 | tx.create({ 17 | id: 'p1', 18 | type: 'paragraph', 19 | content: 'Enter text here' 20 | }); 21 | main.show('p1'); 22 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lens", 3 | "description": "open science content creation and display", 4 | "version": "0.2.0", 5 | "dependencies": { 6 | "body-parser": "1.14.1", 7 | "browserify": "substance/node-browserify#d3caeb6dcdaa97a258d099c7231a57f0f60ec876", 8 | "cheerio": "0.19.0", 9 | "ejs": "2.3.4", 10 | "express": "4.13.3", 11 | "font-awesome": "4.4.0", 12 | "lodash": "3.10.1", 13 | "node-sass": "3.4.2", 14 | "substance": "substance/substance#fix-lens-#96" 15 | }, 16 | "devDependencies": { 17 | "gulp": "3.9.0", 18 | "gulp-sass": "2.1.0", 19 | "gulp-uglify": "1.5.1", 20 | "through2": "2.0.0" 21 | }, 22 | "peerDependencies": { 23 | "substance": "1.0.0-beta.4" 24 | }, 25 | "scripts": { 26 | "bundle": "gulp" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/bibliography/AddBibItemsPanel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('substance/util/helpers'); 4 | var Component = require('substance/ui/Component'); 5 | var $$ = Component.$$; 6 | var BibItemComponent = require('./BibItemComponent'); 7 | var DialogHeader = require('substance/ui/DialogHeader'); 8 | var ScrollPane = require('substance/ui/ScrollPane'); 9 | 10 | // Create new bib items using cross ref search 11 | // ----------------- 12 | 13 | function AddBibItemsPanel() { 14 | Component.apply(this, arguments); 15 | 16 | // action handlers 17 | this.actions({ 18 | "toggleBibItem": this.toggleBibItem 19 | }); 20 | } 21 | 22 | AddBibItemsPanel.Prototype = function() { 23 | 24 | this.toggleBibItem = function(bibItem) { 25 | this.toggleItem(bibItem.id); 26 | }; 27 | 28 | this.didMount = function() { 29 | // push surface selection state so that we can recover it when closing 30 | var $input = this.$el.find('input'); 31 | $input.val(this.state.searchResult.searchStr).focus(); 32 | }; 33 | 34 | this.handleCancel = function(e) { 35 | e.preventDefault(); 36 | this.send('switchContext', 'bib-items'); 37 | }; 38 | 39 | this.getInitialState = function() { 40 | return { 41 | searchResult: { 42 | searchStr: '', 43 | items: [] 44 | }, 45 | runningQueries: [] 46 | }; 47 | }; 48 | 49 | this.render = function() { 50 | return $$('div').addClass('sc-add-bib-items-panel').append( 51 | $$(DialogHeader, { 52 | label: this.i18n.t('add-bib-entries'), 53 | exitContext: 'bib-items' 54 | }), 55 | $$(ScrollPane).ref('scrollPane').append( 56 | $$('div').addClass('se-search-form').append( 57 | $$('input') 58 | .attr({ 59 | type: "text", 60 | placeholder: this.i18n.t('enter_search_term'), 61 | value: this.state.searchResult.searchStr 62 | }) 63 | .ref('searchStr') 64 | .on('keypress', this.onKeyPress.bind(this)), 65 | $$('button').addClass('button float-right') 66 | .append("Search") 67 | .on('click', this.startSearch.bind(this)) 68 | ), 69 | this.renderSearchResult() 70 | ) 71 | ); 72 | }; 73 | 74 | this.isAdded = function(entry) { 75 | return !!this.props.doc.get(entry.data.DOI); 76 | }; 77 | 78 | // Get label for bib item if already added 79 | this.getLabel = function(doi) { 80 | var label = ''; 81 | if (doi) { 82 | var bibItem = this.props.doc.get(doi); 83 | if (bibItem && bibItem.label) label = bibItem.label; 84 | } 85 | return label; 86 | }; 87 | 88 | this.renderSearchResult = function() { 89 | var searchResultEl = $$('div').addClass('se-search-results'); 90 | var items = this.state.searchResult.items; 91 | 92 | _.each(items, function(entry) { 93 | // TODO: usually we don't have a label 94 | // but when the bib-entry is referenced already 95 | var label = this.getLabel(entry.data.DOI); 96 | // Check if item is already in bibliography 97 | var text = entry.text; 98 | var isAdded = this.isAdded(entry); 99 | 100 | searchResultEl.append($$(BibItemComponent, { 101 | node: { 102 | id: entry.data.DOI, 103 | label: label, 104 | text: text 105 | }, 106 | toggleName: this.i18n.t(isAdded ? 'Remove' : 'Add'), 107 | highlighted: isAdded 108 | })); 109 | }, this); 110 | 111 | return searchResultEl; 112 | }; 113 | 114 | this.onKeyPress = function(e) { 115 | if (e.which == 13 /* ENTER */) { 116 | this.startSearch(); 117 | } 118 | }; 119 | 120 | this.toggleItem = function(itemGuid) { 121 | var doc = this.props.doc; 122 | 123 | if (doc.get(itemGuid)) { 124 | this.removeItem(itemGuid); 125 | } else { 126 | this.addItem(itemGuid); 127 | } 128 | }; 129 | 130 | this.getItem = function(itemGuid) { 131 | return _.find(this.state.searchResult.items, function(item) { 132 | return item.data.DOI === itemGuid; 133 | }); 134 | }; 135 | 136 | this.addItem = function(itemGuid) { 137 | var bibEntry = this.getItem(itemGuid); 138 | var doc = this.props.doc; 139 | doc.transaction({}, {}, function(tx) { 140 | var bibItem = { 141 | id: bibEntry.data.DOI, 142 | type: "bib-item", 143 | data: bibEntry.data, 144 | format: 'citeproc' 145 | }; 146 | tx.create(bibItem); 147 | }); 148 | this.rerender(); 149 | }; 150 | 151 | this.removeItem = function(nodeId) { 152 | this.props.doc.transaction({}, {}, function(tx) { 153 | tx.delete(nodeId); 154 | }); 155 | this.rerender(); 156 | }; 157 | 158 | this.startSearch = function() { 159 | var searchStr; 160 | 161 | // Make this robust for now until we have a fix for owner-based refs 162 | if (this.refs.searchStr) { 163 | searchStr = this.refs.searchStr.val(); 164 | } else { 165 | searchStr = this.refs.scrollPane.refs.searchStr.val(); 166 | } 167 | 168 | var self = this; 169 | var doc = this.props.doc; 170 | 171 | var citeprocCompiler = doc.getCiteprocCompiler(); 172 | var runningQueries = this.state.runningQueries; 173 | _.each(runningQueries, function(query) { 174 | query.reject(); 175 | }); 176 | runningQueries = []; 177 | _.each(this.context.bibSearchEngines, function(engine) { 178 | // the promise will deliver results on progress 179 | var promise = engine.find(searchStr); 180 | promise.progress(function(data) { 181 | self.state.searchResult.items.push({ 182 | data: data, 183 | label: '', 184 | text: citeprocCompiler.renderReference(data) 185 | }); 186 | self.rerender(); 187 | }); 188 | promise.done(function() { 189 | var idx = runningQueries.indexOf(promise); 190 | if (idx >= 0) { 191 | runningQueries.splice(idx, 1); 192 | } 193 | }); 194 | // keep the promise so that we can abort it 195 | runningQueries.push(promise); 196 | }); 197 | var searchResult = this.state.searchResult; 198 | searchResult.searchStr = searchStr; 199 | searchResult.items = []; 200 | this.setState({ searchResult: searchResult, runningQueries: runningQueries }); 201 | }; 202 | }; 203 | 204 | Component.extend(AddBibItemsPanel); 205 | module.exports = AddBibItemsPanel; 206 | -------------------------------------------------------------------------------- /packages/bibliography/BibItem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var DocumentNode = require('substance/model/DocumentNode'); 4 | 5 | function BibItem() { 6 | BibItem.super.apply(this, arguments); 7 | } 8 | 9 | BibItem.Prototype = function() { 10 | 11 | this.setLabel = function(label) { 12 | this.label = label; 13 | this.emit('label', label); 14 | }; 15 | 16 | // Store compiled text version of the bib item 17 | // See bib/bibliography.js 18 | this.setText = function(compiledText) { 19 | this.text = compiledText; 20 | }; 21 | 22 | this.updateCollection = function(doc) { 23 | var collection = doc.getCollection(this.type); 24 | if (collection) { 25 | collection.update(); 26 | } 27 | }; 28 | }; 29 | 30 | DocumentNode.extend(BibItem); 31 | 32 | BibItem.static.name = 'bib-item'; 33 | 34 | BibItem.static.defineSchema({ 35 | format: 'string', 36 | data: 'object' 37 | }); 38 | 39 | BibItem.static.citationType = "bib-item-citation"; 40 | 41 | module.exports = BibItem; 42 | -------------------------------------------------------------------------------- /packages/bibliography/BibItemCitation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Citation = require('../citations/Citation'); 4 | 5 | function BibItemCitation() { 6 | BibItemCitation.super.apply(this, arguments); 7 | } 8 | 9 | BibItemCitation.Prototype = function() { 10 | this.getItemType = function() { 11 | return 'bib-item'; 12 | }; 13 | }; 14 | 15 | Citation.extend(BibItemCitation); 16 | 17 | BibItemCitation.static.name = "bib-item-citation"; 18 | 19 | module.exports = BibItemCitation; 20 | -------------------------------------------------------------------------------- /packages/bibliography/BibItemCitationCommand.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var CitationCommand = require('../citations/CitationCommand'); 4 | 5 | function BibItemCitationCommand() { 6 | BibItemCitationCommand.super.apply(this, arguments); 7 | } 8 | 9 | CitationCommand.extend(BibItemCitationCommand); 10 | 11 | BibItemCitationCommand.static.name = 'bibItemCitation'; 12 | BibItemCitationCommand.static.annotationType = 'bib-item-citation'; 13 | 14 | module.exports = BibItemCitationCommand; -------------------------------------------------------------------------------- /packages/bibliography/BibItemCitationTool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AnnotationTool = require('substance/ui/AnnotationTool'); 4 | 5 | function BibItemCitationTool() { 6 | BibItemCitationTool.super.apply(this, arguments); 7 | } 8 | 9 | AnnotationTool.extend(BibItemCitationTool); 10 | 11 | BibItemCitationTool.static.name = 'bibItemCitation'; 12 | BibItemCitationTool.static.command = 'bibItemCitation'; 13 | 14 | module.exports = BibItemCitationTool; 15 | -------------------------------------------------------------------------------- /packages/bibliography/BibItemCitationXMLConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var CitationXMLConverter = require('../citations/CitationXMLConverter'); 4 | 5 | module.exports = { 6 | 7 | type: 'bib-item-citation', 8 | tagName: 'cite', 9 | 10 | matchElement: function(el) { 11 | return el.is('cite') && el.attr('rtype') === 'bib'; 12 | }, 13 | 14 | import: function(el, node) { 15 | CitationXMLConverter.import(el, node); 16 | }, 17 | 18 | export: function(node, el) { 19 | CitationXMLConverter.export(node, el); 20 | // Add specific type 21 | el.attr('rtype', 'bib'); 22 | } 23 | }; -------------------------------------------------------------------------------- /packages/bibliography/BibItemComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Component = require('substance/ui/Component'); 4 | var $$ = Component.$$; 5 | var Icon = require('substance/ui/FontAwesomeIcon'); 6 | 7 | // Used in BibItemsPanel 8 | function BibItemComponent() { 9 | Component.apply(this, arguments); 10 | } 11 | 12 | BibItemComponent.Prototype = function() { 13 | 14 | this.toggleFocus = function() { 15 | this.send('toggleBibItem', this.props.node); 16 | }; 17 | 18 | this.render = function() { 19 | var el = $$('div').addClass('sc-bib-item').attr('data-id', this.props.node.id); 20 | if (this.props.highlighted) { 21 | el.addClass('se-highlighted'); 22 | } 23 | 24 | // Label 25 | el.append($$('div').addClass('se-label').append(this.props.node.label || '')); 26 | // Focus toggle 27 | el.append( 28 | $$('button').addClass('se-focus-toggle').append( 29 | $$(Icon, {icon: 'fa-eye'}), 30 | [' ', this.props.toggleName].join('') 31 | ).on('click', this.toggleFocus) 32 | ); 33 | // Text 34 | el.append($$('div').addClass('se-text').append(this.props.node.text || '')); 35 | return el; 36 | }; 37 | }; 38 | 39 | Component.extend(BibItemComponent); 40 | 41 | module.exports = BibItemComponent; 42 | -------------------------------------------------------------------------------- /packages/bibliography/BibItemEntry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var oo = require('substance/util/oo'); 4 | var Component = require('substance/ui/Component'); 5 | var $$ = Component.$$; 6 | 7 | function BibItemEntry() { 8 | Component.apply(this, arguments); 9 | 10 | this.props.node.connect(this, { 11 | 'label': this.onLabelChanged 12 | }); 13 | } 14 | 15 | BibItemEntry.Prototype = function() { 16 | 17 | this.dispose = function() { 18 | this.props.node.disconnect(this); 19 | }; 20 | 21 | this.render = function() { 22 | var el = $$('div') 23 | .addClass('bib-item border-bottom pad item small clearfix') 24 | .attr('data-id', this.props.node.id); 25 | 26 | el.on('click', this.onClick); 27 | el.on('mousedown', this.onMouseDown); 28 | if (this.props.active) { 29 | el.addClass('active'); 30 | } 31 | if (this.props.node.label) { 32 | el.append($$('div').addClass('label').append(this.props.node.label)); 33 | } 34 | el.append($$('div').addClass('text').append(this.props.node.text)); 35 | return el; 36 | }; 37 | 38 | this.onClick = function(e) { 39 | e.preventDefault(); 40 | e.stopPropagation(); 41 | }; 42 | 43 | this.onMouseDown = function(e) { 44 | e.preventDefault(); 45 | this.props.handleSelection(this.props.node.id); 46 | }; 47 | 48 | this.onLabelChanged = function() { 49 | this.rerender(); 50 | }; 51 | }; 52 | 53 | oo.inherit(BibItemEntry, Component); 54 | 55 | module.exports = BibItemEntry; 56 | -------------------------------------------------------------------------------- /packages/bibliography/BibItemXMLConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | BibItem XML Converter 5 | */ 6 | module.exports = { 7 | 8 | type: 'bib-item', 9 | tagName: 'bib', 10 | 11 | import: function(el, node) { 12 | node.data = JSON.parse(el.text()); 13 | // use DOI as node id 14 | node.id = node.data.DOI; 15 | node.format = 'citeproc'; 16 | }, 17 | 18 | export: function(node, el) { 19 | el.attr('format', 'citeproc') 20 | .text(JSON.stringify(node.data)); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /packages/bibliography/BibItemsPanel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Component = require('substance/ui/Component'); 4 | var ScrollPane = require('substance/ui/ScrollPane'); 5 | var BibItemComponent = require('./BibItemComponent'); 6 | var BibliographySummary = require('./BibliographySummary'); 7 | var $$ = Component.$$; 8 | 9 | // List existing bib items 10 | // ----------------- 11 | 12 | function BibItemsPanel() { 13 | Component.apply(this, arguments); 14 | 15 | var doc = this.props.doc; 16 | this.bibliography = doc.getCollection('bib-item'); 17 | this.bibliography.connect(this, { 18 | 'bibliography:updated': this.rerender 19 | }); 20 | } 21 | 22 | BibItemsPanel.Prototype = function() { 23 | 24 | this.dispose = function() { 25 | this.bibliography.disconnect(this); 26 | }; 27 | 28 | this.didMount = function() { 29 | this._scrollToTarget(); 30 | }; 31 | 32 | this.didReceiveProps = function() { 33 | this._scrollToTarget(); 34 | }; 35 | 36 | this._scrollToTarget = function() { 37 | var bibItemId = this.getFirstActiveBibItemId(); 38 | if (bibItemId) { 39 | this.refs.scrollPane.scrollTo(bibItemId); 40 | } 41 | }; 42 | 43 | this.getFirstActiveBibItemId = function() { 44 | var doc = this.props.doc; 45 | if (this.props.bibItemId) { 46 | return this.props.bibItemId; 47 | } 48 | 49 | if (this.props.citationId) { 50 | var citation = doc.get(this.props.citationId); 51 | return citation.targets[0]; 52 | } 53 | }; 54 | 55 | this.isHighlighted = function(bibItem) { 56 | var doc = this.props.doc; 57 | if (this.props.bibItemId === bibItem.id) { 58 | return true; 59 | } 60 | 61 | if (this.props.citationId) { 62 | var citation = doc.get(this.props.citationId); 63 | if (citation.targets && citation.targets.indexOf(bibItem.id) >= 0) { 64 | return true; 65 | } 66 | } 67 | return false; 68 | }; 69 | 70 | this.render = function() { 71 | var bibItems = this.bibliography.getItems(); 72 | 73 | var bibItemEls = $$('div').addClass('se-bib-items').ref('bibItems'); 74 | bibItemEls.append($$(BibliographySummary, {bibItems: bibItems})); 75 | 76 | bibItems.forEach(function(bibItem) { 77 | bibItemEls.append($$(BibItemComponent, { 78 | node: bibItem, 79 | toggleName: this.i18n.t('focus'), 80 | highlighted: this.isHighlighted(bibItem) 81 | })); 82 | }.bind(this)); 83 | 84 | var el = $$('div').addClass('sc-bib-items-panel').append( 85 | $$(ScrollPane, {doc: this.props.doc}).append( 86 | bibItemEls 87 | ).ref('scrollPane') 88 | ); 89 | 90 | return el; 91 | }; 92 | 93 | // this.handleDeleteBibItem = function(e) { 94 | // e.preventDefault(); 95 | // var bibItemId = e.currentTarget.dataset.id; 96 | // var doc = this.props.doc; 97 | 98 | // doc.deleteBibItem(bibItemId); 99 | // var bibliography = doc.getBibliography(); 100 | // // Recompile bibliography 101 | // bibliography.compile(); 102 | // var bibItems = bibliography.getCompiledItems(); 103 | // this.setState({ 104 | // bibItems: bibItems 105 | // }); 106 | // }; 107 | }; 108 | 109 | Component.extend(BibItemsPanel); 110 | 111 | module.exports = BibItemsPanel; 112 | -------------------------------------------------------------------------------- /packages/bibliography/Bibliography.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('substance/util/helpers'); 4 | var EventEmitter = require('substance/util/EventEmitter'); 5 | var sortBy = require('lodash/collection/sortBy'); 6 | 7 | // Nomenclature: 'Bibliography' is a set of 'References' which are cited from in the manuscript. 8 | 9 | function Bibliography(doc, containerId) { 10 | EventEmitter.call(this); 11 | this.doc = doc; 12 | this.containerId = containerId; 13 | 14 | this.bibitemsByGuid = {}; 15 | this._compileDebounced = _.debounce(this.compile.bind(this), 50); 16 | 17 | this.doc.connect(this, { 18 | 'document:changed': this.onDocumentChanged 19 | }); 20 | } 21 | 22 | 23 | Bibliography.Prototype = function() { 24 | 25 | this.onDocumentChanged = function(change) { 26 | var doc = this.doc; 27 | var needsUpdate = false; 28 | 29 | _.each(change.ops, function(op) { 30 | if (op.isCreate() || op.isSet() || op.isUpdate()) { 31 | var nodeId = op.path[0]; 32 | var node = doc.get(nodeId); 33 | if (!node) return; 34 | 35 | if (op.isCreate()) { 36 | // Create 37 | if (node.type === 'bib-item' || node.type === 'bib-item-citation') { 38 | needsUpdate = true; 39 | } 40 | } else { 41 | // Update/Set 42 | if (node.type === 'bib-item') { 43 | needsUpdate = true; 44 | } else if (node.type === 'bib-item-citation') { 45 | if (op.path[1] === 'targets') { 46 | needsUpdate = true; 47 | } 48 | } 49 | } 50 | } else if (op.isDelete()) { 51 | // Delete 52 | var deletedNode = op.val; 53 | if (deletedNode.type === 'bib-item-citation' || deletedNode.type === 'bib-item') { 54 | console.log('bib-item-(citation) deleted'); 55 | needsUpdate = true; 56 | } 57 | } 58 | }); 59 | 60 | if (needsUpdate) { 61 | // console.log('updating bibliography'); 62 | this.update(); 63 | } 64 | }; 65 | 66 | this.dispose = function() { 67 | this.doc.disconnect(this); 68 | }; 69 | 70 | this.getDocument = function() { 71 | return this.doc; 72 | }; 73 | 74 | this.getCompiler = function() { 75 | return this.getDocument().getCiteprocCompiler(); 76 | }; 77 | 78 | this.compile = function() { 79 | // console.log('Compiling bibliography'); 80 | var doc = this.getDocument(); 81 | var compiler = this.getCompiler(); 82 | var container = doc.get(this.containerId); 83 | // clear information remaining from the previous compilation 84 | compiler.clear(); 85 | 86 | var bibItems = doc.getIndex('type').get('bib-item'); 87 | this.bibitemsByGuid = {}; 88 | 89 | _.each(bibItems, function(bibItem) { 90 | // TODO: this works only with citeproc type of bibitems 91 | if (bibItem.format !== 'citeproc') { 92 | throw new Error("Only 'citeproc' bibliographic items are supported right now."); 93 | } 94 | var data = bibItem.data; 95 | data.id = bibItem.id; 96 | compiler.addRecord(data); 97 | this.bibitemsByGuid[bibItem.guid] = bibItem; 98 | }.bind(this)); 99 | 100 | // get citation nodes sorted by occurrence position. 101 | var citations = doc.getIndex('type').get('bib-item-citation'); 102 | // generate information for sorting 103 | var citationItems = _.map(citations, function(citation) { 104 | var address = container.getAddress(citation.path); 105 | return { 106 | citation: citation, 107 | address: address 108 | }; 109 | }); 110 | 111 | // sort citation by occurrence in the container 112 | sortBy(citationItems, 'address'); 113 | // compile each label 114 | _.each(citationItems, function(item) { 115 | var citation = item.citation; 116 | if (citation.targets.length>0) { 117 | var targets = _.filter(citation.targets, function(id) { 118 | var found = !!bibItems[id]; 119 | if (!found) { 120 | console.error('No Bibitem found with id', id); 121 | } 122 | return found; 123 | }); 124 | var compiledCitation = this.getCompiler().addCitation(targets); 125 | citation.setLabel(compiledCitation.label); 126 | } else { 127 | citation.setLabel('???'); 128 | } 129 | }.bind(this)); 130 | 131 | var compiledBibItems = this.getCompiler().makeBibliography(); 132 | compiledBibItems = _.sortBy(compiledBibItems, "rank"); 133 | this.sortedBibItems = _.map(compiledBibItems, function(item) { 134 | var bibItem = bibItems[item.id]; 135 | bibItem.setLabel(item.label); 136 | bibItem.setText(item.content); 137 | return bibItem; 138 | }.bind(this)); 139 | 140 | this.emit('bibliography:updated'); 141 | }; 142 | 143 | this.update = function() { 144 | // Unfortunately, we need this, as at the moment a compile is triggered whenever a citation is created. 145 | // And, when deleting or pasting a block with lots of citation this would get very slow. 146 | this._compileDebounced(); 147 | // this.compile(); 148 | }; 149 | 150 | this.makeBibliography = function() { 151 | return this.getCompiler().makeBibliography(); 152 | }; 153 | 154 | // Return all available bib items (= references sorted by occurence of first citation in paper) 155 | this.getBibItems = function() { 156 | var result = []; 157 | var ids = this.getCompiler().getSortedIds(); 158 | for (var i = 0; i < ids.length; i++) { 159 | result.push(this.doc.get(ids[i])); 160 | } 161 | return result; 162 | }; 163 | 164 | // TODO: maybe we should this make the default this.getBibItems ? 165 | this.getCompiledItems = function() { 166 | if (!this.sortedBibItems) { 167 | this.compile(); 168 | } 169 | return this.sortedBibItems; 170 | }; 171 | 172 | this.getItems = function() { 173 | return this.getCompiledItems(); 174 | }; 175 | }; 176 | 177 | 178 | EventEmitter.extend(Bibliography); 179 | 180 | module.exports = Bibliography; -------------------------------------------------------------------------------- /packages/bibliography/BibliographyComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('substance/util/helpers'); 4 | var oo = require('substance/util/oo'); 5 | var Component = require('substance/ui/Component'); 6 | var $$ = Component.$$; 7 | 8 | function BibliographyComponent() { 9 | Component.apply(this, arguments); 10 | } 11 | 12 | BibliographyComponent.Prototype = function() { 13 | this.getDocument = function() { 14 | return this.context.controller.getDocument(); 15 | }; 16 | 17 | this.render = function() { 18 | var state = this.state; 19 | if (state.bibItems) { 20 | var bibItemEls = [ 21 | $$('div').addClass('content-node heading level-1').append('References') 22 | ]; 23 | _.each(state.bibItems, function(bibItem) { 24 | if (bibItem.label) { 25 | bibItemEls.push($$('div').addClass('bib-item clearfix').append( 26 | $$('div').addClass('label csl-left-margin').append(bibItem.label), 27 | $$('div').addClass('text csl-right-inline').append(bibItem.text) 28 | )); 29 | } 30 | }); 31 | return $$('div').addClass('bibliography-component bib-items') 32 | .append(bibItemEls); 33 | } else { 34 | return $$('div'); 35 | } 36 | }; 37 | 38 | this.didMount = function() { 39 | var doc = this.getDocument(); 40 | this.bibliography = doc.getCollection('bib-item'); 41 | this.bibliography.connect(this, { 42 | 'bibliography:updated': this.update 43 | }); 44 | }; 45 | 46 | this.update = function() { 47 | var bibItems = this.bibliography.getItems(); 48 | this.setState({ 49 | bibItems: bibItems 50 | }); 51 | }; 52 | }; 53 | 54 | oo.inherit(BibliographyComponent, Component); 55 | 56 | module.exports = BibliographyComponent; 57 | -------------------------------------------------------------------------------- /packages/bibliography/BibliographySummary.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Component = require('substance/ui/Component'); 4 | var $$ = Component.$$; 5 | 6 | function BibliographySummary() { 7 | BibliographySummary.super.apply(this, arguments); 8 | } 9 | 10 | BibliographySummary.Prototype = function() { 11 | this.handleAddBibItems = function(e) { 12 | e.preventDefault(); 13 | this.send('switchContext', 'add-bib-items'); 14 | }; 15 | 16 | this.render = function() { 17 | var el = $$('div').addClass('se-bibliography-summary'); 18 | 19 | el.append( 20 | $$('p').append( 21 | 'Your bibliography has ', 22 | $$('strong').append(this.props.bibItems.length.toString(), ' references'), 23 | ' in total.' 24 | // $$('strong').append('? references'), 25 | // 'are not cited.' 26 | ) 27 | ); 28 | 29 | var config = this.context.config; 30 | if (config.isEditable) { 31 | el.append( 32 | $$('p').append( 33 | $$('a').attr({href: '#'}) 34 | .on('click', this.handleAddBibItems) 35 | .append('Add references') 36 | ) 37 | ); 38 | } 39 | return el; 40 | }; 41 | }; 42 | 43 | Component.extend(BibliographySummary); 44 | 45 | module.exports = BibliographySummary; 46 | -------------------------------------------------------------------------------- /packages/bibliography/CiteprocCompiler.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('substance/util/helpers'); 4 | var $ = require('substance/util/jquery'); 5 | var CSL = require('./citeproc/citeproc').CSL; 6 | var CiteprocDefaultConfig = require('./CiteprocDefaultConfig'); 7 | 8 | function CiteprocCompiler( config ) { 9 | this.config = config || new CiteprocDefaultConfig(); 10 | this.style = this.config.style; 11 | this.clear(); 12 | } 13 | 14 | CiteprocCompiler.prototype.setStyle = function(style) { 15 | this.style = style; 16 | this.clear(); 17 | }; 18 | 19 | CiteprocCompiler.prototype.clear = function() { 20 | this.engine = new CSL.Engine(this, this.style); 21 | this.data = {}; 22 | this.citationLabels = {}; 23 | this.count = 0; 24 | }; 25 | 26 | CiteprocCompiler.prototype.addRecord = function( citeprocData ) { 27 | citeprocData.id = citeprocData.id || "ITEM_"+this.count++; 28 | this.sanitizeData(citeprocData); 29 | this.data[citeprocData.id] = citeprocData; 30 | return citeprocData.id; 31 | }; 32 | 33 | CiteprocCompiler.prototype.addCitation = function( bibItemIds ) { 34 | if (!_.isArray(bibItemIds)) { 35 | bibItemIds = [ bibItemIds ]; 36 | } 37 | var citation = { 38 | "citationItems": [], 39 | "properties": {} 40 | }; 41 | for (var i = 0; i < bibItemIds.length; i++) { 42 | citation.citationItems.push( { id: bibItemIds[i] } ); 43 | } 44 | var result = this.engine.appendCitationCluster(citation); 45 | var citationIndex = result[0][0]; 46 | citation = this.engine.registry.citationreg.citationByIndex[citationIndex]; 47 | var citationId = citation.citationID; 48 | var citationLabel = result[0][1]; 49 | 50 | this.citationLabels[citationId] = citationLabel; 51 | // Update citation labels which may happen due to disambiguation 52 | for (i = 1; i < result.length; i++) { 53 | this.citationLabels[result[i][0]] = result[i][1]; 54 | } 55 | 56 | return { 57 | id: citationId, 58 | label: citationLabel 59 | }; 60 | }; 61 | 62 | CiteprocCompiler.prototype.getCitationCount = function(referenceId) { 63 | var citations = this.engine.registry.citationreg.citationsByItemId[referenceId]; 64 | if (citations) { 65 | return citations.length; 66 | } else { 67 | return 0; 68 | } 69 | }; 70 | 71 | /** 72 | * Compiles bibliography data used to render a bibliography. 73 | * In contrast to the original citeproc implementation, all references are 74 | * considered, and marked as uncited if no citations exist. 75 | * For each reference, its rank, the number of citations, a label as it occurs in the text, and 76 | * the rendered reference as HTML is provided 77 | */ 78 | CiteprocCompiler.prototype.makeBibliography = function() { 79 | var bib = {}, bibList = [], 80 | citationByIndex = this.engine.registry.citationreg.citationByIndex, 81 | citationsPre = [], 82 | sortedIds, i, rank, citation, label, content, id; 83 | 84 | // prepare a citationsPre list as it is used with processCitationCluster 85 | for (i = 0; i < citationByIndex.length; i += 1) { 86 | citation = citationByIndex[i]; 87 | citationsPre.push(["" + citation.citationID, citation.properties.noteIndex]); 88 | } 89 | 90 | // process cited references first 91 | sortedIds = this.engine.registry.getSortedIds(); 92 | for (rank = 0; rank < sortedIds.length; rank++) { 93 | id = sortedIds[rank]; 94 | label = this.engine.previewCitationCluster({ 95 | citationItems: [ { id: id } ], 96 | properties: {} 97 | }, citationsPre, [], 'html'); 98 | content = this.renderReference(this.data[id]); 99 | bib[id] = { 100 | id: id, 101 | rank: rank, 102 | citeCount: this.getCitationCount(id), 103 | label: label, 104 | content: content 105 | }; 106 | bibList.push(bib[id]); 107 | } 108 | 109 | for(id in this.data) { 110 | // skip cited references 111 | if (bib[id]) { 112 | continue; 113 | } 114 | rank += 1; 115 | content = this.renderReference(this.data[id]); 116 | bib[id] = { 117 | id: id, 118 | rank: rank, 119 | citeCount: 0, 120 | label: null, 121 | content: content 122 | }; 123 | bibList.push(bib[id]); 124 | } 125 | return bib; 126 | }; 127 | 128 | CiteprocCompiler.prototype.getSortedIds = function() { 129 | var result, done, i, id; 130 | result = this.engine.registry.getSortedIds(); 131 | done = {}; 132 | for (i = 0; i < result.length; i++) { 133 | done[result[i]] = true; 134 | } 135 | for(id in this.data) { 136 | if (!done[id]) { 137 | result.push(id); 138 | done[id] = true; 139 | } 140 | } 141 | return result; 142 | }; 143 | 144 | CiteprocCompiler.prototype.renderReference = function(reference) { 145 | var refHtml; 146 | 147 | function extractContent(refHtml) { 148 | // Note: we only want the rendered reference, without the surrounding layout stuff 149 | // AFAIK there are only two cases, with label or without. In case that the style 150 | // renderes labels in the bibliography there is an element .csl-right-line containing 151 | // the content we are interested in. 152 | if (refHtml.search('csl-right-inline') >= 0) { 153 | var $el = $('
').append($(refHtml)).find('.csl-right-inline'); 154 | return $el.html(); 155 | } else { 156 | return refHtml; 157 | } 158 | } 159 | 160 | var self = this; 161 | function renderFallback() { 162 | self.sanitizeData(reference); 163 | 164 | // HACK: CiteprocCompiler.getBibliographyEntry is much faster than this implementation. 165 | // However, in certain cases citeproc crashed, so that we use this slower implementation as 166 | // fallback. 167 | var engine = new CSL.Engine({ 168 | retrieveItem: function() { 169 | return reference; 170 | }, 171 | retrieveLocale: function(lang) { 172 | return self.config.locale[lang]; 173 | } 174 | }, self.style ); 175 | // In the case that our custom incremental implementation fails just use citeproc 176 | var citation = { 177 | "citationItems": [ { id: reference.id } ], 178 | "properties": {} 179 | }; 180 | engine.appendCitationCluster(citation); 181 | try { 182 | return engine.makeBibliography()[1][0]; 183 | } catch (err) { 184 | console.error(err); 185 | return "Error: invalid citation data."; 186 | } 187 | } 188 | 189 | if (this.data[reference.id]) { 190 | // Note: this method only works for registered references and sometimes failed even then 191 | try { 192 | refHtml = CiteprocCompiler.getBibliographyEntry.call(this.engine, reference.id); 193 | } catch (err) { 194 | refHtml = renderFallback(); 195 | } 196 | } else { 197 | refHtml = renderFallback(); 198 | } 199 | return extractContent(refHtml); 200 | }; 201 | 202 | CiteprocCompiler.getBibliographyEntry = function (id) { 203 | var item, topblobs, refHtml, collapse_parallel, j, jlen, chr; 204 | 205 | this.tmp.area = "bibliography"; 206 | this.tmp.last_rendered_name = false; 207 | this.tmp.bibliography_errors = []; 208 | this.tmp.bibliography_pos = 0; 209 | this.tmp.disambig_override = true; 210 | this.tmp.just_looking = true; 211 | item = this.retrieveItem(id); 212 | 213 | // this.output.startTag("bib_entry", bib_entry); 214 | this.parallel.StartCitation([[{id: "" + item.id}, item]]); 215 | this.tmp.term_predecessor = false; 216 | CSL.getCite.call(this, item); 217 | // this.output.endTag("bib_entry"); 218 | 219 | // adds prefix and suffix 220 | if (this.output.queue[0].blobs.length && this.output.queue[0].blobs[0].blobs.length) { 221 | if (collapse_parallel || !this.output.queue[0].blobs[0].blobs[0].strings) { 222 | topblobs = this.output.queue[0].blobs; 223 | collapse_parallel = false; 224 | } else { 225 | topblobs = this.output.queue[0].blobs[0].blobs; 226 | } 227 | for (j = topblobs.length - 1; j > -1; j += -1) { 228 | if (topblobs[j].blobs && topblobs[j].blobs.length !== 0) { 229 | var last_locale = this.tmp.cite_locales[this.tmp.cite_locales.length - 1]; 230 | var suffix; 231 | if (this.tmp.cite_affixes[this.tmp.area][last_locale]) { 232 | suffix = this.tmp.cite_affixes[this.tmp.area][last_locale].suffix; 233 | } else { 234 | suffix = this.bibliography.opt.layout_suffix; 235 | } 236 | chr = suffix.slice(0, 1); 237 | if (chr && topblobs[j].strings.suffix.slice(-1) === chr) { 238 | topblobs[j].strings.suffix = topblobs[j].strings.suffix.slice(0, -1); 239 | } 240 | topblobs[j].strings.suffix += suffix; 241 | break; 242 | } 243 | } 244 | topblobs[0].strings.prefix = this.bibliography.opt.layout_prefix + topblobs[0].strings.prefix; 245 | } 246 | for (j=0,jlen=this.output.queue.length;j No. Doc. U.N. Sales No. No. \u201c \u201d \u2018 \u2019 st nd rd th first second third fourth fifth sixth seventh eighth ninth tenth at in ibid accessed retrieved from forthcoming reference references ref refs n.d. and et al. interview letter anonymous anon. and others in press online cited internet presented at the AD BC Spring Summer Autumn Winter with anthropology astronomy biology botany chemistry engineering generic base geography geology history humanities literature math medicine philosophy physics psychology sociology science political science social science theology zoology book books chapter chapters column columns figure figures folio folios number numbers line lines note notes opus opera page pages paragraph paragraph part parts section sections volume volumes edition editions verse verses sub verbo s.vv bk. chap. col. fig. f. no. op. p. pp. para. pt. sec. s.v. s.vv. v. vv. vol. vols. edition ed. ¶¶ § §§ editor editors translator translators ed. eds. tran. trans. edited by translated by to interview by ed. trans. January February March April May June July August September October November December Jan. Feb. Mar. Apr. May Jun. Jul. Aug. Sep. Oct. Nov. Dec. "}; 12 | 13 | module.exports = CiteprocDefaultConfig; 14 | -------------------------------------------------------------------------------- /packages/bibliography/CrossrefSearch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var $ = require('substance/util/jquery'); 4 | 5 | function CrossrefSearch() { 6 | this.lastSearch = ""; 7 | this.lastResult = []; 8 | 9 | this.name = "crossref"; 10 | this.label = "CrossRef"; 11 | } 12 | 13 | CrossrefSearch.resolveDOI = function( doi ) { 14 | var promise = $.Deferred(); 15 | if (!(/^http:/.exec(doi))) { 16 | doi = "http://dx.doi.org/"+doi; 17 | } 18 | $.ajax(doi, { 19 | type: 'GET', 20 | crossDomain: true, 21 | cache: false, 22 | headers: { 23 | Accept: "application/citeproc+json" 24 | }, 25 | success: function(data) { 26 | // window.console.log("Received citeproc-json:", JSON.stringify(data, null, 2) ); 27 | promise.resolve(data); 28 | }, 29 | error: function(req, status, err) { 30 | window.console.error("Could not retrieve data from cross-ref", status, err); 31 | promise.rejectWith(null, err); 32 | } 33 | }); 34 | return promise; 35 | }; 36 | 37 | CrossrefSearch.prototype.find = function( searchStr, context ) { 38 | var searchTerms, doiQueryParams, count, result, 39 | promisedResult = $.Deferred(), 40 | self = this; 41 | 42 | // deliver a cached result if the search has not changed 43 | if (this.lastSearch === searchStr) { 44 | result = this.lastResult; 45 | window.setTimeout(function() { 46 | result.forEach(function(data) { 47 | promisedResult.notifyWith(context, [ data ]); 48 | }); 49 | promisedResult.resolveWith(context); 50 | }, 0); 51 | } 52 | // otherwise start a new search; 53 | else { 54 | this.lastSearch = ""; 55 | this.lastSearch = []; 56 | result = []; 57 | searchTerms = searchStr.trim().toLowerCase().split(/\s+/); 58 | doiQueryParams = searchTerms.join('+'); 59 | count = 0; 60 | $.ajax('http://search.crossref.org/dois', { 61 | data: { 62 | q: doiQueryParams, 63 | sort: "score" 64 | }, 65 | success: function(searchResult) { 66 | function step() { 67 | if (count < searchResult.length) { 68 | var entry = searchResult[count++]; 69 | var promisedData = CrossrefSearch.resolveDOI(entry.doi); 70 | promisedData.done(function(data) { 71 | if (promisedResult.state() === "pending") { 72 | result.push(data); 73 | promisedResult.notifyWith(context, [ data ]); 74 | } 75 | }).always(function() { 76 | if (promisedResult.state() === "pending") { 77 | step(); 78 | } 79 | }); 80 | } else { 81 | self.lastSearch = searchStr; 82 | self.lastResult = result; 83 | promisedResult.resolveWith(context); 84 | } 85 | } 86 | // start the chain 87 | step(); 88 | }, 89 | error: function(req, status, err) { 90 | promisedResult.rejectWith(context, [err]); 91 | } 92 | }); 93 | } 94 | return promisedResult; 95 | }; 96 | 97 | module.exports = CrossrefSearch; 98 | -------------------------------------------------------------------------------- /packages/bibliography/citeproc/csl_jquery.js: -------------------------------------------------------------------------------- 1 | /* global $, CSL */ 2 | var inBrowser = (typeof window !== 'undefined'); 3 | 4 | /** 5 | * This is an XML adapter for CSL which uses jquery which is 6 | * provided by cheerio when used as a nodejs server module. 7 | */ 8 | var CSL_JQUERY = function () { 9 | // Copied from CSL_CHROME: 10 | // This seems horribly tormented, but there might be a reason for it. 11 | // Perhaps this was the only way I found to get namespacing to work ... ? 12 | this.institutionStr = ''; 13 | this.ns = "http://purl.org/net/xbiblio/csl"; 14 | }; 15 | 16 | CSL_JQUERY.prototype._parseDoc = function(xml) { 17 | if (inBrowser) { 18 | var parser = new window.DOMParser(); 19 | return parser.parseFromString(xml, "text/xml"); 20 | } else { 21 | return $(xml); 22 | } 23 | }; 24 | 25 | CSL_JQUERY.prototype.importNode = function(doc, srcElement) { 26 | if (inBrowser) { 27 | return doc.importNode(srcElement, true); 28 | } else { 29 | srcElement._root = doc._root; 30 | return srcElement; 31 | } 32 | }; 33 | 34 | CSL_JQUERY.prototype._hasAttributes = function (node) { 35 | return (node.attributes && node.attributes.length > 0); 36 | }; 37 | 38 | 39 | CSL_JQUERY.prototype._isElementNode = function(el) { 40 | if (inBrowser) { 41 | return (el.nodeType === window.Node.ELEMENT_NODE); 42 | } else { 43 | return el.type === "tag"; 44 | } 45 | }; 46 | 47 | CSL_JQUERY.prototype._getTagName = function(el) { 48 | if (!el.tagName) { 49 | return ""; 50 | } else { 51 | return el.tagName.toLowerCase(); 52 | } 53 | }; 54 | 55 | 56 | /** 57 | * Copied from CSL_CHROME.prototype.clean. 58 | */ 59 | CSL_JQUERY.prototype.clean = function (xml) { 60 | xml = xml.replace(/<\?[^?]+\?>/g, ""); 61 | xml = xml.replace(/]+>/g, ""); 62 | xml = xml.replace(/^\s+/, ""); 63 | xml = xml.replace(/\s+$/, ""); 64 | xml = xml.replace(/^\n*/, ""); 65 | return xml; 66 | }; 67 | 68 | CSL_JQUERY.prototype.getStyleId = function (myxml, styleName) { 69 | var tagName = styleName ? "title" : "id"; 70 | var node = $(myxml).find(tagName); 71 | if (node.length) { 72 | node = node[0]; 73 | } 74 | return $(node).text(); 75 | }; 76 | 77 | CSL_JQUERY.prototype.children = function (myxml) { 78 | return $(myxml).children(); 79 | }; 80 | 81 | CSL_JQUERY.prototype.nodename = function (myxml) { 82 | return this._getTagName(myxml); 83 | }; 84 | 85 | CSL_JQUERY.prototype.attributes = function (myxml) { 86 | var result = {}; 87 | var attributes, attr; 88 | if (!myxml) { 89 | } else if (inBrowser) { 90 | if (this._hasAttributes(myxml)) { 91 | attributes = myxml.attributes; 92 | for (var pos = 0, len=attributes.length; pos < len; pos += 1) { 93 | attr = attributes[pos]; 94 | result["@" + attr.name] = attr.value; 95 | } 96 | } 97 | } else { 98 | if (myxml.attribs) { 99 | attributes = myxml.attribs; 100 | for (var name in attributes) { 101 | if (attributes.hasOwnProperty(name)) { 102 | result["@" + name] = attributes[name]; 103 | } 104 | } 105 | } 106 | } 107 | return result; 108 | }; 109 | 110 | CSL_JQUERY.prototype.content = function (myxml) { 111 | return $(myxml).text(); 112 | }; 113 | 114 | CSL_JQUERY.prototype.namespace = { 115 | "xml":"http://www.w3.org/XML/1998/namespace" 116 | }; 117 | 118 | CSL_JQUERY.prototype.numberofnodes = function (myxml) { 119 | if (myxml) { 120 | return myxml.length; 121 | } else { 122 | return 0; 123 | } 124 | }; 125 | 126 | CSL_JQUERY.prototype.getAttributeName = function (attr) { 127 | // TODO: why should this be called with an attribute element? 128 | // as we never return such 129 | return attr.name; 130 | }; 131 | 132 | CSL_JQUERY.prototype.getAttributeValue = function (myxml,name,namespace) { 133 | if (namespace) { 134 | name = namespace+":"+name; 135 | } 136 | return $(myxml).attr(name); 137 | }; 138 | 139 | CSL_JQUERY.prototype.getNodeValue = function (myxml,name) { 140 | if (name){ 141 | var $el = $(myxml).find(name); 142 | if ($el.length) { 143 | return $el.text(); 144 | } 145 | } else { 146 | return $(myxml).text(); 147 | } 148 | }; 149 | 150 | CSL_JQUERY.prototype.setAttributeOnNodeIdentifiedByNameAttribute = function (myxml,nodename,partname,attrname,val) { 151 | if (attrname.slice(0,1) === '@'){ 152 | attrname = attrname.slice(1); 153 | } 154 | $(myxml).find(nodename).each(function() { 155 | var $node = $(this); 156 | if ($node.attr("name") === partname) { 157 | $node.attr(attrname, val); 158 | } 159 | }); 160 | }; 161 | 162 | CSL_JQUERY.prototype.deleteNodeByNameAttribute = function (myxml,val) { 163 | $(myxml).find('*[name='+val+']').remove(); 164 | }; 165 | 166 | CSL_JQUERY.prototype.deleteAttribute = function (myxml,attr) { 167 | $(myxml).removeAttr(attr); 168 | }; 169 | 170 | CSL_JQUERY.prototype.setAttribute = function (myxml,attr,val) { 171 | $(myxml).attr(attr,val); 172 | // CSL_CHROME does return 'false' 173 | return false; 174 | }; 175 | 176 | CSL_JQUERY.prototype.nodeCopy = function (myxml) { 177 | return $(myxml).clone(); 178 | }; 179 | 180 | CSL_JQUERY.prototype.getNodesByName = function (myxml,name,nameattrval) { 181 | return $(myxml).find(name+'[name='+nameattrval+']'); 182 | }; 183 | 184 | CSL_JQUERY.prototype.nodeNameIs = function (myxml,name) { 185 | return (this._getTagName(myxml) === name); 186 | }; 187 | 188 | CSL_JQUERY.prototype.makeXml = function (myxml) { 189 | if (!myxml) { 190 | myxml = ""; 191 | } 192 | myxml = myxml.replace(/\s*<\?[^>]*\?>\s*\n*/g, ""); 193 | var doc = this._parseDoc(myxml); 194 | var root; 195 | if (inBrowser) { 196 | root = doc.firstChild; 197 | } else { 198 | root = doc._root; 199 | } 200 | return root; 201 | }; 202 | 203 | CSL_JQUERY.prototype.insertChildNodeAfter = function (parent,node,pos,datexml) { 204 | var myxml = this.importNode(node.ownerDocument, datexml); 205 | $(myxml).insertAfter(node); 206 | return parent; 207 | }; 208 | 209 | /** 210 | * Copied from CSL_CHROME and adapted. 211 | * TODO: this could get some explanation 212 | */ 213 | CSL_JQUERY.prototype.insertPublisherAndPlace = function(myxml) { 214 | var group = $(myxml).find("group"); 215 | for (var i = 0, ilen = group.length; i < ilen; i += 1) { 216 | var $group = $(group[i]); 217 | var $publisher = $group.find('[variable=publisher]'); 218 | var $publisherPlace = $group.find('[variable=publisher-place]'); 219 | if ($publisher.length && $publisherPlace.length) { 220 | $group.attr('has-publisher-and-publisher-place', true); 221 | } 222 | } 223 | }; 224 | 225 | // add a name element if it does not contain any name 226 | // is not child of substitute 227 | CSL_JQUERY.prototype.addMissingNameNodes = function(myxml) { 228 | $(myxml).find("names").each(function() { 229 | var $namesEl = $(this); 230 | var $nameEls = $namesEl.find('name'); 231 | if ($nameEls.length === 0 && !$namesEl.parent('substitute')) { 232 | $namesEl.append($('')); 233 | } 234 | }); 235 | }; 236 | 237 | // add to with when missing 238 | CSL_JQUERY.prototype.addInstitutionNodes = function(myxml) { 239 | 240 | function _addInstitutionPartAttributes($institutionPart, $nameEl) { 241 | for (var j = 0, jlen = CSL.INSTITUTION_KEYS.length; j < jlen; j += 1) { 242 | var attrname = CSL.INSTITUTION_KEYS[j]; 243 | var attrval = $nameEl.attr(attrname); 244 | if (attrval) { 245 | $institutionPart.attr(attrname, attrval); 246 | } 247 | } 248 | } 249 | 250 | $(myxml).find('names').each(function() { 251 | var $nameEl = $(this).find('name'); 252 | var $institution = $(this).find('institution'); 253 | if ($nameEl.length && !$institution.length) { 254 | $institution = $(this.institutionStr); 255 | var $institutionPart = $institution.find("institution-part"); 256 | _addInstitutionPartAttributes($institutionPart, $nameEl); 257 | $nameEl.find("name-part[name=family]").each(function() { 258 | _addInstitutionPartAttributes($institutionPart, $(this)); 259 | }); 260 | $institution.insertBefore($nameEl); 261 | } 262 | }); 263 | }; 264 | 265 | CSL_JQUERY.prototype.flagDateMacros = function(myxml) { 266 | $(myxml).find('macro').each(function() { 267 | var $macro = $(this); 268 | var $date = $macro.find('data'); 269 | if ($date.length) { 270 | $macro.attr('macro-has-date', 'true'); 271 | } 272 | }); 273 | }; 274 | 275 | module.exports = CSL_JQUERY; 276 | -------------------------------------------------------------------------------- /packages/bibliography/citeproc/csl_nodejs_jsdom.js: -------------------------------------------------------------------------------- 1 | // just a shim which imports our jquery based xml adapter. 2 | 3 | var XmlAdapter; 4 | if (typeof window === 'undefined') { 5 | XmlAdapter = require('./csl_jquery'); 6 | } else { 7 | XmlAdapter = require('./xmldom'); 8 | } 9 | 10 | module.exports = { 11 | CSL_NODEJS_JSDOM: XmlAdapter 12 | }; 13 | -------------------------------------------------------------------------------- /packages/bibliography/citeproc/xmldom.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2009, 2010 and 2011 Frank G. Bennett, Jr. All Rights 3 | * Reserved. 4 | * 5 | * The contents of this file are subject to the Common Public 6 | * Attribution License Version 1.0 (the “License”); you may not use 7 | * this file except in compliance with the License. You may obtain a 8 | * copy of the License at: 9 | * 10 | * http://bitbucket.org/fbennett/citeproc-js/src/tip/LICENSE. 11 | * 12 | * The License is based on the Mozilla Public License Version 1.1 but 13 | * Sections 14 and 15 have been added to cover use of software over a 14 | * computer network and provide for limited attribution for the 15 | * Original Developer. In addition, Exhibit A has been modified to be 16 | * consistent with Exhibit B. 17 | * 18 | * Software distributed under the License is distributed on an “AS IS” 19 | * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 20 | * the License for the specific language governing rights and limitations 21 | * under the License. 22 | * 23 | * The Original Code is the citation formatting software known as 24 | * "citeproc-js" (an implementation of the Citation Style Language 25 | * [CSL]), including the original test fixtures and software located 26 | * under the ./std subdirectory of the distribution archive. 27 | * 28 | * The Original Developer is not the Initial Developer and is 29 | * __________. If left blank, the Original Developer is the Initial 30 | * Developer. 31 | * 32 | * The Initial Developer of the Original Code is Frank G. Bennett, 33 | * Jr. All portions of the code written by Frank G. Bennett, Jr. are 34 | * Copyright (c) 2009, 2010 and 2011 Frank G. Bennett, Jr. All Rights Reserved. 35 | * 36 | * Alternatively, the contents of this file may be used under the 37 | * terms of the GNU Affero General Public License (the [AGPLv3] 38 | * License), in which case the provisions of [AGPLv3] License are 39 | * applicable instead of those above. If you wish to allow use of your 40 | * version of this file only under the terms of the [AGPLv3] License 41 | * and not to allow others to use your version of this file under the 42 | * CPAL, indicate your decision by deleting the provisions above and 43 | * replace them with the notice and other provisions required by the 44 | * [AGPLv3] License. If you do not delete the provisions above, a 45 | * recipient may use your version of this file under either the CPAL 46 | * or the [AGPLv3] License.” 47 | */ 48 | var CSL_IS_IE; 49 | var CSL_CHROME = function () { 50 | if ("undefined" == typeof DOMParser || CSL_IS_IE) { 51 | CSL_IS_IE = true; 52 | DOMParser = function() {}; 53 | DOMParser.prototype.parseFromString = function(str, contentType) { 54 | if ("undefined" != typeof ActiveXObject) { 55 | var xmldata = new ActiveXObject('MSXML.DomDocument'); 56 | xmldata.async = false; 57 | xmldata.loadXML(str); 58 | return xmldata; 59 | } else if ("undefined" != typeof XMLHttpRequest) { 60 | var xmldata = new XMLHttpRequest; 61 | if (!contentType) { 62 | contentType = 'text/xml'; 63 | } 64 | xmldata.open('GET', 'data:' + contentType + ';charset=utf-8,' + encodeURIComponent(str), false); 65 | if(xmldata.overrideMimeType) { 66 | xmldata.overrideMimeType(contentType); 67 | } 68 | xmldata.send(null); 69 | return xmldata.responseXML; 70 | } 71 | }; 72 | this.hasAttributes = function (node) { 73 | var ret; 74 | if (node.attributes && node.attributes.length) { 75 | ret = true; 76 | } else { 77 | ret = false; 78 | } 79 | return ret; 80 | }; 81 | } else { 82 | this.hasAttributes = function (node) { 83 | var ret; 84 | if (node.attributes && node.attributes.length) { 85 | ret = true; 86 | } else { 87 | ret = false; 88 | } 89 | return ret; 90 | }; 91 | } 92 | this.importNode = function (doc, srcElement) { 93 | if ("undefined" == typeof doc.importNode) { 94 | var ret = this._importNode(doc, srcElement, true); 95 | } else { 96 | var ret = doc.importNode(srcElement, true); 97 | } 98 | return ret; 99 | }; 100 | this._importNode = function(doc, node, allChildren) { 101 | switch (node.nodeType) { 102 | case 1: 103 | var newNode = doc.createElement(node.nodeName); 104 | if (node.attributes && node.attributes.length > 0) 105 | for (var i = 0, il = node.attributes.length; i < il;) 106 | newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i++].nodeName)); 107 | if (allChildren && node.childNodes && node.childNodes.length > 0) 108 | for (var i = 0, il = node.childNodes.length; i < il;) 109 | newNode.appendChild(this._importNode(doc, node.childNodes[i++], allChildren)); 110 | return newNode; 111 | break; 112 | case 3: 113 | case 4: 114 | case 8: 115 | } 116 | }; 117 | this.parser = new DOMParser(); 118 | var str = ""; 119 | var inst_doc = this.parser.parseFromString(str, "text/xml"); 120 | var inst_node = inst_doc.getElementsByTagName("institution"); 121 | this.institution = inst_node.item(0); 122 | var inst_part_node = inst_doc.getElementsByTagName("institution-part"); 123 | this.institutionpart = inst_part_node.item(0); 124 | this.ns = "http://purl.org/net/xbiblio/csl"; 125 | }; 126 | CSL_CHROME.prototype.clean = function (xml) { 127 | xml = xml.replace(/<\?[^?]+\?>/g, ""); 128 | xml = xml.replace(/]+>/g, ""); 129 | xml = xml.replace(/^\s+/, ""); 130 | xml = xml.replace(/\s+$/, ""); 131 | xml = xml.replace(/^\n*/, ""); 132 | return xml; 133 | }; 134 | CSL_CHROME.prototype.getStyleId = function (myxml) { 135 | var text = ""; 136 | var node = myxml.getElementsByTagName("id"); 137 | if (node && node.length) { 138 | node = node.item(0); 139 | } 140 | if (node) { 141 | text = node.textContent; 142 | } 143 | if (!text) { 144 | text = node.innerText; 145 | } 146 | if (!text) { 147 | text = node.innerHTML; 148 | } 149 | return text; 150 | }; 151 | CSL_CHROME.prototype.children = function (myxml) { 152 | var children, pos, len, ret; 153 | if (myxml) { 154 | ret = []; 155 | children = myxml.childNodes; 156 | for (pos = 0, len = children.length; pos < len; pos += 1) { 157 | if (children[pos].nodeName != "#text") { 158 | ret.push(children[pos]); 159 | } 160 | } 161 | return ret; 162 | } else { 163 | return []; 164 | } 165 | }; 166 | CSL_CHROME.prototype.nodename = function (myxml) { 167 | var ret = myxml.nodeName; 168 | return ret; 169 | }; 170 | CSL_CHROME.prototype.attributes = function (myxml) { 171 | var ret, attrs, attr, key, xml, pos, len; 172 | ret = new Object(); 173 | if (myxml && this.hasAttributes(myxml)) { 174 | attrs = myxml.attributes; 175 | for (pos = 0, len=attrs.length; pos < len; pos += 1) { 176 | attr = attrs[pos]; 177 | ret["@" + attr.name] = attr.value; 178 | } 179 | } 180 | return ret; 181 | }; 182 | CSL_CHROME.prototype.content = function (myxml) { 183 | var ret; 184 | if ("undefined" != typeof myxml.textContent) { 185 | ret = myxml.textContent; 186 | } else if ("undefined" != typeof myxml.innerText) { 187 | ret = myxml.innerText; 188 | } else { 189 | ret = myxml.txt; 190 | } 191 | return ret; 192 | }; 193 | CSL_CHROME.prototype.namespace = { 194 | "xml":"http://www.w3.org/XML/1998/namespace" 195 | } 196 | CSL_CHROME.prototype.numberofnodes = function (myxml) { 197 | if (myxml) { 198 | return myxml.length; 199 | } else { 200 | return 0; 201 | } 202 | }; 203 | CSL_CHROME.prototype.getAttributeName = function (attr) { 204 | var ret = attr.name; 205 | return ret; 206 | } 207 | CSL_CHROME.prototype.getAttributeValue = function (myxml,name,namespace) { 208 | var ret = ""; 209 | if (myxml && this.hasAttributes(myxml) && myxml.getAttribute(name)) { 210 | ret = myxml.getAttribute(name); 211 | } 212 | return ret; 213 | } 214 | CSL_CHROME.prototype.getNodeValue = function (myxml,name) { 215 | var ret = ""; 216 | if (name){ 217 | var vals = myxml.getElementsByTagName(name); 218 | if (vals.length > 0) { 219 | if ("undefined" != typeof vals[0].textContent) { 220 | ret = vals[0].textContent; 221 | } else if ("undefined" != typeof vals[0].innerText) { 222 | ret = vals[0].innerText; 223 | } else { 224 | ret = vals[0].text; 225 | } 226 | } 227 | } else { 228 | ret = myxml; 229 | } 230 | if (ret && ret.childNodes && (ret.childNodes.length == 0 || (ret.childNodes.length == 1 && ret.firstChild.nodeName == "#text"))) { 231 | if ("undefined" != typeof ret.textContent) { 232 | ret = ret.textContent; 233 | } else if ("undefined" != typeof ret.innerText) { 234 | ret = ret.innerText; 235 | } else { 236 | ret = ret.text; 237 | } 238 | } 239 | return ret; 240 | } 241 | CSL_CHROME.prototype.setAttributeOnNodeIdentifiedByNameAttribute = function (myxml,nodename,partname,attrname,val) { 242 | var pos, len, xml, nodes, node; 243 | if (attrname.slice(0,1) === '@'){ 244 | attrname = attrname.slice(1); 245 | } 246 | nodes = myxml.getElementsByTagName(nodename); 247 | for (pos = 0, len = nodes.length; pos < len; pos += 1) { 248 | node = nodes[pos]; 249 | if (node.getAttribute("name") != partname) { 250 | continue; 251 | } 252 | node.setAttribute(attrname, val); 253 | } 254 | } 255 | CSL_CHROME.prototype.deleteNodeByNameAttribute = function (myxml,val) { 256 | var pos, len, node, nodes; 257 | nodes = myxml.childNodes; 258 | for (pos = 0, len = nodes.length; pos < len; pos += 1) { 259 | node = nodes[pos]; 260 | if (!node || node.nodeType == node.TEXT_NODE) { 261 | continue; 262 | } 263 | if (this.hasAttributes(node) && node.getAttribute("name") == val) { 264 | myxml.removeChild(nodes[pos]); 265 | } 266 | } 267 | } 268 | CSL_CHROME.prototype.deleteAttribute = function (myxml,attr) { 269 | myxml.removeAttribute(attr); 270 | } 271 | CSL_CHROME.prototype.setAttribute = function (myxml,attr,val) { 272 | if (!myxml.ownerDocument) { 273 | myxml = myxml.firstChild; 274 | } 275 | if (["function", "unknown"].indexOf(typeof myxml.setAttribute) > -1) { 276 | myxml.setAttribute(attr, val); 277 | } 278 | return false; 279 | } 280 | CSL_CHROME.prototype.nodeCopy = function (myxml) { 281 | var cloned_node = myxml.cloneNode(true); 282 | return cloned_node; 283 | } 284 | CSL_CHROME.prototype.getNodesByName = function (myxml,name,nameattrval) { 285 | var ret, nodes, node, pos, len; 286 | ret = []; 287 | nodes = myxml.getElementsByTagName(name); 288 | for (pos = 0, len = nodes.length; pos < len; pos += 1) { 289 | node = nodes.item(pos); 290 | if (nameattrval && !(this.hasAttributes(node) && node.getAttribute("name") == nameattrval)) { 291 | continue; 292 | } 293 | ret.push(node); 294 | } 295 | return ret; 296 | } 297 | CSL_CHROME.prototype.nodeNameIs = function (myxml,name) { 298 | if (name == myxml.nodeName) { 299 | return true; 300 | } 301 | return false; 302 | } 303 | CSL_CHROME.prototype.makeXml = function (myxml) { 304 | var ret, topnode; 305 | if (!myxml) { 306 | myxml = ""; 307 | } 308 | myxml = myxml.replace(/\s*<\?[^>]*\?>\s*\n*/g, ""); 309 | var nodetree = this.parser.parseFromString(myxml, "application/xml"); 310 | return nodetree.firstChild; 311 | }; 312 | CSL_CHROME.prototype.insertChildNodeAfter = function (parent,node,pos,datexml) { 313 | var myxml, xml; 314 | myxml = this.importNode(node.ownerDocument, datexml); 315 | parent.replaceChild(myxml, node); 316 | return parent; 317 | }; 318 | CSL_CHROME.prototype.insertPublisherAndPlace = function(myxml) { 319 | var group = myxml.getElementsByTagName("group"); 320 | for (var i = 0, ilen = group.length; i < ilen; i += 1) { 321 | var node = group.item(i); 322 | var skippers = []; 323 | for (var j = 0, jlen = node.childNodes.length; j < jlen; j += 1) { 324 | if (node.childNodes.item(j).nodeType !== 1) { 325 | skippers.push(j); 326 | } 327 | } 328 | if (node.childNodes.length - skippers.length === 2) { 329 | var twovars = []; 330 | for (var j = 0, jlen = 2; j < jlen; j += 1) { 331 | if (skippers.indexOf(j) > -1) { 332 | continue; 333 | } 334 | var child = node.childNodes.item(j); 335 | var subskippers = []; 336 | for (var k = 0, klen = child.childNodes.length; k < klen; k += 1) { 337 | if (child.childNodes.item(k).nodeType !== 1) { 338 | subskippers.push(k); 339 | } 340 | } 341 | if (child.childNodes.length - subskippers.length === 0) { 342 | twovars.push(child.getAttribute('variable')); 343 | if (child.getAttribute('suffix') 344 | || child.getAttribute('prefix')) { 345 | twovars = []; 346 | break; 347 | } 348 | } 349 | } 350 | if (twovars.indexOf("publisher") > -1 && twovars.indexOf("publisher-place") > -1) { 351 | node.setAttribute('has-publisher-and-publisher-place', true); 352 | } 353 | } 354 | } 355 | }; 356 | CSL_CHROME.prototype.addMissingNameNodes = function(myxml) { 357 | var nameslist = myxml.getElementsByTagName("names"); 358 | for (var i = 0, ilen = nameslist.length; i < ilen; i += 1) { 359 | var names = nameslist.item(i); 360 | var namelist = names.getElementsByTagName("name"); 361 | if ((!namelist || namelist.length === 0) 362 | && names.parentNode.tagName.toLowerCase() !== "substitute") { 363 | var doc = names.ownerDocument; 364 | var name = doc.createElement("name"); 365 | names.appendChild(name); 366 | } 367 | } 368 | }; 369 | // HACK: resolving circular dependency by duplicating this here 370 | var CSL = { 371 | INSTITUTION_KEYS: [ 372 | "font-style", 373 | "font-variant", 374 | "font-weight", 375 | "text-decoration", 376 | "text-case" 377 | ], 378 | }; 379 | CSL_CHROME.prototype.addInstitutionNodes = function(myxml) { 380 | var names, thenames, institution, theinstitution, theinstitutionpart, name, thename, xml, pos, len; 381 | names = myxml.getElementsByTagName("names"); 382 | for (pos = 0, len = names.length; pos < len; pos += 1) { 383 | thenames = names.item(pos); 384 | name = thenames.getElementsByTagName("name"); 385 | if (name.length == 0) { 386 | continue; 387 | } 388 | institution = thenames.getElementsByTagName("institution"); 389 | if (institution.length == 0) { 390 | theinstitution = this.importNode(myxml.ownerDocument, this.institution); 391 | theinstitutionpart = theinstitution.getElementsByTagName("institution-part").item(0); 392 | thename = name.item(0); 393 | thenames.insertBefore(theinstitution, thename.nextSibling); 394 | for (var j = 0, jlen = CSL.INSTITUTION_KEYS.length; j < jlen; j += 1) { 395 | var attrname = CSL.INSTITUTION_KEYS[j]; 396 | var attrval = thename.getAttribute(attrname); 397 | if (attrval) { 398 | theinstitutionpart.setAttribute(attrname, attrval); 399 | } 400 | } 401 | var nameparts = thename.getElementsByTagName("name-part"); 402 | for (var j = 0, jlen = nameparts.length; j < jlen; j += 1) { 403 | if ('family' === nameparts[j].getAttribute('name')) { 404 | for (var k = 0, klen = CSL.INSTITUTION_KEYS.length; k < klen; k += 1) { 405 | var attrname = CSL.INSTITUTION_KEYS[k]; 406 | var attrval = nameparts[j].getAttribute(attrname); 407 | if (attrval) { 408 | theinstitutionpart.setAttribute(attrname, attrval); 409 | } 410 | } 411 | } 412 | } 413 | } 414 | } 415 | }; 416 | CSL_CHROME.prototype.flagDateMacros = function(myxml) { 417 | var pos, len, thenode, thedate, nodes; 418 | nodes = myxml.getElementsByTagName("macro"); 419 | for (pos = 0, len = nodes.length; pos < len; pos += 1) { 420 | thenode = nodes.item(pos); 421 | thedate = thenode.getElementsByTagName("date"); 422 | if (thedate.length) { 423 | thenode.setAttribute('macro-has-date', 'true'); 424 | } 425 | } 426 | }; 427 | 428 | module.exports = CSL_CHROME; 429 | -------------------------------------------------------------------------------- /packages/citations/Citation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var InlineNode = require('substance/model/InlineNode'); 4 | 5 | function Citation() { 6 | Citation.super.apply(this, arguments); 7 | } 8 | 9 | Citation.Prototype = function() { 10 | 11 | this.getDefaultProperties = function() { 12 | return { 13 | targets: [] 14 | }; 15 | }; 16 | 17 | this.setLabel = function(label) { 18 | if (this.label !== label) { 19 | this.label = label; 20 | this.emit('label:changed'); 21 | } 22 | }; 23 | 24 | this.onTargetChange = function(change, info, doc) { 25 | this.updateCollection(doc); 26 | }; 27 | 28 | }; 29 | 30 | InlineNode.extend(Citation); 31 | 32 | Citation.static.name = "citation"; 33 | 34 | Citation.static.defineSchema({ 35 | targets: ["array", "id"], 36 | // volatile properties 37 | // label: computed dynamically 38 | }); 39 | 40 | Citation.static.tagName = 'cite'; 41 | 42 | module.exports = Citation; 43 | -------------------------------------------------------------------------------- /packages/citations/CitationCommand.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var SurfaceCommand = require('substance/ui/SurfaceCommand'); 4 | var insertInlineNode = require('substance/model/transform/insertInlineNode'); 5 | var extend = require('lodash/object/extend'); 6 | 7 | function CitationCommand() { 8 | CitationCommand.super.apply(this, arguments); 9 | } 10 | 11 | CitationCommand.Prototype = function() { 12 | 13 | this.getCommandState = function() { 14 | var sel = this.getSelection(); 15 | var newState = { 16 | disabled: true, 17 | active: false 18 | }; 19 | if (sel && !sel.isNull() && sel.isPropertySelection()) { 20 | newState.disabled = false; 21 | } 22 | return newState; 23 | }; 24 | 25 | this.getAnnotationData = function() { 26 | return { 27 | targets: [] 28 | }; 29 | }; 30 | 31 | this.getAnnotationType = function() { 32 | if (this.constructor.static.annotationType) { 33 | return this.constructor.static.annotationType; 34 | } else { 35 | throw new Error('Contract: CitationCommand.static.annotationType should be associated to a document citation type.'); 36 | } 37 | }; 38 | 39 | this.execute = function() { 40 | var state = this.getCommandState(); 41 | var surface = this.getSurface(); 42 | var newAnno; 43 | 44 | // Return if command is disabled 45 | if (state.disabled) return; 46 | 47 | surface.transaction(function(tx, args) { 48 | args.containerId = surface.getContainerId(); 49 | args.node = extend({ 50 | type: this.getAnnotationType() 51 | }, this.getAnnotationData()); 52 | 53 | args = insertInlineNode(tx, args); 54 | newAnno = args.result; 55 | return args; 56 | }.bind(this)); 57 | 58 | return { 59 | mode: 'create', 60 | anno: newAnno 61 | }; 62 | }; 63 | }; 64 | 65 | SurfaceCommand.extend(CitationCommand); 66 | 67 | module.exports = CitationCommand; 68 | -------------------------------------------------------------------------------- /packages/citations/CitationComponent.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var InlineNodeComponent = require('substance/ui/InlineNodeComponent'); 4 | 5 | function CitationComponent() { 6 | InlineNodeComponent.apply(this, arguments); 7 | 8 | this.props.node.connect(this, { 9 | "label:changed": this.onLabelChanged 10 | }); 11 | } 12 | 13 | CitationComponent.Prototype = function() { 14 | 15 | var _super = InlineNodeComponent.prototype; 16 | 17 | this.dispose = function() { 18 | _super.dispose.call(this); 19 | this.props.node.disconnect(this); 20 | }; 21 | 22 | this.render = function() { 23 | var el = _super.render.call(this); 24 | el.addClass(this.getClassNames()) 25 | .attr("data-id", this.props.node.id) 26 | .on('click', this.onClick) 27 | .on('mousedown', this.onMouseDown) 28 | .append(this.props.node.label || ""); 29 | return el; 30 | }; 31 | 32 | this.getClassNames = function() { 33 | var classNames = ['sc-citation', 'sm-'+this.props.node.type]; 34 | if (this.props.node.highlighted) { 35 | classNames.push('sm-highlighted'); 36 | // classNames.push('sm-'+this.props.node.highlightedScope); 37 | } 38 | return classNames.join(' '); 39 | }; 40 | 41 | this.onMouseDown = function(e) { 42 | e.preventDefault(); 43 | e.stopPropagation(); 44 | var citation = this.props.node; 45 | var surface = this.context.surface; 46 | surface.setSelection(citation.getSelection()); 47 | var controller = this.context.controller; 48 | controller.emit('citation:selected', citation); 49 | }; 50 | 51 | this.onClick = function(e) { 52 | e.preventDefault(); 53 | e.stopPropagation(); 54 | }; 55 | 56 | this.onLabelChanged = function() { 57 | this.rerender(); 58 | }; 59 | }; 60 | 61 | InlineNodeComponent.extend(CitationComponent); 62 | 63 | module.exports = CitationComponent; 64 | -------------------------------------------------------------------------------- /packages/citations/CitationXMLConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var compact = require('lodash/array/compact'); 4 | 5 | // This is never used directly 6 | module.exports = { 7 | 8 | type: 'citation', 9 | 10 | import: function(el, node) { 11 | node.id = el.attr('id') || node.id; // legacy ids 12 | node.targets = compact(el.attr('rid').split(' ')); 13 | }, 14 | 15 | export: function(node, el) { 16 | var targets = node.targets.join(' '); 17 | el.attr('rid', targets); 18 | } 19 | }; -------------------------------------------------------------------------------- /packages/citations/CitePanel.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('substance/util/helpers'); 4 | var Component = require('substance/ui/Component'); 5 | var ScrollPane = require('substance/ui/ScrollPane'); 6 | var DialogHeader = require('substance/ui/DialogHeader'); 7 | var $$ = Component.$$; 8 | 9 | function CitePanel() { 10 | Component.apply(this, arguments); 11 | 12 | this._initialize(this.props); 13 | } 14 | 15 | CitePanel.Prototype = function() { 16 | 17 | this.render = function() { 18 | var componentRegistry = this.context.componentRegistry; 19 | var items; 20 | if (this.items.length > 0) { 21 | items = this.items.map(function(item) { 22 | var comp = componentRegistry.get(this.props.citationType+'-entry'); 23 | return $$(comp, { 24 | node: item, 25 | active: this.isItemActive(item.id), 26 | handleSelection: this.handleSelection.bind(this) 27 | }).ref(item.id); 28 | }.bind(this)); 29 | } else { 30 | items = [$$('div').addClass("no-results").append("Nothing to reference.")]; 31 | } 32 | 33 | return $$('div').addClass('sc-cite-panel').append( 34 | $$(DialogHeader, {label: this.i18n.t('choose_referenced_items')}), 35 | $$(ScrollPane).append( 36 | items 37 | ).ref('panelEl') 38 | ); 39 | }; 40 | 41 | this.willReceiveProps = function(nextProps) { 42 | this._initialize(nextProps); 43 | }; 44 | 45 | this._initialize = function() { 46 | this.items = this.getItems(this.props.citationType); 47 | }; 48 | 49 | this.dispose = function() { 50 | this.$el.off('click', '.back', this.handleCancel); 51 | }; 52 | 53 | this.didMount = function() { 54 | this._scrollToTarget(); 55 | }; 56 | 57 | this.didReceiveProps = function() { 58 | this._scrollToTarget(); 59 | }; 60 | 61 | this._scrollToTarget = function() { 62 | var citationTargetId = this.getFirstCitationTarget(); 63 | if (citationTargetId) { 64 | this.refs.panelEl.scrollTo(citationTargetId); 65 | } 66 | }; 67 | 68 | this.getFirstCitationTarget = function() { 69 | var doc = this.props.doc; 70 | var citation = doc.get(this.props.citationId); 71 | if (citation) { 72 | return citation.targets[0]; 73 | } else { 74 | return null; 75 | } 76 | }; 77 | 78 | // Determines wheter an item is active 79 | this.isItemActive = function(itemId) { 80 | if (!this.props.citationId) return false; 81 | var doc = this.props.doc; 82 | var citation = doc.get(this.props.citationId); 83 | if (citation) { 84 | return _.includes(citation.targets, itemId); 85 | } else { 86 | return false; 87 | } 88 | }; 89 | 90 | this.handleCancel = function(e) { 91 | e.preventDefault(); 92 | this.send("switchContext", "toc"); 93 | }; 94 | 95 | this.getItems = function(citationType) { 96 | var doc = this.props.doc; 97 | var collection = doc.getCollection(citationType); 98 | return collection.getItems(); 99 | }; 100 | 101 | // Called with entityId when an entity has been clicked 102 | this.handleSelection = function(targetId) { 103 | var citationId = this.props.citationId; 104 | var doc = this.props.doc; 105 | var citation = doc.get(citationId); 106 | var newTargets = citation.targets.slice(); 107 | if (_.includes(newTargets, targetId)) { 108 | newTargets = _.without(newTargets, targetId); 109 | } else { 110 | newTargets.push(targetId); 111 | } 112 | 113 | var ctrl = this.context.controller; 114 | ctrl.transaction(function(tx, args) { 115 | tx.set([citation.id, "targets"], newTargets); 116 | return args; 117 | }); 118 | this.rerender(); 119 | }; 120 | }; 121 | 122 | Component.extend(CitePanel); 123 | 124 | module.exports = CitePanel; 125 | -------------------------------------------------------------------------------- /packages/citations/citation.scss: -------------------------------------------------------------------------------- 1 | // TODO: We chould possibly split this into bibliography and figures packages 2 | // ------------------ 3 | 4 | // Default citation style (blue-ish) 5 | .sc-citation { 6 | background: rgba(11, 157, 217, 0.075); 7 | color: #1B6685; 8 | border-bottom: 1px solid rgba(11, 157, 217, 0.4); 9 | cursor: pointer; 10 | white-space: nowrap; 11 | 12 | &:hover { 13 | background: rgba(11, 157, 217, 0.15); 14 | } 15 | 16 | &.sm-highlighted { 17 | background: $highlight-color-1; 18 | } 19 | } 20 | 21 | /* Figure citation style (overrides default style) 22 | -----------------------------------------------------*/ 23 | 24 | .sc-citation.sm-image-figure-citation, 25 | .sc-citation.sm-table-figure-citation { 26 | background: rgba(145, 187, 4, 0.15); 27 | border-bottom: 1px solid rgba(145, 187, 4, 0.6); 28 | color: #495A11; 29 | 30 | &:hover { 31 | background: rgba(145, 187, 4, 0.2); 32 | } 33 | 34 | &.sm-highlighted { 35 | background: $highlight-color-2; 36 | } 37 | } 38 | 39 | /* Custom Scrollbar styles 40 | -----------------------------------------------------*/ 41 | 42 | .sc-scrollbar .se-highlight.sm-bib-item { 43 | background-color: $highlight-color-1; 44 | } 45 | 46 | .sc-scrollbar .se-highlight.sm-figure { 47 | background-color: $highlight-color-2; 48 | } -------------------------------------------------------------------------------- /packages/figures/ImageFigure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Figure = require('substance/packages/figure/Figure'); 4 | 5 | function ImageFigure() { 6 | ImageFigure.super.apply(this, arguments); 7 | } 8 | 9 | Figure.extend(ImageFigure); 10 | 11 | ImageFigure.static.name = "image-figure"; 12 | ImageFigure.static.citationType = 'image-figure-citation'; 13 | ImageFigure.static.isBlock = true; 14 | 15 | module.exports = ImageFigure; 16 | -------------------------------------------------------------------------------- /packages/figures/ImageFigureCitation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Citation = require('../citations/Citation'); 4 | 5 | function ImageFigureCitation() { 6 | ImageFigureCitation.super.apply(this, arguments); 7 | } 8 | 9 | ImageFigureCitation.Prototype = function() { 10 | this.getItemType = function() { 11 | return "image-figure"; 12 | }; 13 | }; 14 | 15 | Citation.extend(ImageFigureCitation); 16 | 17 | ImageFigureCitation.static.name = "image-figure-citation"; 18 | 19 | module.exports = ImageFigureCitation; 20 | -------------------------------------------------------------------------------- /packages/figures/ImageFigureCitationCommand.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var CitationCommand = require('../citations/CitationCommand'); 4 | 5 | function ImageFigureCitationCommand() { 6 | ImageFigureCitationCommand.super.apply(this, arguments); 7 | } 8 | 9 | CitationCommand.extend(ImageFigureCitationCommand); 10 | 11 | ImageFigureCitationCommand.static.name = 'imageFigureCitation'; 12 | ImageFigureCitationCommand.static.annotationType = 'image-figure-citation'; 13 | 14 | module.exports = ImageFigureCitationCommand; 15 | -------------------------------------------------------------------------------- /packages/figures/ImageFigureCitationTool.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var AnnotationTool = require('substance/ui/AnnotationTool'); 4 | 5 | function ImageFigureCitationTool() { 6 | ImageFigureCitationTool.super.apply(this, arguments); 7 | } 8 | 9 | AnnotationTool.extend(ImageFigureCitationTool); 10 | 11 | ImageFigureCitationTool.static.name = 'imageFigureCitation'; 12 | ImageFigureCitationTool.static.command = 'imageFigureCitation'; 13 | 14 | module.exports = ImageFigureCitationTool; 15 | -------------------------------------------------------------------------------- /packages/figures/ImageFigureCitationXMLConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var CitationXMLConverter = require('../citations/CitationXMLConverter'); 4 | 5 | module.exports = { 6 | 7 | type: 'image-figure-citation', 8 | tagName: 'cite', 9 | 10 | matchElement: function(el) { 11 | return el.is('cite') && el.attr('rtype') === 'image-figure'; 12 | }, 13 | 14 | import: function(el, node) { 15 | CitationXMLConverter.import(el, node); 16 | }, 17 | 18 | export: function(node, el) { 19 | CitationXMLConverter.export(node, el); 20 | // Add specific type 21 | el.attr("rtype", "image-figure"); 22 | } 23 | }; -------------------------------------------------------------------------------- /packages/figures/ImageFigureEntry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Component = require('substance/ui/Component'); 4 | var $$ = Component.$$; 5 | 6 | function ImageFigureEntry() { 7 | Component.apply(this, arguments); 8 | } 9 | 10 | ImageFigureEntry.Prototype = function() { 11 | 12 | this.render = function() { 13 | var el = $$('div') 14 | .addClass('figure border-bottom item pad clearfix small') 15 | .attr('data-id', this.props.node.id) 16 | .on('click', this.onClick) 17 | .on('mousedown', this.onMouseDown); 18 | if (this.props.active) { 19 | el.addClass('active'); 20 | } 21 | el.append($$('img') 22 | .addClass('image') 23 | .attr('src', this.props.node.getContentNode().src) 24 | ); 25 | el.append($$('div') 26 | .addClass('title') 27 | .append([this.props.node.label, this.props.node.title].join(' ')) 28 | ); 29 | el.append($$('div') 30 | .addClass('caption truncate').append(this.props.node.caption) 31 | ); 32 | return el; 33 | }; 34 | 35 | this.onClick = function(e) { 36 | e.preventDefault(); 37 | e.stopPropagation(); 38 | }; 39 | 40 | this.onMouseDown = function(e) { 41 | e.preventDefault(); 42 | this.props.handleSelection(this.props.node.id); 43 | }; 44 | }; 45 | 46 | Component.extend(ImageFigureEntry); 47 | 48 | module.exports = ImageFigureEntry; 49 | -------------------------------------------------------------------------------- /packages/figures/ImageFigureXMLConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var FigureXMLConverter = require('substance/packages/figure/FigureXMLConverter'); 4 | 5 | module.exports = { 6 | 7 | type: 'image-figure', 8 | tagName: 'image-figure', 9 | 10 | import: function(el, node, converter) { 11 | FigureXMLConverter.import(el, node, converter); 12 | 13 | var contentNode; 14 | // HACK: We abuse this for embed nodes, as they can't live on their own atm 15 | var image = el.find('image'); 16 | var embed = el.find('embed'); 17 | if (image) { 18 | contentNode = converter.convertElement(image/*, { parent: imageFigure.id }*/); 19 | } else if (embed) { 20 | contentNode = converter.convertElement(embed/*, { parent: imageFigure.id }*/); 21 | } 22 | node.content = contentNode.id; 23 | }, 24 | 25 | export: function(node, el, converter) { 26 | FigureXMLConverter.export(node, el, converter); 27 | } 28 | }; -------------------------------------------------------------------------------- /packages/figures/ManageCollectionComponent.js: -------------------------------------------------------------------------------- 1 | // 'use strict'; 2 | 3 | // var Substance = require('substance'); 4 | // var _ = Substance._; 5 | // var OO = Substance.OO; 6 | // var Component = Substance.Component; 7 | // var $$ = Component.$$; 8 | // var Icon = require("substance/ui/FontAwesomeIcon"); 9 | // var Surface = require("substance/ui/Surface"); 10 | 11 | // var ENABLED_TOOLS = ["strong", "emphasis", "comment"]; 12 | 13 | // var CONTEXTS = [ 14 | // // {contextId: 'list', label: 'Upload figures', icon: 'fa-plus'} 15 | // ]; 16 | 17 | // function ManageCollection() { 18 | // Component.apply(this, arguments); 19 | 20 | // var doc = this.props.doc; 21 | // // retrieve items from collection 22 | // this.collection = doc.getCollection(this.props.itemType); 23 | 24 | // // create surface 25 | // var surfaceOptions = { 26 | // name: 'collection', 27 | // logger: this.context.notifications 28 | // }; 29 | // this.surface = new Surface(this.context.surfaceManager, doc, 30 | // new Surface.FormEditor(), surfaceOptions); 31 | 32 | // this.childContext = { 33 | // surface: this.surface 34 | // }; 35 | // } 36 | 37 | // ManageCollection.Prototype = function() { 38 | 39 | // this.didMount = function() { 40 | // var surface = this.surface; 41 | // var app = this.context.app; 42 | // // push surface selection state so that we can recover it when closing 43 | // this.context.surfaceManager.pushState(); 44 | // surface.attach(this.refs.collection.$el[0]); 45 | // }; 46 | 47 | // this.dispose = function() { 48 | // this.surface.dispose(); 49 | // this.context.surfaceManager.popState(); 50 | // }; 51 | 52 | // this.render = function() { 53 | // var doc = this.props.doc; 54 | // var navItems = _.map(CONTEXTS, function(context) { 55 | // return $$('button').addClass('pill') 56 | // .attr("data-id", context.contextId) 57 | // .on('click', this.handleContextSwitch) 58 | // .append( 59 | // $$(Icon).addProps({icon: context.icon}).append(" "+context.label) 60 | // ); 61 | // }.bind(this)); 62 | 63 | // var itemEls; 64 | // var componentRegistry = this.context.componentRegistry; 65 | // var ItemClass = componentRegistry.get(this.props.itemType); 66 | 67 | // var items = this.collection.getItems(); 68 | // if (items.length > 0 ) { 69 | // itemEls = _.map(items, function(item) { 70 | // return $$(ItemClass).key(item.id) 71 | // .addProps({ 72 | // node: item, 73 | // doc: doc 74 | // }); 75 | // }, this); 76 | // } else { 77 | // itemEls = [$$('div').append(this.i18n.t("no_items_found"))]; 78 | // } 79 | 80 | // return $$('div').addClass('manage-collection-component').append( 81 | // $$('div').addClass('header toolbar clearfix menubar fill-light').append( 82 | // $$('div').addClass('title float-left large') 83 | // .append(this.getPanelLabel()), 84 | // $$('div').addClass('menu-group small') 85 | // .append(navItems), 86 | // $$('button').addClass('button close-modal float-right') 87 | // .append($$(Icon).addProps({icon: 'fa-close'})) 88 | // .on('click', this.onCloseModal) 89 | // ), 90 | // $$('div').key('collection') 91 | // .addClass('content collection') 92 | // .attr('contentEditable', true) 93 | // .append(itemEls) 94 | // ); 95 | // }; 96 | 97 | // this.getPanelLabel = function() { 98 | // var items = this.collection.getItems(); 99 | // var prefix = this.collection.labelPrefix; 100 | // if (items.length > 1) prefix += 's'; 101 | // return [items.length, prefix].join(' '); 102 | // }; 103 | 104 | 105 | // this.handleItemDeletion = function(itemId) { 106 | // console.log('handling item deletion', itemId); 107 | // }; 108 | 109 | // this.onCloseModal = function(e) { 110 | // e.preventDefault(); 111 | // this.send('close-modal'); 112 | // }; 113 | 114 | // }; 115 | 116 | // OO.inherit(ManageCollection, Component); 117 | 118 | // // Panel Configuration 119 | // // ----------------- 120 | 121 | // ManageCollection.contextId = "manageCollection"; 122 | // // ManageCollection.modalSize = "medium"; 123 | 124 | // module.exports = ManageCollection; 125 | -------------------------------------------------------------------------------- /packages/metadata/ArticleMeta.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var DocumentNode = require('substance/model/DocumentNode'); 4 | 5 | function ArticleMeta() { 6 | ArticleMeta.super.apply(this, arguments); 7 | } 8 | 9 | DocumentNode.extend(ArticleMeta); 10 | 11 | ArticleMeta.static.name = "article-meta"; 12 | 13 | ArticleMeta.static.defineSchema({ 14 | "title": "string", 15 | "authors": {type: ["array", "string"], default: []}, 16 | "abstract": "string" 17 | }); 18 | 19 | module.exports = ArticleMeta; 20 | -------------------------------------------------------------------------------- /packages/metadata/Author.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var DocumentNode = require('substance/model/DocumentNode'); 4 | 5 | function Author() { 6 | Author.super.apply(this, arguments); 7 | } 8 | 9 | DocumentNode.extend(Author); 10 | 11 | Author.static.name = "author"; 12 | 13 | Author.static.defineSchema({ 14 | "name": "string" 15 | }); 16 | 17 | module.exports = Author; 18 | -------------------------------------------------------------------------------- /packages/metadata/MetadataXMLConverter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | HTML converter for Paragraph. 5 | 6 | Markup: 7 | 8 | ``` 9 | 10 | The <em>Substance</em> Article Format 11 | Article abstract with annotations 12 | 13 | ``` 14 | */ 15 | module.exports = { 16 | 17 | type: 'article-meta', 18 | tagName: 'meta', 19 | 20 | import: function(el, node, converter) { 21 | node.id = 'article-meta'; 22 | 23 | // Extract title 24 | var titleEl = el.find('title'); 25 | if (titleEl) { 26 | node.title = converter.annotatedText(titleEl, [node.id, 'title']); 27 | } else { 28 | console.warn('ArticleMeta: no title found.'); 29 | node.title = ''; 30 | } 31 | 32 | var abstractEl = el.find('abstract'); 33 | // Extract abstract 34 | if (abstractEl) { 35 | node.abstract = converter.annotatedText(abstractEl, [node.id, 'abstract']); 36 | } else { 37 | console.warn('ArticleMeta: no abstract found.'); 38 | node.abstract = ''; 39 | } 40 | 41 | // Extract authors 42 | node.authors = []; 43 | 44 | }, 45 | 46 | export: function(node, el, converter) { 47 | // id does not need to be exported 48 | el.setId(null); 49 | var $$ = converter.$$; 50 | return el.append( 51 | $$('title').append( 52 | converter.annotatedText([node.id, 'title']) 53 | ), 54 | $$('abstract').append( 55 | converter.annotatedText([node.id, 'abstract']) 56 | ) 57 | ); 58 | } 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /packages/reader/Cover.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Component = require('substance/ui/Component'); 4 | var TextPropertyEditor = require('substance/ui/TextPropertyEditor'); 5 | var $$ = require('substance/ui/Component').$$; 6 | 7 | function Cover() { 8 | Cover.super.apply(this, arguments); 9 | } 10 | 11 | Cover.Prototype = function() { 12 | 13 | this.render = function() { 14 | var doc = this.context.controller.getDocument(); 15 | var metaNode = doc.getDocumentMeta(); 16 | return $$("div").addClass("document-cover") 17 | .append( 18 | $$(TextPropertyEditor, { 19 | name: 'title', 20 | tagName: "div", 21 | path: [metaNode.id, "title"], 22 | editing: 'readonly' 23 | }).addClass('title'), 24 | 25 | // Abstract 26 | $$('div').addClass('abstract').append( 27 | $$(TextPropertyEditor, { 28 | name: 'abstract', 29 | tagName: "div", 30 | path: [metaNode.id, "abstract"], 31 | editing: 'readonly' 32 | }).addClass('abstract') 33 | ) 34 | ); 35 | }; 36 | }; 37 | 38 | Component.extend(Cover); 39 | 40 | module.exports = Cover; 41 | -------------------------------------------------------------------------------- /packages/writer/CoverEditor.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var Component = require('substance/ui/Component'); 4 | var $$ = require('substance/ui/Component').$$; 5 | var TextPropertyEditor = require('substance/ui/TextPropertyEditor'); 6 | 7 | var CoverEditor = function() { 8 | CoverEditor.super.apply(this, arguments); 9 | }; 10 | 11 | CoverEditor.Prototype = function() { 12 | 13 | this.render = function() { 14 | var doc = this.context.controller.getDocument(); 15 | var config = this.context.config; 16 | 17 | var metaNode = doc.getDocumentMeta(); 18 | return $$("div").addClass("document-cover") 19 | .append( 20 | // Editable title 21 | $$(TextPropertyEditor, { 22 | name: 'title', 23 | tagName: "div", 24 | commands: config.title.commands, 25 | path: [metaNode.id, "title"], 26 | editing: 'full' 27 | }).addClass('title'), 28 | 29 | // Editable abstract 30 | $$('div').addClass('abstract').append( 31 | $$(TextPropertyEditor, { 32 | name: 'abstract', 33 | tagName: 'div', 34 | commands: config.abstract.commands, 35 | path: [metaNode.id, 'abstract'], 36 | editing: 'full' 37 | }).addClass('abstract') 38 | ) 39 | ); 40 | }; 41 | }; 42 | 43 | Component.extend(CoverEditor); 44 | 45 | module.exports = CoverEditor; 46 | -------------------------------------------------------------------------------- /packages/writer/WriterTools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Toolbar = require('substance/ui/Toolbar'); 4 | var Component = require('substance/ui/Component'); 5 | var $$ = Component.$$; 6 | var SwitchTextTypeTool = require('substance/packages/text/SwitchTextTypeTool'); 7 | var UndoTool = require('substance/ui/UndoTool'); 8 | var RedoTool = require('substance/ui/RedoTool'); 9 | var SaveTool = require('substance/ui/SaveTool'); 10 | var StrongTool = require('substance/packages/strong/StrongTool'); 11 | var SubscriptTool = require('substance/packages/subscript/SubscriptTool'); 12 | var SuperscriptTool = require('substance/packages/superscript/SuperscriptTool'); 13 | var CodeTool = require('substance/packages/code/CodeTool'); 14 | var EmphasisTool = require('substance/packages/emphasis/EmphasisTool'); 15 | var Icon = require('substance/ui/FontAwesomeIcon'); 16 | var EmbedTool = require('substance/packages/embed/EmbedTool'); 17 | var LinkTool = require('substance/packages/link/LinkTool'); 18 | var InsertFigureTool = require('substance/packages/figure/InsertFigureTool'); 19 | var ImageFigureCitationTool = require('../figures/ImageFigureCitationTool'); 20 | var BibItemCitationTool = require('../bibliography/BibItemCitationTool'); 21 | 22 | function WriterTools() { 23 | WriterTools.super.apply(this, arguments); 24 | } 25 | 26 | WriterTools.Prototype = function() { 27 | 28 | this.render = function() { 29 | return $$('div').append( 30 | $$(Toolbar.Group).append( 31 | $$(SwitchTextTypeTool) 32 | ), 33 | $$(Toolbar.Group).append( 34 | $$(UndoTool).append($$(Icon, {icon: 'fa-undo'})), 35 | $$(RedoTool).append($$(Icon, {icon: 'fa-repeat'})), 36 | $$(SaveTool).append($$(Icon, {icon: 'fa-save'})) 37 | ), 38 | $$(Toolbar.Dropdown, {label: $$(Icon, {icon: 'fa-image'}),}).append( 39 | $$(InsertFigureTool).removeClass('tool').addClass('option').append(this.i18n.t('insert')), 40 | $$(ImageFigureCitationTool).append(this.i18n.t('cite')) 41 | ), 42 | $$(Toolbar.Dropdown, {label: $$(Icon, {icon: 'fa-book'}),}).append( 43 | $$(BibItemCitationTool).append(this.i18n.t('cite')) 44 | ), 45 | $$(Toolbar.Group).append( 46 | $$(EmbedTool).append($$(Icon, {icon: 'fa-file-code-o'})) 47 | ), 48 | $$(Toolbar.Group).addClass('float-right').append( 49 | $$(StrongTool).append($$(Icon, {icon: 'fa-bold'})), 50 | $$(EmphasisTool).append($$(Icon, {icon: 'fa-italic'})), 51 | $$(LinkTool).append($$(Icon, {icon: 'fa-link'})), 52 | $$(SubscriptTool).append($$(Icon, {icon: 'fa-subscript'})), 53 | $$(SuperscriptTool).append($$(Icon, {icon: 'fa-superscript'})), 54 | $$(CodeTool).append($$(Icon, {icon: 'fa-code'})) 55 | ) 56 | ); 57 | }; 58 | }; 59 | 60 | Component.extend(WriterTools); 61 | 62 | module.exports = WriterTools; 63 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var server = require('substance/util/server'); 4 | var bodyParser = require('body-parser'); 5 | 6 | var app = express(); 7 | var port = process.env.PORT || 5000; 8 | 9 | // use body parser so we can get info from POST and/or URL parameters 10 | app.use(bodyParser.json({limit: '3mb'})); 11 | app.use(bodyParser.urlencoded({ extended: true })); 12 | 13 | // use static server 14 | app.use(express.static(__dirname)); 15 | app.use(express.static(path.join(__dirname, "app/assets"))); 16 | app.use(express.static(path.join(__dirname, "app/data"))); 17 | app.use('/i18n', express.static(path.join(__dirname, "app/i18n"))); 18 | app.use('/fonts', express.static(path.join(__dirname, 'node_modules/font-awesome/fonts'))); 19 | 20 | server.serveStyles(app, '/app.css', path.join(__dirname, 'app', 'app.scss')); 21 | server.serveJS(app, '/app.js', path.join(__dirname, 'app', 'app.js')); 22 | 23 | app.listen(port, function(){ 24 | console.log("Lens running on port " + port); 25 | console.log("http://127.0.0.1:"+port+"/"); 26 | }); 27 | 28 | // Export app for requiring in test files 29 | module.exports = app; -------------------------------------------------------------------------------- /styles/_lens.scss: -------------------------------------------------------------------------------- 1 | $fa-font-path: "./fonts" !default; 2 | @import '../node_modules/font-awesome/scss/font-awesome'; 3 | 4 | // Substance base styles 5 | @import '../node_modules/substance/styles/base/index'; 6 | 7 | // Substance modules 8 | @import '../node_modules/substance/styles/components/_all'; 9 | @import '../node_modules/substance/packages/_all'; 10 | 11 | // Lens stuff 12 | @import './components/_bib-items-panel'; 13 | @import './components/_add-bib-items-panel'; 14 | @import './components/_bib-item'; 15 | @import './components/_cite-panel'; 16 | @import '../packages/citations/citation.scss'; 17 | 18 | // Most layouting is now done using SplitPane 19 | %lens { 20 | position: absolute; 21 | top: 0px; 22 | left: 0px; 23 | right: 0px; 24 | bottom: 0px; 25 | 26 | .se-main-section .se-content { 27 | padding: 40px; 28 | } 29 | 30 | .se-context-section { 31 | border-left: 1px solid #ddd; 32 | background-color: #fafafa; 33 | box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05); 34 | } 35 | } 36 | 37 | // TODO: Get rid of it one by one! 38 | @import '_wild_overrides'; 39 | -------------------------------------------------------------------------------- /styles/_wild_overrides.scss: -------------------------------------------------------------------------------- 1 | /* TODO: The following styles need to be moved into component modules */ 2 | 3 | /* CSL Styles 4 | -----------------------------------------------------*/ 5 | 6 | .csl-entry { 7 | margin-top: 0.5em; 8 | margin-bottom: 0.5em; 9 | line-height: 25px; 10 | padding: 5px 20px; 11 | } 12 | 13 | .csl-block { 14 | padding:0.4em 0px 0.4em 0em; 15 | } 16 | 17 | .csl-left-margin { 18 | float: left; 19 | font-weight: 600; 20 | font-size: 13px; 21 | } 22 | 23 | .csl-right-inline { 24 | margin-left: 40px; 25 | } 26 | 27 | /* Content Tools 28 | -----------------------------------------------------*/ 29 | 30 | 31 | /* Custom Annotation styles 32 | -----------------------------------------------------*/ 33 | 34 | 35 | /* Bibliography in manuscript area 36 | -----------------------------------------------------*/ 37 | 38 | .bibliography-component { 39 | cursor: not-allowed; 40 | } 41 | 42 | .bibliography-component .bib-item { 43 | padding-bottom: 10px; 44 | font-size: 14px; 45 | } 46 | 47 | /* Document Cover 48 | -----------------------------------------------------*/ 49 | 50 | .document-cover { 51 | margin-bottom: 30px; 52 | padding-bottom: 30px; 53 | 54 | border-bottom: 1px solid #ddd; 55 | 56 | .title { 57 | font-size: 30px; 58 | font-weight: 600; 59 | letter-spacing: $heading-letterspacing; 60 | line-height: 40px; 61 | } 62 | 63 | .abstract { 64 | padding-top: 15px; 65 | font-size: 13px; 66 | line-height: 18px; 67 | } 68 | 69 | .authors { 70 | font-size: 20px; 71 | padding: 30px 0px; 72 | 73 | .author { 74 | display: block; 75 | float: left; 76 | margin-right: 20px; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /styles/components/_add-bib-items-panel.scss: -------------------------------------------------------------------------------- 1 | .sc-add-bib-items-panel { 2 | .sc-scroll-pane { 3 | top: 40px; 4 | } 5 | 6 | .se-search-form { 7 | padding: 20px; 8 | } 9 | } -------------------------------------------------------------------------------- /styles/components/_bib-item.scss: -------------------------------------------------------------------------------- 1 | .sc-bib-item { 2 | position: relative; 3 | 4 | font-size: $small-font-size; 5 | padding: $default-padding; 6 | border-top: 1px solid $border-color; 7 | border-left: 3px solid transparent; 8 | 9 | .se-label { 10 | padding-bottom: 10px; 11 | color: #888; 12 | min-height: 30px; 13 | } 14 | 15 | .se-focus-toggle { 16 | font-size: 12px; 17 | font-weight: bold; 18 | position: absolute; 19 | color: #888; 20 | padding: 10px; 21 | top: 10px; 22 | right: 10px; 23 | cursor: pointer; 24 | 25 | &:hover { 26 | color: $highlight-color-1; 27 | } 28 | } 29 | 30 | // When highlighted 31 | &.se-highlighted { 32 | border-left: 3px solid $highlight-color-1; 33 | background: #fff; 34 | 35 | .se-focus-toggle { 36 | color: $highlight-color-1; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /styles/components/_bib-items-panel.scss: -------------------------------------------------------------------------------- 1 | .sc-bib-items-panel { 2 | .se-bibliography-summary { 3 | padding: $default-padding; 4 | } 5 | } -------------------------------------------------------------------------------- /styles/components/_cite-panel.scss: -------------------------------------------------------------------------------- 1 | /* Cite Panel 2 | -----------------------------------------------------*/ 3 | 4 | .sc-cite-panel { 5 | 6 | .sc-scroll-pane { 7 | top: 40px; 8 | } 9 | 10 | .item { 11 | cursor: pointer; 12 | padding: 20px; 13 | border-left: 2px solid transparent; 14 | 15 | &.active { background: white; border-left: 2px solid #444; } 16 | &:hover { background: white; } 17 | 18 | /* Figure Item */ 19 | &.figure { 20 | 21 | &.active { border-left: 2px solid rgba(145, 187, 4, 1); } 22 | 23 | .title { 24 | font-weight: 600; 25 | padding-bottom: 10px; 26 | } 27 | 28 | img { 29 | padding-right: 20px; 30 | width: 70px; 31 | float: left; 32 | } 33 | } 34 | 35 | // BibItem 36 | &.item.bib-item { 37 | &.active { background: white; border-left: 2px solid rgba(11, 157, 217, 1); } 38 | 39 | .label { 40 | padding-right: 5px; 41 | float: left; 42 | font-weight: 600; 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /styles/lens-reader.scss: -------------------------------------------------------------------------------- 1 | @import '_lens'; 2 | 3 | .sc-lens-reader { 4 | @extend %lens; 5 | } 6 | -------------------------------------------------------------------------------- /styles/lens-writer.scss: -------------------------------------------------------------------------------- 1 | @import '_lens'; 2 | 3 | .sc-lens-writer { 4 | @extend %lens; 5 | } 6 | --------------------------------------------------------------------------------