diff --git a/.gitignore b/.gitignore index 298448b20..18416bd09 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ src/extensions/index.ts src/themes/index.ts src/selectors.ts src/selectors.css +src/gallery.ejs +src/gallery.ejs.media +src/themes.json diff --git a/README.md b/README.md index 824474c4a..eb655bdf5 100644 --- a/README.md +++ b/README.md @@ -118,16 +118,16 @@ Extensions provide styling, and potentially interactivity, for node types that d -| Name | Description | -| ----------------------------- | ---------------------------- | -| [cite](./themes/cite) | Citation extension | -| [cite-apa](./themes/cite-apa) | APA citation style extension | -| [cite-mla](./themes/cite-mla) | MLA citation style extension | -| [code](./themes/code) | Code nodes extension | -| [headings](./themes/headings) | Headings extension | -| [math](./themes/math) | Math styling extension. | -| [pages](./themes/pages) | Pages extension | -| [person](./themes/person) | Person extension | +| Name | Description | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [cite](./themes/cite) | Provides styling for in-text citations (i.e. `Cite` and `CiteGroup` nodes) and bibliographies (i.e. `CreativeWork` nodes in the `references` property of another `CreativeWork`). | +| [cite-apa](./themes/cite-apa) | Provides styling for in-text citations and bibliographies in accordance with the [American Psychological Association (APA) style](https://en.wikipedia.org/wiki/APA_style). | +| [cite-mla](./themes/cite-mla) | Provides styling for in-text citations and bibliographies in accordance with the [Modern Language Association (MLA) style](https://style.mla.org/). | +| [code](./themes/code) | Provides syntax highlighting for `CodeFragment` and `CodeBlock` nodes using [Prism](https://prismjs.com/). Will not style executable node types like `CodeExpression` and `CodeChunk` which are styled by the base Stencila Web Components. | +| [headings](./themes/headings) | A temporary extensions that changes the way that `Heading` nodes are represented. Ensures that there is only one `

` tag (for the `title` property) and that `Heading` nodes are represented as ``. | +| [math](./themes/math) | Provides styling of math nodes using MathJax fonts and styles. Use this if there is any likely to be math content, i.e. `MathFragment` and/or `MathBlock` nodes, in documents that your theme targets. | +| [pages](./themes/pages) | Provides a [`@media print` CSS at-rule](https://developer.mozilla.org/en-US/docs/Web/CSS/@page) to modify properties when printing a document e.g. to PDF. | +| [person](./themes/person) | Provides styling of `Person` nodes e.g the `authors` of an article, or authors for each `citation` in it's `references`. | diff --git a/package-lock.json b/package-lock.json index 82009bc14..c1d65604e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10705,6 +10705,48 @@ "integrity": "sha512-cuIMtJwxvzumSAkqaaoGY/L6Fc/t6YvoP9/VIaK0V/CyqKLEQ8sqODmYfy/cjXEdZ9+OOL8TecbJu+1RsofGDw==", "dev": true }, + "ejs-loader": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/ejs-loader/-/ejs-loader-0.3.5.tgz", + "integrity": "sha512-96Zt17hrKINvbdYUxk5TC5a18J9lIdKLPKIngl9dSyZBsNDKAFibY3z/VBcyq0jWGQkIemLsjdIJIAu4T0CB8A==", + "dev": true, + "requires": { + "loader-utils": "^0.2.7", + "lodash": "^4.17.15" + }, + "dependencies": { + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, + "loader-utils": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", + "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=", + "dev": true, + "requires": { + "big.js": "^3.1.3", + "emojis-list": "^2.0.0", + "json5": "^0.5.0", + "object-assign": "^4.0.1" + } + } + } + }, "electron-to-chromium": { "version": "1.3.322", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", diff --git a/package.json b/package.json index 816a29280..07383dae3 100644 --- a/package.json +++ b/package.json @@ -81,6 +81,7 @@ "css-loader": "3.4.2", "cssnano": "4.1.10", "cssnano-preset-default": "4.0.7", + "ejs-loader": "^0.3.5", "eslint-plugin-react": "^7.18.3", "extract-loader": "4.0.3", "file-loader": "4.3.0", diff --git a/src/demo/editor/header.tsx b/src/demo/editor/header.tsx index 47ab93346..22c5998ed 100644 --- a/src/demo/editor/header.tsx +++ b/src/demo/editor/header.tsx @@ -3,6 +3,7 @@ import ReactDOM from 'react-dom' import { examples } from '../../examples' import { ViewportToggle } from './viewportToggle' import { getExample, setExample } from '../utils/preview' +import { HeaderBase } from './headerBase' interface Props { /* onExampleChange: (e: React.ChangeEvent) => string */ @@ -15,9 +16,7 @@ const HeaderComponent = ({ examples }: Props): JSX.Element => { return ( <> - - - Thema +
diff --git a/src/demo/editor/headerBase.tsx b/src/demo/editor/headerBase.tsx new file mode 100644 index 000000000..446a9d435 --- /dev/null +++ b/src/demo/editor/headerBase.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +export const HeaderBase = (): JSX.Element => { + return ( + + + + Thema + + ) +} diff --git a/src/demo/editor/index.tsx b/src/demo/editor/index.tsx index d1a200f89..778d7e213 100644 --- a/src/demo/editor/index.tsx +++ b/src/demo/editor/index.tsx @@ -12,7 +12,7 @@ export const ThemeEditor = (): JSX.Element => {

- Current Theme + Theme {
-

- Themes are designed to be customizable, if you’d like to make extensive - changes you can extend a theme, or make one from scratch.{' '} - Read the documentation{' '} - to learn how. -

-

Customize

diff --git a/src/demo/gallery.tsx b/src/demo/gallery.tsx new file mode 100644 index 000000000..a098761c7 --- /dev/null +++ b/src/demo/gallery.tsx @@ -0,0 +1,9 @@ +/** + * Header for gallery page + */ + +import React from 'react' +import ReactDOM from 'react-dom' +import { HeaderBase } from './editor/headerBase' + +ReactDOM.render(, document.getElementById('header')) diff --git a/src/demo/styles.css b/src/demo/styles.css index 55e883e9e..9d9bf1014 100644 --- a/src/demo/styles.css +++ b/src/demo/styles.css @@ -13,11 +13,14 @@ html { } body { - display: flex; - flex-wrap: wrap; height: 100%; margin: 0; padding: 3rem 0 0; + + &.editor { + display: flex; + flex-wrap: wrap; + } } body > * { @@ -65,6 +68,11 @@ header { color: var(--color-neutral-700); } + a:link, + a:visited { + text-decoration: none; + } + img { height: 2rem; } @@ -99,6 +107,81 @@ main { background-color: var(--color-neutral-100); } +:--root > :--List > :--ListItem { + box-shadow: 0 0 8px rgba(0, 0, 0, 0.035), 0 0 40px rgba(0, 0, 0, 0.07); + overflow: hidden; + transition: box-shadow 200ms ease-out; + padding-bottom: var(--spacer-sm); + + &:hover { + box-shadow: 0 0 8px rgba(0, 0, 0, 0.045), 0 0 40px rgba(0, 0, 0, 0.17); + } +} + +/* Gallery page */ + +:--root > :--List > :--ListItem > :--CreativeWork { + :--title { + display: none; + } + + :--Heading { + font-size: var(--font-size-h1); + color: var(--color-key); + order: -1; + margin-top: var(--spacer-md); + margin-bottom: var(--spacer-md); + text-transform: capitalize; + } + + & > a[href^='/editor?'] { + order: -1; + display: block; + border-bottom: 1px solid var(--color-neutral-400); + margin-bottom: var(--spacer-md); + } + + & > :--Organization { + display: none; + } + + & > :--Paragraph:last-of-type > a { + background-color: var(--color-neutral-300); + border-radius: 4px; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.035), 0 0 40px rgba(0, 0, 0, 0.07); + border: 1px solid var(--color-neutral-400); + font-size: 12px; + font-weight: bold; + letter-spacing: 1px; + padding: 0.25rem 0.5rem; + text-decoration: none; + text-transform: uppercase; + transition: color 200ms ease-out, background-color 200ms ease-out, + border-color 200ms ease-out; + + &:hover { + background-color: var(--color-neutral-400); + } + + &::after { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' width='24' height='24' stroke='#363636' stroke-width='2.5' fill='none' stroke-linecap='round' stroke-linejoin='round' class='css-i6dzq1'%3E%3Cpolyline points='9 18 15 12 9 6'%3E%3C/polyline%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-size: contain; + color: red; + content: ''; + display: inline-block; + height: 16px; + transition: transform 125ms ease-out; + vertical-align: middle; + width: 14px; + } + + &:hover::after { + transform: translateX(0.25rem); + } + } +} + iframe { background-color: white; border: 1px solid var(--color-neutral-300); @@ -145,10 +228,11 @@ menu p { #contribute:link, #sidebar > a.button, #sidebar > button { - background-color: #1d64f3; + background-color: var(--color-neutral-400); border-radius: 2px; - border: none; - color: white; + border: 1px solid var(--color-neutral-500); + box-shadow: 0 0 8px rgba(0, 0, 0, 0.035), 0 0 40px rgba(0, 0, 0, 0.07); + color: var(--color-key); font-weight: bold; letter-spacing: 1px; margin: 1rem 0; @@ -161,6 +245,7 @@ menu p { #contribute:hover, #sidebar > a.button:hover, #sidebar > button:hover { + color: white; background-color: #164ebf; } @@ -182,8 +267,8 @@ menu p { width: 100%; } -h2, -h3 { +#sidebar h2, +#sidebar h3 { font-size: 12px; font-weight: bold; color: var(--color-neutral-700); @@ -192,7 +277,7 @@ h3 { position: relative; } -h3::after { +#sidebar h3::after { content: ''; position: absolute; -webkit-transform: translateY(-50%); @@ -202,7 +287,7 @@ h3::after { } hr, -h3::after { +#sidebar h3::after { background-color: var(--color-neutral-500); border: none; height: 1px; @@ -240,17 +325,12 @@ h3::after { } #themeVariables label.modified { - color: #066860; cursor: help; font-style: italic; font-weight: bold; padding: 1px 0; } -#themeVariables label.modified::after { - content: '*'; -} - #themeVariables .labelWrapper button { background-color: var(--color-neutral-100); border: 1px solid var(--color-neutral-400); diff --git a/src/galleryTemplate.ejs b/src/galleryTemplate.ejs new file mode 100644 index 000000000..13ca3586f --- /dev/null +++ b/src/galleryTemplate.ejs @@ -0,0 +1,16 @@ + + + + Stencila Thema + + + + + + + +
+ <%= require("./gallery.ejs")() %> +
+ + diff --git a/src/scripts/gallery.ts b/src/scripts/gallery.ts index 544e77044..51d59efe1 100644 --- a/src/scripts/gallery.ts +++ b/src/scripts/gallery.ts @@ -1,29 +1,31 @@ /** - * A script to generate `../docs/gallery.html` * * Run using `npm run docs:gallery`. Noting that this * uses built themes in `docs/themes` so `npm run docs:app` * needs to be done first. */ -import { read, write, dump, shutdown } from '@stencila/encoda' +import { dump, read, shutdown, write } from '@stencila/encoda' import { Article, article, CreativeWork, creativeWork, + heading, imageObject, link, list, listItem, - organization + organization, + paragraph } from '@stencila/schema' import { tmpdir } from 'os' import path from 'path' -import { themes } from '../themes' +import { themes } from '../themes/index' const themesDir = path.join(__dirname, '..', 'themes') const examplesDir = path.join(__dirname, '..', 'examples') +const srcDir = path.join(__dirname, '..') const docsDir = path.join(__dirname, '..', '..', 'docs') const stencila = organization({ @@ -52,7 +54,11 @@ async function generateGallery(): Promise { Object.keys(themes).map( async (theme): Promise<[string, CreativeWork]> => [ theme, - await generateSummary(theme, `?theme=${theme}`, example as Article) + await generateSummary( + theme, + `/editor?theme=${theme}`, + example as Article + ) ] ) ) @@ -64,7 +70,7 @@ async function generateGallery(): Promise { {} ) - await write(summaries, path.join(docsDir, 'themes.json')) + await write(summaries, path.join(srcDir, 'themes.json')) const gallery = article({ title: 'Thema Gallery', @@ -72,6 +78,21 @@ async function generateGallery(): Promise { publisher: stencila, datePublished: new Date().toISOString(), content: [ + paragraph({ + content: [ + 'Thema provides semantic themes for use with ', + link({ + target: 'https://github.com/stencila/encoda/', + content: ['Stencila’s Encoda'] + }), + '. Themes are designed to be customizable, or you can ', + link({ + target: 'https://github.com/stencila/thema/', + content: ['make one from scratch'] + }), + '.' + ] + }), list({ items: Object.entries(summaries).map(([theme, summary]) => { return listItem({ @@ -85,9 +106,10 @@ async function generateGallery(): Promise { ] }) - await write(gallery, path.join(docsDir, 'gallery.html'), { - isStandalone: true, - theme: path.join(docsDir, 'themes', 'galleria') + await write(gallery, path.join(srcDir, 'gallery.ejs'), { + isStandalone: false, + format: 'html', + theme: path.join(srcDir, 'themes', 'galleria') }) await shutdown() @@ -113,6 +135,7 @@ async function generateSummary( // Generate a screenshot using the local build of the theme const screenshot = path.join(tmpdir(), 'screenshots', `${theme}.png`) + await write(example, screenshot, { theme: path.join(docsDir, 'themes', theme), size: { height: 500, width: 800 } @@ -124,13 +147,22 @@ async function generateSummary( publisher, // New content includes the screenshot content: [ - ...content, link({ target: url, content: [imageObject({ contentUrl: screenshot })] + }), + heading({ depth: 3, content: [theme] }), + ...content, + paragraph({ + content: [ + link({ + target: url, + content: ['View demo & customize'] + }) + ] }) ], - // If their is not a description in the YAML meta data of the + // If there is not a description in the YAML meta data of the // README, then make it the plain text version of the original content description: description !== undefined ? description : await dump(content, 'txt'), diff --git a/src/template.html b/src/template.html index 106522199..8e956d704 100644 --- a/src/template.html +++ b/src/template.html @@ -6,7 +6,7 @@ - + diff --git a/src/themes/galleria/styles.css b/src/themes/galleria/styles.css index d8db3666a..3c6c164f7 100644 --- a/src/themes/galleria/styles.css +++ b/src/themes/galleria/styles.css @@ -1,11 +1,78 @@ @import '../stencila/styles.css'; -:--root > :--List { +:--root { + --max-width-media: 1024px; + + max-width: var(--max-width-media); +} + +:--root > :--title, +:--root > :--authors, +:--root > span, +:--root > :--Organization { + display: none; +} + +:--root > :--ListTypes { list-style: none; + margin: 0; + padding: 0; + width: 100% !important; + + @media (--mq-lg) { + align-items: flex-start; + display: flex; + flex-flow: row wrap; + max-width: none !important; + } + & > :--ListItem { - border: 1px solid #777; - border-radius: 3px; - padding: 1rem; - margin: 1rem; + border: 1px solid var(--color-neutral-400); + background-color: var(--color-stock); + border-radius: 4px; + margin: var(--spacer-md); + + @media (--mq-lg) { + flex-basis: calc(50% - var(--spacer-md) * 2); + } + + :--Heading, + :--Paragraph { + margin-left: 0; + margin-right: 0; + padding-left: var(--spacer-md); + padding-right: var(--spacer-md); + } + + :--ListTypes { + list-style-position: inside; + } + } + + img { + max-width: 100%; + } +} + +:--root > :--List > :--ListItem > :--CreativeWork { + display: flex; + flex-flow: column; + + :--Organization { + padding: 0 var(--spacer-md); + + /* Only show organization name if logo is not present */ + span:not(:only-child) { + display: none; + } + + :--ImageObject { + display: inline-block; + + img { + max-height: 32px; + width: auto; + } + } } } diff --git a/src/themes/skeleton/styles.css b/src/themes/skeleton/styles.css index 3bad7738d..5a84ab95c 100644 --- a/src/themes/skeleton/styles.css +++ b/src/themes/skeleton/styles.css @@ -261,7 +261,7 @@ h2:--Heading { max-width: var(--max-width); & + & { - margin-top: 1em; + margin-top: 0; } } diff --git a/src/themes/stencila/styles.css b/src/themes/stencila/styles.css index 9c5ecedf3..cdd1be7ee 100644 --- a/src/themes/stencila/styles.css +++ b/src/themes/stencila/styles.css @@ -57,6 +57,10 @@ :--Heading { font-weight: 600; + + & + :--Paragraph { + margin-top: 0; + } } h2:--Heading:not(:--Heading + h2:--Heading):not(figcaption h2) { diff --git a/webpack.config.js b/webpack.config.js index 698c473f9..ad29e0091 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -21,15 +21,6 @@ const themes = [ 'wilmore' ] -// Generate configurations for ScriptExtHtmlWebpackPlugin to add theme ID and class attributes -// to Stylesheet imports for documentation site. This is needed for the Theme switcher to function. -const themePaths = themes.map(theme => `themes/${theme}/styles`) -const themeIds = themes.map(theme => ({ - test: `themes/${theme}/styles`, - attribute: 'id', - value: theme -})) - const contentSource = 'src' // Convert absolute filepaths to project relative ones to use as @@ -55,7 +46,7 @@ module.exports = (env = {}, { mode }) => { const entries = [ './src/**/*.{css,ts,tsx,html,ttf,woff,woff2}', // template.html is used as a basis for HtmlWebpackPlugin, and should not be used as an entry point - '!./src/template.html', + '!./src/{gallery,template}.html', // Don’t compile test files for package distribution '!**/*.{d,test}.ts', // These files make use of Node APIs, and do not need to be packaged for Browser targets @@ -65,7 +56,7 @@ module.exports = (env = {}, { mode }) => { '!**/extensions/extensions.ts', // Don’t build HTML demo files for package distribution ...(isDocs || isDevelopment - ? [] + ? ['./src/**/*.{jpg,png,gif}'] : ['!**/*.html', '!**/demo/*', '!**/examples/*']) ] @@ -84,21 +75,17 @@ module.exports = (env = {}, { mode }) => { isDocs || isDevelopment ? [ new HtmlWebpackPlugin({ + filename: 'editor.html', template: './src/template.html', - chunks: ['demo/styles', 'demo/app.tsx'] + chunks: ['demo/styles', 'themes/stencila/styles', 'demo/app.tsx'] }), - new ScriptExtHtmlWebpackPlugin({ - custom: [ - { - test: 'themes/', - attribute: 'disabled' - }, - { - test: 'themes/', - attribute: 'class', - value: 'theme' - }, - ...themeIds + new HtmlWebpackPlugin({ + filename: 'index.html', + template: './src/galleryTemplate.ejs', + chunks: [ + 'demo/styles', + 'demo/gallery.tsx', + 'themes/galleria/styles' ] }) ] @@ -115,7 +102,28 @@ module.exports = (env = {}, { mode }) => { filename: '[name].js' }, devServer: { - contentBase: `./${contentBase}` + contentBase: path.join(__dirname, contentBase), + overlay: true, + staticOptions: { + extensions: ['.html', '.htm'] + }, + // Resolve URLS without explicit file extensions + // the above `devServer.staticOptions.extensions` seems to have no effect + before: function(app, server, compiler) { + app.use(function(req, res, next) { + if (req.path !== '/' && req.path.indexOf('.') === -1) { + let url = req.url.split('?') + let [reqPath, ...rest] = url + + if (url.length > 1) { + req.url = [reqPath, '.html', '?', ...rest].join('') + } else { + req.url = `${reqPath}.html` + } + next() + } else next() + }) + } }, plugins: [ new CleanWebpackPlugin(), @@ -152,6 +160,7 @@ module.exports = (env = {}, { mode }) => { } } }, + { test: /\.ejs$/, loader: 'ejs-loader' }, { test: /\.html$/i, // Don't transform HtmlWebpackPlugin generated file