├── .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 PRC2-AEBP2 Complex .
12 |
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 |
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 | A B C D E F
27 |
28 |
29 | 1 2 3 4 5 6
30 | 7 8 9 10 11 2
31 | 13 14 15 16 17 18
32 | 19 20 21 22 23 24
33 | 25 26 27 28 29 30
34 |
35 |
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 | 'A B C D E F ',
11 | ' ',
12 | '',
13 | '1 2 3 4 5 6 ',
14 | '7 8 9 10 11 2 ',
15 | '13 14 15 16 17 18 ',
16 | '19 20 21 22 23 24 ',
17 | '25 26 27 28 29 30 ',
18 | ' ',
19 | '
',
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 Substance 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 |
--------------------------------------------------------------------------------