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:
Yuki Hattori 2019-06-06 14:02:04 +09:00 committed by GitHub
parent 0a361a00b5
commit d4bfc4e8bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 13896 additions and 9 deletions

35
.circleci/config.yml Normal file
View 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
View File

@ -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 ###

View File

@ -1 +1 @@
v10.13.0
v10.16.0

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
.git/
.vscode/
/website/.cache/
/website/public
node_modules
package.json

3
.prettierrc.yml Normal file
View File

@ -0,0 +1,3 @@
semi: false
singleQuote: true
trailingComma: es5

View File

@ -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

View File

@ -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"

View File

@ -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"
}
}

View File

@ -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
View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"esModuleInterop": true,
"lib": ["es2016", "es2017", "dom"],
"module": "commonjs",
"noEmit": true,
"noImplicitAny": false,
"resolveJsonModule": true,
"strict": true,
"target": "es2015"
}
}

View File

@ -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]&nbsp;(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.

View 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
View 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
View 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
View 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"
}
}

View 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

View 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>
&nbsp;by&nbsp;
<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`}
/>
&nbsp;
<span className={style.metaGithubAuthor}>{author}</span>
</a>
) : (
author
)}
</span>
{reserved && (
<>
&nbsp;<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)
}
`

View 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

View 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

View 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

View 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

View 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>>

View 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

View File

@ -0,0 +1 @@
@import '../../style/common';

View 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;
}

View 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;
}
}

View 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;
}
}

View 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;
}
}
}
}
}

View 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;
}
}

View 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;
}

View 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;
}

View 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;
}
}

View 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%;
}

View 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;
}
}
}

View 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
View 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&#39;t exist... the sadness.</p>
</Contents>
</Layout>
)
export default NotFoundPage

View 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
}
}
}
}
`

View 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

View File

@ -0,0 +1,2 @@
@import './variables';
@import './mixins';

View File

@ -0,0 +1,5 @@
@mixin portrait {
@media (max-aspect-ratio: 1/1) {
@content;
}
}

View 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;

View 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
View 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
View File

@ -0,0 +1 @@
exports.layoutFocusTarget = Symbol('layout-focus-target')

9
website/tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"jsx": "react",
"noEmit": true,
"target": "esnext"
},
"extends": "../tsconfig.base.json",
"include": ["src"]
}

12290
yarn.lock Normal file

File diff suppressed because it is too large Load Diff