├── .gitignore ├── src ├── transforms │ ├── .git-ignore │ ├── hover-box-include.js │ ├── byline.js │ ├── toc.js │ ├── include.js │ ├── citation-list.js │ ├── reorder.js │ ├── markdown.js │ ├── banner.js │ ├── html.js │ ├── footnote.js │ ├── code.js │ ├── front-matter.js │ ├── mathematics.js │ ├── optional-components.js │ ├── hover-box.txt │ ├── polyfills.js │ ├── typeset.js │ ├── generate-crossref.js │ └── meta.js ├── assets │ ├── distill-favicon.png │ ├── distill-logo.svg │ ├── distill-favicon.svg │ └── distill-favicon.base64 ├── helpers │ ├── domContentLoaded.js │ ├── layout.js │ ├── bibtex.js │ ├── polyfills.js │ ├── citation.js │ └── katex-auto-render.js ├── components │ ├── d-title.js │ ├── d-references.js │ ├── d-abstract.js │ ├── d-article.js │ ├── d-appendix.js │ ├── d-byline.js │ ├── d-toc.js │ ├── d-citation-list.js │ ├── d-footnote-list.js │ ├── d-footnote.js │ ├── d-front-matter.js │ ├── d-code.js │ ├── d-bibliography.js │ ├── d-math.js │ ├── d-hover-box.js │ ├── d-interstitial.js │ ├── d-cite.js │ └── d-figure.js ├── styles │ ├── d-math.css │ ├── styles-print.css │ ├── styles.js │ ├── d-title.css │ ├── d-byline.css │ ├── styles-base.css │ ├── styles-layout.css │ └── d-article.css ├── distill-components │ ├── distill-footer.js │ ├── distill-header.js │ ├── distill-header-template.js │ ├── distill-footer-template.js │ └── distill-appendix.js ├── extractors │ ├── citations.js │ ├── front-matter.js │ └── bibliography.js ├── distill-transforms │ ├── distill-footer.js │ ├── distill-header.js │ └── distill-appendix.js ├── mixins │ ├── mutating.js │ ├── template.js │ └── properties.js ├── transforms.js ├── components.js └── controller.js ├── test ├── mocha.opts ├── helpers.js ├── scaffolds │ ├── usesV1.html │ └── usesV2.html └── template_v1.js ├── examples ├── momentum.png ├── bibliography.bib └── index.html ├── .npmignore ├── .editorconfig ├── AUTHORS ├── .travis.yml ├── .eslintrc.json ├── rollup.config.prod.js ├── README.md ├── rollup.config.dev.js ├── CONTRIBUTING.md ├── bin └── render.js ├── package.json └── rollup.config.common.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /src/transforms/.git-ignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -r jsdom-global/register 2 | -------------------------------------------------------------------------------- /examples/momentum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/distillpub/template/HEAD/examples/momentum.png -------------------------------------------------------------------------------- /src/assets/distill-favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/distillpub/template/HEAD/src/assets/distill-favicon.png -------------------------------------------------------------------------------- /src/helpers/domContentLoaded.js: -------------------------------------------------------------------------------- 1 | export function domContentLoaded() { 2 | return ['interactive', 'complete'].indexOf(document.readyState) !== -1; 3 | } 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | build 3 | .editorconfig 4 | .eslintrc.json 5 | .gitignore 6 | .travis.yml 7 | rollup.config.dev.js 8 | rollup.config.js 9 | yarn-error.log 10 | -------------------------------------------------------------------------------- /src/assets/distill-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = spaces 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,yml}] 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the list of The Distill Template authors for copyright purposes. 2 | # 3 | # This does not necessarily list everyone who has contributed code, since in 4 | # some cases, their employer may be the copyright holder. To see the full list 5 | # of contributors, see the revision history in source control. 6 | Google LLC 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "node" # latest version 4 | cache: 5 | directories: 6 | - node_modules 7 | script: 8 | - npm run build 9 | - npm test 10 | deploy: 11 | provider: releases 12 | api_key: $GITHUB_OAUTH_TOKEN 13 | file_glob: true 14 | file: dist/* 15 | skip_cleanup: true 16 | overwrite: true 17 | on: 18 | tags: true 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "sourceType": "module" 10 | }, 11 | "rules": { 12 | "no-unused-vars": ["warn", { "vars": "all", "args": "after-used" }], 13 | "no-console": ["off", { "allow": ["warn", "error"] } ], 14 | "no-empty": ["error", { "allowEmptyCatch": true }], 15 | "indent": [ "warn", 2 ], 16 | "linebreak-style": [ "error", "unix" ], 17 | "quotes": [ "warn", "single" ], 18 | "semi": [ "warn", "always" ], 19 | "no-extra-semi": [ "warn" ], 20 | "no-debugger": [ "warn" ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /rollup.config.prod.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import configs from "./rollup.config.common"; 16 | 17 | export default configs; 18 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | function nodeListToArray(nodeList) { 16 | return Array.prototype.slice.call(nodeList); 17 | } 18 | -------------------------------------------------------------------------------- /src/components/d-title.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export class Title extends HTMLElement { 16 | static get is() { return 'd-title'; } 17 | } 18 | -------------------------------------------------------------------------------- /src/assets/distill-favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/transforms/hover-box-include.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import code from './hover-box.txt'; 16 | 17 | export default function(dom) { 18 | let s = dom.createElement('script'); 19 | s.textContent = code; 20 | dom.querySelector('body').appendChild(s); 21 | } 22 | -------------------------------------------------------------------------------- /src/transforms/byline.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { bylineTemplate } from '../components/d-byline.js'; 16 | 17 | export default function(dom, data) { 18 | const byline = dom.querySelector('d-byline'); 19 | if (byline) { 20 | byline.innerHTML = bylineTemplate(data); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/scaffolds/usesV1.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /test/scaffolds/usesV2.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Distill Template 2 | 3 | This is the repository for the Distill web framework. 4 | 5 | ## Contributing 6 | 7 | 8 | To give feedback, report a bug, or request a feature, please open an issue. 9 | 10 | To contribute a change, [check out the contributing guide](CONTRIBUTING.md). 11 | 12 | ### Local Development 13 | 14 | First, run `npm install` to install all node modules required. Then, run `npm run dev` to start a watching build rollup server. To view the sample pages in the repo, you can run `npm run serve` as a separate process which starts a static server. `npm run build` will run a one-time build. 15 | 16 | 17 | ## Disclaimer & License 18 | 19 | _This project is research code. It is not an official product of Google or any other institution supporting Distill._ 20 | 21 | Copyright 2018, The Distill Template Authors. 22 | 23 | Licensed under the Apache License, Version 2.0 24 | 25 | See the [full license](LICENSE). 26 | -------------------------------------------------------------------------------- /src/styles/d-math.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Distill Template Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | span.katex-display { 18 | text-align: left; 19 | padding: 8px 0 8px 0; 20 | margin: 0.5em 0 0.5em 1em; 21 | } 22 | 23 | span.katex { 24 | -webkit-font-smoothing: antialiased; 25 | color: rgba(0, 0, 0, 0.8); 26 | font-size: 1.18em; 27 | } 28 | -------------------------------------------------------------------------------- /src/components/d-references.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template'; 16 | 17 | const T = Template('d-references', ` 18 | 23 | `, false); 24 | 25 | export class References extends T(HTMLElement) { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/distill-components/distill-footer.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template'; 16 | 17 | import {footerTemplate} from './distill-footer-template'; 18 | 19 | const T = Template('distill-footer', footerTemplate); 20 | 21 | export class DistillFooter extends T(HTMLElement) { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/distill-components/distill-header.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template'; 16 | 17 | import {headerTemplate} from './distill-header-template'; 18 | 19 | const T = Template('distill-header', headerTemplate, false); 20 | 21 | export class DistillHeader extends T(HTMLElement) { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/extractors/citations.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { collect_citations } from '../helpers/citation.js'; 16 | 17 | export default function(dom, data) { 18 | const citations = new Set(data.citations); 19 | const newCitations = collect_citations(dom); 20 | for (const citation of newCitations) { 21 | citations.add(citation); 22 | } 23 | data.citations = Array.from(citations); 24 | } 25 | -------------------------------------------------------------------------------- /src/transforms/toc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { renderTOC } from '../components/d-toc'; 16 | 17 | export default function(dom) { 18 | const article = dom.querySelector('d-article'); 19 | const toc = dom.querySelector('d-toc'); 20 | if (toc) { 21 | const headings = article.querySelectorAll('h2, h3'); 22 | renderTOC(toc, headings); 23 | toc.setAttribute('prerendered', 'true'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/distill-transforms/distill-footer.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { footerTemplate } from '../distill-components/distill-footer-template'; 16 | 17 | export default function(dom) { 18 | const footerTag = dom.querySelector('distill-footer'); 19 | if(!footerTag) { 20 | const footer = dom.createElement('distill-footer'); 21 | footer.innerHTML = footerTemplate; 22 | const body = dom.querySelector('body'); 23 | body.appendChild(footer); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/extractors/front-matter.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { parseFrontmatter } from '../components/d-front-matter'; 16 | import { mergeFromYMLFrontmatter } from '../front-matter.js'; 17 | 18 | export default function(dom, data) { 19 | const frontMatterTag = dom.querySelector('d-front-matter'); 20 | if (!frontMatterTag) { 21 | console.warn('No front matter tag found!'); 22 | return; 23 | } 24 | const extractedData = parseFrontmatter(frontMatterTag); 25 | mergeFromYMLFrontmatter(data, extractedData); 26 | } 27 | -------------------------------------------------------------------------------- /rollup.config.dev.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import configs from "./rollup.config.common"; 16 | import serve from "rollup-plugin-serve"; 17 | 18 | const [componentsConfig, transformsConfig] = configs; 19 | 20 | componentsConfig.plugins.push( 21 | serve({ 22 | open: true, 23 | openPage: "/index.html", 24 | contentBase: ["dist", "examples"], 25 | headers: { 26 | "Access-Control-Allow-Origin": "*" 27 | }, 28 | port: 8088 29 | }) 30 | ); 31 | 32 | export default [componentsConfig, transformsConfig]; 33 | -------------------------------------------------------------------------------- /src/distill-transforms/distill-header.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | 16 | import { headerTemplate } from '../distill-components/distill-header-template'; 17 | 18 | export default function(dom, data) { 19 | const headerTag = dom.querySelector('distill-header'); 20 | if (!headerTag) { 21 | const header = dom.createElement('distill-header'); 22 | header.innerHTML = headerTemplate; 23 | header.setAttribute('distill-prerendered', ""); 24 | const body = dom.querySelector('body'); 25 | body.insertBefore(header, body.firstChild); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/transforms/include.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import fetch from 'fetch'; 16 | let fetchUrl = fetch.fetchUrl; 17 | 18 | export default function(dom, data) { 19 | 20 | var includeTags = [].slice.apply(dom.querySelectorAll('dt-include')); 21 | 22 | includeTags.forEach(el => { 23 | let src = el.getAttribute('src'); 24 | fetchUrl(src, (err, meta, body) => { 25 | console.log(err, meta, body); 26 | el.innerHTML = body.toString(); 27 | }); 28 | }); 29 | data.bibliography = bibliography; 30 | data.citations = citations; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/transforms/citation-list.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { renderCitationList } from '../components/d-citation-list'; // (element, entries) 16 | 17 | export default function(dom, data) { 18 | const citationListTag = dom.querySelector('d-citation-list'); 19 | if (citationListTag) { 20 | const entries = new Map(data.citations.map( citationKey => { 21 | return [citationKey, data.bibliography.get(citationKey)]; 22 | })); 23 | renderCitationList(citationListTag, entries, dom); 24 | citationListTag.setAttribute('distill-prerendered', 'true'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). 29 | -------------------------------------------------------------------------------- /src/styles/styles-print.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Distill Template Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @media print { 18 | 19 | @page { 20 | size: 8in 11in; 21 | @bottom-right { 22 | content: counter(page) " of " counter(pages); 23 | } 24 | } 25 | 26 | html { 27 | /* no general margins -- CSS Grid takes care of those */ 28 | } 29 | 30 | p, code { 31 | page-break-inside: avoid; 32 | } 33 | 34 | h2, h3 { 35 | page-break-after: avoid; 36 | } 37 | 38 | d-header { 39 | visibility: hidden; 40 | } 41 | 42 | d-footer { 43 | display: none!important; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/components/d-abstract.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template'; 16 | import { body } from '../helpers/layout'; 17 | 18 | const T = Template('d-abstract', ` 19 | 34 | 35 | 36 | `); 37 | 38 | export class Abstract extends T(HTMLElement) { 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/transforms/reorder.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* 16 | Try to only reorder things that MAY be user defined. 17 | Try to use templates etc to define the order of our own tags. 18 | */ 19 | 20 | export default function render(dom) { 21 | const head = dom.head; 22 | 23 | const metaIE = head.querySelector('meta[http-equiv]'); 24 | head.insertBefore(metaIE, head.firstChild); 25 | 26 | const metaViewport = head.querySelector('meta[name=viewport]'); 27 | head.insertBefore(metaViewport, head.firstChild); 28 | 29 | const metaCharset = head.querySelector('meta[charset]'); 30 | head.insertBefore(metaCharset, head.firstChild); 31 | } 32 | -------------------------------------------------------------------------------- /src/transforms/markdown.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import marked from 'marked'; 16 | 17 | marked.setOptions({ 18 | gfm: true, 19 | smartypants: true 20 | }); 21 | 22 | export default function(dom, data) { 23 | let markdownElements = [].slice.call(dom.querySelectorAll('[markdown]')); 24 | markdownElements.forEach(el => { 25 | let content = el.innerHTML; 26 | // Set default indents 27 | content = content.replace(/\n/, ''); 28 | let tabs = content.match(/\s*/); 29 | content = content.replace(new RegExp('\n' + tabs, 'g'), '\n'); 30 | content = content.trim(); 31 | 32 | el.innerHTML = marked(content); 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /src/distill-transforms/distill-appendix.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { appendixTemplate } from '../distill-components/distill-appendix'; 16 | 17 | export default function(dom, data) { 18 | 19 | const appendixTag = dom.querySelector('d-appendix'); 20 | if (!appendixTag) { 21 | console.warn('No appendix tag found!'); 22 | return; 23 | } 24 | const distillAppendixTag = appendixTag.querySelector('distill-appendix'); 25 | if (!distillAppendixTag) { 26 | const distillAppendix = dom.createElement('distill-appendix'); 27 | appendixTag.appendChild(distillAppendix); 28 | distillAppendix.innerHTML = appendixTemplate(data); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/transforms/banner.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const html = ` 16 | 26 |
This article is a draft, awaiting review for publication in Distill
27 | `; 28 | 29 | export default function(dom, data) { 30 | let banner = dom.createElement('dt-banner'); 31 | banner.innerHTML = html; 32 | let b = dom.querySelector('body'); 33 | b.insertBefore(banner, b.firstChild); 34 | banner.addEventListener('click', function() { 35 | banner.style.display = 'none'; 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/assets/distill-favicon.base64: -------------------------------------------------------------------------------- 1 | iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA99JREFUeNrsG4t1ozDMzQSM4A2ODUonKBucN2hugtIJ6E1AboLcBiQTkJsANiAb9OCd/OpzMWBJBl5TvaeXPiiyJetry0J8wW3D3QpjRh3GjneXDq+fSQA9s2mH9x3KDhN4foJfCb8N/Jrv+2fnDn8vLRQOplWHVYdvHZYdZsBcZP1vBmh/n8DzEmhUQDPaOuP9pFuY+JwJHwHnCLQE2tnWBGEyXozY9xCUgHMhhjE2I4heVWtgIkZ83wL6Qgxj1obfWBxymPwe+b00BCCRNPbwfb60yleAkkBHGT5AEehIYz7eJrFDMF9CvH4wwhcGHiHMneFvLDQwlwvMLQq58trRcYBWfYn0A0OgHWQUSu25mE+BnoYKnnEJoeIWAifzOv7vLWd2ZKRfWAIme3tOiUaQ3UnLkb0xj1FxRIeEGKaGIHOs9nEgLaaA9i0JRYo1Ic67wJW86KSKE/ZAM8KuVMk8ITVhmxUxJ3Cl2xlm9Vtkeju1+mpCQNxaEGNCY8bs9X2YqwNoQeGjBWut/ma0QAWy/TqAsHx9wSya3I5IRxOfTC+leG+kA/4vSeEcGBtNUN6byhu3+keEZCQJUNh8MAO7HL6H8pQLnsW/Hd4T4lv93TPjfM7A46iEEqbB5EDOvwYNW6tGNZzT/o+CZ6sqZ6wUtR/wf7mi/VL8iNciT6rHih48Y55b4nKCHJCCzb4y0nwFmin3ZEMIoLfZF8F7nncFmvnWBaBj7CGAYA/WGJsUwHdYqVDwAmNsUgAx4CGgAA7GOOxADYOFWOaIKifuVYzmOpREqA21Mo7aPsgiY1PhOMAmxtR+AUbYH3Id2wc0SAFIQTsn9IUGWR8k9jx3vtXSiAacFxTAGakBk9UudkNECd6jLe+6HrshshvIuC6IlLMRy7er+JpcKma24SlE4cFZSZJDGVVrsNvitQhQrDhW0jfiOLfFd47C42eHT56D/BK0To+58Ahj+cAT8HT1UWlfLZCCd/uKawzU0Rh2EyIX/Icqth3niG8ybNroezwe6khdCNxRN+l4XGdOLVLlOOt2hTRJlr1ETIuMAltVTMz70mJrkdGAaZLSmnBEqmAE32JCMmuTlCnRgsBENtOUpHhvvsYIL0ibnBkaC6QvKcR7738GKp0AKnim7xgUSNv1bpS8QwhBt8r+EP47v/oyRK/S34yJ9nT+AN0Tkm4OdB9E4BsmXM3SnMlRFUrtp6IDpV2eKzdYvF3etm3KhQksbOLChGkSmcBdmcEwvqkrMy5BzL00NZeu3qPYJOOuCc+5NjcWKXQxFvTa3NoXJ4d8in7fiAUuTt781dkvuHX4K8AA2Usy7yNKLy0AAAAASUVORK5CYII= 2 | -------------------------------------------------------------------------------- /src/transforms/html.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export default function(dom) { 16 | 17 | const head = dom.querySelector('head'); 18 | 19 | // set language to 'en' 20 | if (!dom.querySelector('html').getAttribute('lang')) { 21 | dom.querySelector('html').setAttribute('lang', 'en'); 22 | } 23 | 24 | // set charset to 'utf-8' 25 | if (!dom.querySelector('meta[charset]')) { 26 | const meta = dom.createElement('meta'); 27 | meta.setAttribute('charset', 'utf-8'); 28 | head.appendChild(meta); 29 | } 30 | 31 | // set viewport 32 | if (!dom.querySelector('meta[name=viewport]')) { 33 | const meta = dom.createElement('meta'); 34 | meta.setAttribute('name', 'viewport'); 35 | meta.setAttribute('content', 'width=device-width, initial-scale=1'); 36 | head.appendChild(meta); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/transforms/footnote.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export default function(dom, data) { 16 | 17 | var fnTags = [].slice.apply(dom.querySelectorAll('dt-fn')); 18 | var fnContent = []; 19 | fnTags.forEach((el,n) => { 20 | var content = el.innerHTML; 21 | fnContent.push(content); 22 | n = (n+1)+''; 23 | var key = 'fn-'+n; 24 | var escaped_content = content.replace(/"/g, '''); 25 | el.innerHTML = `${n}`; 26 | }); 27 | 28 | let fnList = dom.querySelector('dt-fn-list'); 29 | if (fnList) { 30 | let ol = dom.createElement('ol'); 31 | fnContent.forEach(content => { 32 | let el = dom.createElement('li'); 33 | el.innerHTML = content; 34 | ol.appendChild(el); 35 | }); 36 | fnList.appendChild(ol); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/helpers/layout.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // const marginSmall = 16; 16 | // const marginLarge = 3 * marginSmall; 17 | // const margin = marginSmall + marginLarge; 18 | // const gutter = marginSmall; 19 | // const outsetAmount = margin / 2; 20 | // const numCols = 4; 21 | // const numGutters = numCols - 1; 22 | // const columnWidth = (768 - 2 * marginLarge - numGutters * gutter) / numCols; 23 | // 24 | // const screenwidth = 768; 25 | // const pageWidth = screenwidth - 2 * marginLarge; 26 | // const bodyWidth = pageWidth - columnWidth - gutter; 27 | 28 | export function body(selector) { 29 | return `${selector} { 30 | grid-column: left / text; 31 | } 32 | `; 33 | } 34 | 35 | export function page(selector) { 36 | return `${selector} { 37 | grid-column: left / page; 38 | } 39 | `; 40 | } 41 | 42 | export function screen(selector) { 43 | return `${selector} { 44 | grid-column: start / end; 45 | } 46 | `; 47 | } 48 | -------------------------------------------------------------------------------- /src/styles/styles.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import base from './styles-base.css'; 16 | import layout from './styles-layout.css'; 17 | import print from './styles-print.css'; 18 | import byline from './d-byline.css'; 19 | import article from './d-article.css'; 20 | import title from './d-title.css'; 21 | import math from './d-math.css'; 22 | 23 | export const styles = base + layout + title + byline + article + math + print; 24 | 25 | export function makeStyleTag(dom) { 26 | 27 | const styleTagId = 'distill-prerendered-styles'; 28 | const prerenderedTag = dom.getElementById(styleTagId); 29 | if (!prerenderedTag) { 30 | const styleTag = dom.createElement('style'); 31 | styleTag.id = styleTagId; 32 | styleTag.type = 'text/css'; 33 | const cssTextTag = dom.createTextNode(styles); 34 | styleTag.appendChild(cssTextTag); 35 | const firstScriptTag = dom.head.querySelector('script'); 36 | dom.head.insertBefore(styleTag, firstScriptTag); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/extractors/bibliography.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { parseBibtex } from '../helpers/bibtex'; 16 | import fs from 'fs'; 17 | import { parseBibliography } from '../components/d-bibliography'; 18 | 19 | export default function(dom, data) { 20 | const bibliographyTag = dom.querySelector('d-bibliography'); 21 | if (!bibliographyTag) { 22 | console.warn('No bibliography tag found!'); 23 | return; 24 | } 25 | 26 | const src = bibliographyTag.getAttribute('src'); 27 | if (src) { 28 | const path = data.inputDirectory + '/' + src; 29 | const text = fs.readFileSync(path, 'utf-8'); 30 | const bibliography = parseBibtex(text); 31 | const scriptTag = dom.createElement('script'); 32 | scriptTag.type = 'text/json'; 33 | scriptTag.textContent = JSON.stringify([...bibliography]); 34 | bibliographyTag.appendChild(scriptTag); 35 | bibliographyTag.removeAttribute('src'); 36 | } 37 | 38 | data.bibliography = parseBibliography(bibliographyTag); 39 | } 40 | -------------------------------------------------------------------------------- /src/styles/d-title.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Distill Template Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | d-title { 18 | padding: 2rem 0 1.5rem; 19 | contain: layout style; 20 | overflow-x: hidden; 21 | } 22 | 23 | @media(min-width: 768px) { 24 | d-title { 25 | padding: 4rem 0 1.5rem; 26 | } 27 | } 28 | 29 | d-title h1 { 30 | grid-column: text; 31 | font-size: 40px; 32 | font-weight: 700; 33 | line-height: 1.1em; 34 | margin: 0 0 0.5rem; 35 | } 36 | 37 | @media(min-width: 768px) { 38 | d-title h1 { 39 | font-size: 50px; 40 | } 41 | } 42 | 43 | d-title p { 44 | font-weight: 300; 45 | font-size: 1.2rem; 46 | line-height: 1.55em; 47 | grid-column: text; 48 | } 49 | 50 | d-title .status { 51 | margin-top: 0px; 52 | font-size: 12px; 53 | color: #009688; 54 | opacity: 0.8; 55 | grid-column: kicker; 56 | } 57 | 58 | d-title .status span { 59 | line-height: 1; 60 | display: inline-block; 61 | padding: 6px 0; 62 | border-bottom: 1px solid #80cbc4; 63 | font-size: 11px; 64 | text-transform: uppercase; 65 | } 66 | -------------------------------------------------------------------------------- /src/transforms/code.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import Prism from 'prismjs'; 16 | 17 | export default function(dom, data) { 18 | let codeElements = [].slice.call(dom.querySelectorAll('dt-code')); 19 | codeElements.forEach(el => { 20 | let content = el.textContent; 21 | el.innerHTML = ''; 22 | let language = el.getAttribute('language'); 23 | let c = dom.createElement('code'); 24 | if (el.getAttribute('block') === '') { 25 | // Let's normalize the tab indents 26 | content = content.replace(/\n/, ''); 27 | let tabs = content.match(/\s*/); 28 | content = content.replace(new RegExp('\n' + tabs, 'g'), '\n'); 29 | content = content.trim(); 30 | let p = dom.createElement('pre'); 31 | p.appendChild(c); 32 | el.appendChild(p); 33 | } else { 34 | el.appendChild(c); 35 | } 36 | let highlighted = content; 37 | if (Prism.languages[language]) { 38 | c.setAttribute('class', 'language-' + language); 39 | highlighted = Prism.highlight(content, Prism.languages[language]); 40 | } 41 | c.innerHTML = highlighted; 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/mixins/mutating.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export const Mutating = (superclass) => { 16 | return class extends superclass { 17 | 18 | constructor() { 19 | super(); 20 | 21 | // set up mutation observer 22 | const options = {childList: true, characterData: true, subtree: true}; 23 | const observer = new MutationObserver( () => { 24 | observer.disconnect(); 25 | this.renderIfPossible(); 26 | observer.observe(this, options); 27 | }); 28 | 29 | // ...and listen for changes 30 | observer.observe(this, options); 31 | } 32 | 33 | connectedCallback() { 34 | super.connectedCallback(); 35 | 36 | this.renderIfPossible(); 37 | } 38 | 39 | // potential TODO: check if this is enough for all our usecases 40 | // maybe provide a custom function to tell if we have enough information to render 41 | renderIfPossible() { 42 | if (this.textContent && this.root) { 43 | this.renderContent(); 44 | } 45 | } 46 | 47 | renderContent() { 48 | console.error(`Your class ${this.constructor.name} must provide a custom renderContent() method!` ); 49 | } 50 | 51 | }; // end class 52 | }; // end mixin function 53 | -------------------------------------------------------------------------------- /src/distill-components/distill-header-template.js: -------------------------------------------------------------------------------- 1 | import logo from '../assets/distill-logo.svg'; 2 | 3 | export const headerTemplate = ` 4 | 68 |
69 | 73 | 78 |
79 | `; 80 | -------------------------------------------------------------------------------- /src/distill-components/distill-footer-template.js: -------------------------------------------------------------------------------- 1 | import logo from '../assets/distill-logo.svg'; 2 | 3 | export const footerTemplate = ` 4 | 53 | 54 | 73 | 74 | `; 75 | -------------------------------------------------------------------------------- /src/components/d-article.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // import { Template } from '../mixins/template'; 16 | // import { Controller } from '../controller'; 17 | 18 | const isOnlyWhitespace = /^\s*$/; 19 | 20 | export class Article extends HTMLElement { 21 | static get is() { return 'd-article'; } 22 | 23 | constructor() { 24 | super(); 25 | 26 | new MutationObserver( (mutations) => { 27 | for (const mutation of mutations) { 28 | for (const addedNode of mutation.addedNodes) { 29 | switch (addedNode.nodeName) { 30 | case '#text': { // usually text nodes are only linebreaks. 31 | const text = addedNode.nodeValue; 32 | if (!isOnlyWhitespace.test(text)) { 33 | console.warn('Use of unwrapped text in distill articles is discouraged as it breaks layout! Please wrap any text in a or

tag. We found the following text: ' + text); 34 | const wrapper = document.createElement('span'); 35 | wrapper.innerHTML = addedNode.nodeValue; 36 | addedNode.parentNode.insertBefore(wrapper, addedNode); 37 | addedNode.parentNode.removeChild(addedNode); 38 | } 39 | } break; 40 | } 41 | } 42 | } 43 | }).observe(this, {childList: true}); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/components/d-appendix.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template'; 16 | 17 | const T = Template('d-appendix', ` 18 | 75 | 76 | `, false); 77 | 78 | export class Appendix extends T(HTMLElement) { 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/styles/d-byline.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Distill Template Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | d-byline { 18 | contain: style; 19 | overflow: hidden; 20 | border-top: 1px solid rgba(0, 0, 0, 0.1); 21 | font-size: 0.8rem; 22 | line-height: 1.8em; 23 | padding: 1.5rem 0; 24 | min-height: 1.8em; 25 | } 26 | 27 | 28 | d-byline .byline { 29 | grid-template-columns: 1fr 1fr; 30 | grid-column: text; 31 | } 32 | 33 | @media(min-width: 768px) { 34 | d-byline .byline { 35 | grid-template-columns: 1fr 1fr 1fr 1fr; 36 | } 37 | } 38 | 39 | d-byline .authors-affiliations { 40 | grid-column-end: span 2; 41 | grid-template-columns: 1fr 1fr; 42 | margin-bottom: 1em; 43 | } 44 | 45 | @media(min-width: 768px) { 46 | d-byline .authors-affiliations { 47 | margin-bottom: 0; 48 | } 49 | } 50 | 51 | d-byline h3 { 52 | font-size: 0.6rem; 53 | font-weight: 400; 54 | color: rgba(0, 0, 0, 0.5); 55 | margin: 0; 56 | text-transform: uppercase; 57 | } 58 | 59 | d-byline p { 60 | margin: 0; 61 | } 62 | 63 | d-byline a, 64 | d-article d-byline a { 65 | color: rgba(0, 0, 0, 0.8); 66 | text-decoration: none; 67 | border-bottom: none; 68 | } 69 | 70 | d-article d-byline a:hover { 71 | text-decoration: underline; 72 | border-bottom: none; 73 | } 74 | 75 | d-byline p.author { 76 | font-weight: 500; 77 | } 78 | 79 | d-byline .affiliations { 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/helpers/bibtex.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import bibtexParse from 'bibtex-parse-js'; 16 | 17 | function normalizeTag(string) { 18 | return string 19 | .replace(/[\t\n ]+/g, ' ') 20 | .replace(/{\\["^`.'acu~Hvs]( )?([a-zA-Z])}/g, (full, x, char) => char) 21 | .replace(/{\\([a-zA-Z])}/g, (full, char) => char) 22 | .replace(/[{}]/gi,''); // Replace curly braces forcing plaintext in latex. 23 | } 24 | 25 | export function parseBibtex(bibtex) { 26 | const bibliography = new Map(); 27 | const parsedEntries = bibtexParse.toJSON(bibtex); 28 | for (const entry of parsedEntries) { 29 | // normalize tags; note entryTags is an object, not Map 30 | for (const [key, value] of Object.entries(entry.entryTags)) { 31 | entry.entryTags[key.toLowerCase()] = normalizeTag(value); 32 | } 33 | entry.entryTags.type = entry.entryType; 34 | // add to bibliography 35 | bibliography.set(entry.citationKey, entry.entryTags); 36 | } 37 | return bibliography; 38 | } 39 | 40 | export function serializeFrontmatterToBibtex(frontMatter) { 41 | return `@article{${frontMatter.slug}, 42 | author = {${frontMatter.bibtexAuthors}}, 43 | title = {${frontMatter.title}}, 44 | journal = {${frontMatter.journal.title}}, 45 | year = {${frontMatter.publishedYear}}, 46 | note = {${frontMatter.url}}, 47 | doi = {${frontMatter.doi}} 48 | }`; 49 | } 50 | -------------------------------------------------------------------------------- /src/transforms/front-matter.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import ymlParse from 'js-yaml'; 16 | 17 | export default function(dom, data) { 18 | let localData = {}; 19 | let el = dom.querySelector('script[type="text/front-matter"]'); 20 | if (el) { 21 | let text = el.textContent; 22 | localData = ymlParse.safeLoad(text); 23 | } 24 | 25 | data.title = localData.title ? localData.title : 'Untitled'; 26 | data.description = localData.description ? localData.description : 'No description.'; 27 | 28 | data.authors = localData.authors ? localData.authors : []; 29 | 30 | data.authors = data.authors.map((author, i) =>{ 31 | let a = {}; 32 | let name = Object.keys(author)[0]; 33 | if ((typeof author) === 'string') { 34 | name = author; 35 | } else { 36 | a.personalURL = author[name]; 37 | } 38 | let names = name.split(' '); 39 | a.name = name; 40 | a.firstName = names.slice(0, names.length - 1).join(' '); 41 | a.lastName = names[names.length -1]; 42 | if(localData.affiliations[i]) { 43 | let affiliation = Object.keys(localData.affiliations[i])[0]; 44 | if ((typeof localData.affiliations[i]) === 'string') { 45 | affiliation = localData.affiliations[i]; 46 | } else { 47 | a.affiliationURL = localData.affiliations[i][affiliation]; 48 | } 49 | a.affiliation = affiliation; 50 | } 51 | return a; 52 | }); 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/transforms/mathematics.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import katex from 'katex'; 16 | import { renderMathInElement } from '../helpers/katex-auto-render'; 17 | 18 | export default function(dom, data) { 19 | let needsCSS = false; 20 | const body = dom.querySelector('body'); 21 | 22 | if (!body) { 23 | console.warn("No body tag found!"); 24 | return; 25 | } 26 | 27 | if (data.katex && data.katex.delimiters) { 28 | global.document = dom; 29 | renderMathInElement(body, data.katex); 30 | } 31 | 32 | // render d-math tags 33 | const mathTags = body.querySelectorAll('d-math'); 34 | if (mathTags.length > 0) { 35 | needsCSS = true; 36 | console.warn(`Prerendering ${mathTags.length} math tags...`); 37 | for (const mathTag of mathTags) { 38 | const localOptions = { displayMode: mathTag.hasAttribute('block') }; 39 | const options = Object.assign(localOptions, data.katex); 40 | const html = katex.renderToString(mathTag.textContent, options); 41 | const container = dom.createElement('span'); 42 | container.innerHTML = html; 43 | mathTag.parentElement.insertBefore(container, mathTag); 44 | mathTag.parentElement.removeChild(mathTag); 45 | } 46 | } 47 | 48 | if (needsCSS) { 49 | const katexCSSTag = ''; 50 | dom.head.insertAdjacentHTML('beforeend', katexCSSTag); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /bin/render.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // Copyright 2018 The Distill Template Authors 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"); 6 | // you may not use this file except in compliance with the License. 7 | // You may obtain a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, 13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | // See the License for the specific language governing permissions and 15 | // limitations under the License. 16 | 17 | const path = require('path'); 18 | const fs = require('fs'); 19 | const program = require('commander'); 20 | const jsdom = require('jsdom'); 21 | const { JSDOM } = jsdom; 22 | const transforms = require('../dist/transforms.v2.js'); 23 | 24 | program 25 | .version('1.0.0') 26 | .description('Pre-renders distill articles for publication.') 27 | .usage('-i -o ') 28 | .option('-i, --input-path ', 'path to input HTML file.') 29 | .option('-o, --output-path ', 'path to write rendered HTML file to.') 30 | .parse(process.argv); 31 | 32 | const virtualConsole = new jsdom.VirtualConsole(); 33 | // omitJSDOMErrors as JSDOM routinely can't parse modern CSS 34 | virtualConsole.sendTo(console, { omitJSDOMErrors: true }); 35 | 36 | const options = { runScripts: 'outside-only', QuerySelector: true, virtualConsole: virtualConsole }; 37 | JSDOM.fromFile(program.inputPath, options).then(dom => { 38 | const window = dom.window; 39 | const document = window.document; 40 | const HTMLElement = window.HTMLElement; 41 | 42 | const data = new transforms.FrontMatter; 43 | data.inputHTMLPath = program.inputPath; // may be needed to resolve relative links! 44 | data.inputDirectory = path.dirname(program.inputPath); 45 | transforms.render(document, data); 46 | transforms.distillify(document, data); 47 | 48 | const transformedHtml = dom.serialize(); 49 | fs.writeFileSync(program.outputPath, transformedHtml); 50 | }).catch(console.error); 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "distill-template", 3 | "version": "2.8.0", 4 | "description": "Template for creating Distill articles.", 5 | "main": "dist/template.v2.js", 6 | "bin": { 7 | "distill-render": "./bin/render.js" 8 | }, 9 | "author": "Shan Carter", 10 | "homepage": "https://github.com/distillpub/distill-template#readme", 11 | "bugs": { 12 | "url": "https://github.com/distillpub/distill-template/issues" 13 | }, 14 | "scripts": { 15 | "dev": "rollup -c rollup.config.dev.js -w", 16 | "serve": "http-server", 17 | "test": "mocha", 18 | "lint": "eslint", 19 | "build": "rollup -c rollup.config.prod.js", 20 | "prepare": "npm run build" 21 | }, 22 | "repository": { 23 | "url": "git+https://github.com/distillpub/distill-template.git", 24 | "type": "git" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.9.0", 28 | "@rollup/plugin-babel": "^5.0.0", 29 | "bibtex-parse-js": "^0.0.23", 30 | "chai": "^3.5.0", 31 | "eslint": "^4.19.1", 32 | "eslint-config-google": "^0.9.1", 33 | "js-yaml": "^3.13.1", 34 | "jsdom": "11.3.0", 35 | "jsdom-global": "3.0.2", 36 | "marked": "^0.8.2", 37 | "mocha": "^5.2.0", 38 | "prismjs": "^1.20.0", 39 | "rollup": "^2.7.3", 40 | "rollup-plugin-commonjs": "^10.1.0", 41 | "rollup-plugin-copy": "^0.2.3", 42 | "rollup-plugin-grapher": "^0.2.0", 43 | "rollup-plugin-gzip": "^1.4.0", 44 | "rollup-plugin-node-resolve": "^2.0.0", 45 | "rollup-plugin-serve": "^1.0.1", 46 | "rollup-plugin-string": "^2.0.2", 47 | "rollup-plugin-uglify": "^1.0.1", 48 | "rollup-watch": "^4.3.1", 49 | "should": "^13.2.3", 50 | "source-map-support": "^0.5.19" 51 | }, 52 | "dependencies": { 53 | "@webcomponents/webcomponentsjs": "^1.3.3", 54 | "assert": "^1.5.0", 55 | "commander": "^2.20.3", 56 | "d3-array": "^2.4.0", 57 | "d3-drag": "^1.2.5", 58 | "d3-scale": "^3.2.1", 59 | "d3-selection": "^1.4.1", 60 | "d3-time-format": "^2.2.3", 61 | "escape-html": "^1.0.3", 62 | "intersection-observer": "^0.4.3", 63 | "jsdom-wc": "^11.0.0-alpha-1", 64 | "katex": "^0.8.3" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /rollup.config.common.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import resolve from "rollup-plugin-node-resolve"; 16 | import string from "rollup-plugin-string"; 17 | import commonjs from "rollup-plugin-commonjs"; 18 | import babel from '@rollup/plugin-babel'; 19 | 20 | // uncomment to show dependencies [1/2] 21 | // import rollupGrapher from 'rollup-plugin-grapher' 22 | 23 | const defaultConfig = { 24 | plugins: [ 25 | resolve({ 26 | jsnext: true, 27 | browser: true 28 | }), 29 | commonjs(), 30 | string({ 31 | include: ["**/*.txt", "**/*.svg", "**/*.html", "**/*.css", "**/*.base64"] 32 | }) 33 | ] 34 | }; 35 | 36 | const componentsConfig = { 37 | input: "src/components.js", 38 | output: [{ format: "umd", name: "dl", file: "dist/template.v2.js", sourcemap: true }], 39 | plugins: [ 40 | babel({ 41 | "babelHelpers": "bundled", 42 | "targets": "defaults" 43 | }) 44 | ] 45 | }; 46 | 47 | const transformsConfig = { 48 | input: "src/transforms.js", 49 | output: [ 50 | { 51 | format: "umd", 52 | name: "dl", 53 | file: "dist/transforms.v2.js", 54 | globals: { fs: "fs" }, 55 | sourcemap: true, 56 | } 57 | ], 58 | external: ["fs"], 59 | plugins: [ 60 | babel({ 61 | "babelHelpers": "bundled", 62 | "targets": { 63 | "node": "current" 64 | } 65 | }) 66 | ] 67 | }; 68 | 69 | Object.assign(componentsConfig, defaultConfig); 70 | Object.assign(transformsConfig, defaultConfig); 71 | 72 | export default [componentsConfig, transformsConfig]; 73 | -------------------------------------------------------------------------------- /src/mixins/template.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*global ShadyCSS*/ 16 | 17 | export const Template = (name, templateString, useShadow = true) => { 18 | 19 | return (superclass) => { 20 | 21 | const template = document.createElement('template'); 22 | template.innerHTML = templateString; 23 | 24 | if (useShadow && 'ShadyCSS' in window) { 25 | ShadyCSS.prepareTemplate(template, name); 26 | } 27 | 28 | return class extends superclass { 29 | 30 | static get is() { return name; } 31 | 32 | constructor() { 33 | super(); 34 | 35 | this.clone = document.importNode(template.content, true); 36 | if (useShadow) { 37 | this.attachShadow({mode: 'open'}); 38 | this.shadowRoot.appendChild(this.clone); 39 | } 40 | } 41 | 42 | connectedCallback() { 43 | if (this.hasAttribute('distill-prerendered')) { 44 | return; 45 | } 46 | if (useShadow) { 47 | if ('ShadyCSS' in window) { 48 | ShadyCSS.styleElement(this); 49 | } 50 | } else { 51 | this.insertBefore(this.clone, this.firstChild); 52 | } 53 | } 54 | 55 | get root() { 56 | if (useShadow) { 57 | return this.shadowRoot; 58 | } else { 59 | return this; 60 | } 61 | } 62 | 63 | /* TODO: Are we using these? Should we even? */ 64 | $(query) { 65 | return this.root.querySelector(query); 66 | } 67 | 68 | $$(query) { 69 | return this.root.querySelectorAll(query); 70 | } 71 | }; 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/d-byline.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // import style from '../styles/d-byline.css'; 16 | 17 | export function bylineTemplate(frontMatter) { 18 | return ` 19 |

49 | `; 50 | } 51 | 52 | export class Byline extends HTMLElement { 53 | 54 | static get is() { return 'd-byline'; } 55 | 56 | set frontMatter(frontMatter) { 57 | this.innerHTML = bylineTemplate(frontMatter); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/components/d-toc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export class TOC extends HTMLElement { 16 | 17 | static get is() { return 'd-toc'; } 18 | 19 | connectedCallback() { 20 | if (!this.getAttribute('prerendered')) { 21 | window.onload = () => { 22 | const article = document.querySelector('d-article'); 23 | const headings = article.querySelectorAll('h2, h3'); 24 | renderTOC(this, headings); 25 | }; 26 | } 27 | } 28 | 29 | } 30 | 31 | export function renderTOC(element, headings) { 32 | 33 | let ToC =` 34 | 55 | 56 |

Table of contents

57 | '; 79 | element.innerHTML = ToC; 80 | } 81 | -------------------------------------------------------------------------------- /src/components/d-citation-list.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { bibliography_cite } from '../helpers/citation'; 16 | 17 | const styles = ` 18 | d-citation-list { 19 | contain: style; 20 | } 21 | 22 | d-citation-list .references { 23 | grid-column: text; 24 | } 25 | 26 | d-citation-list .references .title { 27 | font-weight: 500; 28 | } 29 | `; 30 | 31 | export function renderCitationList(element, entries, dom=document) { 32 | if (entries.size > 0) { 33 | element.style.display = ''; 34 | let list = element.querySelector('.references'); 35 | if (list) { 36 | list.innerHTML = ''; 37 | } else { 38 | const stylesTag = dom.createElement('style'); 39 | stylesTag.innerHTML = styles; 40 | element.appendChild(stylesTag); 41 | 42 | const heading = dom.createElement('h3'); 43 | heading.id = 'references'; 44 | heading.textContent = 'References'; 45 | element.appendChild(heading); 46 | 47 | list = dom.createElement('ol'); 48 | list.id = 'references-list'; 49 | list.className = 'references'; 50 | element.appendChild(list); 51 | } 52 | 53 | for (const [key, entry] of entries) { 54 | const listItem = dom.createElement('li'); 55 | listItem.id = key; 56 | listItem.innerHTML = bibliography_cite(entry); 57 | list.appendChild(listItem); 58 | } 59 | } else { 60 | element.style.display = 'none'; 61 | } 62 | } 63 | 64 | export class CitationList extends HTMLElement { 65 | 66 | static get is() { return 'd-citation-list'; } 67 | 68 | connectedCallback() { 69 | if (!this.hasAttribute('distill-prerendered')) { 70 | this.style.display = 'none'; 71 | } 72 | } 73 | 74 | set citations(citations) { 75 | renderCitationList(this, citations); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/components/d-footnote-list.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template'; 16 | 17 | const T = Template('d-footnote-list', ` 18 | 34 | 35 |

Footnotes

36 |
    37 | `, false); 38 | 39 | export class FootnoteList extends T(HTMLElement) { 40 | 41 | connectedCallback() { 42 | super.connectedCallback(); 43 | 44 | this.list = this.root.querySelector('ol'); 45 | // footnotes list is initially hidden 46 | this.root.style.display = 'none'; 47 | // look through document and register existing footnotes 48 | // Store.subscribeTo('footnotes', (footnote) => { 49 | // this.renderFootnote(footnote); 50 | // }); 51 | } 52 | 53 | // TODO: could optimize this to accept individual footnotes? 54 | set footnotes(footnotes) { 55 | this.list.innerHTML = ''; 56 | if (footnotes.length) { 57 | // ensure footnote list is visible 58 | this.root.style.display = ''; 59 | 60 | for (const footnote of footnotes) { 61 | // construct and append list item to show footnote 62 | const listItem = document.createElement('li'); 63 | listItem.id = footnote.id + '-listing'; 64 | listItem.innerHTML = footnote.innerHTML; 65 | 66 | const backlink = document.createElement('a'); 67 | backlink.setAttribute('class', 'footnote-backlink'); 68 | backlink.textContent = '[↩]'; 69 | backlink.href = '#' + footnote.id; 70 | 71 | listItem.appendChild(backlink); 72 | this.list.appendChild(listItem); 73 | } 74 | } else { 75 | // ensure footnote list is invisible 76 | this.root.style.display = 'none'; 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/components/d-footnote.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template.js'; 16 | 17 | const T = Template('d-footnote', ` 18 | 46 | 47 | 48 |
    49 | 50 |
    51 |
    52 | 53 | 54 | 55 | 56 | 57 | `); 58 | 59 | export class Footnote extends T(HTMLElement) { 60 | 61 | constructor() { 62 | super(); 63 | 64 | const options = {childList: true, characterData: true, subtree: true}; 65 | const observer = new MutationObserver(this.notify); 66 | observer.observe(this, options); 67 | } 68 | 69 | notify() { 70 | const options = { detail: this, bubbles: true }; 71 | const event = new CustomEvent('onFootnoteChanged', options); 72 | document.dispatchEvent(event); 73 | } 74 | 75 | connectedCallback() { 76 | // listen and notify about changes to slotted content 77 | // const slot = this.shadowRoot.querySelector('#slot'); 78 | // console.warn(slot.textContent); 79 | // slot.addEventListener('slotchange', this.notify); 80 | this.hoverBox = this.root.querySelector('d-hover-box'); 81 | window.customElements.whenDefined('d-hover-box').then(() => { 82 | this.hoverBox.listen(this); 83 | }); 84 | // create numeric ID 85 | Footnote.currentFootnoteId += 1; 86 | const IdString = Footnote.currentFootnoteId.toString(); 87 | this.root.host.id = 'd-footnote-' + IdString; 88 | 89 | // set up hidden hover box 90 | const id = 'dt-fn-hover-box-' + IdString; 91 | this.hoverBox.id = id 92 | 93 | // set up visible footnote marker 94 | const span = this.root.querySelector('#fn-'); 95 | span.setAttribute('id', 'fn-' + IdString); 96 | span.setAttribute('data-hover-ref', id); 97 | span.textContent = IdString; 98 | } 99 | 100 | } 101 | 102 | Footnote.currentFootnoteId = 0; 103 | -------------------------------------------------------------------------------- /test/template_v1.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* global it, should, describe */ 16 | 17 | // Test format: https://mochajs.org/#bdd 18 | // Assertion format: http://chaijs.com/api/bdd/ 19 | 20 | let expect = require('chai').expect; 21 | let jsdom = require('jsdom'); 22 | // let distill = require('../dist/template.v1.js'); 23 | 24 | describe.skip('Distill v1', function() { 25 | 26 | describe('render', function() { 27 | it('Should have a render function.', function() { 28 | expect(distill.render).to.be.an.instanceof(Function); 29 | }); 30 | }); 31 | 32 | // 33 | // html 34 | // 35 | describe.skip('html', function() { 36 | it('Should have a html function.', function() { 37 | expect(distill.html).to.be.an.instanceof(Function); 38 | }); 39 | it('Should add a language attribute to html element, if not present.', function() { 40 | var doc = jsdom.jsdom(''); 41 | let before = jsdom.serializeDocument(doc); 42 | distill.html(doc, {}); 43 | let after = jsdom.serializeDocument(doc); 44 | expect(after).to.match(new RegExp('')); 45 | }); 46 | it('Should not add a language attribute to html element, if already present.', function() { 47 | var doc = jsdom.jsdom(''); 48 | let before = jsdom.serializeDocument(doc); 49 | distill.html(doc, {}); 50 | let after = jsdom.serializeDocument(doc); 51 | expect(after).to.not.match(new RegExp('lang="en"')); 52 | }); 53 | it('Should add a meta charset tag, if not present.', function() { 54 | var doc = jsdom.jsdom(''); 55 | let before = jsdom.serializeDocument(doc); 56 | distill.html(doc, {}); 57 | let after = jsdom.serializeDocument(doc); 58 | expect(after).to.match(new RegExp('')); 59 | }); 60 | it('Should add a meta viewport tag, if not present.', function() { 61 | var doc = jsdom.jsdom(''); 62 | let before = jsdom.serializeDocument(doc); 63 | distill.html(doc, {}); 64 | let after = jsdom.serializeDocument(doc); 65 | expect(after).to.match(new RegExp('')); 66 | }); 67 | }); 68 | 69 | // 70 | // styles 71 | // 72 | describe.skip('styles', function() { 73 | it('Should have a styles function.', function() { 74 | expect(distill.styles).to.be.an.instanceof(Function); 75 | }); 76 | }) 77 | }); 78 | -------------------------------------------------------------------------------- /src/components/d-front-matter.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function _moveLegacyAffiliationFormatIntoArray(frontMatter) { 16 | // authors used to have propoerties "affiliation" and "affiliationURL". 17 | // We now encourage using an array for affiliations containing objects with 18 | // properties "name" and "url". 19 | for (let author of frontMatter.authors) { 20 | const hasOldStyle = Boolean(author.affiliation) 21 | const hasNewStyle = Boolean(author.affiliations) 22 | if (!hasOldStyle) continue; 23 | if (hasNewStyle) { 24 | console.warn(`Author ${author.author} has both old-style ("affiliation" & "affiliationURL") and new style ("affiliations") affiliation information!`) 25 | } else { 26 | let newAffiliation = { 27 | "name": author.affiliation 28 | } 29 | if (author.affiliationURL) newAffiliation.url = author.affiliationURL; 30 | author.affiliations = [newAffiliation]; 31 | } 32 | } 33 | return frontMatter 34 | } 35 | 36 | export function parseFrontmatter(element) { 37 | const scriptTag = element.firstElementChild; 38 | if (scriptTag) { 39 | const type = scriptTag.getAttribute('type'); 40 | if (type.split('/')[1] == 'json') { 41 | const content = scriptTag.textContent; 42 | const parsed = JSON.parse(content); 43 | return _moveLegacyAffiliationFormatIntoArray(parsed); 44 | } else { 45 | console.error('Distill only supports JSON frontmatter tags anymore; no more YAML.'); 46 | } 47 | } else { 48 | console.error('You added a frontmatter tag but did not provide a script tag with front matter data in it. Please take a look at our templates.'); 49 | } 50 | return {}; 51 | } 52 | 53 | export class FrontMatter extends HTMLElement { 54 | 55 | static get is() { return 'd-front-matter'; } 56 | 57 | constructor() { 58 | super(); 59 | 60 | const options = {childList: true, characterData: true, subtree: true}; 61 | const observer = new MutationObserver( (entries) => { 62 | for (const entry of entries) { 63 | if (entry.target.nodeName === 'SCRIPT' || entry.type === 'characterData') { 64 | const data = parseFrontmatter(this); 65 | this.notify(data); 66 | } 67 | } 68 | }); 69 | observer.observe(this, options); 70 | } 71 | 72 | notify(data) { 73 | const options = { detail: data, bubbles: true }; 74 | const event = new CustomEvent('onFrontMatterChanged', options); 75 | document.dispatchEvent(event); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/helpers/polyfills.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function addPolyfill(polyfill, polyfillLoadedCallback) { 16 | console.debug('Runlevel 0: Polyfill required: ' + polyfill.name); 17 | const script = document.createElement('script'); 18 | script.src = polyfill.url; 19 | script.async = false; 20 | if (polyfillLoadedCallback) { 21 | script.onload = function() { polyfillLoadedCallback(polyfill); }; 22 | } 23 | script.onerror = function() { 24 | new Error('Runlevel 0: Polyfills failed to load script ' + polyfill.name); 25 | }; 26 | document.head.appendChild(script); 27 | } 28 | 29 | export const polyfills = [ 30 | { 31 | name: 'WebComponents', 32 | support: function() { 33 | return 'customElements' in window && 34 | 'attachShadow' in Element.prototype && 35 | 'getRootNode' in Element.prototype && 36 | 'content' in document.createElement('template') && 37 | 'Promise' in window && 38 | 'from' in Array; 39 | }, 40 | url: 'https://distill.pub/third-party/polyfills/webcomponents-lite.js' 41 | }, { 42 | name: 'IntersectionObserver', 43 | support: function() { 44 | return 'IntersectionObserver' in window && 45 | 'IntersectionObserverEntry' in window; 46 | }, 47 | url: 'https://distill.pub/third-party/polyfills/intersection-observer.js' 48 | }, 49 | ]; 50 | 51 | export class Polyfills { 52 | 53 | static browserSupportsAllFeatures() { 54 | return polyfills.every((poly) => poly.support()); 55 | } 56 | 57 | static load(callback) { 58 | // Define an intermediate callback that checks if all is loaded. 59 | const polyfillLoaded = function(polyfill) { 60 | polyfill.loaded = true; 61 | console.debug('Runlevel 0: Polyfill has finished loading: ' + polyfill.name); 62 | // console.debug(window[polyfill.name]); 63 | if (Polyfills.neededPolyfills.every((poly) => poly.loaded)) { 64 | console.debug('Runlevel 0: All required polyfills have finished loading.'); 65 | console.debug('Runlevel 0->1.'); 66 | window.distillRunlevel = 1; 67 | callback(); 68 | } 69 | }; 70 | // Add polyfill script tags 71 | for (const polyfill of Polyfills.neededPolyfills) { 72 | addPolyfill(polyfill, polyfillLoaded); 73 | } 74 | } 75 | 76 | static get neededPolyfills() { 77 | if (!Polyfills._neededPolyfills) { 78 | Polyfills._neededPolyfills = polyfills.filter((poly) => !poly.support()); 79 | } 80 | return Polyfills._neededPolyfills; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/transforms/optional-components.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // no appendix -> add appendix 16 | // title in front, no h1 -> add it 17 | // no title in front, h1 -> read and put into frontMatter 18 | // footnote -> footnote list 19 | // break up bib 20 | // if citation, no bib-list -> add citation-list 21 | 22 | // if authors, no byline -> add byline 23 | 24 | export default function(dom, data) { 25 | const body = dom.body; 26 | const article = body.querySelector('d-article'); 27 | 28 | // If we don't have an article tag, something weird is going on—giving up. 29 | if (!article) { 30 | console.warn('No d-article tag found; skipping adding optional components!'); 31 | return; 32 | } 33 | 34 | let byline = dom.querySelector('d-byline'); 35 | if (!byline) { 36 | if (data.authors) { 37 | byline = dom.createElement('d-byline'); 38 | body.insertBefore(byline, article); 39 | } else { 40 | console.warn('No authors found in front matter; please add them before submission!'); 41 | } 42 | } 43 | 44 | let title = dom.querySelector('d-title'); 45 | if (!title) { 46 | title = dom.createElement('d-title'); 47 | body.insertBefore(title, byline); 48 | } 49 | 50 | let h1 = title.querySelector('h1'); 51 | if (!h1) { 52 | h1 = dom.createElement('h1'); 53 | h1.textContent = data.title; 54 | title.insertBefore(h1, title.firstChild); 55 | } 56 | 57 | const hasPassword = typeof data.password !== 'undefined'; 58 | let interstitial = body.querySelector('d-interstitial'); 59 | if (hasPassword && !interstitial) { 60 | const inBrowser = typeof window !== 'undefined'; 61 | const onLocalhost = inBrowser && window.location.hostname.includes('localhost'); 62 | if (!inBrowser || !onLocalhost) { 63 | interstitial = dom.createElement('d-interstitial'); 64 | interstitial.password = data.password; 65 | body.insertBefore(interstitial, body.firstChild); 66 | } 67 | } else if (!hasPassword && interstitial) { 68 | interstitial.parentElement.removeChild(this); 69 | } 70 | 71 | let appendix = dom.querySelector('d-appendix'); 72 | if (!appendix) { 73 | appendix = dom.createElement('d-appendix'); 74 | dom.body.appendChild(appendix); 75 | } 76 | 77 | let footnoteList = dom.querySelector('d-footnote-list'); 78 | if (!footnoteList) { 79 | footnoteList = dom.createElement('d-footnote-list'); 80 | appendix.appendChild(footnoteList); 81 | } 82 | 83 | let citationList = dom.querySelector('d-citation-list'); 84 | if (!citationList) { 85 | citationList = dom.createElement('d-citation-list'); 86 | appendix.appendChild(citationList); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/components/d-code.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import Prism from 'prismjs'; 16 | import 'prismjs/components/prism-python'; 17 | import 'prismjs/components/prism-clike'; 18 | import 'prismjs/components/prism-python'; 19 | import 'prismjs/components/prism-clike'; 20 | import 'prismjs/components/prism-lua'; 21 | import 'prismjs/components/prism-bash'; 22 | import 'prismjs/components/prism-go'; 23 | import 'prismjs/components/prism-markdown'; 24 | import 'prismjs/components/prism-julia'; 25 | import css from 'prismjs/themes/prism.css'; 26 | 27 | import { Template } from '../mixins/template.js'; 28 | import { Mutating } from '../mixins/mutating.js'; 29 | 30 | const T = Template('d-code', ` 31 | 50 | 51 | 52 | 53 | `); 54 | 55 | export class Code extends Mutating(T(HTMLElement)) { 56 | 57 | renderContent() { 58 | 59 | // check if language can be highlighted 60 | this.languageName = this.getAttribute('language'); 61 | if (!this.languageName) { 62 | console.warn('You need to provide a language attribute to your block to let us know how to highlight your code; e.g.:\n zeros = np.zeros(shape).'); 63 | return; 64 | } 65 | const language = Prism.languages[this.languageName]; 66 | if (language == undefined) { 67 | console.warn(`Distill does not yet support highlighting your code block in "${this.languageName}'.`); 68 | return; 69 | } 70 | 71 | let content = this.textContent; 72 | const codeTag = this.shadowRoot.querySelector('#code-container'); 73 | 74 | if (this.hasAttribute('block')) { 75 | // normalize the tab indents 76 | content = content.replace(/\n/, ''); 77 | const tabs = content.match(/\s*/); 78 | content = content.replace(new RegExp('\n' + tabs, 'g'), '\n'); 79 | content = content.trim(); 80 | // wrap code block in pre tag if needed 81 | if (codeTag.parentNode instanceof ShadowRoot) { 82 | const preTag = document.createElement('pre'); 83 | this.shadowRoot.removeChild(codeTag); 84 | preTag.appendChild(codeTag); 85 | this.shadowRoot.appendChild(preTag); 86 | } 87 | 88 | } 89 | 90 | codeTag.className = `language-${this.languageName}`; 91 | codeTag.innerHTML = Prism.highlight(content, language); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/styles/styles-base.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Distill Template Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | html { 18 | font-size: 14px; 19 | line-height: 1.6em; 20 | /* font-family: "Libre Franklin", "Helvetica Neue", sans-serif; */ 21 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif; 22 | /*, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";*/ 23 | text-size-adjust: 100%; 24 | -ms-text-size-adjust: 100%; 25 | -webkit-text-size-adjust: 100%; 26 | } 27 | 28 | @media(min-width: 768px) { 29 | html { 30 | font-size: 16px; 31 | } 32 | } 33 | 34 | body { 35 | margin: 0; 36 | } 37 | 38 | a { 39 | color: #004276; 40 | } 41 | 42 | figure { 43 | margin: 0; 44 | } 45 | 46 | table { 47 | border-collapse: collapse; 48 | border-spacing: 0; 49 | } 50 | 51 | table th { 52 | text-align: left; 53 | } 54 | 55 | table thead { 56 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 57 | } 58 | 59 | table thead th { 60 | padding-bottom: 0.5em; 61 | } 62 | 63 | table tbody :first-child td { 64 | padding-top: 0.5em; 65 | } 66 | 67 | pre { 68 | overflow: auto; 69 | max-width: 100%; 70 | } 71 | 72 | p { 73 | margin-top: 0; 74 | margin-bottom: 1em; 75 | } 76 | 77 | sup, sub { 78 | vertical-align: baseline; 79 | position: relative; 80 | top: -0.4em; 81 | line-height: 1em; 82 | } 83 | 84 | sub { 85 | top: 0.4em; 86 | } 87 | 88 | .kicker, 89 | .marker { 90 | font-size: 15px; 91 | font-weight: 600; 92 | color: rgba(0, 0, 0, 0.5); 93 | } 94 | 95 | 96 | /* Headline */ 97 | 98 | @media(min-width: 1024px) { 99 | d-title h1 span { 100 | display: block; 101 | } 102 | } 103 | 104 | /* Figure */ 105 | 106 | figure { 107 | position: relative; 108 | margin-bottom: 2.5em; 109 | margin-top: 1.5em; 110 | } 111 | 112 | figcaption+figure { 113 | 114 | } 115 | 116 | figure img { 117 | width: 100%; 118 | } 119 | 120 | figure svg text, 121 | figure svg tspan { 122 | } 123 | 124 | figcaption, 125 | .figcaption { 126 | color: rgba(0, 0, 0, 0.6); 127 | font-size: 12px; 128 | line-height: 1.5em; 129 | } 130 | 131 | @media(min-width: 1024px) { 132 | figcaption, 133 | .figcaption { 134 | font-size: 13px; 135 | } 136 | } 137 | 138 | figure.external img { 139 | background: white; 140 | border: 1px solid rgba(0, 0, 0, 0.1); 141 | box-shadow: 0 1px 8px rgba(0, 0, 0, 0.1); 142 | padding: 18px; 143 | box-sizing: border-box; 144 | } 145 | 146 | figcaption a { 147 | color: rgba(0, 0, 0, 0.6); 148 | } 149 | 150 | figcaption b, 151 | figcaption strong, { 152 | font-weight: 600; 153 | color: rgba(0, 0, 0, 1.0); 154 | } 155 | -------------------------------------------------------------------------------- /src/mixins/properties.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function propName(attr) { 16 | return attr.replace(/(-[a-z])/g, (s) => s.toUpperCase().replace('-', '')); 17 | } 18 | 19 | export function attrName(prop) { 20 | return prop.replace(/([A-Z])/g, (s) => '-' + s.toLowerCase()); 21 | } 22 | 23 | export function deserializeAttribute(value, type) { 24 | switch (type) { 25 | case String: 26 | break; 27 | case Array: 28 | case Object: 29 | try { 30 | value = JSON.parse(value); 31 | } catch (e) {} 32 | break; 33 | case Boolean: 34 | value = value != 'false' && value != '0' && value; 35 | break; 36 | default: 37 | } 38 | return value; 39 | } 40 | 41 | const immediately = window.setImmediate || function(fn, args) { 42 | window.setTimeout(function() { 43 | fn.apply(this, args); 44 | }, 0); 45 | }; 46 | 47 | export const Properties = (properties) => { 48 | const keys = Object.keys(properties); 49 | const attrs = keys.map((k) => attrName(k)); 50 | return (superclass) => { 51 | const cls = class extends superclass { 52 | static get observedAttributes() { 53 | return attrs; 54 | } 55 | attributeChangedCallback(attr, oldValue, newValue) { 56 | const prop = propName(attr); 57 | const value = deserializeAttribute(newValue, properties[prop].type); 58 | this[prop] = value; 59 | } 60 | _propertiesChanged() { 61 | if (!this.propertiesChangedCallback) { 62 | return; 63 | } 64 | clearTimeout(this._propertiesChangedTimeout); 65 | this._propertiesChangedTimeout = immediately(() => { 66 | this.propertiesChangedCallback(this); 67 | }); 68 | } 69 | }; 70 | keys.forEach(function(k) { 71 | const secret = `_${k}`; 72 | const callback = `${k}Changed`; 73 | const defaultValue = properties[k].value; 74 | Object.defineProperty(cls.prototype, k, { 75 | get: function() { 76 | let value = this[secret]; 77 | if (value) { 78 | return value; 79 | } 80 | if (defaultValue && typeof defaultValue == 'function') { 81 | this[secret] = defaultValue(); 82 | } else { 83 | this[secret] = defaultValue; 84 | } 85 | return this[secret]; 86 | }, 87 | set: function(value) { 88 | const oldValue = this[secret]; 89 | this[secret] = value; 90 | if (this[callback]) { 91 | this[callback](value, oldValue); 92 | } 93 | this._propertiesChanged(); 94 | } 95 | }); 96 | }); 97 | return cls; 98 | }; 99 | }; 100 | -------------------------------------------------------------------------------- /src/distill-components/distill-appendix.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { serializeFrontmatterToBibtex } from '../helpers/bibtex'; 16 | 17 | const styles = ` 18 | 43 | `; 44 | 45 | export function appendixTemplate(frontMatter) { 46 | let html = styles; 47 | 48 | if (typeof frontMatter.githubUrl !== 'undefined') { 49 | html += ` 50 |

    Updates and Corrections

    51 |

    `; 52 | if (frontMatter.githubCompareUpdatesUrl) { 53 | html += `View all changes to this article since it was first published.`; 54 | } 55 | html += ` 56 | If you see mistakes or want to suggest changes, please create an issue on GitHub.

    57 | `; 58 | } 59 | 60 | const journal = frontMatter.journal; 61 | if (typeof journal !== 'undefined' && journal.title === 'Distill') { 62 | html += ` 63 |

    Reuse

    64 |

    Diagrams and text are licensed under Creative Commons Attribution CC-BY 4.0 with the source available on GitHub, unless noted otherwise. The figures that have been reused from other sources don’t fall under this license and can be recognized by a note in their caption: “Figure from …”.

    65 | `; 66 | } 67 | 68 | if (typeof frontMatter.publishedDate !== 'undefined') { 69 | html += ` 70 |

    Citation

    71 |

    For attribution in academic contexts, please cite this work as

    72 |
    ${frontMatter.concatenatedAuthors}, "${frontMatter.title}", Distill, ${frontMatter.publishedYear}.
    73 |

    BibTeX citation

    74 |
    ${serializeFrontmatterToBibtex(frontMatter)}
    75 | `; 76 | } 77 | 78 | return html; 79 | } 80 | 81 | export class DistillAppendix extends HTMLElement { 82 | 83 | static get is() { return 'distill-appendix'; } 84 | 85 | set frontMatter(frontMatter) { 86 | this.innerHTML = appendixTemplate(frontMatter); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /src/components/d-bibliography.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { parseBibtex } from '../helpers/bibtex'; 16 | 17 | export function parseBibliography(element) { 18 | const scriptTag = element.firstElementChild; 19 | if (scriptTag && scriptTag.tagName === 'SCRIPT') { 20 | if (scriptTag.type == 'text/bibtex') { 21 | const bibtex = element.firstElementChild.textContent; 22 | return parseBibtex(bibtex); 23 | } else if (scriptTag.type == 'text/json') { 24 | return new Map(JSON.parse(scriptTag.textContent)); 25 | } else { 26 | console.warn('Unsupported bibliography script tag type: ' + scriptTag.type); 27 | } 28 | } else { 29 | console.warn('Bibliography did not have any script tag.'); 30 | } 31 | } 32 | 33 | export class Bibliography extends HTMLElement { 34 | 35 | static get is() { return 'd-bibliography'; } 36 | 37 | constructor() { 38 | super(); 39 | 40 | // set up mutation observer 41 | const options = {childList: true, characterData: true, subtree: true}; 42 | const observer = new MutationObserver( (entries) => { 43 | for (const entry of entries) { 44 | if (entry.target.nodeName === 'SCRIPT' || entry.type === 'characterData') { 45 | this.parseIfPossible(); 46 | } 47 | } 48 | }); 49 | observer.observe(this, options); 50 | } 51 | 52 | connectedCallback() { 53 | requestAnimationFrame(() => { 54 | this.parseIfPossible(); 55 | }); 56 | } 57 | 58 | parseIfPossible() { 59 | const scriptTag = this.querySelector('script'); 60 | if (!scriptTag) return; 61 | if (scriptTag.type == 'text/bibtex') { 62 | const newBibtex = scriptTag.textContent; 63 | if (this.bibtex !== newBibtex) { 64 | this.bibtex = newBibtex; 65 | const bibliography = parseBibtex(this.bibtex); 66 | this.notify(bibliography); 67 | } 68 | } else if (scriptTag.type == 'text/json') { 69 | const bibliography = new Map(JSON.parse(scriptTag.textContent)); 70 | this.notify(bibliography); 71 | } else { 72 | console.warn('Unsupported bibliography script tag type: ' + scriptTag.type); 73 | } 74 | } 75 | 76 | notify(bibliography) { 77 | const options = { detail: bibliography, bubbles: true }; 78 | const event = new CustomEvent('onBibliographyChanged', options); 79 | this.dispatchEvent(event); 80 | } 81 | 82 | /* observe 'src' attribute */ 83 | 84 | static get observedAttributes() { 85 | return ['src']; 86 | } 87 | 88 | receivedBibtex(event) { 89 | const bibliography = parseBibtex(event.target.response); 90 | this.notify(bibliography); 91 | } 92 | 93 | attributeChangedCallback(name, oldValue, newValue) { 94 | var oReq = new XMLHttpRequest(); 95 | oReq.onload = (e) => this.receivedBibtex(e); 96 | oReq.onerror = () => console.warn(`Could not load Bibtex! (tried ${newValue})`); 97 | oReq.responseType = 'text'; 98 | oReq.open('GET', newValue, true); 99 | oReq.send(); 100 | } 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /src/transforms/hover-box.txt: -------------------------------------------------------------------------------- 1 | // DistillHoverBox 2 | //===================================== 3 | 4 | function DistillHoverBox(key, pos){ 5 | 6 | if (!(key in DistillHoverBox.contentMap)){ 7 | console.error("No DistillHoverBox content registered for key", key); 8 | } 9 | if (key in DistillHoverBox.liveBoxes) { 10 | console.error("There already exists a DistillHoverBox for key", key); 11 | } else { 12 | for (var k in DistillHoverBox.liveBoxes) 13 | DistillHoverBox.liveBoxes[k].remove(); 14 | DistillHoverBox.liveBoxes[key] = this; 15 | } 16 | this.key = key; 17 | 18 | var pretty = window.innerWidth > 600; 19 | 20 | var padding = pretty? 18 : 12; 21 | var outer_padding = pretty ? 18 : 0; 22 | var bbox = document.querySelector("body").getBoundingClientRect(); 23 | var left = pos[0] - bbox.left, top = pos[1] - bbox.top; 24 | var width = Math.min(window.innerWidth-2*outer_padding, 648); 25 | left = Math.min(left, window.innerWidth-width-outer_padding); 26 | width = width - 2*padding; 27 | 28 | var str = `
    40 | ${DistillHoverBox.contentMap[key]} 41 |
    `; 42 | 43 | this.div = appendBody(str); 44 | 45 | DistillHoverBox.bind (this.div, key); 46 | } 47 | 48 | DistillHoverBox.prototype.remove = function remove(){ 49 | if (this.div) this.div.remove(); 50 | if (this.timeout) clearTimeout(this.timeout); 51 | delete DistillHoverBox.liveBoxes[this.key]; 52 | } 53 | 54 | DistillHoverBox.prototype.stopTimeout = function stopTimeout() { 55 | if (this.timeout) clearTimeout(this.timeout); 56 | } 57 | 58 | DistillHoverBox.prototype.extendTimeout = function extendTimeout(T) { 59 | //console.log("extend", T) 60 | var this_ = this; 61 | this.stopTimeout(); 62 | this.timeout = setTimeout(() => this_.remove(), T); 63 | } 64 | 65 | DistillHoverBox.liveBoxes = {}; 66 | DistillHoverBox.contentMap = {}; 67 | 68 | DistillHoverBox.bind = function bind(node, key) { 69 | if (typeof node == "string"){ 70 | node = document.querySelector(node); 71 | } 72 | node.addEventListener("mouseover", () => { 73 | var bbox = node.getBoundingClientRect(); 74 | if (!(key in DistillHoverBox.liveBoxes)){ 75 | new DistillHoverBox(key, [bbox.right, bbox.bottom]); 76 | } 77 | DistillHoverBox.liveBoxes[key].stopTimeout(); 78 | }); 79 | node.addEventListener("mouseout", () => { 80 | if (key in DistillHoverBox.liveBoxes){ 81 | DistillHoverBox.liveBoxes[key].extendTimeout(250); 82 | } 83 | }); 84 | 85 | } 86 | 87 | 88 | function appendBody(str){ 89 | var node = nodeFromString(str); 90 | var body = document.querySelector("body"); 91 | body.appendChild(node); 92 | return node; 93 | } 94 | 95 | function nodeFromString(str) { 96 | var div = document.createElement("div"); 97 | div.innerHTML = str; 98 | return div.firstChild; 99 | } 100 | 101 | var hover_es = document.querySelectorAll("span[data-hover]"); 102 | hover_es = [].slice.apply(hover_es); 103 | hover_es.forEach((e,n) => { 104 | var key = "hover-"+n; 105 | var content = e.getAttribute("data-hover"); 106 | DistillHoverBox.contentMap[key] = content; 107 | DistillHoverBox.bind(e, key); 108 | }); 109 | -------------------------------------------------------------------------------- /src/components/d-math.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /*global katex */ 16 | import { Mutating } from '../mixins/mutating.js'; 17 | import { Template } from '../mixins/template.js'; 18 | 19 | import style from '../styles/d-math.css'; 20 | 21 | // attaches renderMathInElement to window 22 | import { renderMathInElement } from '../helpers/katex-auto-render'; 23 | 24 | export const katexJSURL = 'https://distill.pub/third-party/katex/katex.min.js'; 25 | export const katexCSSTag = ''; 26 | 27 | const T = Template('d-math', ` 28 | ${katexCSSTag} 29 | 42 | 43 | `); 44 | 45 | // DMath, not Math, because that would conflict with the JS built-in 46 | export class DMath extends Mutating(T(HTMLElement)) { 47 | 48 | static set katexOptions(options) { 49 | DMath._katexOptions = options; 50 | if (DMath.katexOptions.delimiters) { 51 | if (!DMath.katexAdded) { 52 | DMath.addKatex(); 53 | } else { 54 | DMath.katexLoadedCallback(); 55 | } 56 | } 57 | } 58 | 59 | static get katexOptions() { 60 | if (!DMath._katexOptions) { 61 | DMath._katexOptions = { 62 | delimiters: [ { 'left':'$$', 'right':'$$', 'display': false } ] 63 | }; 64 | } 65 | return DMath._katexOptions; 66 | } 67 | 68 | static katexLoadedCallback() { 69 | // render all d-math tags 70 | const mathTags = document.querySelectorAll('d-math'); 71 | for (const mathTag of mathTags) { 72 | mathTag.renderContent(); 73 | } 74 | // transform inline delimited math to d-math tags 75 | if (DMath.katexOptions.delimiters) { 76 | renderMathInElement(document.body, DMath.katexOptions); 77 | } 78 | } 79 | 80 | static addKatex() { 81 | // css tag can use this convenience function 82 | document.head.insertAdjacentHTML('beforeend', katexCSSTag); 83 | // script tag has to be created to work properly 84 | const scriptTag = document.createElement('script'); 85 | scriptTag.src = katexJSURL; 86 | scriptTag.async = true; 87 | scriptTag.onload = DMath.katexLoadedCallback; 88 | scriptTag.crossorigin = 'anonymous'; 89 | document.head.appendChild(scriptTag); 90 | 91 | DMath.katexAdded = true; 92 | } 93 | 94 | get options() { 95 | const localOptions = { displayMode: this.hasAttribute('block') }; 96 | return Object.assign(localOptions, DMath.katexOptions); 97 | } 98 | 99 | connectedCallback() { 100 | super.connectedCallback(); 101 | if (!DMath.katexAdded) { 102 | DMath.addKatex(); 103 | } 104 | } 105 | 106 | renderContent() { 107 | if (typeof katex !== 'undefined') { 108 | const container = this.root.querySelector('#katex-container'); 109 | katex.render(this.textContent, container, this.options); 110 | } 111 | } 112 | 113 | } 114 | 115 | DMath.katexAdded = false; 116 | DMath.inlineMathRendered = false; 117 | window.DMath = DMath; // TODO: check if this can be removed, or if we should expose a distill global 118 | -------------------------------------------------------------------------------- /src/transforms/polyfills.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const webcomponentPath = 'https://distill.pub/third-party/polyfills/webcomponents-lite.js'; 16 | const intersectionObserverPath = 'https://distill.pub/third-party/polyfills/intersection-observer.js'; 17 | 18 | // const template = ` 19 | // if ('IntersectionObserver' in window && 20 | // 'IntersectionObserverEntry' in window && 21 | // 'intersectionRatio' in IntersectionObserverEntry.prototype) { 22 | // // Platform supports IntersectionObserver natively! :-) 23 | // if (!('isIntersecting' in IntersectionObserverEntry.prototype)) { 24 | // Object.defineProperty(IntersectionObserverEntry.prototype, 25 | // 'isIntersecting', { 26 | // get: function () { 27 | // return this.intersectionRatio > 0; 28 | // } 29 | // }); 30 | // } 31 | // } else { 32 | // // Platform does not support webcomponents--loading polyfills synchronously. 33 | // const scriptTag = document.createElement('script'); 34 | // scriptTag.src = '${intersectionObserverPath}'; 35 | // scriptTag.async = false; 36 | // document.currentScript.parentNode.insertBefore(scriptTag, document.currentScript.nextSibling); 37 | // } 38 | // 39 | // if ('registerElement' in document && 40 | // 'import' in document.createElement('link') && 41 | // 'content' in document.createElement('template')) { 42 | // // Platform supports webcomponents natively! :-) 43 | // } else { 44 | // // Platform does not support webcomponents--loading polyfills synchronously. 45 | // const scriptTag = document.createElement('script'); 46 | // scriptTag.src = '${webcomponentPath}'; 47 | // scriptTag.async = false; 48 | // document.currentScript.parentNode.insertBefore(scriptTag, document.currentScript.nextSibling); 49 | // } 50 | // 51 | // 52 | // `; 53 | 54 | 55 | const addBackIn = ` 56 | window.addEventListener('WebComponentsReady', function() { 57 | console.warn('WebComponentsReady'); 58 | const loaderTag = document.createElement('script'); 59 | loaderTag.src = 'https://distill.pub/template.v2.js'; 60 | document.head.insertBefore(loaderTag, document.head.firstChild); 61 | }); 62 | `; 63 | 64 | export default function render(dom) { 65 | // pull out template script tag 66 | const templateTag = dom.querySelector('script[src*="template.v2.js"]'); 67 | if (templateTag) { 68 | templateTag.parentNode.removeChild(templateTag); 69 | } else { 70 | console.debug('FYI: Did not find template tag when trying to remove it. You may not have added it. Be aware that our polyfills will add it.') 71 | } 72 | 73 | // add loader 74 | const loaderTag = dom.createElement('script'); 75 | loaderTag.src = 'https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.17/webcomponents-loader.js'; 76 | dom.head.insertBefore(loaderTag, dom.head.firstChild); 77 | 78 | // add loader event listener to add tempalrte back in 79 | const addTag = dom.createElement('script'); 80 | addTag.innerHTML = addBackIn; 81 | dom.head.insertBefore(addTag, dom.head.firstChild); 82 | 83 | 84 | // create polyfill script tag 85 | // const polyfillScriptTag = dom.createElement('script'); 86 | // polyfillScriptTag.innerHTML = template; 87 | // polyfillScriptTag.id = 'polyfills'; 88 | 89 | // insert at appropriate position--before any other script tag 90 | // const firstScriptTag = dom.head.querySelector('script'); 91 | // dom.head.insertBefore(polyfillScriptTag, firstScriptTag); 92 | } 93 | -------------------------------------------------------------------------------- /src/components/d-hover-box.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template.js'; 16 | 17 | const T = Template('d-hover-box', ` 18 | 54 | 55 |
    56 |
    57 | 58 |
    59 |
    60 | `); 61 | 62 | export class HoverBox extends T(HTMLElement) { 63 | 64 | constructor() { 65 | super(); 66 | } 67 | 68 | connectedCallback() { 69 | 70 | } 71 | 72 | listen(element) { 73 | // console.log(element) 74 | this.bindDivEvents(this); 75 | this.bindTriggerEvents(element); 76 | // this.style.display = "block"; 77 | } 78 | 79 | bindDivEvents(element) { 80 | // For mice, same behavior as hovering on links 81 | element.addEventListener('mouseover', () => { 82 | if (!this.visible) this.showAtNode(element); 83 | this.stopTimeout(); 84 | }); 85 | element.addEventListener('mouseout', () => { 86 | this.extendTimeout(500); 87 | }); 88 | // Don't trigger body touchstart event when touching within box 89 | element.addEventListener('touchstart', (event) => { 90 | event.stopPropagation(); 91 | }, {passive: true}); 92 | // Close box when touching outside box 93 | document.body.addEventListener('touchstart', () => { 94 | this.hide(); 95 | }, {passive: true}); 96 | } 97 | 98 | bindTriggerEvents(node) { 99 | node.addEventListener('mouseover', () => { 100 | if (!this.visible) { 101 | this.showAtNode(node); 102 | } 103 | this.stopTimeout(); 104 | }); 105 | 106 | node.addEventListener('mouseout', () => { 107 | this.extendTimeout(300); 108 | }); 109 | 110 | node.addEventListener('touchstart', (event) => { 111 | if (this.visible) { 112 | this.hide(); 113 | } else { 114 | this.showAtNode(node); 115 | } 116 | // Don't trigger body touchstart event when touching link 117 | event.stopPropagation(); 118 | }, {passive: true}); 119 | } 120 | 121 | show(position) { 122 | this.visible = true; 123 | this.style.display = 'block'; 124 | // 10px extra offset from element 125 | this.style.top = Math.round(position[1] + 10) + 'px'; 126 | } 127 | 128 | showAtNode(node) { 129 | // https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop 130 | const bbox = node.getBoundingClientRect(); 131 | this.show([node.offsetLeft + bbox.width, node.offsetTop + bbox.height]); 132 | } 133 | 134 | hide() { 135 | this.visible = false; 136 | this.style.display = 'none'; 137 | this.stopTimeout(); 138 | } 139 | 140 | stopTimeout() { 141 | if (this.timeout) { 142 | clearTimeout(this.timeout); 143 | } 144 | } 145 | 146 | extendTimeout(time) { 147 | this.stopTimeout(); 148 | this.timeout = setTimeout(() => { 149 | this.hide(); 150 | }, time); 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /examples/bibliography.bib: -------------------------------------------------------------------------------- 1 | @article{gregor2015draw, 2 | title={DRAW: A recurrent neural network for image generation}, 3 | author={Gregor, Karol and Danihelka, Ivo and Graves, Alex and Rezende, Danilo Jimenez and Wierstra, Daan}, 4 | journal={arXiv preprint arXiv:1502.04623}, 5 | year={2015}, 6 | url ={https://arxiv.org/pdf/1502.04623.pdf} 7 | } 8 | @article{mercier2011humans, 9 | title={Why do humans reason? Arguments for an argumentative theory}, 10 | author={Mercier, Hugo and Sperber, Dan}, 11 | journal={Behavioral and brain sciences}, 12 | volume={34}, 13 | number={02}, 14 | pages={57--74}, 15 | year={2011}, 16 | publisher={Cambridge Univ Press}, 17 | doi={10.1017/S0140525X10000968} 18 | } 19 | 20 | @article{dong2014image, 21 | title={Image super-resolution using deep convolutional networks}, 22 | author={Dong, Chao and Loy, Chen Change and He, Kaiming and Tang, Xiaoou}, 23 | journal={arXiv preprint arXiv:1501.00092}, 24 | year={2014}, 25 | url={https://arxiv.org/pdf/1501.00092.pdf} 26 | } 27 | 28 | @article{dumoulin2016adversarially, 29 | title={Adversarially Learned Inference}, 30 | author={Dumoulin, Vincent and Belghazi, Ishmael and Poole, Ben and Lamb, Alex and Arjovsky, Martin and Mastropietro, Olivier and Courville, Aaron}, 31 | journal={arXiv preprint arXiv:1606.00704}, 32 | year={2016}, 33 | url={https://arxiv.org/pdf/1606.00704.pdf} 34 | } 35 | 36 | @article{dumoulin2016guide, 37 | title={A guide to convolution arithmetic for deep learning}, 38 | author={Dumoulin, Vincent and Visin, Francesco}, 39 | journal={arXiv preprint arXiv:1603.07285}, 40 | year={2016}, 41 | url={https://arxiv.org/pdf/1603.07285.pdf} 42 | } 43 | 44 | @article{gauthier2014conditional, 45 | title={Conditional generative adversarial nets for convolutional face generation}, 46 | author={Gauthier, Jon}, 47 | journal={Class Project for Stanford CS231N: Convolutional Neural Networks for Visual Recognition, Winter semester}, 48 | volume={2014}, 49 | year={2014}, 50 | url={http://www.foldl.me/uploads/papers/tr-cgans.pdf} 51 | } 52 | 53 | @article{johnson2016perceptual, 54 | title={Perceptual losses for real-time style transfer and super-resolution}, 55 | author={Johnson, Justin and Alahi, Alexandre and Fei-Fei, Li}, 56 | journal={arXiv preprint arXiv:1603.08155}, 57 | year={2016}, 58 | url={https://arxiv.org/pdf/1603.08155.pdf} 59 | } 60 | 61 | @article{mordvintsev2015inceptionism, 62 | title={Inceptionism: Going deeper into neural networks}, 63 | author={Mordvintsev, Alexander and Olah, Christopher and Tyka, Mike}, 64 | journal={Google Research Blog}, 65 | year={2015}, 66 | url={https://research.googleblog.com/2015/06/inceptionism-going-deeper-into-neural.html} 67 | } 68 | 69 | @misc{mordvintsev2016deepdreaming, 70 | title={DeepDreaming with TensorFlow}, 71 | author={Mordvintsev, Alexander}, 72 | year={2016}, 73 | url={https://github.com/tensorflow/tensorflow/blob/master/tensorflow/examples/tutorials/deepdream/deepdream.ipynb}, 74 | } 75 | 76 | @article{radford2015unsupervised, 77 | title={Unsupervised representation learning with deep convolutional generative adversarial networks}, 78 | author={Radford, Alec and Metz, Luke and Chintala, Soumith}, 79 | journal={arXiv preprint arXiv:1511.06434}, 80 | year={2015}, 81 | url={https://arxiv.org/pdf/1511.06434.pdf} 82 | } 83 | 84 | @inproceedings{salimans2016improved, 85 | title={Improved techniques for training gans}, 86 | author={Salimans, Tim and Goodfellow, Ian and Zaremba, Wojciech and Cheung, Vicki and Radford, Alec and Chen, Xi}, 87 | booktitle={Advances in Neural Information Processing Systems}, 88 | pages={2226--2234}, 89 | year={2016}, 90 | url={https://arxiv.org/pdf/1606.03498.pdf} 91 | } 92 | 93 | @article{shi2016deconvolution, 94 | title={Is the deconvolution layer the same as a convolutional layer?}, 95 | author={Shi, Wenzhe and Caballero, Jose and Theis, Lucas and Huszar, Ferenc and Aitken, Andrew and Ledig, Christian and Wang, Zehan}, 96 | journal={arXiv preprint arXiv:1609.07009}, 97 | year={2016}, 98 | url={https://arxiv.org/pdf/1609.07009.pdf} 99 | } 100 | 101 | @misc{openai2018charter, 102 | author={OpenAI}, 103 | title={OpenAI Charter}, 104 | type={Blog}, 105 | number={April 9}, 106 | year={2018}, 107 | url={https://blog.openai.com/charter}, 108 | } 109 | -------------------------------------------------------------------------------- /src/styles/styles-layout.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Distill Template Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | @supports not (display: grid) { 18 | .base-grid, 19 | distill-header, 20 | d-title, 21 | d-abstract, 22 | d-article, 23 | d-appendix, 24 | distill-appendix, 25 | d-byline, 26 | d-footnote-list, 27 | d-citation-list, 28 | distill-footer { 29 | display: block; 30 | padding: 8px; 31 | } 32 | } 33 | 34 | .base-grid, 35 | distill-header, 36 | d-title, 37 | d-abstract, 38 | d-article, 39 | d-appendix, 40 | distill-appendix, 41 | d-byline, 42 | d-footnote-list, 43 | d-citation-list, 44 | distill-footer { 45 | display: grid; 46 | justify-items: stretch; 47 | grid-template-columns: [screen-start] 8px [page-start kicker-start text-start gutter-start middle-start] 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr [text-end page-end gutter-end kicker-end middle-end] 8px [screen-end]; 48 | grid-column-gap: 8px; 49 | } 50 | 51 | .grid { 52 | display: grid; 53 | grid-column-gap: 8px; 54 | } 55 | 56 | @media(min-width: 768px) { 57 | .base-grid, 58 | distill-header, 59 | d-title, 60 | d-abstract, 61 | d-article, 62 | d-appendix, 63 | distill-appendix, 64 | d-byline, 65 | d-footnote-list, 66 | d-citation-list, 67 | distill-footer { 68 | grid-template-columns: [screen-start] 1fr [page-start kicker-start middle-start text-start] 45px 45px 45px 45px 45px 45px 45px 45px [ kicker-end text-end gutter-start] 45px [middle-end] 45px [page-end gutter-end] 1fr [screen-end]; 69 | grid-column-gap: 16px; 70 | } 71 | 72 | .grid { 73 | grid-column-gap: 16px; 74 | } 75 | } 76 | 77 | @media(min-width: 1000px) { 78 | .base-grid, 79 | distill-header, 80 | d-title, 81 | d-abstract, 82 | d-article, 83 | d-appendix, 84 | distill-appendix, 85 | d-byline, 86 | d-footnote-list, 87 | d-citation-list, 88 | distill-footer { 89 | grid-template-columns: [screen-start] 1fr [page-start kicker-start] 50px [middle-start] 50px [text-start kicker-end] 50px 50px 50px 50px 50px 50px 50px 50px [text-end gutter-start] 50px [middle-end] 50px [page-end gutter-end] 1fr [screen-end]; 90 | grid-column-gap: 16px; 91 | } 92 | 93 | .grid { 94 | grid-column-gap: 16px; 95 | } 96 | } 97 | 98 | @media(min-width: 1180px) { 99 | .base-grid, 100 | distill-header, 101 | d-title, 102 | d-abstract, 103 | d-article, 104 | d-appendix, 105 | distill-appendix, 106 | d-byline, 107 | d-footnote-list, 108 | d-citation-list, 109 | distill-footer { 110 | grid-template-columns: [screen-start] 1fr [page-start kicker-start] 60px [middle-start] 60px [text-start kicker-end] 60px 60px 60px 60px 60px 60px 60px 60px [text-end gutter-start] 60px [middle-end] 60px [page-end gutter-end] 1fr [screen-end]; 111 | grid-column-gap: 32px; 112 | } 113 | 114 | .grid { 115 | grid-column-gap: 32px; 116 | } 117 | } 118 | 119 | 120 | 121 | 122 | .base-grid { 123 | grid-column: screen; 124 | } 125 | 126 | /* .l-body, 127 | d-article > * { 128 | grid-column: text; 129 | } 130 | 131 | .l-page, 132 | d-title > *, 133 | d-figure { 134 | grid-column: page; 135 | } */ 136 | 137 | .l-gutter { 138 | grid-column: gutter; 139 | } 140 | 141 | .l-text, 142 | .l-body { 143 | grid-column: text; 144 | } 145 | 146 | .l-page { 147 | grid-column: page; 148 | } 149 | 150 | .l-body-outset { 151 | grid-column: middle; 152 | } 153 | 154 | .l-page-outset { 155 | grid-column: page; 156 | } 157 | 158 | .l-screen { 159 | grid-column: screen; 160 | } 161 | 162 | .l-screen-inset { 163 | grid-column: screen; 164 | padding-left: 16px; 165 | padding-left: 16px; 166 | } 167 | 168 | 169 | /* Aside */ 170 | 171 | d-article aside { 172 | grid-column: gutter; 173 | font-size: 12px; 174 | line-height: 1.6em; 175 | color: rgba(0, 0, 0, 0.6) 176 | } 177 | 178 | @media(min-width: 768px) { 179 | aside { 180 | grid-column: gutter; 181 | } 182 | 183 | .side { 184 | grid-column: gutter; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/transforms/typeset.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export default function(dom) { 16 | 17 | var textNodes = dom.createTreeWalker( 18 | dom.body, 19 | dom.defaultView.NodeFilter.SHOW_TEXT 20 | ); 21 | while (textNodes.nextNode()) { 22 | var n = textNodes.currentNode, 23 | text = n.nodeValue; 24 | if (text && acceptNode(n)) { 25 | text = quotes(text); 26 | text = punctuation(text); 27 | // TODO: Add back support for ligatures once their uppercased versions don't hang Chrome search anymore 28 | // see: https://bugs.chromium.org/p/chromium/issues/detail?id=862648 29 | // text = ligatures(text); 30 | n.nodeValue = text; 31 | } 32 | } 33 | } 34 | 35 | // 2018-07-11 shancarter@ and ludwigschubert@ no longer know what this was meant to accomplish 36 | // if it was trying to not replace text in any child nodes of those listed here, 37 | // then it does not accomplish that. 38 | function acceptNode(node) { 39 | var parent = node.parentElement; 40 | var isMath = (parent && parent.getAttribute && parent.getAttribute('class')) ? parent.getAttribute('class').includes('katex') || parent.getAttribute('class').includes('MathJax') : false; 41 | return parent && 42 | parent.nodeName !== 'SCRIPT' && 43 | parent.nodeName !== 'STYLE' && 44 | parent.nodeName !== 'CODE' && 45 | parent.nodeName !== 'PRE' && 46 | parent.nodeName !== 'SPAN' && 47 | parent.nodeName !== 'D-HEADER' && 48 | parent.nodeName !== 'D-BYLINE' && 49 | parent.nodeName !== 'D-MATH' && 50 | parent.nodeName !== 'D-CODE' && 51 | parent.nodeName !== 'D-BIBLIOGRAPHY' && 52 | parent.nodeName !== 'D-FOOTER' && 53 | parent.nodeName !== 'D-APPENDIX' && 54 | parent.nodeName !== 'D-FRONTMATTER' && 55 | parent.nodeName !== 'D-TOC' && 56 | parent.nodeType !== 8 && //comment nodes 57 | !isMath; 58 | } 59 | 60 | 61 | /*! 62 | * typeset - Typesetting for the web 63 | * @version v0.1.6 64 | * @link https://github.com/davidmerfield/Typeset.js 65 | * @author David Merfield 66 | */ 67 | // which has a CC0 license 68 | // http://creativecommons.org/publicdomain/zero/1.0/ 69 | 70 | 71 | function punctuation(text){ 72 | 73 | // Dashes 74 | text = text.replace(/--/g, '\u2014'); 75 | text = text.replace(/\s*\u2014\s*/g,'\u2009\u2014\u2009'); //this has thin spaces 76 | 77 | // Elipses 78 | text = text.replace(/\.\.\./g,'…'); 79 | 80 | // Nbsp for punc with spaces 81 | var NBSP = '\u00a0'; 82 | var NBSP_PUNCTUATION_START = /([«¿¡]) /g; 83 | var NBSP_PUNCTUATION_END = / ([!?:;.,‽»])/g; 84 | 85 | text = text.replace(NBSP_PUNCTUATION_START, '$1' + NBSP); 86 | text = text.replace(NBSP_PUNCTUATION_END, NBSP + '$1'); 87 | 88 | return text; 89 | } 90 | 91 | function quotes(text) { 92 | 93 | text = text 94 | .replace(/(\W|^)"([^\s!?:;.,‽»])/g, '$1\u201c$2') // beginning " 95 | .replace(/(\u201c[^"]*)"([^"]*$|[^\u201c"]*\u201c)/g, '$1\u201d$2') // ending " 96 | .replace(/([^0-9])"/g,'$1\u201d') // remaining " at end of word 97 | .replace(/(\W|^)'(\S)/g, '$1\u2018$2') // beginning ' 98 | .replace(/([a-z])'([a-z])/ig, '$1\u2019$2') // conjunction's possession 99 | .replace(/((\u2018[^']*)|[a-z])'([^0-9]|$)/ig, '$1\u2019$3') // ending ' 100 | .replace(/(\u2018)([0-9]{2}[^\u2019]*)(\u2018([^0-9]|$)|$|\u2019[a-z])/ig, '\u2019$2$3') // abbrev. years like '93 101 | .replace(/(\B|^)\u2018(?=([^\u2019]*\u2019\b)*([^\u2019\u2018]*\W[\u2019\u2018]\b|[^\u2019\u2018]*$))/ig, '$1\u2019') // backwards apostrophe 102 | .replace(/'''/g, '\u2034') // triple prime 103 | .replace(/("|'')/g, '\u2033') // double prime 104 | .replace(/'/g, '\u2032'); 105 | 106 | // Allow escaped quotes 107 | text = text.replace(/\\“/, '"'); 108 | text = text.replace(/\\”/, '"'); 109 | text = text.replace(/\\’/, '\''); 110 | text = text.replace(/\\‘/, '\''); 111 | 112 | return text; 113 | } 114 | 115 | function ligatures(text){ 116 | 117 | text = text.replace(/fi/g, 'fi'); 118 | text = text.replace(/fl/g, 'fl'); 119 | 120 | return text; 121 | } 122 | -------------------------------------------------------------------------------- /src/transforms.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* eslint-env node, mocha */ 16 | 17 | import { FrontMatter } from './front-matter'; 18 | 19 | /* Extractors */ 20 | import ExtractFrontmatter from './extractors/front-matter'; 21 | import ExtractBibliography from './extractors/bibliography'; 22 | import ExtractCitations from './extractors/citations'; 23 | 24 | const extractors = new Map([ 25 | ['ExtractFrontmatter', ExtractFrontmatter], 26 | ['ExtractBibliography', ExtractBibliography], 27 | ['ExtractCitations', ExtractCitations], 28 | ]); 29 | 30 | /* Transforms */ 31 | import HTML from './transforms/html'; 32 | import Byline from './transforms/byline'; 33 | import OptionalComponents from './transforms/optional-components'; 34 | import Mathematics from './transforms/mathematics'; 35 | import Meta from './transforms/meta'; 36 | import { makeStyleTag } from './styles/styles'; 37 | import TOC from './transforms/toc'; 38 | import Typeset from './transforms/typeset'; 39 | import Polyfills from './transforms/polyfills'; 40 | import CitationList from './transforms/citation-list'; 41 | import Reorder from './transforms/reorder'; 42 | 43 | const transforms = new Map([ 44 | ['HTML', HTML], 45 | ['makeStyleTag', makeStyleTag], 46 | ['OptionalComponents', OptionalComponents], 47 | ['TOC', TOC], 48 | ['Byline', Byline], 49 | ['Mathematics', Mathematics], 50 | ['Meta', Meta], 51 | ['Typeset', Typeset], 52 | ['Polyfills', Polyfills], 53 | ['CitationList', CitationList], 54 | ['Reorder', Reorder] // keep last 55 | ]); 56 | 57 | /* Distill Transforms */ 58 | import DistillHeader from './distill-transforms/distill-header'; 59 | import DistillAppendix from './distill-transforms/distill-appendix'; 60 | import DistillFooter from './distill-transforms/distill-footer'; 61 | 62 | const distillTransforms = new Map([ 63 | ['DistillHeader', DistillHeader], 64 | ['DistillAppendix', DistillAppendix], 65 | ['DistillFooter', DistillFooter], 66 | ]); 67 | 68 | /* Exported functions */ 69 | 70 | export function render(dom, data, verbose=true) { 71 | let frontMatter; 72 | if (data instanceof FrontMatter) { 73 | frontMatter = data; 74 | } else { 75 | frontMatter = FrontMatter.fromObject(data); 76 | } 77 | // first, we collect static data from the dom 78 | for (const [name, extract] of extractors.entries()) { 79 | if (verbose) console.warn('Running extractor: ' + name); 80 | extract(dom, frontMatter, verbose); 81 | } 82 | // secondly we use it to transform parts of the dom 83 | for (const [name, transform] of transforms.entries()) { 84 | if (verbose) console.warn('Running transform: ' + name); 85 | // console.warn('Running transform: ', transform); 86 | transform(dom, frontMatter, verbose); 87 | } 88 | dom.body.setAttribute('distill-prerendered', ''); 89 | // the function calling us can now use the transformed dom and filled data object 90 | if (data instanceof FrontMatter) { 91 | // frontMatter will already have needed properties 92 | } else { 93 | frontMatter.assignToObject(data); 94 | } 95 | } 96 | 97 | export function distillify(dom, data, verbose=true) { 98 | // thirdly, we can use these additional transforms when publishing on the Distill website 99 | for (const [name, transform] of distillTransforms.entries()) { 100 | if (verbose) console.warn('Running distillify: ', name); 101 | transform(dom, data, verbose); 102 | } 103 | } 104 | 105 | export function usesTemplateV2(dom) { 106 | const tags = dom.querySelectorAll('script'); 107 | let usesV2 = undefined; 108 | for (const tag of tags) { 109 | const src = tag.src; 110 | if (src.includes('template.v1.js')) { 111 | usesV2 = false; 112 | } else if (src.includes('template.v2.js')) { 113 | usesV2 = true; 114 | } else if (src.includes('template.')) { 115 | throw new Error('Uses distill template, but unknown version?!'); 116 | } 117 | } 118 | 119 | if (usesV2 === undefined) { 120 | throw new Error('Does not seem to use Distill template at all.'); 121 | } else { 122 | return usesV2; 123 | } 124 | } 125 | 126 | export { FrontMatter }; // TODO: removable? 127 | 128 | export const testing = { 129 | extractors: extractors, 130 | transforms: transforms, 131 | distillTransforms: distillTransforms 132 | }; 133 | -------------------------------------------------------------------------------- /src/components/d-interstitial.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from '../mixins/template'; 16 | 17 | // This overlay is not secure. 18 | // It is only meant as a social deterrent. 19 | 20 | const productionHostname = 'distill.pub'; 21 | const T = Template('d-interstitial', ` 22 | 113 | 114 |
    115 |
    116 |

    This article is in review.

    117 |

    Do not share this URL or the contents of this article. Thank you!

    118 | 119 |

    Enter the password we shared with you as part of the review process to view the article.

    120 |
    121 |
    122 | `); 123 | 124 | export class Interstitial extends T(HTMLElement) { 125 | 126 | connectedCallback() { 127 | if (this.shouldRemoveSelf()) { 128 | this.parentElement.removeChild(this); 129 | } else { 130 | const passwordInput = this.root.querySelector('#interstitial-password-input'); 131 | passwordInput.oninput = (event) => this.passwordChanged(event); 132 | } 133 | } 134 | 135 | passwordChanged(event) { 136 | const entered = event.target.value; 137 | if (entered === this.password) { 138 | console.log('Correct password entered.'); 139 | this.parentElement.removeChild(this); 140 | if (typeof(Storage) !== 'undefined') { 141 | console.log('Saved that correct password was entered.'); 142 | localStorage.setItem(this.localStorageIdentifier(), 'true'); 143 | } 144 | } 145 | } 146 | 147 | shouldRemoveSelf() { 148 | // should never be visible in production 149 | if (window && window.location.hostname === productionHostname) { 150 | console.warn('Interstitial found on production, hiding it.'); 151 | return true 152 | } 153 | // should only have to enter password once 154 | if (typeof(Storage) !== 'undefined') { 155 | if (localStorage.getItem(this.localStorageIdentifier()) === 'true') { 156 | console.log('Loaded that correct password was entered before; skipping interstitial.'); 157 | return true; 158 | } 159 | } 160 | // otherwise, leave visible 161 | return false; 162 | } 163 | 164 | localStorageIdentifier() { 165 | const prefix = 'distill-drafts' 166 | const suffix = 'interstitial-password-correct'; 167 | return prefix + (window ? window.location.pathname : '-') + suffix 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /src/components/d-cite.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Template } from "../mixins/template"; 16 | import { hover_cite, bibliography_cite } from "../helpers/citation"; 17 | 18 | const T = Template( 19 | "d-cite", 20 | ` 21 | 68 | 69 | 70 | 71 |
    72 | 73 |
    74 | ` 75 | ); 76 | 77 | export class Cite extends T(HTMLElement) { 78 | /* Lifecycle */ 79 | constructor() { 80 | super(); 81 | this._numbers = []; 82 | this._entries = []; 83 | } 84 | 85 | connectedCallback() { 86 | this.outerSpan = this.root.querySelector("#citation-"); 87 | this.innerSpan = this.root.querySelector(".citation-number"); 88 | this.hoverBox = this.root.querySelector("d-hover-box"); 89 | window.customElements.whenDefined("d-hover-box").then(() => { 90 | this.hoverBox.listen(this); 91 | }); 92 | // in case this component got connected after values were set 93 | if (this.numbers) { 94 | this.displayNumbers(this.numbers); 95 | } 96 | if (this.entries) { 97 | this.displayEntries(this.entries); 98 | } 99 | } 100 | 101 | //TODO This causes an infinite loop on firefox with polyfills. 102 | // This is only needed for interactive editing so no priority. 103 | // disconnectedCallback() { 104 | // const options = { detail: [this, this.keys], bubbles: true }; 105 | // const event = new CustomEvent('onCiteKeyRemoved', options); 106 | // document.dispatchEvent(event); 107 | // } 108 | 109 | /* observe 'key' attribute */ 110 | 111 | static get observedAttributes() { 112 | return ["key", "bibtex-key"]; 113 | } 114 | 115 | attributeChangedCallback(name, oldValue, newValue) { 116 | const eventName = oldValue ? "onCiteKeyChanged" : "onCiteKeyCreated"; 117 | const keys = newValue.split(",").map(k => k.trim()); 118 | const options = { detail: [this, keys], bubbles: true }; 119 | const event = new CustomEvent(eventName, options); 120 | document.dispatchEvent(event); 121 | } 122 | 123 | set key(value) { 124 | this.setAttribute("key", value); 125 | } 126 | 127 | get key() { 128 | return this.getAttribute("key") || this.getAttribute("bibtex-key"); 129 | } 130 | 131 | get keys() { 132 | const result = this.key.split(","); 133 | console.log(result); 134 | return result; 135 | } 136 | 137 | /* Setters & Rendering */ 138 | 139 | set numbers(numbers) { 140 | this._numbers = numbers; 141 | this.displayNumbers(numbers); 142 | } 143 | 144 | get numbers() { 145 | return this._numbers; 146 | } 147 | 148 | displayNumbers(numbers) { 149 | if (!this.innerSpan) return; 150 | const numberStrings = numbers.map(index => { 151 | return index == -1 ? "?" : index + 1 + ""; 152 | }); 153 | const textContent = "[" + numberStrings.join(", ") + "]"; 154 | this.innerSpan.textContent = textContent; 155 | } 156 | 157 | set entries(entries) { 158 | this._entries = entries; 159 | this.displayEntries(entries); 160 | } 161 | 162 | get entries() { 163 | return this._entries; 164 | } 165 | 166 | displayEntries(entries) { 167 | if (!this.hoverBox) return; 168 | this.hoverBox.innerHTML = `
      169 | ${entries 170 | .map(hover_cite) 171 | .map(html => `
    • ${html}
    • `) 172 | .join("\n")} 173 |
    `; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/components.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { Controller } from './controller'; 16 | import { domContentLoaded } from './helpers/domContentLoaded.js'; 17 | 18 | /* Transforms */ 19 | import { makeStyleTag } from './styles/styles'; 20 | import { Polyfills } from './helpers/polyfills'; 21 | 22 | /* Components */ 23 | import { Abstract } from './components/d-abstract'; 24 | import { Appendix } from './components/d-appendix'; 25 | import { Article } from './components/d-article'; 26 | import { Bibliography } from './components/d-bibliography'; 27 | import { Byline } from './components/d-byline'; 28 | import { Cite } from './components/d-cite'; 29 | import { CitationList } from './components/d-citation-list'; 30 | import { Code } from './components/d-code'; 31 | import { Footnote } from './components/d-footnote'; 32 | import { FootnoteList } from './components/d-footnote-list'; 33 | import { FrontMatter } from './components/d-front-matter'; 34 | import { HoverBox } from './components/d-hover-box'; 35 | import { Title } from './components/d-title'; 36 | import { DMath } from './components/d-math'; 37 | import { References } from './components/d-references'; 38 | import { TOC } from './components/d-toc'; 39 | import { Figure } from './components/d-figure'; 40 | import { Interstitial } from './components/d-interstitial'; 41 | import { Slider } from './ui/d-slider'; 42 | 43 | /* Distill website specific components */ 44 | import { DistillHeader } from './distill-components/distill-header'; 45 | import { DistillAppendix } from './distill-components/distill-appendix'; 46 | import { DistillFooter } from './distill-components/distill-footer'; 47 | 48 | let templateIsLoading = false; 49 | let runlevel = 0; 50 | const initialize = function() { 51 | if (window.distill.runlevel < 1) { 52 | throw new Error("Insufficient Runlevel for Distill Template!"); 53 | } 54 | 55 | /* 1. Flag that we're being loaded */ 56 | if ("distill" in window && window.distill.templateIsLoading) { 57 | throw new Error( 58 | "Runlevel 1: Distill Template is getting loaded more than once, aborting!" 59 | ); 60 | } else { 61 | window.distill.templateIsLoading = true; 62 | console.debug("Runlevel 1: Distill Template has started loading."); 63 | } 64 | 65 | /* 2. Add styles if they weren't added during prerendering */ 66 | makeStyleTag(document); 67 | console.debug("Runlevel 1: Static Distill styles have been added."); 68 | console.debug("Runlevel 1->2."); 69 | window.distill.runlevel += 1; 70 | 71 | /* 3. Register Controller listener functions */ 72 | /* Needs to happen before components to their connected callbacks have a controller to talk to. */ 73 | for (const [functionName, callback] of Object.entries(Controller.listeners)) { 74 | if (typeof callback === "function") { 75 | document.addEventListener(functionName, callback); 76 | } else { 77 | console.error("Runlevel 2: Controller listeners need to be functions!"); 78 | } 79 | } 80 | console.debug("Runlevel 2: We can now listen to controller events."); 81 | console.debug("Runlevel 2->3."); 82 | window.distill.runlevel += 1; 83 | 84 | /* 4. Register components */ 85 | const components = [ 86 | Abstract, Appendix, Article, Bibliography, Byline, Cite, CitationList, Code, 87 | Footnote, FootnoteList, FrontMatter, HoverBox, Title, DMath, References, TOC, Figure, 88 | Slider, Interstitial 89 | ]; 90 | 91 | const distillComponents = [DistillHeader, DistillAppendix, DistillFooter]; 92 | 93 | if (window.distill.runlevel < 2) { 94 | throw new Error("Insufficient Runlevel for adding custom elements!"); 95 | } 96 | const allComponents = components.concat(distillComponents); 97 | for (const component of allComponents) { 98 | console.debug("Runlevel 2: Registering custom element: " + component.is); 99 | customElements.define(component.is, component); 100 | } 101 | 102 | console.debug( 103 | "Runlevel 3: Distill Template finished registering custom elements." 104 | ); 105 | console.debug("Runlevel 3->4."); 106 | window.distill.runlevel += 1; 107 | 108 | // If template was added after DOMContentLoaded we may have missed that event. 109 | // Controller will check for that case, so trigger the event explicitly: 110 | if (domContentLoaded()) { 111 | Controller.listeners.DOMContentLoaded(); 112 | } 113 | 114 | console.debug("Runlevel 4: Distill Template initialisation complete."); 115 | window.distill.templateIsLoading = false; 116 | window.distill.templateHasLoaded = true; 117 | }; 118 | 119 | window.distill = { runlevel, initialize, templateIsLoading }; 120 | 121 | /* 0. Check browser feature support; synchronously polyfill if needed */ 122 | if (Polyfills.browserSupportsAllFeatures()) { 123 | console.debug("Runlevel 0: No need for polyfills."); 124 | console.debug("Runlevel 0->1."); 125 | window.distill.runlevel += 1; 126 | window.distill.initialize(); 127 | } else { 128 | console.debug("Runlevel 0: Distill Template is loading polyfills."); 129 | Polyfills.load(window.distill.initialize); 130 | } 131 | -------------------------------------------------------------------------------- /src/styles/d-article.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 The Distill Template Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | d-article { 18 | contain: layout style; 19 | overflow-x: hidden; 20 | border-top: 1px solid rgba(0, 0, 0, 0.1); 21 | padding-top: 2rem; 22 | color: rgba(0, 0, 0, 0.8); 23 | } 24 | 25 | d-article > * { 26 | grid-column: text; 27 | } 28 | 29 | @media(min-width: 768px) { 30 | d-article { 31 | font-size: 16px; 32 | } 33 | } 34 | 35 | @media(min-width: 1024px) { 36 | d-article { 37 | font-size: 1.06rem; 38 | line-height: 1.7em; 39 | } 40 | } 41 | 42 | 43 | /* H2 */ 44 | 45 | 46 | d-article .marker { 47 | text-decoration: none; 48 | border: none; 49 | counter-reset: section; 50 | grid-column: kicker; 51 | line-height: 1.7em; 52 | } 53 | 54 | d-article .marker:hover { 55 | border: none; 56 | } 57 | 58 | d-article .marker span { 59 | padding: 0 3px 4px; 60 | border-bottom: 1px solid rgba(0, 0, 0, 0.2); 61 | position: relative; 62 | top: 4px; 63 | } 64 | 65 | d-article .marker:hover span { 66 | color: rgba(0, 0, 0, 0.7); 67 | border-bottom: 1px solid rgba(0, 0, 0, 0.7); 68 | } 69 | 70 | d-article h2 { 71 | font-weight: 600; 72 | font-size: 24px; 73 | line-height: 1.25em; 74 | margin: 2rem 0 1.5rem 0; 75 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 76 | padding-bottom: 1rem; 77 | } 78 | 79 | @media(min-width: 1024px) { 80 | d-article h2 { 81 | font-size: 36px; 82 | } 83 | } 84 | 85 | /* H3 */ 86 | 87 | d-article h3 { 88 | font-weight: 700; 89 | font-size: 18px; 90 | line-height: 1.4em; 91 | margin-bottom: 1em; 92 | margin-top: 2em; 93 | } 94 | 95 | @media(min-width: 1024px) { 96 | d-article h3 { 97 | font-size: 20px; 98 | } 99 | } 100 | 101 | /* H4 */ 102 | 103 | d-article h4 { 104 | font-weight: 600; 105 | text-transform: uppercase; 106 | font-size: 14px; 107 | line-height: 1.4em; 108 | } 109 | 110 | d-article a { 111 | color: inherit; 112 | } 113 | 114 | d-article p, 115 | d-article ul, 116 | d-article ol, 117 | d-article blockquote { 118 | margin-top: 0; 119 | margin-bottom: 1em; 120 | margin-left: 0; 121 | margin-right: 0; 122 | } 123 | 124 | d-article blockquote { 125 | border-left: 2px solid rgba(0, 0, 0, 0.2); 126 | padding-left: 2em; 127 | font-style: italic; 128 | color: rgba(0, 0, 0, 0.6); 129 | } 130 | 131 | d-article a { 132 | border-bottom: 1px solid rgba(0, 0, 0, 0.4); 133 | text-decoration: none; 134 | } 135 | 136 | d-article a:hover { 137 | border-bottom: 1px solid rgba(0, 0, 0, 0.8); 138 | } 139 | 140 | d-article .link { 141 | text-decoration: underline; 142 | cursor: pointer; 143 | } 144 | 145 | d-article ul, 146 | d-article ol { 147 | padding-left: 24px; 148 | } 149 | 150 | d-article li { 151 | margin-bottom: 1em; 152 | margin-left: 0; 153 | padding-left: 0; 154 | } 155 | 156 | d-article li:last-child { 157 | margin-bottom: 0; 158 | } 159 | 160 | d-article pre { 161 | font-size: 14px; 162 | margin-bottom: 20px; 163 | } 164 | 165 | d-article hr { 166 | grid-column: screen; 167 | width: 100%; 168 | border: none; 169 | border-bottom: 1px solid rgba(0, 0, 0, 0.1); 170 | margin-top: 60px; 171 | margin-bottom: 60px; 172 | } 173 | 174 | d-article section { 175 | margin-top: 60px; 176 | margin-bottom: 60px; 177 | } 178 | 179 | d-article span.equation-mimic { 180 | font-family: georgia; 181 | font-size: 115%; 182 | font-style: italic; 183 | } 184 | 185 | d-article > d-code, 186 | d-article section > d-code { 187 | display: block; 188 | } 189 | 190 | d-article > d-math[block], 191 | d-article section > d-math[block] { 192 | display: block; 193 | } 194 | 195 | @media (max-width: 768px) { 196 | d-article > d-code, 197 | d-article section > d-code, 198 | d-article > d-math[block], 199 | d-article section > d-math[block] { 200 | overflow-x: scroll; 201 | -ms-overflow-style: none; // IE 10+ 202 | overflow: -moz-scrollbars-none; // Firefox 203 | } 204 | 205 | d-article > d-code::-webkit-scrollbar, 206 | d-article section > d-code::-webkit-scrollbar, 207 | d-article > d-math[block]::-webkit-scrollbar, 208 | d-article section > d-math[block]::-webkit-scrollbar { 209 | display: none; // Safari and Chrome 210 | } 211 | } 212 | 213 | d-article .citation { 214 | color: #668; 215 | cursor: pointer; 216 | } 217 | 218 | d-include { 219 | width: auto; 220 | display: block; 221 | } 222 | 223 | d-figure { 224 | contain: layout style; 225 | } 226 | 227 | /* KaTeX */ 228 | 229 | .katex, .katex-prerendered { 230 | contain: style; 231 | display: inline-block; 232 | } 233 | 234 | /* Tables */ 235 | 236 | d-article table { 237 | border-collapse: collapse; 238 | margin-bottom: 1.5rem; 239 | border-bottom: 1px solid rgba(0, 0, 0, 0.2); 240 | } 241 | 242 | d-article table th { 243 | border-bottom: 1px solid rgba(0, 0, 0, 0.2); 244 | } 245 | 246 | d-article table td { 247 | border-bottom: 1px solid rgba(0, 0, 0, 0.05); 248 | } 249 | 250 | d-article table tr:last-of-type td { 251 | border-bottom: none; 252 | } 253 | 254 | d-article table th, 255 | d-article table td { 256 | font-size: 15px; 257 | padding: 2px 8px; 258 | } 259 | 260 | d-article table tbody :first-child td { 261 | padding-top: 2px; 262 | } 263 | -------------------------------------------------------------------------------- /src/transforms/generate-crossref.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //import xml from "xml"; 16 | 17 | export default function(data) { 18 | 19 | 20 | var date = data.publishedDate; 21 | 22 | var batch_timestamp = Math.floor(Date.now() / 1000); 23 | var batch_id = data.authors.length ? data.authors[0].lastName.toLowerCase().slice(0,20) : 'Anonymous'; 24 | batch_id += '_' + date.getFullYear(); 25 | batch_id += '_' + data.title.split(' ')[0].toLowerCase().slice(0,20) + '_' + batch_timestamp; 26 | // generate XML 27 | var crf_data = 28 | {doi_batch : [ 29 | 30 | { _attr: { 31 | version: '4.3.7', 32 | xmlns: 'http://www.crossref.org/schema/4.3.7', 33 | 'xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance', 34 | 'xsi:schemaLocation': 'http://www.crossref.org/schema/4.3.7 http://www.crossref.org/schemas/crossref4.3.7.xsd', 35 | }}, 36 | 37 | { head: [ 38 | {doi_batch_id: batch_id}, 39 | {timestamp: batch_timestamp}, 40 | {depositor: [ 41 | {depositor_name: data.journal.publisherName}, 42 | {email_address: data.journal.publisherEmail}, 43 | ]}, 44 | {registrant: data.journal.publisherName}, 45 | ]}, 46 | 47 | {body: [ 48 | {journal: [ 49 | 50 | {journal_metadata: [ 51 | {full_title: data.journal.full_title || data.journal.title}, 52 | {abbrev_title: data.journal.abbrev_title || data.journal.title || data.journal.full_title}, 53 | {issn: data.journal.issn}, 54 | {doi_data: [ 55 | {doi: data.journal.doi}, 56 | {resource: data.journal.url}, 57 | ]}, 58 | ]}, 59 | 60 | {journal_issue: [ 61 | {publication_date: [ 62 | {month: date.getMonth()+1}, 63 | {year: date.getFullYear()}, 64 | ]}, 65 | {journal_volume: [ 66 | {volume: data.volume}, 67 | ]}, 68 | {issue: data.issue}, 69 | ]}, 70 | 71 | {journal_article: [ 72 | {titles: [ 73 | {title: data.title}, 74 | ]}, 75 | { contributors: 76 | data.authors.map((author, ind) => ( 77 | {person_name: [ 78 | { _attr: { 79 | contributor_role: 'author', 80 | sequence: (ind == 0)? 'first' : 'additional' 81 | }}, 82 | {given_name: author.firstName}, 83 | {surname: author.lastName}, 84 | {affiliation: author.affiliation} 85 | // TODO: ORCID? 86 | ]} 87 | )) 88 | }, 89 | {publication_date: [ 90 | {month: date.getMonth()+1}, 91 | {day: date.getDate()}, 92 | {year: date.getFullYear()} 93 | ]}, 94 | { publisher_item: [ 95 | {item_number: data.doi} 96 | ]}, 97 | {doi_data: [ 98 | {doi: data.doi}, 99 | //{timestamp: ""}, 100 | {resource: data.url}, 101 | ]}, 102 | {citation_list: 103 | data.citations.map(key => 104 | citation_xml(key, data.bibliography[key])) 105 | } 106 | 107 | ]}, 108 | 109 | ]}, 110 | ]}, 111 | ]}; 112 | 113 | return xml(crf_data); 114 | } 115 | 116 | function citation_xml(key, ent){ 117 | if (ent == undefined) return {}; 118 | var info = []; 119 | info.push({_attr: {key: key}}); 120 | if ('title' in ent) 121 | info.push({article_title: ent.title}); 122 | if ('author' in ent) 123 | info.push({author: ent.author.split(' and ')[0].split(',')[0].trim()}); 124 | if ('journal' in ent) 125 | info.push({journal_title: ent.journal}); 126 | if ('booktitle' in ent) 127 | info.push({volume_title: ent.booktitle}); 128 | if ('volume' in ent) 129 | info.push({volume: ent.volume}); 130 | if ('issue' in ent) 131 | info.push({issue: ent.issue}); 132 | if ('doi' in ent) 133 | info.push({doi: ent.doi}); 134 | return {citation: info}; 135 | } 136 | 137 | function xml(obj) { 138 | //console.log(typeof(obj), obj) 139 | if (typeof obj === 'string') return obj; 140 | if (typeof obj === 'number') return ''+obj; 141 | let keys = Object.keys(obj); 142 | if (keys.length != 1) console.error('can\'t interpret ', obj, 'as xml'); 143 | let name = keys[0]; 144 | var full_content = obj[name]; 145 | var attr = {}; 146 | if (Array.isArray(full_content)){ 147 | var content = []; 148 | for (var i in full_content) { 149 | var obj = full_content[i]; 150 | var obj_name = Object.keys(obj)[0]; 151 | if ('_attr' == obj_name) { 152 | attr = obj['_attr']; 153 | } else { 154 | //console.log(Object.keys(obj)[0]) 155 | content.push(obj); 156 | } 157 | } 158 | } else { 159 | content = full_content; 160 | } 161 | if (content == undefined){ 162 | content = 'undefined'; 163 | } 164 | 165 | let attr_string = ''; 166 | for (var k in attr) { 167 | attr_string += ` ${k}=\"${attr[k]}\"`; 168 | } 169 | 170 | //console.log(typeof content, Array.isArray(content), content instanceof String, content) 171 | if (Array.isArray(content)){ 172 | content = content.map(xml); 173 | content = content.join('\n').split('\n'); 174 | content = content.map(s => ' ' + s).join('\n'); 175 | var result = `<${name}${attr_string}>\n${content}\n`; 176 | } else { 177 | content = xml(content); 178 | var result = `<${name}${attr_string}>${content}`; 179 | } 180 | return result; 181 | } 182 | -------------------------------------------------------------------------------- /src/components/d-figure.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Figure 16 | // 17 | // d-figure provides a state-machine of visibility events: 18 | // 19 | // scroll out of view 20 | // +----------------+ 21 | // *do work here* | | 22 | // +----------------+ +-+---------+ +-v---------+ 23 | // | ready +----> onscreen | | offscreen | 24 | // +----------------+ +---------^-+ +---------+-+ 25 | // | | 26 | // +----------------+ 27 | // scroll into view 28 | // 29 | 30 | export class Figure extends HTMLElement { 31 | 32 | static get is() { return 'd-figure'; } 33 | 34 | static get readyQueue() { 35 | if (!Figure._readyQueue) { 36 | Figure._readyQueue = []; 37 | } 38 | return Figure._readyQueue; 39 | } 40 | 41 | static addToReadyQueue(figure) { 42 | if (Figure.readyQueue.indexOf(figure) === -1) { 43 | Figure.readyQueue.push(figure); 44 | Figure.runReadyQueue(); 45 | } 46 | } 47 | 48 | static runReadyQueue() { 49 | // console.log("Checking to run readyQueue, length: " + Figure.readyQueue.length + ", scrolling: " + Figure.isScrolling); 50 | // if (Figure.isScrolling) return; 51 | // console.log("Running ready Queue"); 52 | const figure = Figure.readyQueue 53 | .sort((a,b) => a._seenOnScreen - b._seenOnScreen ) 54 | .filter((figure) => !figure._ready) 55 | .pop(); 56 | if (figure) { 57 | figure.ready(); 58 | requestAnimationFrame(Figure.runReadyQueue); 59 | } 60 | 61 | } 62 | 63 | constructor() { 64 | super(); 65 | // debugger 66 | this._ready = false; 67 | this._onscreen = false; 68 | this._offscreen = true; 69 | } 70 | 71 | connectedCallback() { 72 | this.loadsWhileScrolling = this.hasAttribute('loadsWhileScrolling'); 73 | Figure.marginObserver.observe(this); 74 | Figure.directObserver.observe(this); 75 | } 76 | 77 | disconnectedCallback() { 78 | Figure.marginObserver.unobserve(this); 79 | Figure.directObserver.unobserve(this); 80 | } 81 | 82 | // We use two separate observers: 83 | // One with an extra 1000px margin to warn if the viewpoint gets close, 84 | // And one for the actual on/off screen events 85 | 86 | static get marginObserver() { 87 | if (!Figure._marginObserver) { 88 | // if (!('IntersectionObserver' in window)) { 89 | // throw new Error('no interscetionobbserver!'); 90 | // } 91 | const viewportHeight = window.innerHeight; 92 | const margin = Math.floor(2 * viewportHeight); 93 | const options = {rootMargin: margin + 'px 0px ' + margin + 'px 0px', threshold: 0.01}; 94 | const callback = Figure.didObserveMarginIntersection; 95 | const observer = new IntersectionObserver(callback, options); 96 | Figure._marginObserver = observer; 97 | } 98 | return Figure._marginObserver; 99 | } 100 | 101 | static didObserveMarginIntersection(entries) { 102 | for (const entry of entries) { 103 | const figure = entry.target; 104 | if (entry.isIntersecting && !figure._ready) { 105 | Figure.addToReadyQueue(figure); 106 | } 107 | } 108 | } 109 | 110 | static get directObserver() { 111 | if (!Figure._directObserver) { 112 | Figure._directObserver = new IntersectionObserver( 113 | Figure.didObserveDirectIntersection, { 114 | rootMargin: '0px', threshold: [0, 1.0], 115 | } 116 | ); 117 | } 118 | return Figure._directObserver; 119 | } 120 | 121 | static didObserveDirectIntersection(entries) { 122 | for (const entry of entries) { 123 | const figure = entry.target; 124 | if (entry.isIntersecting) { 125 | figure._seenOnScreen = new Date(); 126 | // if (!figure._ready) { figure.ready(); } 127 | if (figure._offscreen) { figure.onscreen(); } 128 | } else { 129 | if (figure._onscreen) { figure.offscreen(); } 130 | } 131 | } 132 | } 133 | 134 | // Notify listeners that registered late, too: 135 | 136 | addEventListener(eventName, callback) { 137 | super.addEventListener(eventName, callback); 138 | // if we had already dispatched something while presumingly no one was listening, we do so again 139 | // debugger 140 | if (eventName === 'ready') { 141 | if (Figure.readyQueue.indexOf(this) !== -1) { 142 | this._ready = false; 143 | Figure.runReadyQueue(); 144 | } 145 | } 146 | if (eventName === 'onscreen') { 147 | this.onscreen(); 148 | } 149 | } 150 | 151 | // Custom Events 152 | 153 | ready() { 154 | // debugger 155 | this._ready = true; 156 | Figure.marginObserver.unobserve(this); 157 | const event = new CustomEvent('ready'); 158 | this.dispatchEvent(event); 159 | } 160 | 161 | onscreen() { 162 | this._onscreen = true; 163 | this._offscreen = false; 164 | const event = new CustomEvent('onscreen'); 165 | this.dispatchEvent(event); 166 | } 167 | 168 | offscreen() { 169 | this._onscreen = false; 170 | this._offscreen = true; 171 | const event = new CustomEvent('offscreen'); 172 | this.dispatchEvent(event); 173 | } 174 | 175 | } 176 | 177 | if (typeof window !== 'undefined') { 178 | 179 | Figure.isScrolling = false; 180 | let timeout; 181 | const resetTimer = () => { 182 | Figure.isScrolling = true; 183 | clearTimeout(timeout); 184 | timeout = setTimeout(() => { 185 | Figure.isScrolling = false; 186 | Figure.runReadyQueue(); 187 | }, 500); 188 | }; 189 | window.addEventListener('scroll', resetTimer, true); 190 | 191 | } 192 | -------------------------------------------------------------------------------- /src/helpers/citation.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | export function collect_citations(dom = document) { 16 | const citations = new Set(); 17 | const citeTags = dom.querySelectorAll("d-cite"); 18 | for (const tag of citeTags) { 19 | const keyString = tag.getAttribute("key") || tag.getAttribute("bibtex-key"); 20 | const keys = keyString.split(",").map(k => k.trim()); 21 | for (const key of keys) { 22 | citations.add(key); 23 | } 24 | } 25 | return [...citations]; 26 | } 27 | 28 | export function inline_cite_short(keys) { 29 | function cite_string(key) { 30 | if (key in data.bibliography) { 31 | var n = data.citations.indexOf(key) + 1; 32 | return "" + n; 33 | } else { 34 | return "?"; 35 | } 36 | } 37 | return "[" + keys.map(cite_string).join(", ") + "]"; 38 | } 39 | 40 | export function inline_cite_long(keys) { 41 | function cite_string(key) { 42 | if (key in data.bibliography) { 43 | var ent = data.bibliography[key]; 44 | var names = ent.author.split(" and "); 45 | names = names.map(name => name.split(",")[0].trim()); 46 | var year = ent.year; 47 | if (names.length == 1) return names[0] + ", " + year; 48 | if (names.length == 2) return names[0] + " & " + names[1] + ", " + year; 49 | if (names.length > 2) return names[0] + ", et al., " + year; 50 | } else { 51 | return "?"; 52 | } 53 | } 54 | return keys.map(cite_string).join(", "); 55 | } 56 | 57 | function author_string(ent, template, sep, finalSep) { 58 | if (ent.author == null) { 59 | return ""; 60 | } 61 | var names = ent.author.split(" and "); 62 | let name_strings = names.map(name => { 63 | name = name.trim(); 64 | if (name.indexOf(",") != -1) { 65 | var last = name.split(",")[0].trim(); 66 | var firsts = name.split(",")[1]; 67 | } else if (name.indexOf(" ") != -1) { 68 | var last = name 69 | .split(" ") 70 | .slice(-1)[0] 71 | .trim(); 72 | var firsts = name 73 | .split(" ") 74 | .slice(0, -1) 75 | .join(" "); 76 | } else { 77 | var last = name.trim(); 78 | } 79 | var initials = ""; 80 | if (firsts != undefined) { 81 | initials = firsts 82 | .trim() 83 | .split(" ") 84 | .map(s => s.trim()[0]); 85 | initials = initials.join(".") + "."; 86 | } 87 | return template 88 | .replace("${F}", firsts) 89 | .replace("${L}", last) 90 | .replace("${I}", initials) 91 | .trim(); // in case one of first or last was empty 92 | }); 93 | if (names.length > 1) { 94 | var str = name_strings.slice(0, names.length - 1).join(sep); 95 | str += (finalSep || sep) + name_strings[names.length - 1]; 96 | return str; 97 | } else { 98 | return name_strings[0]; 99 | } 100 | } 101 | 102 | function venue_string(ent) { 103 | var cite = ent.journal || ent.booktitle || ""; 104 | if ("volume" in ent) { 105 | var issue = ent.issue || ent.number; 106 | issue = issue != undefined ? "(" + issue + ")" : ""; 107 | cite += ", Vol " + ent.volume + issue; 108 | } 109 | if ("pages" in ent) { 110 | cite += ", pp. " + ent.pages; 111 | } 112 | if (cite != "") cite += ". "; 113 | if ("publisher" in ent) { 114 | cite += ent.publisher; 115 | if (cite[cite.length - 1] != ".") cite += "."; 116 | } 117 | return cite; 118 | } 119 | 120 | function link_string(ent) { 121 | if ("url" in ent) { 122 | var url = ent.url; 123 | var arxiv_match = /arxiv\.org\/abs\/([0-9\.]*)/.exec(url); 124 | if (arxiv_match != null) { 125 | url = `http://arxiv.org/pdf/${arxiv_match[1]}.pdf`; 126 | } 127 | 128 | if (url.slice(-4) == ".pdf") { 129 | var label = "PDF"; 130 | } else if (url.slice(-5) == ".html") { 131 | var label = "HTML"; 132 | } 133 | return `  [${label || "link"}]`; 134 | } /* else if ("doi" in ent){ 135 | return `  [DOI]`; 136 | }*/ else { 137 | return ""; 138 | } 139 | } 140 | function doi_string(ent, new_line) { 141 | if ("doi" in ent) { 142 | return `${new_line ? "
    " : ""} DOI: ${ent.doi}`; 145 | } else { 146 | return ""; 147 | } 148 | } 149 | 150 | function title_string(ent) { 151 | return '' + ent.title + " "; 152 | } 153 | 154 | export function bibliography_cite(ent, fancy) { 155 | if (ent) { 156 | var cite = title_string(ent); 157 | cite += link_string(ent) + "
    "; 158 | if (ent.author) { 159 | cite += author_string(ent, "${L}, ${I}", ", ", " and "); 160 | if (ent.year || ent.date) { 161 | cite += ", "; 162 | } 163 | } 164 | if (ent.year || ent.date) { 165 | cite += (ent.year || ent.date) + ". "; 166 | } else { 167 | cite += ". "; 168 | } 169 | cite += venue_string(ent); 170 | cite += doi_string(ent); 171 | return cite; 172 | /*var cite = author_string(ent, "${L}, ${I}", ", ", " and "); 173 | if (ent.year || ent.date){ 174 | cite += ", " + (ent.year || ent.date) + ". " 175 | } else { 176 | cite += ". " 177 | } 178 | cite += "" + ent.title + ". "; 179 | cite += venue_string(ent); 180 | cite += doi_string(ent); 181 | cite += link_string(ent); 182 | return cite*/ 183 | } else { 184 | return "?"; 185 | } 186 | } 187 | 188 | export function hover_cite(ent) { 189 | if (ent) { 190 | var cite = ""; 191 | cite += "" + ent.title + ""; 192 | cite += link_string(ent); 193 | cite += "
    "; 194 | 195 | var a_str = author_string(ent, "${I} ${L}", ", ") + "."; 196 | var v_str = 197 | venue_string(ent).trim() + " " + ent.year + ". " + doi_string(ent, true); 198 | 199 | if ((a_str + v_str).length < Math.min(40, ent.title.length)) { 200 | cite += a_str + " " + v_str; 201 | } else { 202 | cite += a_str + "
    " + v_str; 203 | } 204 | return cite; 205 | } else { 206 | return "?"; 207 | } 208 | } 209 | 210 | //https://scholar.google.com/scholar?q=allintitle%3ADocument+author%3Aolah 211 | function get_GS_URL(ent) { 212 | if (ent) { 213 | var names = ent.author.split(" and "); 214 | names = names.map(name => name.split(",")[0].trim()); 215 | var title = ent.title.split(" "); //.replace(/[,:]/, "") 216 | var url = "http://search.labs.crossref.org/dois?"; //""https://scholar.google.com/scholar?" 217 | url += uris({ q: names.join(" ") + " " + title.join(" ") }); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/transforms/meta.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // TODO: rewrite as template to make order dependencies easier 16 | 17 | import favicon from '../assets/distill-favicon.base64'; 18 | import escape from 'escape-html'; 19 | 20 | export default function(dom, data) { 21 | let head = dom.querySelector('head'); 22 | let appendHead = html => appendHtml(head, html); 23 | 24 | function meta(name, content, force) { 25 | if (content || force) 26 | appendHead(` \n`); 27 | } 28 | 29 | appendHead(` 30 | 31 | 32 | 33 | `); 34 | 35 | if (data.title) { 36 | appendHead(` 37 | ${escape(data.title)} 38 | `); 39 | } 40 | 41 | if (data.url) { 42 | appendHead(` 43 | 44 | `); 45 | } 46 | 47 | 48 | if (data.publishedDate){ 49 | appendHead(` 50 | 51 | 52 | 53 | 54 | `); 55 | } 56 | 57 | if (data.updatedDate) { 58 | appendHead(` 59 | 60 | `); 61 | } 62 | 63 | (data.authors || []).forEach((a) => { 64 | appendHtml(head, ` 65 | `); 66 | }); 67 | 68 | appendHead(` 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | `); 78 | 79 | appendHead(` 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | `); 89 | 90 | // if this is a proprer article, generate Google Scholar meta data 91 | if (data.doiSuffix){ 92 | appendHead(` 93 | \n`); 94 | 95 | meta('citation_title', data.title); 96 | meta('citation_fulltext_html_url', data.url); 97 | meta('citation_volume', data.volume); 98 | meta('citation_issue', data.issue); 99 | meta('citation_firstpage', data.doiSuffix ? `e${data.doiSuffix}` : undefined); 100 | meta('citation_doi', data.doi); 101 | 102 | let journal = data.journal || {}; 103 | meta('citation_journal_title', journal.full_title || journal.title); 104 | meta('citation_journal_abbrev', journal.abbrev_title); 105 | meta('citation_issn', journal.issn); 106 | meta('citation_publisher', journal.publisher); 107 | meta('citation_fulltext_world_readable', '', true); 108 | 109 | if (data.publishedDate){ 110 | meta('citation_online_date', `${data.publishedYear}/${data.publishedMonthPadded}/${data.publishedDayPadded}`); 111 | meta('citation_publication_date', `${data.publishedYear}/${data.publishedMonthPadded}/${data.publishedDayPadded}`); 112 | } 113 | 114 | (data.authors || []).forEach((a) => { 115 | meta('citation_author', `${a.lastName}, ${a.firstName}`); 116 | meta('citation_author_institution', a.affiliation); 117 | }); 118 | } else { 119 | console.warn('No DOI suffix in data; not adding citation meta tags!'); 120 | } 121 | 122 | if (data.citations) { 123 | data.citations.forEach(key => { 124 | if (data.bibliography && data.bibliography.has(key)) { 125 | const entry = data.bibliography.get(key); 126 | meta('citation_reference', citation_meta_content(entry) ); 127 | } else { 128 | console.warn('No bibliography data found for ' + key); 129 | } 130 | }); 131 | } else { 132 | console.warn('No citations found; not adding any references meta tags!'); 133 | } 134 | } 135 | 136 | function appendHtml(el, html) { 137 | el.innerHTML += html; 138 | } 139 | 140 | function citation_meta_content(ref){ 141 | var content = `citation_title=${ref.title};`; 142 | 143 | if (ref.author && ref.author !== '') { 144 | ref.author.split(' and ').forEach(name => { 145 | name = name.trim(); 146 | let last, firsts; 147 | if (name.indexOf(',') != -1){ 148 | last = name.split(',')[0].trim(); 149 | firsts = name.split(',')[1].trim(); 150 | } else { 151 | last = name.split(' ').slice(-1)[0].trim(); 152 | firsts = name.split(' ').slice(0,-1).join(' '); 153 | } 154 | content += `citation_author=${firsts} ${last};`; 155 | }); 156 | } 157 | 158 | if ('year' in ref) { 159 | content += `citation_publication_date=${ref.year};`; 160 | } 161 | 162 | // Special test for arxiv 163 | let arxiv_id_search = /https?:\/\/arxiv\.org\/pdf\/([0-9]*\.[0-9]*)\.pdf/.exec(ref.url); 164 | arxiv_id_search = arxiv_id_search || /https?:\/\/arxiv\.org\/abs\/([0-9]*\.[0-9]*)/.exec(ref.url); 165 | arxiv_id_search = arxiv_id_search || /arXiv preprint arXiv:([0-9]*\.[0-9]*)/.exec(ref.journal); 166 | if (arxiv_id_search && arxiv_id_search[1]){ 167 | content += `citation_arxiv_id=${arxiv_id_search[1]};`; 168 | return content; // arXiv is not considered a journal, so we don't need journal/volume/issue 169 | } 170 | if ('journal' in ref){ 171 | content += `citation_journal_title=${escape(ref.journal)};`; 172 | } 173 | if ('volume' in ref) { 174 | content += `citation_volume=${escape(ref.volume)};`; 175 | } 176 | if ('issue' in ref || 'number' in ref){ 177 | content += `citation_number=${escape(ref.issue || ref.number)};`; 178 | } 179 | return content; 180 | } 181 | -------------------------------------------------------------------------------- /src/helpers/katex-auto-render.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // This is a straight concatenation of code from KaTeX's contrib folder, 16 | // but we aren't using some of their helpers that don't work well outside a browser environment. 17 | 18 | /*global katex */ 19 | 20 | const findEndOfMath = function(delimiter, text, startIndex) { 21 | // Adapted from 22 | // https://github.com/Khan/perseus/blob/master/src/perseus-markdown.jsx 23 | let index = startIndex; 24 | let braceLevel = 0; 25 | 26 | const delimLength = delimiter.length; 27 | 28 | while (index < text.length) { 29 | const character = text[index]; 30 | 31 | if ( 32 | braceLevel <= 0 && 33 | text.slice(index, index + delimLength) === delimiter 34 | ) { 35 | return index; 36 | } else if (character === "\\") { 37 | index++; 38 | } else if (character === "{") { 39 | braceLevel++; 40 | } else if (character === "}") { 41 | braceLevel--; 42 | } 43 | 44 | index++; 45 | } 46 | 47 | return -1; 48 | }; 49 | 50 | const splitAtDelimiters = function(startData, leftDelim, rightDelim, display) { 51 | const finalData = []; 52 | 53 | for (let i = 0; i < startData.length; i++) { 54 | if (startData[i].type === "text") { 55 | const text = startData[i].data; 56 | 57 | let lookingForLeft = true; 58 | let currIndex = 0; 59 | let nextIndex; 60 | 61 | nextIndex = text.indexOf(leftDelim); 62 | if (nextIndex !== -1) { 63 | currIndex = nextIndex; 64 | finalData.push({ 65 | type: "text", 66 | data: text.slice(0, currIndex) 67 | }); 68 | lookingForLeft = false; 69 | } 70 | 71 | while (true) { 72 | // eslint-disable-line no-constant-condition 73 | if (lookingForLeft) { 74 | nextIndex = text.indexOf(leftDelim, currIndex); 75 | if (nextIndex === -1) { 76 | break; 77 | } 78 | 79 | finalData.push({ 80 | type: "text", 81 | data: text.slice(currIndex, nextIndex) 82 | }); 83 | 84 | currIndex = nextIndex; 85 | } else { 86 | nextIndex = findEndOfMath( 87 | rightDelim, 88 | text, 89 | currIndex + leftDelim.length 90 | ); 91 | if (nextIndex === -1) { 92 | break; 93 | } 94 | 95 | finalData.push({ 96 | type: "math", 97 | data: text.slice(currIndex + leftDelim.length, nextIndex), 98 | rawData: text.slice(currIndex, nextIndex + rightDelim.length), 99 | display: display 100 | }); 101 | 102 | currIndex = nextIndex + rightDelim.length; 103 | } 104 | 105 | lookingForLeft = !lookingForLeft; 106 | } 107 | 108 | finalData.push({ 109 | type: "text", 110 | data: text.slice(currIndex) 111 | }); 112 | } else { 113 | finalData.push(startData[i]); 114 | } 115 | } 116 | 117 | return finalData; 118 | }; 119 | 120 | const splitWithDelimiters = function(text, delimiters) { 121 | let data = [{ type: "text", data: text }]; 122 | for (let i = 0; i < delimiters.length; i++) { 123 | const delimiter = delimiters[i]; 124 | data = splitAtDelimiters( 125 | data, 126 | delimiter.left, 127 | delimiter.right, 128 | delimiter.display || false 129 | ); 130 | } 131 | return data; 132 | }; 133 | 134 | /* Note: optionsCopy is mutated by this method. If it is ever exposed in the 135 | * API, we should copy it before mutating. 136 | */ 137 | const renderMathInText = function(text, optionsCopy) { 138 | const data = splitWithDelimiters(text, optionsCopy.delimiters); 139 | const fragment = document.createDocumentFragment(); 140 | 141 | for (let i = 0; i < data.length; i++) { 142 | if (data[i].type === "text") { 143 | fragment.appendChild(document.createTextNode(data[i].data)); 144 | } else { 145 | const tag = document.createElement("d-math"); 146 | const math = data[i].data; 147 | // Override any display mode defined in the settings with that 148 | // defined by the text itself 149 | optionsCopy.displayMode = data[i].display; 150 | try { 151 | tag.textContent = math; 152 | if (optionsCopy.displayMode) { 153 | tag.setAttribute("block", ""); 154 | } 155 | } catch (e) { 156 | if (!(e instanceof katex.ParseError)) { 157 | throw e; 158 | } 159 | optionsCopy.errorCallback( 160 | "KaTeX auto-render: Failed to parse `" + data[i].data + "` with ", 161 | e 162 | ); 163 | fragment.appendChild(document.createTextNode(data[i].rawData)); 164 | continue; 165 | } 166 | fragment.appendChild(tag); 167 | } 168 | } 169 | 170 | return fragment; 171 | }; 172 | 173 | const renderElem = function(elem, optionsCopy) { 174 | for (let i = 0; i < elem.childNodes.length; i++) { 175 | const childNode = elem.childNodes[i]; 176 | if (childNode.nodeType === 3) { 177 | // Text node 178 | const text = childNode.textContent; 179 | if (optionsCopy.mightHaveMath(text)) { 180 | const frag = renderMathInText(text, optionsCopy); 181 | i += frag.childNodes.length - 1; 182 | elem.replaceChild(frag, childNode); 183 | } 184 | } else if (childNode.nodeType === 1) { 185 | // Element node 186 | const shouldRender = 187 | optionsCopy.ignoredTags.indexOf(childNode.nodeName.toLowerCase()) === 188 | -1; 189 | 190 | if (shouldRender) { 191 | renderElem(childNode, optionsCopy); 192 | } 193 | } 194 | // Otherwise, it's something else, and ignore it. 195 | } 196 | }; 197 | 198 | const defaultAutoRenderOptions = { 199 | delimiters: [ 200 | { left: "$$", right: "$$", display: true }, 201 | { left: "\\[", right: "\\]", display: true }, 202 | { left: "\\(", right: "\\)", display: false } 203 | // LaTeX uses this, but it ruins the display of normal `$` in text: 204 | // {left: '$', right: '$', display: false}, 205 | ], 206 | 207 | ignoredTags: [ 208 | "script", 209 | "noscript", 210 | "style", 211 | "textarea", 212 | "pre", 213 | "code", 214 | "svg" 215 | ], 216 | 217 | errorCallback: function(msg, err) { 218 | console.error(msg, err); 219 | } 220 | }; 221 | 222 | export const renderMathInElement = function(elem, options) { 223 | if (!elem) { 224 | throw new Error("No element provided to render"); 225 | } 226 | 227 | const optionsCopy = Object.assign({}, defaultAutoRenderOptions, options); 228 | const delimiterStrings = optionsCopy.delimiters.flatMap(d => [ 229 | d.left, 230 | d.right 231 | ]); 232 | const mightHaveMath = text => 233 | delimiterStrings.some(d => text.indexOf(d) !== -1); 234 | optionsCopy.mightHaveMath = mightHaveMath; 235 | renderElem(elem, optionsCopy); 236 | }; 237 | -------------------------------------------------------------------------------- /src/controller.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Distill Template Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import { FrontMatter, mergeFromYMLFrontmatter } from "./front-matter"; 16 | import { DMath } from "./components/d-math"; 17 | import { collect_citations } from "./helpers/citation.js"; 18 | import { domContentLoaded } from "./helpers/domContentLoaded.js"; 19 | import { parseFrontmatter } from "./components/d-front-matter"; 20 | import optionalComponents from "./transforms/optional-components"; 21 | 22 | const frontMatter = new FrontMatter(); 23 | 24 | export const Controller = { 25 | frontMatter: frontMatter, 26 | waitingOn: { 27 | bibliography: [], 28 | citations: [] 29 | }, 30 | listeners: { 31 | onCiteKeyCreated(event) { 32 | const [citeTag, keys] = event.detail; 33 | 34 | // ensure we have citations 35 | if (!frontMatter.citationsCollected) { 36 | // console.debug('onCiteKeyCreated, but unresolved dependency ("citations"). Enqueing.'); 37 | Controller.waitingOn.citations.push(() => 38 | Controller.listeners.onCiteKeyCreated(event) 39 | ); 40 | return; 41 | } 42 | 43 | // ensure we have a loaded bibliography 44 | if (!frontMatter.bibliographyParsed) { 45 | // console.debug('onCiteKeyCreated, but unresolved dependency ("bibliography"). Enqueing.'); 46 | Controller.waitingOn.bibliography.push(() => 47 | Controller.listeners.onCiteKeyCreated(event) 48 | ); 49 | return; 50 | } 51 | 52 | const numbers = keys.map(key => frontMatter.citations.indexOf(key)); 53 | citeTag.numbers = numbers; 54 | const entries = keys.map(key => frontMatter.bibliography.get(key)); 55 | citeTag.entries = entries; 56 | }, 57 | 58 | onCiteKeyChanged() { 59 | // const [citeTag, keys] = event.detail; 60 | 61 | // update citations 62 | frontMatter.citations = collect_citations(); 63 | frontMatter.citationsCollected = true; 64 | for (const waitingCallback of Controller.waitingOn.citations.slice()) { 65 | waitingCallback(); 66 | } 67 | 68 | // update bibliography 69 | const citationListTag = document.querySelector("d-citation-list"); 70 | const bibliographyEntries = new Map( 71 | frontMatter.citations.map(citationKey => { 72 | return [citationKey, frontMatter.bibliography.get(citationKey)]; 73 | }) 74 | ); 75 | citationListTag.citations = bibliographyEntries; 76 | 77 | const citeTags = document.querySelectorAll("d-cite"); 78 | for (const citeTag of citeTags) { 79 | console.log(citeTag); 80 | const keys = citeTag.keys; 81 | const numbers = keys.map(key => frontMatter.citations.indexOf(key)); 82 | citeTag.numbers = numbers; 83 | const entries = keys.map(key => frontMatter.bibliography.get(key)); 84 | citeTag.entries = entries; 85 | } 86 | }, 87 | 88 | onCiteKeyRemoved(event) { 89 | Controller.listeners.onCiteKeyChanged(event); 90 | }, 91 | 92 | onBibliographyChanged(event) { 93 | const citationListTag = document.querySelector("d-citation-list"); 94 | 95 | const bibliography = event.detail; 96 | 97 | frontMatter.bibliography = bibliography; 98 | frontMatter.bibliographyParsed = true; 99 | for (const waitingCallback of Controller.waitingOn.bibliography.slice()) { 100 | waitingCallback(); 101 | } 102 | 103 | // ensure we have citations 104 | if (!frontMatter.citationsCollected) { 105 | Controller.waitingOn.citations.push(function() { 106 | Controller.listeners.onBibliographyChanged({ 107 | target: event.target, 108 | detail: event.detail 109 | }); 110 | }); 111 | return; 112 | } 113 | 114 | if (citationListTag.hasAttribute("distill-prerendered")) { 115 | console.debug("Citation list was prerendered; not updating it."); 116 | } else { 117 | const entries = new Map( 118 | frontMatter.citations.map(citationKey => { 119 | return [citationKey, frontMatter.bibliography.get(citationKey)]; 120 | }) 121 | ); 122 | citationListTag.citations = entries; 123 | } 124 | }, 125 | 126 | onFootnoteChanged() { 127 | // const footnote = event.detail; 128 | //TODO: optimize to only update current footnote 129 | const footnotesList = document.querySelector("d-footnote-list"); 130 | if (footnotesList) { 131 | const footnotes = document.querySelectorAll("d-footnote"); 132 | footnotesList.footnotes = footnotes; 133 | } 134 | }, 135 | 136 | onFrontMatterChanged(event) { 137 | const data = event.detail; 138 | mergeFromYMLFrontmatter(frontMatter, data); 139 | 140 | const interstitial = document.querySelector("d-interstitial"); 141 | if (interstitial) { 142 | if (typeof frontMatter.password !== "undefined") { 143 | interstitial.password = frontMatter.password; 144 | } else { 145 | interstitial.parentElement.removeChild(interstitial); 146 | } 147 | } 148 | 149 | const prerendered = document.body.hasAttribute("distill-prerendered"); 150 | if (!prerendered && domContentLoaded()) { 151 | optionalComponents(document, frontMatter); 152 | 153 | const appendix = document.querySelector("distill-appendix"); 154 | if (appendix) { 155 | appendix.frontMatter = frontMatter; 156 | } 157 | 158 | const byline = document.querySelector("d-byline"); 159 | if (byline) { 160 | byline.frontMatter = frontMatter; 161 | } 162 | 163 | if (data.katex) { 164 | DMath.katexOptions = data.katex; 165 | } 166 | } 167 | }, 168 | 169 | DOMContentLoaded() { 170 | if (Controller.loaded) { 171 | console.warn( 172 | "Controller received DOMContentLoaded but was already loaded!" 173 | ); 174 | return; 175 | } else if (!domContentLoaded()) { 176 | console.warn( 177 | "Controller received DOMContentLoaded at document.readyState: " + 178 | document.readyState + 179 | "!" 180 | ); 181 | return; 182 | } else { 183 | Controller.loaded = true; 184 | console.debug("Runlevel 4: Controller running DOMContentLoaded"); 185 | } 186 | 187 | const frontMatterTag = document.querySelector("d-front-matter"); 188 | if (frontMatterTag) { 189 | const data = parseFrontmatter(frontMatterTag); 190 | Controller.listeners.onFrontMatterChanged({ detail: data }); 191 | } 192 | 193 | // Resolving "citations" dependency due to initial DOM load 194 | frontMatter.citations = collect_citations(); 195 | frontMatter.citationsCollected = true; 196 | for (const waitingCallback of Controller.waitingOn.citations.slice()) { 197 | waitingCallback(); 198 | } 199 | 200 | if (frontMatter.bibliographyParsed) { 201 | for (const waitingCallback of Controller.waitingOn.bibliography.slice()) { 202 | waitingCallback(); 203 | } 204 | } 205 | 206 | const footnotesList = document.querySelector("d-footnote-list"); 207 | if (footnotesList) { 208 | const footnotes = document.querySelectorAll("d-footnote"); 209 | footnotesList.footnotes = footnotes; 210 | } 211 | } 212 | } // listeners 213 | }; // Controller 214 | -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 57 | 58 | 59 |
    61 |

    We often think of Momentum as a means of dampening oscillations and 62 | speeding up the iterations, leading to faster convergence. But it has other interesting behavior. It allows a 63 | larger range of step-sizes to be used, and creates its own oscillations. What is going on?

    64 |
    65 | 66 | 67 | 1 68 |

    A Brief Survey of Techniques

    69 |

    Before diving in: if you haven’t encountered t-SNE before, here’s what you need to know about the math behind it. 70 | The goal is to take a set of points in a high-dimensional space and find a faithful representation of those points 71 | in a lower-dimensional space, typically the 2D plane. The algorithm is non-linear and adapts to the underlying 72 | data, performing different transformations on different regions. Those differences can be a major source of 73 | confusion.

    74 |

    This is the first paragraph of the article. Test a long — dash -- here it is.

    75 |

    Test for owner's possessive. Test for "quoting a passage." And another sentence. Or two. Some flopping fins; for 76 | diving.

    77 | 78 |

    Here's a test of an inline equation c = a^2 + b^2. Also with configurable katex standards just 79 | using inline '$' signs: $$x^2$$ And then there's a block equation:

    80 | 81 | c = \pm \sqrt{ \sum_{i=0}^{n}{a^{222} + b^2}} 82 | 83 |

    Math can also be quite involved:

    84 | 85 | \frac{1}{\Bigl(\sqrt{\phi \sqrt{5}}-\phi\Bigr) e^{\frac25 \pi}} = 1+\frac{e^{-2\pi}} {1+\frac{e^{-4\pi}} 86 | {1+\frac{e^{-6\pi}} {1+\frac{e^{-8\pi}} {1+\cdots} } } } 87 | 88 | 1.1 89 |

    Citations

    90 |

    91 | 92 |

    93 |

    We can also cite external publications. . We should also be testing footnotes 96 | This will become a hoverable footnote. This will become a hoverable footnote. This will become a 97 | hoverable footnote. This will become a hoverable footnote. This will become a hoverable footnote. This will 98 | become a hoverable footnote. This will become a hoverable footnote. This will become a hoverable footnote. 99 | . There are multiple footnotes, and they appear in the appendixGiven I have coded them 100 | right. Also, here's math in a footnote: c = \sum_0^i{x}. Also, a citation. Box-ception! as well.

    102 | 2 103 |

    Displaying code snippets

    104 |

    Some inline javascript:var x = 25;. And here's a javascript code block. 105 |

    106 | 107 | var x = 25; 108 | function(x){ 109 | return x * x; 110 | } 111 | 112 |

    We also support python.

    113 | 114 | # Python 3: Fibonacci series up to n 115 | def fib(n): 116 | a, b = 0, 1 117 | while a < n: print(a, end=' ' ) a, b=b, a+b 118 |

    And a table

    119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 |
    FirstSecondThird
    2365423
    145434
    2345423
    145 | 146 | 167 |

    That's it for the example article!

    168 | 169 |
    170 | 171 | 172 | 173 |

    Contributions

    174 |

    Some text describing who did what.

    175 |

    Reviewers

    176 |

    Some text with links describing who reviewed the article.

    177 | 178 | 179 |
    180 | 181 | 182 | 183 | --------------------------------------------------------------------------------