mirror of
https://github.com/marp-team/marp.git
synced 2024-11-30 09:46:32 +03:00
Host marp.app website built on Gatsby (#11)
* Setup Gatsby * Simplized Gatsby template * Use TypeScript through gatsby-plugin-typescript * Use only start script instead of develop to serve gatsby * Add the skelton of hero contents * Renew layout of hero to be simplify * Fix the order of style declarations in Hero component * More simplized hero element * Include web fonts only to the layout SCSS * Add header styling * Upgrade dependent packages to latest * Use Node v10.14.0 * Add button component * Add menuitem component * Stop to blur focus on clicking menuitem * Update header style * Implement sticky header powered by react-sticky * Remove lorem placeholders * Update layout to switch whether display hero component * Add blog page * Use location prop provided by router * Enlarge hit area of Menuitem component * Fix style of primary outline button * Fix active highlight color of menu item * Use Link component provided by Gatsby for navigating without new request * Assign unique title per page with title prop of Layout * [WIP] List blog page title * Implement Blog component * Update style of Blog component * Update style of GithubAuthor in Blog component * Improve blog interface for GraphQL and meta styling * Add remark plugins * Handle the excerpted content with more comment and add read more button * Add style for HTML elements for writing blog contents * Fix style about components * Upgrade Node to v10.14.1 * Upgrade dependent packages to latest version * Fix deprecated overriden ref in react-sticky by wrapping class * Update style about code and tables * Improve keyboard navigation of Layout component * Fix to work Gatsby static build * Prepare reserved field to use the reserved post in future * Filter reserved blog posts in index page of blog * Add style for reserved blog post * [WIP] Add the first post about the story of marp * Fix YAML format in the first post * Clear cache and current public directory on building * Upgrade Node to v10.15.0 * Upgrade dependent packages to latest version * Update blog article * Update Node version to v10.15.1 * Update the Marpit section of blog article * Upgrade dependent packages to latest * Update blog font to use readble sans-serif * Fix tap highlight on sticky container * Fix too thin font of blog contents in mobile device * Fix scroll behavior in sticky container with touch device * Use -webkit-overflow-scrolling: touch * [WIP] Add Marp Core and Marp CLI contents * Update blog articles to add about Marp Web and integrations * Add section about migration plan * Minor fix of grammar * Upgrade dependent packages to the latest * Update article * Update integration and added conclsuion * Upgrade dependent packages to the latest version * Update style of contents * Update blog article * Update article * Finalize * Add button of link to Marp repository to hero component * Update date of article to 2019-06-10 * Update LICENSE * Update netlify.toml * Upgrade dependent packages to the latest version * Remove public/_redirects * Upgrade Node to v10.16.0 * Update publish date of article to 2019-06-06
This commit is contained in:
parent
0a361a00b5
commit
d4bfc4e8bb
35
.circleci/config.yml
Normal file
35
.circleci/config.yml
Normal file
@ -0,0 +1,35 @@
|
||||
version: 2
|
||||
jobs:
|
||||
current:
|
||||
docker:
|
||||
- image: circleci/node:10.16.0
|
||||
working_directory: ~/marp
|
||||
steps:
|
||||
- run: node --version
|
||||
|
||||
- checkout
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-dependencies-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-{{ .Branch }}
|
||||
- v1-dependencies-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-
|
||||
- v1-dependencies-{{ .Environment.CIRCLE_JOB }}-
|
||||
|
||||
- run: yarn install
|
||||
- run: yarn audit
|
||||
|
||||
- save_cache:
|
||||
key: v1-dependencies-{{ .Environment.CIRCLE_JOB }}-{{ checksum "yarn.lock" }}-{{ .Branch }}
|
||||
paths:
|
||||
- node_modules
|
||||
- ~/.cache/yarn
|
||||
|
||||
- run:
|
||||
name: Prettier formatting
|
||||
command: yarn format:check
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build:
|
||||
jobs:
|
||||
- current
|
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,3 +1,7 @@
|
||||
# Website gatsby
|
||||
/website/.cache/
|
||||
/website/public
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,windows,macos,linux,sublimetext,emacs,vim,visualstudiocode
|
||||
|
||||
### Emacs ###
|
||||
|
@ -1 +1 @@
|
||||
v10.13.0
|
||||
v10.16.0
|
||||
|
6
.prettierignore
Normal file
6
.prettierignore
Normal file
@ -0,0 +1,6 @@
|
||||
.git/
|
||||
.vscode/
|
||||
/website/.cache/
|
||||
/website/public
|
||||
node_modules
|
||||
package.json
|
3
.prettierrc.yml
Normal file
3
.prettierrc.yml
Normal file
@ -0,0 +1,3 @@
|
||||
semi: false
|
||||
singleQuote: true
|
||||
trailingComma: es5
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Marp team (marp-team@marp.app)
|
||||
Copyright (c) 2018-2019 Marp team (marp-team@marp.app)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
@ -1,8 +1,3 @@
|
||||
[build]
|
||||
publish = "public"
|
||||
command = ""
|
||||
|
||||
# TODO: Remove branch context after than merged #11
|
||||
[context.gatsby]
|
||||
publish = "website/public"
|
||||
command = "cd website && yarn build"
|
||||
|
11
package.json
11
package.json
@ -16,5 +16,16 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/marp-team/marp"
|
||||
},
|
||||
"workspaces": [
|
||||
"website"
|
||||
],
|
||||
"scripts": {
|
||||
"format": "prettier \"**/*.{css,html,js,json,jsx,md,scss,ts,tsx,yaml,yml}\"",
|
||||
"format:check": "yarn -s --mutex file format -c"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^1.17.1",
|
||||
"typescript": "^3.5.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,2 +0,0 @@
|
||||
https://marp.netlify.com/* https://marp.app/:splat 301!
|
||||
/* https://github.com/marp-team/marp 302
|
12
tsconfig.base.json
Normal file
12
tsconfig.base.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"lib": ["es2016", "es2017", "dom"],
|
||||
"module": "commonjs",
|
||||
"noEmit": true,
|
||||
"noImplicitAny": false,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"target": "es2015"
|
||||
}
|
||||
}
|
@ -0,0 +1,210 @@
|
||||
---
|
||||
author: Yuki Hattori
|
||||
date: 2019-06-06
|
||||
github: yhatt
|
||||
title: The story of Marp Next
|
||||
---
|
||||
|
||||
The first version of [Marp](https://yhatt.github.io/marp/) was released at almost 3 years ago. At first, it was started from a simple tool for personal usage called "mdSlide". And now, Marp has been used by a lot of users who would recognize the real value of the presentation writer. Marp is amassed around [8,000 stars](https://github.com/yhatt/marp/stargazers) until now.
|
||||
|
||||
However, our headache brought from lacked maintainability to develop. We had received so many requests to the old Marp app, and it has to evolve to keep providing the best writing environment of presentation deck.
|
||||
|
||||
Today, I'm so excited to introduce the story of Marp Next! The full-rewritten Marp is not only just a writer. To be usable in various situations, we build **a brand-new Marp ecosystem** consisted of multiple modules. They are developed with JavaScript and TypeScript, and much more maintainable than the previous Marp.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
# Marp ecosystem
|
||||
|
||||
Marp Next has two core components: **[Marpit]** framework and **[Marp Core]**. Tools by Marp ecosystem are usually based on these.
|
||||
|
||||
## Marpit
|
||||
|
||||
**[Marpit]** is _the skinny framework_ for creating HTML slide deck from Markdown. It is designed to convert Markdown into only minimum assets consisted of static HTML and CSS, and the output can convert into PDF slide deck by printing through Chrome / Chromium.
|
||||
|
||||
Marpit has created for using as the base of Marp ecosystem, but it is also independent framework. You may integrate Marpit's Markdown conversion with your tool, even if it's not Marp: [reveal.js](https://codesandbox.io/embed/nw80vrxvpp), [WebSlides](https://codesandbox.io/embed/j3wo2091yw), and so on.
|
||||
|
||||
One of real-world use cases is [MetaBake](https://www.metabake.org) project. It provides Marpit integration from our early phase, to be easy to build web page with presentation style. ([See example](https://github.com/metabake/mbake/tree/master/examples/slidesEx))
|
||||
|
||||
[marpit]: https://marpit.marp.app/
|
||||
|
||||
### [Marpit Markdown]: Keep compatibillity with a plain Markdown document
|
||||
|
||||
We had received [many requests][issues] to the old Marp, about the additional syntax to help creating beautiful slide deck. On the other hand, we also have received a request that [must respect Markdown syntax strictly](https://github.com/yhatt/marp/issues/87). We have to deal with these contradicted issues.
|
||||
|
||||
Additional syntax provided by Marpit should never break [CommonMark](https://commonmark.org/) document. Thus, the result of rendering keeps looking nice even if you open the Marpit Markdown in a general Markdown editor. And you can even extend the additional syntax via [markdown-it plugins](https://marpit.marp.app/usage?id=extend-marpit-by-plugins) if you need.
|
||||
|
||||
[marpit markdown]: https://marpit.marp.app/markdown
|
||||
[issues]: https://github.com/yhatt/marp/issues
|
||||
|
||||
### [Theme CSS]: Design your deck with clean markup
|
||||
|
||||
Marpit has the theming system to allow designing everything of slides by CSS.
|
||||
|
||||
The old Marp had the _limited_ theming system and required deep diving to internal for customization: Build system, [Sass], the logic of Marp app, and so on. So we had to create a brand-new theming system for easy customization of theme with only general CSS knowledge.
|
||||
|
||||
Marpit's it only requires a pure CSS, and no additional knowledges! You have only to focus styling HTML semantic elements. It means that you can create theme CSS from now!
|
||||
|
||||
In addition, Marpit has the pixel-perfect slide system like PowerPoint and Keynote. Theme creator never needs to worry about the responsive layout, and could provide design exactly as the author wanted with less effort.
|
||||
|
||||
[theme css]: https://marpit.marp.app/theme-css
|
||||
[sass]: https://sass-lang.com/
|
||||
|
||||
### [Inline SVG slide] (Experimental)
|
||||
|
||||
Our unique idea is wrapping each slides by inline SVG. It might feel a bit strange, but makes many advantages.
|
||||
|
||||
- Supports pixel-perfect scaling via style definition and **realizes Zero-JS slide deck**.
|
||||
- Isolates Markdown contents and prevents that injected DOM by Marpit's advanced feature breaks design defined in theme CSS.
|
||||
|
||||
Thanks to the power of SVG, we can keep a framework simple and maintainable. [Marp Core] is based on inline SVG slide by default.
|
||||
|
||||
[inline svg slide]: https://marpit.marp.app/inline-svg
|
||||
|
||||
## Marp Core
|
||||
|
||||
**[Marp Core]** is a base converter for our projects extended from Marpit. In short, it is a battery-included Marpit.
|
||||
|
||||
Marpit only has bare essential features, so it might have not enough to start writing your deck. Marp Core provides the practical syntax, additional features, and built-in themes.
|
||||
|
||||
Many of the features are based on the old desktop app, and have improved to be suitable to Marpit. Of course, we added the new features for creating more beautiful deck.
|
||||
|
||||
[marp core]: https://github.com/marp-team/marp-core
|
||||
|
||||
- Built-in themes (Default, Gaia, and _new_ UNCOVER theme)
|
||||
- Included Emoji support 😁
|
||||
- [KaTeX](https://katex.org/) Math typesetting
|
||||
- Auto scaling features (_new_)
|
||||
- Fitting header via `<!-- fit -->` annotation
|
||||
- Scale-down overflowed fence, code, and math block
|
||||
|
||||
# Applications
|
||||
|
||||
## Marp CLI
|
||||
|
||||
[marp cli]: https://github.com/marp-team/marp-cli
|
||||
|
||||
**[Marp CLI]** is a CLI interface of Marpit and Marp Core converter. It's a Swiss-Army knife for Marp slide deck!
|
||||
|
||||
[![](https://raw.githubusercontent.com/marp-team/marp-cli/master/docs/images/marp-cli.gif)][marp cli]
|
||||
|
||||
You can use it right now by running `npx @marp-team/marp-cli` if [Node.js](https://nodejs.org/) is installed.
|
||||
|
||||
- Export to HTML, PDF, and image
|
||||
- Watch the change of your Markdown and theme (`--watch`)
|
||||
- Open preview window for presentation (`--preview`)
|
||||
- Full-customizable engine based on Marpit framework
|
||||
|
||||
Marp had a text editor originally, but you might think that want to write the slide deck with your favorite editor. If you use Vim, you would feel uncomfortable not to be usable Vim style key-binding. From now on, use Marp CLI's watch mode together with original Vim!
|
||||
|
||||
And Marp CLI can create really practicable static HTML as like as a presentation mode! It is powered by deep integration with [Bespoke.js](https://github.com/bespokejs/bespoke).
|
||||
|
||||
Thanks to [Netlify], [Now], and more hosting services, Marp CLI also brings a efficient Git management for creating slide deck just like [GitPitch]. I've created [an example slide](https://yhatt-marp-cli-example.netlify.com/) managed via [GitHub repository](https://github.com/yhatt/marp-cli-example) as a good starter to help writing your slide deck. Try to use it via "Deploy to Netlify" button on [README](https://github.com/yhatt/marp-cli-example/blob/master/README.md#usage)!
|
||||
|
||||
[netlify]: https://www.netlify.com/
|
||||
[now]: https://zeit.co/now/
|
||||
[gitpitch]: https://gitpitch.com/
|
||||
|
||||
## Marp Web (_tech demo_)
|
||||
|
||||
**[Marp Web]** is a Web interface of Marp presentation writer. It allows writing your slide deck as like as a traditional desktop app.
|
||||
|
||||
> The current Marp Web is just a tech demo. We are planning to re-implement Marp Web based on well-known framework (like React) for building SPA.
|
||||
|
||||
[marp web]: https://web.marp.app/
|
||||
|
||||
### Progressive Web Apps
|
||||
|
||||
It made [some strong oppositions by users that is using Marp in offline](https://github.com/yhatt/marp/issues/174#issuecomment-294594856) when an idea of migration to web-based app is proposed for keeping maintainability of Marp. It was caused that a thinking of PWA was not general at that time.
|
||||
|
||||
And 2 years later, the time has come to use PWA! After the first access to **[https://web.marp.app/][marp web]**, Marp Web would be ready to use in both of online and offline. Online resources to use the web interface would be cached in your browser, and use them when network is offline.
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/marp-team/marp-web/master/desktop-pwa.png" width="600">][marp web]
|
||||
|
||||
### Use via any devices
|
||||
|
||||
By migrating to the web-based app, Marp will be able using in mobile device: Android and iOS. That's sure it's well suited to the tablet device like iPad.
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/3993388/50569518-5305c800-0daa-11e9-8fa4-08053c9b51cd.png" width="600">
|
||||
|
||||
Marp Web would work also in Chrome OS well. Marp especially has many users in the field of education, and supporting Chrome OS that has large share in its field is meaningful.
|
||||
|
||||
### Blazing-fast live preview ⚡️
|
||||
|
||||
We think Marp's important feature is a blazing-fast live preview. In the web-based app, realizing the same feature had many difficulties.
|
||||
|
||||
In currently published tech-demo, you can try Marp's really fast preview on the web. The preview applies as soon as typing, and it would not block your typing even if you have a large Markdown slides over than 100 pages.
|
||||
|
||||
# Integrations
|
||||
|
||||
The modulized Marp Core brought Marp integrations for some tools.
|
||||
|
||||
## [Marp for VS Code][marp vscode]
|
||||
|
||||
Honestly, I don't think to want to make a new editor because there are many great Markdown editors in the world. I had been thinking it would be awesome if Marp could integrate with a something else powerful Markdown editor. And now, Marp can use in [Visual Studio Code](https://code.visualstudio.com/)!
|
||||
|
||||
[<img src="https://raw.githubusercontent.com/marp-team/marp-vscode/master/images/screenshot.png" width="600" />][marp vscode]
|
||||
|
||||
It was realized because VS Code is using the same Markdown engine (markdown-it) as Marpit framework. Of course you can export slides as PDF and HTML easily, powered by [Marp CLI].
|
||||
|
||||
[marp vscode]: https://marketplace.visualstudio.com/items?itemName=marp-team.marp-vscode
|
||||
|
||||
## [Marp React] & [Marp Vue] (In development)
|
||||
|
||||
[marp react]: https://github.com/marp-team/marp-react
|
||||
[marp vue]: https://github.com/marp-team/marp-vue
|
||||
|
||||
Marp's blazing fast live-preview is not only for ours! We provide Marp renderer component into [React][marp react] and [Vue][marp vue]. Both Marp React and Marp Vue have supported the incremental update using framework's virtual DOM, and they are been easy to build your app.
|
||||
|
||||
Especially, Marp React would become to the base of the future of [Marp Web].
|
||||
|
||||
# Migration plan
|
||||
|
||||
## Desktop app ([yhatt/marp](https://github.com/yhatt/marp))
|
||||
|
||||
If you are using an old Marp application, **you should migrate to use Marp Next tools.** I NEVER recommend continue to use the old Marp, because _its maintainance has stopped 2 years ago and there is concern about security issues._
|
||||
|
||||
In future, the main interface would become to Marp Web. We have bet to PWA technology that has a lot of advantages. The desktop app is planned as "Marp Desktop" but it just may become a wrapper of Web interface.
|
||||
|
||||
I would stop publishing the old Marp and archive its repository if Marp Web has grown to become replacable the old Marp.
|
||||
|
||||
## Your slide deck
|
||||
|
||||
Your Markdown slides written in the old Marp syntax should rewrite to suit to the brand-new Marp ecosystem.
|
||||
|
||||
In a new Marp, we have reconsidered Markdown syntax based on feedback to the old Marp app. So, some syntaxs are losing compatibillity.
|
||||
|
||||
### Syntax
|
||||
|
||||
- In Marp Core, non-whitelisted HTML elements are disabled by default because of security reason. Currently our whitelist includes only `<br>` element. Some Marp Next tools has provided preference to enable HTML, but you should take care for enabling HTML in untrusted Markdown.
|
||||
|
||||
### Directives
|
||||
|
||||
- Directives would be parsed by YAML parser tuned for Marp (Marpit). Thus spot directive prefix `*` is changed to `_` for keeping YAML syntax.
|
||||
- Slide size is no longer changeable by Markdown. So `size` directive is removed too. Instead use theme CSS.
|
||||
- `page_number` directive is renamed to `paginate`.
|
||||
- `template` directive is renewed to use `class` directive. It can define HTML class per slides.
|
||||
- `prerender` directive is removed. It brings user confusing about exported PDF quality.
|
||||
|
||||
### Image
|
||||
|
||||
- Background image `![bg]()` has no filter applied by default. Try using `![bg opacity]()` if you want.
|
||||
- The inline image is no longer scalable by percentage `![50%]()`. (It's not supported in Firefox) Instead you can use `width` (`w`) and `height` (`w`) keyword to resize image.
|
||||
- `![center]()` won't work. It requires changing image to the block element and brings confusion to theme author. You can tweak style if you want.
|
||||
|
||||
```html
|
||||
<style>
|
||||
img[alt~='center'] {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
# Try Marp Next!
|
||||
|
||||
Marp Next just focuses to build the ecosystem for Markdown slide deck with pure open source. We expect to expand Marp productivity together with open source community.
|
||||
|
||||
We still have stood at the beginning of the brand-new ecosystem. Are you interested to Marp team and our ecosystem? We welcome to start your contribution! See [our contributing guideline](https://github.com/marp-team/marp/blob/master/.github/CONTRIBUTING.md) and get started!
|
||||
|
||||
> PS. I've started [Patreon](https://www.patreon.com/yhatt) and stood in a line of [GitHub Sponsors](https://github.com/sponsors). These are also good contribution if you want to help my working for open source.
|
6
website/gatsby-browser.js
Normal file
6
website/gatsby-browser.js
Normal file
@ -0,0 +1,6 @@
|
||||
const { layoutFocusTarget } = require('./symbol')
|
||||
|
||||
exports.onRouteUpdate = () => {
|
||||
const focusTarget = window[layoutFocusTarget]
|
||||
if (focusTarget) focusTarget.focus()
|
||||
}
|
55
website/gatsby-config.js
Normal file
55
website/gatsby-config.js
Normal file
@ -0,0 +1,55 @@
|
||||
const autoprefixer = require('autoprefixer')
|
||||
|
||||
module.exports = {
|
||||
siteMetadata: {
|
||||
title: 'Marp: Markdown Presentation Ecosystem',
|
||||
description:
|
||||
'Marp is the ecosystem to write your presentation with plain Markdown.',
|
||||
keywords: ['markdown', 'slide', 'deck', 'presentation', 'marp', 'marpit'],
|
||||
},
|
||||
plugins: [
|
||||
'gatsby-plugin-typescript',
|
||||
'gatsby-plugin-sharp',
|
||||
'gatsby-plugin-react-helmet',
|
||||
{
|
||||
resolve: 'gatsby-plugin-sass',
|
||||
options: {
|
||||
cssLoaderOptions: {
|
||||
postCssPlugins: [autoprefixer()],
|
||||
camelCase: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-source-filesystem',
|
||||
options: {
|
||||
path: `${__dirname}/blog`,
|
||||
name: 'blog-pages',
|
||||
},
|
||||
},
|
||||
{
|
||||
resolve: 'gatsby-transformer-remark',
|
||||
options: {
|
||||
excerpt_separator: '<!-- more -->',
|
||||
plugins: [
|
||||
'gatsby-remark-autolink-headers',
|
||||
{
|
||||
resolve: 'gatsby-remark-external-links',
|
||||
options: { target: '_blank', rel: 'noopener' },
|
||||
},
|
||||
'gatsby-remark-prismjs',
|
||||
{
|
||||
resolve: `gatsby-remark-images`,
|
||||
options: {
|
||||
maxWidth: 1200,
|
||||
quality: 65,
|
||||
withWebp: true,
|
||||
wrapperStyle: 'margin-top:2rem;margin-bottom:2rem;',
|
||||
},
|
||||
},
|
||||
'gatsby-remark-copy-linked-files',
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
43
website/gatsby-node.js
Normal file
43
website/gatsby-node.js
Normal file
@ -0,0 +1,43 @@
|
||||
const path = require('path')
|
||||
const { createFilePath } = require('gatsby-source-filesystem')
|
||||
const blogTpl = path.resolve('src/templates/blog.tsx')
|
||||
|
||||
exports.createPages = ({ actions, graphql }) => {
|
||||
const { createPage } = actions
|
||||
|
||||
return graphql(`
|
||||
{
|
||||
allMarkdownRemark(
|
||||
sort: { order: DESC, fields: [frontmatter___date] }
|
||||
limit: 1000
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
fields {
|
||||
path
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`).then(result => {
|
||||
if (result.errors) return Promise.reject(result.errors)
|
||||
|
||||
result.data.allMarkdownRemark.edges.forEach(({ node }) =>
|
||||
createPage({ path: node.fields.path, component: blogTpl })
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
exports.onCreateNode = ({ node, actions, getNode }) => {
|
||||
const { createNodeField } = actions
|
||||
|
||||
if (node.internal.type === `MarkdownRemark`) {
|
||||
const date = new Date(node.frontmatter.date)
|
||||
const slug = path.basename(createFilePath({ node, getNode }))
|
||||
|
||||
createNodeField({ node, name: 'slug', value: slug })
|
||||
createNodeField({ node, name: 'path', value: `/blog/${slug}` })
|
||||
createNodeField({ node, name: 'reserved', value: date > new Date() })
|
||||
}
|
||||
}
|
36
website/package.json
Normal file
36
website/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "@marp-team/marp-website",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "yarn run -s clean && gatsby build",
|
||||
"check-ts": "tsc",
|
||||
"clean": "rimraf .cache public",
|
||||
"start": "gatsby develop"
|
||||
},
|
||||
"dependencies": {
|
||||
"autoprefixer": "^9.6.0",
|
||||
"gatsby": "^2.8.5",
|
||||
"gatsby-plugin-react-helmet": "^3.0.12",
|
||||
"gatsby-plugin-sass": "^2.0.11",
|
||||
"gatsby-plugin-sharp": "^2.1.3",
|
||||
"gatsby-plugin-typescript": "^2.0.15",
|
||||
"gatsby-remark-autolink-headers": "^2.0.16",
|
||||
"gatsby-remark-copy-linked-files": "^2.0.13",
|
||||
"gatsby-remark-external-links": "^0.0.4",
|
||||
"gatsby-remark-images": "^3.0.14",
|
||||
"gatsby-remark-prismjs": "^3.2.10",
|
||||
"gatsby-source-filesystem": "^2.0.38",
|
||||
"gatsby-transformer-remark": "^2.3.12",
|
||||
"node-sass": "^4.12.0",
|
||||
"normalize.css": "^8.0.1",
|
||||
"prismjs": "^1.16.0",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-helmet": "^5.2.1",
|
||||
"react-sticky": "^6.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"rimraf": "^2.6.3"
|
||||
}
|
||||
}
|
1
website/src/components/assets/marp-logo.svg
Normal file
1
website/src/components/assets/marp-logo.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 40"><path fill="#009bda" d="M40 0L0 40h20l20-20V0z"/><path fill="#78c5e9" d="M40 20v20h20V0L40 20z"/><path fill="#007aad" d="M20 40h20V20L20 40z"/></svg>
|
After Width: | Height: | Size: 209 B |
143
website/src/components/blog.tsx
Normal file
143
website/src/components/blog.tsx
Normal file
@ -0,0 +1,143 @@
|
||||
import { Link, graphql } from 'gatsby'
|
||||
import React from 'react'
|
||||
import Button from './button'
|
||||
import Contents from './contents'
|
||||
import { combineClass } from './utils'
|
||||
import style from './style/blog.module.scss'
|
||||
|
||||
interface BlogBaseProps {
|
||||
fields: {
|
||||
path: string
|
||||
reserved: boolean
|
||||
}
|
||||
frontmatter: {
|
||||
author: string
|
||||
date: string
|
||||
github?: string
|
||||
title: string
|
||||
}
|
||||
key?: any
|
||||
}
|
||||
|
||||
export interface BlogProps extends BlogBaseProps {
|
||||
html: string
|
||||
}
|
||||
|
||||
export interface BlogExcerptedProps extends BlogBaseProps {
|
||||
excerpt: string
|
||||
}
|
||||
|
||||
const Meta: React.FC<{
|
||||
author: string
|
||||
date: string
|
||||
github?: string
|
||||
reserved: boolean
|
||||
}> = ({ author, date, github, reserved }) => (
|
||||
<span className={style.meta}>
|
||||
<time className={style.metaTime} dateTime={date}>
|
||||
Posted {date}
|
||||
</time>
|
||||
by
|
||||
<span className={style.metaAuthor}>
|
||||
{github ? (
|
||||
<a
|
||||
className={style.metaGithub}
|
||||
href={`https://github.com/${github}`}
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
alt={author}
|
||||
className={style.metaGithubIcon}
|
||||
src={`https://github.com/${github}.png`}
|
||||
/>
|
||||
|
||||
<span className={style.metaGithubAuthor}>{author}</span>
|
||||
</a>
|
||||
) : (
|
||||
author
|
||||
)}
|
||||
</span>
|
||||
{reserved && (
|
||||
<>
|
||||
<span className={style.metaReserved}>Reserved</span>
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
|
||||
const BlogBase: React.FC<BlogBaseProps> = ({
|
||||
fields: { path, reserved },
|
||||
frontmatter: { author, date, github, title },
|
||||
children,
|
||||
...props
|
||||
}) => (
|
||||
<Contents {...props}>
|
||||
<Link className={style.titleLink} to={path}>
|
||||
<h1 className={style.title}>{title}</h1>
|
||||
</Link>
|
||||
<p>
|
||||
<Meta author={author} github={github} date={date} reserved={reserved} />
|
||||
</p>
|
||||
{children}
|
||||
</Contents>
|
||||
)
|
||||
|
||||
const Blog: React.FC<BlogProps> = ({ html, ...props }) => (
|
||||
<BlogBase {...props}>
|
||||
<section
|
||||
className={style.blog}
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</BlogBase>
|
||||
)
|
||||
|
||||
export default Blog
|
||||
|
||||
export const BlogExcerpted: React.FC<BlogExcerptedProps> = ({
|
||||
excerpt,
|
||||
...props
|
||||
}) => (
|
||||
<BlogBase
|
||||
{...combineClass(
|
||||
props,
|
||||
style.excerpt,
|
||||
props.fields.reserved && style.reserved
|
||||
)}
|
||||
>
|
||||
<section
|
||||
className={style.blog}
|
||||
dangerouslySetInnerHTML={{ __html: excerpt }}
|
||||
/>
|
||||
<p>
|
||||
<Button color="primary" outline to={props.fields.path}>
|
||||
Read more
|
||||
</Button>
|
||||
</p>
|
||||
</BlogBase>
|
||||
)
|
||||
|
||||
export const query = graphql`
|
||||
fragment BlogBase on MarkdownRemark {
|
||||
fields {
|
||||
path
|
||||
reserved
|
||||
}
|
||||
frontmatter {
|
||||
author
|
||||
date(formatString: "MMMM DD, YYYY")
|
||||
github
|
||||
title
|
||||
}
|
||||
}
|
||||
|
||||
fragment Blog on MarkdownRemark {
|
||||
...BlogBase
|
||||
html
|
||||
}
|
||||
|
||||
fragment BlogExcerpted on MarkdownRemark {
|
||||
...BlogBase
|
||||
excerpt(pruneLength: 210, format: HTML)
|
||||
}
|
||||
`
|
33
website/src/components/button.tsx
Normal file
33
website/src/components/button.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Link } from 'gatsby'
|
||||
import React from 'react'
|
||||
import style from './style/button.module.scss'
|
||||
import { combineClass } from './utils'
|
||||
|
||||
enum ButtonColor {
|
||||
default = 'default',
|
||||
primary = 'primary',
|
||||
}
|
||||
|
||||
export interface ButtonProps {
|
||||
color?: ButtonColor | keyof typeof ButtonColor
|
||||
href?: string // Link to external resource
|
||||
outline?: boolean
|
||||
to?: string // Link to internal page
|
||||
[delegatedProp: string]: any
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = props => {
|
||||
const { href, outline, to } = props
|
||||
const colorStyle = style[`color-${props.color || ButtonColor.default}`]
|
||||
const element = to ? Link : href ? 'a' : 'span'
|
||||
|
||||
return React.createElement(element, {
|
||||
...combineClass(props, style.button, outline && style.outline, colorStyle),
|
||||
tabIndex: 0,
|
||||
role: 'button', // Use <span> instead of <button> to handle focus ring correctly
|
||||
color: undefined,
|
||||
outline: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
export default Button
|
9
website/src/components/contents.tsx
Normal file
9
website/src/components/contents.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
import style from './style/contents.module.scss'
|
||||
import { combineClass } from './utils'
|
||||
|
||||
const Contents: React.FC = props => (
|
||||
<div {...combineClass(props, style.contents)} />
|
||||
)
|
||||
|
||||
export default Contents
|
38
website/src/components/header.tsx
Normal file
38
website/src/components/header.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import { Link } from 'gatsby'
|
||||
import React from 'react'
|
||||
import { Locations } from './layout'
|
||||
import Menuitem from './menuitem'
|
||||
import logo from './assets/marp-logo.svg'
|
||||
import style from './style/header.module.scss'
|
||||
import { combineClass } from './utils'
|
||||
|
||||
export interface HeaderProps {
|
||||
stuck: boolean
|
||||
location: string | Locations
|
||||
[delegated: string]: any
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = ({ stuck, location, ...props }) => (
|
||||
<header {...combineClass(props, style.header, stuck && style.stuck)}>
|
||||
<Link to="/" className={style.logoLink}>
|
||||
<img alt="Marp" className={style.logo} src={logo} />
|
||||
</Link>
|
||||
<nav className={style.nav}>
|
||||
<Menuitem
|
||||
to="/blog"
|
||||
active={!!location && location.startsWith(Locations.blog)}
|
||||
>
|
||||
Blog
|
||||
</Menuitem>
|
||||
<Menuitem
|
||||
href="https://github.com/marp-team/marp/"
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
>
|
||||
GitHub
|
||||
</Menuitem>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
|
||||
export default Header
|
23
website/src/components/hero.tsx
Normal file
23
website/src/components/hero.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react'
|
||||
import logo from '../../../marp.png'
|
||||
import style from './style/hero.module.scss'
|
||||
import Button from './button'
|
||||
|
||||
const Hero: React.FC = () => (
|
||||
<section className={style.hero}>
|
||||
<div className={style.lead}>
|
||||
<img alt="Marp" className={style.logo} src={logo} />
|
||||
<p className={style.description}>Markdown presentation ecosystem</p>
|
||||
<p>
|
||||
<Button
|
||||
color="primary"
|
||||
href="https://github.com/marp-team/marp/#readme"
|
||||
>
|
||||
Find Marp tools at repo...
|
||||
</Button>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
export default Hero
|
81
website/src/components/layout.tsx
Normal file
81
website/src/components/layout.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
import React from 'react'
|
||||
import Helmet from 'react-helmet'
|
||||
import { StickyContainer, Sticky } from 'react-sticky'
|
||||
import { StaticQuery, graphql } from 'gatsby'
|
||||
import Header from './header'
|
||||
import Hero from './hero'
|
||||
import layoutStyle from './style/layout.module.scss'
|
||||
import { layoutFocusTarget } from '../../symbol'
|
||||
import './style/layout.scss'
|
||||
|
||||
export interface LayoutProps {
|
||||
children?: React.ReactNode
|
||||
hero?: boolean
|
||||
location: string | Locations
|
||||
title?: string
|
||||
}
|
||||
|
||||
export enum Locations {
|
||||
root = '/',
|
||||
blog = '/blog',
|
||||
}
|
||||
|
||||
const renderHelmet = (meta, title?: string) => {
|
||||
const titleText = title ? `${title} | ${meta.title}` : meta.title
|
||||
|
||||
return (
|
||||
<Helmet
|
||||
title={titleText}
|
||||
meta={[
|
||||
{ name: 'description', content: meta.description },
|
||||
{ name: 'keywords', content: meta.keywords.join(',') },
|
||||
]}
|
||||
>
|
||||
<html lang="en" />
|
||||
</Helmet>
|
||||
)
|
||||
}
|
||||
|
||||
const StickyWrapper = class extends React.Component {
|
||||
render = () => this.props.children
|
||||
}
|
||||
|
||||
const Layout: React.FC<LayoutProps> = ({ children, hero, location, title }) => (
|
||||
<StaticQuery
|
||||
query={graphql`
|
||||
query SiteTitleQuery {
|
||||
site {
|
||||
siteMetadata {
|
||||
title
|
||||
description
|
||||
keywords
|
||||
}
|
||||
}
|
||||
}
|
||||
`}
|
||||
render={data => (
|
||||
<StickyContainer
|
||||
className={layoutStyle.container}
|
||||
ref={cont => (window[layoutFocusTarget] = cont && cont.getParent())}
|
||||
tabIndex="-1"
|
||||
>
|
||||
{renderHelmet(data.site.siteMetadata, title)}
|
||||
{hero && <Hero />}
|
||||
<Sticky relative>
|
||||
{({ style, isSticky }) => (
|
||||
<StickyWrapper>
|
||||
<Header
|
||||
location={location}
|
||||
stuck={!hero || isSticky}
|
||||
style={style}
|
||||
/>
|
||||
</StickyWrapper>
|
||||
)}
|
||||
</Sticky>
|
||||
{children}
|
||||
</StickyContainer>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
|
||||
export default Layout as React.FC<Partial<LayoutProps>>
|
22
website/src/components/menuitem.tsx
Normal file
22
website/src/components/menuitem.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import Button, { ButtonProps } from './button'
|
||||
import style from './style/menuitem.module.scss'
|
||||
import { combineClass } from './utils'
|
||||
|
||||
export interface MenuitemProps extends ButtonProps {
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
const Menuitem: React.FC<MenuitemProps> = props => {
|
||||
return (
|
||||
<Button
|
||||
{...combineClass({ ...props, active: undefined }, style.menuitem)}
|
||||
outline={false}
|
||||
data-active={`${!!props.active}`}
|
||||
>
|
||||
<span className={style.menuitemContainer}>{props.children}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default Menuitem
|
1
website/src/components/style/_common.scss
Normal file
1
website/src/components/style/_common.scss
Normal file
@ -0,0 +1 @@
|
||||
@import '../../style/common';
|
123
website/src/components/style/_fonts.scss
Normal file
123
website/src/components/style/_fonts.scss
Normal file
@ -0,0 +1,123 @@
|
||||
// https://fonts.googleapis.com/css?family=Quicksand:500,700|Source+Code+Pro:500,700
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: local('Quicksand Medium'), local('Quicksand-Medium'),
|
||||
url(https://fonts.gstatic.com/s/quicksand/v8/6xKodSZaM9iE8KbpRA_p2HcYQML_FYzokA9q.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: local('Quicksand Medium'), local('Quicksand-Medium'),
|
||||
url(https://fonts.gstatic.com/s/quicksand/v8/6xKodSZaM9iE8KbpRA_p2HcYQcL_FYzokA9q.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
|
||||
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: local('Quicksand Medium'), local('Quicksand-Medium'),
|
||||
url(https://fonts.gstatic.com/s/quicksand/v8/6xKodSZaM9iE8KbpRA_p2HcYT8L_FYzokA.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
||||
U+FEFF, U+FFFD;
|
||||
}
|
||||
/* vietnamese */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Quicksand Bold'), local('Quicksand-Bold'),
|
||||
url(https://fonts.gstatic.com/s/quicksand/v8/6xKodSZaM9iE8KbpRA_pkHEYQML_FYzokA9q.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0102-0103, U+0110-0111, U+1EA0-1EF9, U+20AB;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Quicksand Bold'), local('Quicksand-Bold'),
|
||||
url(https://fonts.gstatic.com/s/quicksand/v8/6xKodSZaM9iE8KbpRA_pkHEYQcL_FYzokA9q.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
|
||||
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Quicksand';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Quicksand Bold'), local('Quicksand-Bold'),
|
||||
url(https://fonts.gstatic.com/s/quicksand/v8/6xKodSZaM9iE8KbpRA_pkHEYT8L_FYzokA.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
||||
U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'),
|
||||
url(https://fonts.gstatic.com/s/sourcecodepro/v8/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDvecq7Gq0DDzS.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
|
||||
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: local('Source Code Pro Medium'), local('SourceCodePro-Medium'),
|
||||
url(https://fonts.gstatic.com/s/sourcecodepro/v8/HI_XiYsKILxRpg3hIP6sJ7fM7PqtzsjDs-cq7Gq0DA.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
||||
U+FEFF, U+FFFD;
|
||||
}
|
||||
/* latin-ext */
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Source Code Pro Bold'), local('SourceCodePro-Bold'),
|
||||
url(https://fonts.gstatic.com/s/sourcecodepro/v8/HI_XiYsKILxRpg3hIP6sJ7fM7Pqths7Dvecq7Gq0DDzS.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
|
||||
U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: local('Source Code Pro Bold'), local('SourceCodePro-Bold'),
|
||||
url(https://fonts.gstatic.com/s/sourcecodepro/v8/HI_XiYsKILxRpg3hIP6sJ7fM7Pqths7Ds-cq7Gq0DA.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
|
||||
U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
|
||||
U+FEFF, U+FFFD;
|
||||
}
|
73
website/src/components/style/_prism.scss
Normal file
73
website/src/components/style/_prism.scss
Normal file
@ -0,0 +1,73 @@
|
||||
.namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token {
|
||||
&.comment,
|
||||
&.prolog,
|
||||
&.doctype,
|
||||
&.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
&.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
&.property,
|
||||
&.tag,
|
||||
&.boolean,
|
||||
&.number,
|
||||
&.constant,
|
||||
&.symbol,
|
||||
&.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
&.selector,
|
||||
&.attr-name,
|
||||
&.string,
|
||||
&.char,
|
||||
&.builtin,
|
||||
&.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
&.operator,
|
||||
&.entity,
|
||||
&.url,
|
||||
.language-css &.string,
|
||||
.style &.string {
|
||||
color: #9a6e3a;
|
||||
}
|
||||
|
||||
&.atrule,
|
||||
&.attr-value,
|
||||
&.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
&.function,
|
||||
&.class-name {
|
||||
color: #dd4a68;
|
||||
}
|
||||
|
||||
&.regex,
|
||||
&.important,
|
||||
&.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
&.important,
|
||||
&.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
&.entity {
|
||||
cursor: help;
|
||||
}
|
||||
}
|
59
website/src/components/style/blog.module.scss
Normal file
59
website/src/components/style/blog.module.scss
Normal file
@ -0,0 +1,59 @@
|
||||
@import './common';
|
||||
|
||||
.blog {
|
||||
font-weight: 400;
|
||||
font-family: $font-family;
|
||||
}
|
||||
|
||||
.excerpt {
|
||||
&.reserved {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.meta {
|
||||
&,
|
||||
&-github {
|
||||
align-items: center;
|
||||
color: #888;
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&-github {
|
||||
color: currentColor;
|
||||
text-decoration: none;
|
||||
|
||||
&-icon {
|
||||
background: #f0f0f0;
|
||||
border-radius: 1.75em;
|
||||
color: transparent;
|
||||
display: block;
|
||||
height: 1.75em;
|
||||
overflow: hidden;
|
||||
text-indent: 1.75em;
|
||||
white-space: nowrap;
|
||||
width: 1.75em;
|
||||
}
|
||||
|
||||
@at-root {
|
||||
a#{&}:hover {
|
||||
color: #666;
|
||||
|
||||
&:active {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-reserved {
|
||||
border-radius: 0.375em;
|
||||
border: 1px solid currentColor;
|
||||
box-sizing: border-box;
|
||||
color: #930;
|
||||
font-size: 0.75em;
|
||||
padding: 0.375em;
|
||||
}
|
||||
}
|
104
website/src/components/style/button.module.scss
Normal file
104
website/src/components/style/button.module.scss
Normal file
@ -0,0 +1,104 @@
|
||||
@import './common';
|
||||
|
||||
.button {
|
||||
appearance: none;
|
||||
border-radius: 1.5em;
|
||||
border: 0;
|
||||
box-shadow: 0 3px 6px rgba(#000, 0.25);
|
||||
box-sizing: content-box;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
letter-spacing: 1px;
|
||||
line-height: 1em;
|
||||
margin: 0.25em;
|
||||
outline: 0;
|
||||
overflow: hidden;
|
||||
padding: 0.4em 0.75em;
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
|
||||
&.outline {
|
||||
border-style: solid;
|
||||
border-width: 0.125em;
|
||||
padding-bottom: 0.25em;
|
||||
padding-top: 0.25em;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 0.15em rgba($brand-secondary, 0.5),
|
||||
0 3px 6px rgba(#000, 0.25);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transition: color 0.15s linear, background-color 0.15s linear;
|
||||
|
||||
&:active {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.color {
|
||||
&-default {
|
||||
background: $base-background;
|
||||
color: #666;
|
||||
|
||||
&:hover {
|
||||
background: mix($base-background, #000, 95%);
|
||||
color: #666;
|
||||
|
||||
&:active {
|
||||
background: mix($base-background, #000, 90%);
|
||||
}
|
||||
}
|
||||
|
||||
&.outline {
|
||||
border-color: #666;
|
||||
|
||||
&:hover {
|
||||
background: #666;
|
||||
color: $base-background;
|
||||
|
||||
&:active {
|
||||
background: $brand-dark;
|
||||
border-color: $brand-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-primary {
|
||||
background: $brand-primary
|
||||
linear-gradient(30deg, transparent, rgba($base-background, 0.2));
|
||||
color: $base-background;
|
||||
|
||||
&:hover {
|
||||
background-color: mix($brand-primary, $brand-dark);
|
||||
color: $base-background;
|
||||
|
||||
&:active {
|
||||
background-color: $brand-dark;
|
||||
color: $base-background;
|
||||
}
|
||||
}
|
||||
|
||||
&.outline {
|
||||
background: $base-background;
|
||||
border-color: $brand-primary;
|
||||
color: $brand-primary;
|
||||
|
||||
&:hover {
|
||||
color: $base-background;
|
||||
background: $brand-primary;
|
||||
|
||||
&:active {
|
||||
background: $brand-dark;
|
||||
border-color: $brand-dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
13
website/src/components/style/contents.module.scss
Normal file
13
website/src/components/style/contents.module.scss
Normal file
@ -0,0 +1,13 @@
|
||||
@import './common';
|
||||
|
||||
.contents {
|
||||
box-sizing: border-box;
|
||||
line-height: 1.45;
|
||||
margin: 40px auto;
|
||||
max-width: 900px;
|
||||
padding: 0 40px;
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
}
|
41
website/src/components/style/header.module.scss
Normal file
41
website/src/components/style/header.module.scss
Normal file
@ -0,0 +1,41 @@
|
||||
@import './common';
|
||||
|
||||
.header {
|
||||
align-items: center;
|
||||
background: $base-background;
|
||||
border-bottom: 1px solid $base-background;
|
||||
box-sizing: border-box;
|
||||
color: #444;
|
||||
display: flex;
|
||||
font-size: 1rem;
|
||||
height: $header-height;
|
||||
overflow: hidden;
|
||||
padding: 0 25px;
|
||||
transition: border-color 0.15s linear;
|
||||
z-index: 1;
|
||||
|
||||
> * {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
&.stuck {
|
||||
border-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
height: 40px;
|
||||
width: auto;
|
||||
|
||||
&-link {
|
||||
margin: 0 25px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nav {
|
||||
display: flex;
|
||||
flex: 1 0 auto;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
}
|
31
website/src/components/style/hero.module.scss
Normal file
31
website/src/components/style/hero.module.scss
Normal file
@ -0,0 +1,31 @@
|
||||
@import './common';
|
||||
|
||||
.hero {
|
||||
margin: 0;
|
||||
background: $hero-background;
|
||||
display: flex;
|
||||
height: calc(100vh - #{$header-height});
|
||||
min-height: 320px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.lead {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: inline-block;
|
||||
max-width: 700px;
|
||||
max-height: 50%;
|
||||
object-fit: contain;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: calc(12px + 0.75vw);
|
||||
margin: 20px 0 0 0;
|
||||
}
|
17
website/src/components/style/layout.module.scss
Normal file
17
website/src/components/style/layout.module.scss
Normal file
@ -0,0 +1,17 @@
|
||||
@import './common';
|
||||
|
||||
.container {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow: auto;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
|
||||
-webkit-overflow-scrolling: touch;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
> * {
|
||||
-webkit-tap-highlight-color: initial;
|
||||
}
|
||||
}
|
179
website/src/components/style/layout.scss
Normal file
179
website/src/components/style/layout.scss
Normal file
@ -0,0 +1,179 @@
|
||||
@import '~normalize.css';
|
||||
@import './common';
|
||||
@import './fonts';
|
||||
@import './prism';
|
||||
|
||||
html,
|
||||
body {
|
||||
background: $base-background;
|
||||
color: $base-color;
|
||||
font-family: $font-family-brand;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
height: 100%;
|
||||
letter-spacing: 0.04em;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-bottom: 2px solid mix($base-background, $base-color, 75%);
|
||||
height: 0;
|
||||
margin: 2rem 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
margin: 2rem 0 1rem 0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.7em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.25em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $brand-primary;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: $brand-dark;
|
||||
transition: color 0.15s linear;
|
||||
|
||||
&:active {
|
||||
color: mix($base-color, $brand-dark);
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol,
|
||||
pre,
|
||||
blockquote {
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding: 0 0 0 1.75em;
|
||||
|
||||
li {
|
||||
margin: 0.5rem 0;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
code,
|
||||
pre {
|
||||
background-color: $base-code-background;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 1px 4px rgba(#000, 0.15);
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: 'Source Code Pro', 'Courier New', Courier, monospace;
|
||||
font-size: 0.85em;
|
||||
margin: 0;
|
||||
padding: 0.15em 0.5em;
|
||||
}
|
||||
|
||||
pre {
|
||||
box-sizing: border-box;
|
||||
line-height: 1.1em;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
|
||||
> code {
|
||||
background-color: transparent;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
display: inline-block;
|
||||
margin: 1em;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: 2px solid $brand-secondary;
|
||||
padding: 0 0 0 1em;
|
||||
|
||||
& & {
|
||||
border-color: mix($base-background, $brand-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
margin: 0 auto;
|
||||
|
||||
> thead,
|
||||
> tbody,
|
||||
> tfoot {
|
||||
> tr {
|
||||
> td,
|
||||
> th {
|
||||
border-bottom: 1px solid mix($base-background, $base-color, 75%);
|
||||
padding: 0.4em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> thead > tr,
|
||||
> tbody > tr:last-child {
|
||||
> td,
|
||||
> th {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
max-width: 100%;
|
||||
}
|
47
website/src/components/style/menuitem.module.scss
Normal file
47
website/src/components/style/menuitem.module.scss
Normal file
@ -0,0 +1,47 @@
|
||||
@import './common';
|
||||
|
||||
.menuitem {
|
||||
background: $base-background !important;
|
||||
box-sizing: border-box;
|
||||
color: currentColor;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-shadow: none !important;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
letter-spacing: 2px;
|
||||
margin: 0;
|
||||
text-transform: uppercase;
|
||||
|
||||
&-container {
|
||||
padding: 0.4em 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: currentColor;
|
||||
|
||||
.menuitem-container {
|
||||
box-shadow: inset 0 -0.2em rgba(#000, 0.15);
|
||||
transition: box-shadow 0.2s linear;
|
||||
}
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:hover:active,
|
||||
&:focus {
|
||||
color: currentColor;
|
||||
|
||||
.menuitem-container {
|
||||
box-shadow: inset 0 -0.2em mix(mix(#000, #fff, 15%), $brand-primary);
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
&[data-active='true'] {
|
||||
.menuitem-container {
|
||||
box-shadow: inset 0 -0.2em $brand-primary;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
6
website/src/components/utils.ts
Normal file
6
website/src/components/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function combineClass(from, ...classes: any[]) {
|
||||
return {
|
||||
...from,
|
||||
className: [from.className, ...classes].filter(k => k).join(' '),
|
||||
}
|
||||
}
|
14
website/src/pages/404.tsx
Normal file
14
website/src/pages/404.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import React from 'react'
|
||||
import Contents from '../components/contents'
|
||||
import Layout from '../components/layout'
|
||||
|
||||
const NotFoundPage = () => (
|
||||
<Layout>
|
||||
<Contents>
|
||||
<h1>NOT FOUND</h1>
|
||||
<p>You just hit a route that doesn't exist... the sadness.</p>
|
||||
</Contents>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
export default NotFoundPage
|
40
website/src/pages/blog.tsx
Normal file
40
website/src/pages/blog.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import { graphql } from 'gatsby'
|
||||
import React from 'react'
|
||||
import { BlogExcerpted } from '../components/blog'
|
||||
import Layout from '../components/layout'
|
||||
|
||||
const blog = ({
|
||||
location,
|
||||
data: {
|
||||
allMarkdownRemark: { edges },
|
||||
},
|
||||
}) => (
|
||||
<Layout location={location.pathname} title="Blog">
|
||||
{edges
|
||||
.map(
|
||||
({ node }) =>
|
||||
(process.env.NODE_ENV === 'development' || !node.fields.reserved) && (
|
||||
<BlogExcerpted {...node} key={node.id} />
|
||||
)
|
||||
)
|
||||
.filter(n => n)}
|
||||
</Layout>
|
||||
)
|
||||
|
||||
export default blog
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query {
|
||||
allMarkdownRemark(
|
||||
sort: { order: DESC, fields: [frontmatter___date] }
|
||||
limit: 30
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
...BlogExcerpted
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
6
website/src/pages/index.tsx
Normal file
6
website/src/pages/index.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import React from 'react'
|
||||
import Layout from '../components/layout'
|
||||
|
||||
const index = ({ location }) => <Layout hero location={location.pathname} />
|
||||
|
||||
export default index
|
2
website/src/style/_common.scss
Normal file
2
website/src/style/_common.scss
Normal file
@ -0,0 +1,2 @@
|
||||
@import './variables';
|
||||
@import './mixins';
|
5
website/src/style/_mixins.scss
Normal file
5
website/src/style/_mixins.scss
Normal file
@ -0,0 +1,5 @@
|
||||
@mixin portrait {
|
||||
@media (max-aspect-ratio: 1/1) {
|
||||
@content;
|
||||
}
|
||||
}
|
26
website/src/style/_variables.scss
Normal file
26
website/src/style/_variables.scss
Normal file
@ -0,0 +1,26 @@
|
||||
// Fonts
|
||||
$font-family-base: 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
|
||||
$font-family-brand: 'Quicksand', 'Avenir', 'Century Gothic', sans-serif,
|
||||
$font-family-base;
|
||||
|
||||
$font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif,
|
||||
$font-family-base;
|
||||
|
||||
// Brand colors
|
||||
$brand-primary: #0288d1;
|
||||
$brand-secondary: #67b8e3;
|
||||
$brand-dark: #02669d;
|
||||
|
||||
// Base
|
||||
$base-background: #fff;
|
||||
$base-color: #333;
|
||||
$base-code-background: mix($base-color, $base-background, 3%);
|
||||
|
||||
// Hero
|
||||
$hero-background: linear-gradient(to bottom, $base-background, #f9f9f9);
|
||||
|
||||
// Header
|
||||
$header-height: 100px;
|
20
website/src/templates/blog.tsx
Normal file
20
website/src/templates/blog.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react'
|
||||
import { graphql } from 'gatsby'
|
||||
import Blog from '../components/blog'
|
||||
import Layout from '../components/layout'
|
||||
|
||||
const BlogTemplate = ({ data: { markdownRemark }, location }) => (
|
||||
<Layout location={location.pathname} title={markdownRemark.frontmatter.title}>
|
||||
<Blog {...markdownRemark} />
|
||||
</Layout>
|
||||
)
|
||||
|
||||
export default BlogTemplate
|
||||
|
||||
export const pageQuery = graphql`
|
||||
query($path: String!) {
|
||||
markdownRemark(fields: { path: { eq: $path } }) {
|
||||
...Blog
|
||||
}
|
||||
}
|
||||
`
|
16
website/src/typings.d.ts
vendored
Normal file
16
website/src/typings.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
declare const filePath: string
|
||||
|
||||
declare module '*.png' {
|
||||
export default filePath
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
export default filePath
|
||||
}
|
||||
|
||||
declare module '*.module.scss' {
|
||||
const cssModule: {
|
||||
[className: string]: string
|
||||
}
|
||||
export default cssModule
|
||||
}
|
1
website/symbol.js
Normal file
1
website/symbol.js
Normal file
@ -0,0 +1 @@
|
||||
exports.layoutFocusTarget = Symbol('layout-focus-target')
|
9
website/tsconfig.json
Normal file
9
website/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"noEmit": true,
|
||||
"target": "esnext"
|
||||
},
|
||||
"extends": "../tsconfig.base.json",
|
||||
"include": ["src"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user