16 |
17 |
18 | )
19 | }
20 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: 'babel-eslint',
3 | extends: ['airbnb', 'prettier'],
4 | plugins: ['react', 'jsx-a11y', 'import'],
5 | ignorePatterns: ['slides/'],
6 | rules: {
7 | // Next imports React into pages automatically
8 | 'react/react-in-jsx-scope': 'off',
9 | // Next includes packages like MDX using their bundle
10 | 'import/no-extraneous-dependencies': 'off',
11 | // Turn off const conversion
12 | 'prefer-const': 'off',
13 | },
14 | env: {
15 | browser: true,
16 | },
17 | }
18 |
--------------------------------------------------------------------------------
/public/zeit.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/context/ModeContext.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useEffect, useState } from 'react'
2 | import { useRouter } from 'next/router'
3 | import { MODES } from '../constants/modes'
4 |
5 |
6 | export const ModeContext = createContext({})
7 |
8 | export function ModeProvider({ children }) {
9 | const [mode, setMode] = useState(MODES.SLIDESHOW)
10 | const router = useRouter()
11 | const newMode = router.query.mode
12 |
13 | useEffect(() => {
14 | if (newMode) setMode(newMode)
15 | }, [newMode])
16 |
17 |
18 | return (
19 |
20 | {children}
21 |
22 | )
23 | }
24 |
25 | export const useMode = () => useContext(ModeContext)
26 |
--------------------------------------------------------------------------------
/context/CurrentSlideContext.jsx:
--------------------------------------------------------------------------------
1 | import { createContext, useContext, useState } from 'react'
2 |
3 | export const CurrentSlideContext = createContext({
4 | currentSlide: 0,
5 | setSlide: () => {},
6 | })
7 |
8 | export function CurrentSlideProvider({ children }) {
9 | // Grab initial slide from hash (#) in URL
10 | const initialSlide =
11 | process.browser && window.location.hash
12 | ? parseInt(window.location.hash.replace('#', ''))
13 | : 0
14 | const [currentSlide, setSlide] = useState(initialSlide)
15 |
16 | return (
17 |
18 | {children}
19 |
20 | )
21 | }
22 |
23 | export const useCurrentSlide = () => useContext(CurrentSlideContext)
24 |
--------------------------------------------------------------------------------
/pages/_document.js:
--------------------------------------------------------------------------------
1 | import Document from 'next/document'
2 | import { ServerStyleSheet } from 'styled-components'
3 |
4 | export default class MyDocument extends Document {
5 | static async getInitialProps(ctx) {
6 | const sheet = new ServerStyleSheet()
7 | const originalRenderPage = ctx.renderPage
8 |
9 | try {
10 | ctx.renderPage = () =>
11 | originalRenderPage({
12 | enhanceApp: (App) => (props) =>
13 | sheet.collectStyles(),
14 | })
15 |
16 | const initialProps = await Document.getInitialProps(ctx)
17 | return {
18 | ...initialProps,
19 | styles: (
20 | <>
21 | {initialProps.styles}
22 | {sheet.getStyleElement()}
23 | >
24 | ),
25 | }
26 | } finally {
27 | sheet.seal()
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pages/_app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { AnimatePresence } from 'framer-motion'
3 | import { CurrentSlideProvider } from '../context/CurrentSlideContext'
4 | import { ModeProvider } from '../context/ModeContext'
5 | import MDXProvider from '../components/MDXProvider'
6 | import { ThemeProvider } from '../components/ThemeProvider'
7 | import TransitionPage from '../layouts/TransitionPage'
8 |
9 | export default ({ Component, pageProps }) => (
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | )
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Getting Started With Open Source
3 |
4 | Interested in contributing to open source? Not sure where to start? Start this month at our Hacktober event with Monica Powell.
5 |
6 | Topics the workshop will cover:
7 | - What is Open Source? What are some of the different roles within Open Source?
8 | - Best practices for contributing to open source
9 | - How to identify opportunities to contribute to open source
10 | - Opening an open source pull request
11 |
12 | What you'll need before the workshop:
13 | a GitHub account https://github.com/
14 |
15 | About our speaker:
16 | Monica Powell is a software engineer who is passionate about making contributing to open-source more approachable and building community. She currently works at Newsela as a software engineer building educational software and was recently selected to be an inaugural GitHub Star based on her community involvement.
17 |
18 |
--------------------------------------------------------------------------------
/components/MDXProvider.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { MDXProvider } from '@mdx-js/react'
3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
4 | import okaidia from 'react-syntax-highlighter/dist/cjs/styles/prism/okaidia'
5 | import SlidePage from '../layouts/SlidePage'
6 | import Cover from './Cover'
7 | import SpeakerNotes from './SpeakerNotes'
8 |
9 | const mdComponents = {
10 | h1: (props) => ,
11 | pre: (props) => props.children,
12 | code: (props) => {
13 | const { className } = props
14 | const language = className.replace('language-', '')
15 | return (
16 |
22 | )
23 | },
24 | Cover,
25 | SlidePage,
26 | SpeakerNotes,
27 | }
28 |
29 | export default ({ children }) => (
30 | {children}
31 | )
32 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2020 Ryosuke Hana
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
5 |
6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
9 |
--------------------------------------------------------------------------------
/hooks/useEventListener.js:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect } from 'react'
2 |
3 | export default function useEventListener(eventName, handler, element = window) {
4 | // Create a ref that stores handler
5 | const savedHandler = useRef()
6 |
7 | // Update ref.current value if handler changes.
8 | // This allows our effect below to always get latest handler ...
9 | // ... without us needing to pass it in effect deps array ...
10 | // ... and potentially cause effect to re-run every render.
11 | useEffect(() => {
12 | savedHandler.current = handler
13 | }, [handler])
14 |
15 | useEffect(
16 | () => {
17 | // Make sure element supports addEventListener
18 | // On
19 | const isSupported = element && element.addEventListener
20 | if (!isSupported) return
21 |
22 | // Create event listener that calls handler function stored in ref
23 | const eventListener = (event) => savedHandler.current(event)
24 |
25 | // Add event listener
26 | element.addEventListener(eventName, eventListener)
27 |
28 | // Remove event listener on cleanup
29 | return () => {
30 | element.removeEventListener(eventName, eventListener)
31 | }
32 | },
33 | [eventName, element] // Re-run if eventName or element changes
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "getting-started-with-open-source",
3 | "version": "0.1.0",
4 | "description": "A presentation on getting started with Open Source",
5 | "private": true,
6 | "author": {
7 | "name": "Monica Powell",
8 | "email": "github@aboutmonica.com",
9 | "url": "https://aboutmonica.com/"
10 | },
11 | "scripts": {
12 | "dev": "next dev",
13 | "build": "next build",
14 | "format": "prettier --write '**/*.{js,jsx}'",
15 | "start": "next start",
16 | "export": "next export"
17 | },
18 | "dependencies": {
19 | "@mdx-js/loader": "^1.5.8",
20 | "@next/mdx": "^9.3.4",
21 | "framer-motion": "^1.10.3",
22 | "next": "9.3.4",
23 | "react": "16.13.1",
24 | "react-dom": "16.13.1",
25 | "react-swipeable": "^5.5.1",
26 | "react-syntax-highlighter": "^12.2.1",
27 | "styled-components": "^5.1.0"
28 | },
29 | "devDependencies": {
30 | "babel-eslint": "^10.1.0",
31 | "babel-plugin-styled-components": "^1.10.7",
32 | "eslint": "^6.8.0",
33 | "eslint-config-airbnb": "^18.1.0",
34 | "eslint-config-prettier": "^6.10.1",
35 | "eslint-plugin-import": "^2.20.2",
36 | "eslint-plugin-jsx-a11y": "^6.2.3",
37 | "eslint-plugin-react": "^7.19.0",
38 | "eslint-plugin-react-hooks": "^2.5.1",
39 | "prettier": "^2.0.4"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/components/PresentationMode.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import { MODES } from '../constants/modes'
4 |
5 | const PresentationFrame = styled.div`
6 | display: flex;
7 | padding: 3rem;
8 | max-height: 100vh;
9 | `
10 |
11 | const SlideWindow = styled.div`
12 | width: 65%;
13 | overflow-y: scroll;
14 |
15 | & > div {
16 | padding: 1rem;
17 | max-height: 80vh;
18 | }
19 |
20 | #slide {
21 | border: 1px solid #fff;
22 | align-items:flex-start;
23 | height:auto;
24 | }
25 | `
26 |
27 | const Sidebar = styled.div`
28 | width: 35%;
29 |
30 | & > div {
31 | padding: 1rem;
32 | }
33 | `
34 |
35 | const SpeakerNotesWindow = styled.div`
36 | width: 100%;
37 | height: 50vh;
38 | overflow-y: scroll;
39 | border: 1px solid #fff;
40 |
41 | font-size: 16px;
42 |
43 | & > div {
44 | padding: 1rem;
45 | }
46 | `
47 |
48 | export default function PresentationMode({
49 | mode,
50 | notes,
51 | currentSlide,
52 | children,
53 | }) {
54 | if (mode === MODES.SPEAKER) {
55 | return (
56 |
57 |
58 |
{children}
59 |
60 |
61 |
62 | {/* */}
63 |
64 |
{notes[currentSlide]}
65 |
66 |
67 |
68 |
69 | )
70 | }
71 | return children
72 | }
73 |
--------------------------------------------------------------------------------
/pages/slides/[slide].js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import path from 'path'
3 | import Head from 'next/head'
4 | import dynamic from 'next/dynamic'
5 | import Header from '../../components/Header'
6 | import { TotalPagesContext } from '../../context/TotalPagesContext'
7 | import { siteConfig } from '../../site.config.js'
8 |
9 | const SlideshowPage = ({ totalSlidePages, currentSlide, filename }) => {
10 | const MDXContent = dynamic(() => import(`../../${filename}`))
11 | return (
12 |
13 |
14 |
15 | {siteConfig.name} - {siteConfig.title}
16 |
17 |
18 |
22 |
23 |
29 |
30 |
31 | )
32 | }
33 |
34 | export async function getStaticProps({ params }) {
35 | const filename = path.join('slides', `${params.slide}.mdx`)
36 | const slidesDirectory = path.join(process.cwd(), 'slides')
37 | const mdxFiles = fs.readdirSync(slidesDirectory)
38 | const totalSlidePages = mdxFiles.length
39 |
40 | return {
41 | props: {
42 | totalSlidePages,
43 | currentSlide: params.slide,
44 | filename,
45 | },
46 | }
47 | }
48 |
49 | export async function getStaticPaths() {
50 | const postsDirectory = path.join(process.cwd(), 'slides')
51 | const mdxFiles = fs.readdirSync(postsDirectory)
52 | // Loop through all post files and create array of slugs (to create links)
53 | const paths = mdxFiles.map((filename) => ({
54 | params: {
55 | slide: filename.replace('.mdx', ''),
56 | },
57 | }))
58 |
59 | return {
60 | paths,
61 | fallback: false,
62 | }
63 | }
64 |
65 | export default SlideshowPage
66 |
--------------------------------------------------------------------------------
/hooks/useStorage.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { useRouter } from 'next/router'
3 | import { useCurrentSlide } from '../context/CurrentSlideContext'
4 |
5 | const keys = {
6 | slide: 'next-mdx-deck-slide',
7 | page: 'next-mdx-deck-page',
8 | }
9 |
10 | export const useStorage = () => {
11 | const { currentSlide, setSlide } = useCurrentSlide()
12 | const router = useRouter()
13 | const currentPage =
14 | router.query && 'slide' in router.query && parseInt(router.query.slide, 10)
15 | const [focused, setFocused] = useState(false)
16 |
17 | /**
18 | * Checks when user enters (focus) or
19 | * leaves (blur) browser window/tab
20 | */
21 | const handleFocus = () => setFocused(true)
22 | const handleBlur = () => setFocused(false)
23 |
24 | /**
25 | * Updates route or context with local storage data
26 | * from event listener
27 | * @param {*} e
28 | */
29 | const handleStorageChange = (e) => {
30 | const n = parseInt(e.newValue, 10)
31 | const syncedSlide = localStorage.getItem(keys.slide)
32 | // if (focused) return
33 | if (Number.isNaN(n)) return
34 | switch (e.key) {
35 | case keys.page:
36 | router.push(`/slides/${parseInt(n, 10)}#${syncedSlide}`)
37 | break
38 | case keys.slide:
39 | window.location.hash = `#${n}`
40 | setSlide(n)
41 | break
42 | default:
43 | break
44 | }
45 | }
46 |
47 | useEffect(() => {
48 | setFocused(document.hasFocus())
49 | }, [])
50 |
51 | useEffect(() => {
52 | if (!focused) window.addEventListener('storage', handleStorageChange)
53 | window.addEventListener('focus', handleFocus)
54 | window.addEventListener('blur', handleBlur)
55 | return () => {
56 | if (!focused) window.removeEventListener('storage', handleStorageChange)
57 | window.removeEventListener('focus', handleFocus)
58 | window.removeEventListener('blur', handleBlur)
59 | }
60 | }, [focused])
61 |
62 | /**
63 | * Sync localstorage with changes to slides or pages
64 | */
65 | useEffect(() => {
66 | if (!focused) return
67 | localStorage.setItem(keys.slide, currentSlide)
68 | localStorage.setItem(keys.page, currentPage)
69 | }, [focused, currentSlide, currentPage])
70 | }
71 |
72 | export const Storage = () => {
73 | useStorage()
74 | return false
75 | }
76 |
77 | export default useStorage
78 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue,
4 | Twitter, or any other method with the owners of this repository before making a change.
5 |
6 | Please note we have a code of conduct (see below), please follow it in all your interactions with the project. Be excellent to eachother, and party on 🤘
7 |
8 | ## Setup
9 |
10 | 1. Clone project `git clone git@github.com:whoisryosuke/next-mdx-deck.git`
11 | 1. Navigate to project `cd next-mdx-deck`
12 | 1. Install dependencies `yarn`
13 | 1. Run the development server `yarn dev`
14 |
15 | ## Development
16 |
17 | See README.md for information on working with project.
18 |
19 | ## Pull Request Process
20 |
21 | 1. Make a PR with working or conceptual code.
22 | 1. Submit it.
23 |
24 | > That's it. I'd say more if we had tests, a CI/CD setup, versioning, etc. But it's pretty casual right now. Make sure if you have working code to test the build process and ensure it doesn't fail.
25 |
26 | ## Code of Conduct
27 |
28 | ### Our Pledge
29 |
30 | In the interest of fostering an open and welcoming environment, we as
31 | contributors and maintainers pledge to making participation in our project and
32 | our community a harassment-free experience for everyone, regardless of age, body
33 | size, disability, ethnicity, gender identity and expression, level of experience,
34 | nationality, personal appearance, race, religion, or sexual identity and
35 | orientation.
36 |
37 | ### Our Standards
38 |
39 | Examples of behavior that contributes to creating a positive environment
40 | include:
41 |
42 | - Using welcoming and inclusive language
43 | - Being respectful of differing viewpoints and experiences
44 | - Gracefully accepting constructive criticism
45 | - Focusing on what is best for the community
46 | - Showing empathy towards other community members
47 |
48 | Examples of unacceptable behavior by participants include:
49 |
50 | - The use of sexualized language or imagery and unwelcome sexual attention or
51 | advances
52 | - Trolling, insulting/derogatory comments, and personal or political attacks
53 | - Public or private harassment
54 | - Publishing others' private information, such as a physical or electronic
55 | address, without explicit permission
56 | - Other conduct which could reasonably be considered inappropriate in a
57 | professional setting
58 |
59 | ### Our Responsibilities
60 |
61 | Project maintainers are responsible for clarifying the standards of acceptable
62 | behavior and are expected to take appropriate and fair corrective action in
63 | response to any instances of unacceptable behavior.
64 |
65 | Project maintainers have the right and responsibility to remove, edit, or
66 | reject comments, commits, code, wiki edits, issues, and other contributions
67 | that are not aligned to this Code of Conduct, or to ban temporarily or
68 | permanently any contributor for other behaviors that they deem inappropriate,
69 | threatening, offensive, or harmful.
70 |
71 | ### Scope
72 |
73 | This Code of Conduct applies both within project spaces and in public spaces
74 | when an individual is representing the project or its community. Examples of
75 | representing a project or community include using an official project e-mail
76 | address, posting via an official social media account, or acting as an appointed
77 | representative at an online or offline event. Representation of a project may be
78 | further defined and clarified by project maintainers.
79 |
80 | ### Enforcement
81 |
82 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
83 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
84 | complaints will be reviewed and investigated and will result in a response that
85 | is deemed necessary and appropriate to the circumstances. The project team is
86 | obligated to maintain confidentiality with regard to the reporter of an incident.
87 | Further details of specific enforcement policies may be posted separately.
88 |
89 | Project maintainers who do not follow or enforce the Code of Conduct in good
90 | faith may face temporary or permanent repercussions as determined by other
91 | members of the project's leadership.
92 |
93 | ### Attribution
94 |
95 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
96 | available at [http://contributor-covenant.org/version/1/4][version]
97 |
98 | [homepage]: http://contributor-covenant.org
99 | [version]: http://contributor-covenant.org/version/1/4/
100 |
--------------------------------------------------------------------------------
/INSTRUCTIONS.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Next MDX Deck
4 |
5 | Create presentation decks using MDX, React, and [Next.js](https://nextjs.org/).
6 |
7 | ## Features
8 |
9 | - 📽 React-based Slideshow
10 | - ✍️ Write using Markdown, React components, even HTML!
11 | - 🎨 Themeable with CSS vars or Styled Components
12 | - 👉 Swipe to change slides
13 | - ♻️ Sync slides between browser tabs
14 | - 👨💻 Presentation Mode
15 | - 📝 Speaker Notes
16 |
17 | ## Getting Started
18 |
19 | 1. Clone the project: `git clone https://github.com/whoisryosuke/next-mdx-deck`
20 | 2. Install dependencies: `npm i` or `yarn`
21 | 3. Run the dev server: `npm run dev` or `yarn dev`
22 | 4. Edit the first slide in `/slides/1.mdx` and save to [**see changes**](http://localhost:3000/)!
23 |
24 | When you're done, run `npm run build && npm run export` or `yarn build && yarn export` will create a static app you can deploy anywhere (or use locally). See below for more details.
25 |
26 | ### Deploying
27 |
28 | This project is easy to build locally or using a host with build services (like Netlify or Now).
29 |
30 | 1. ⚙️ Run the build process: `npm run build && npm run export`
31 | 1. 🚀 Upload the static contents of `out` folder to host/CDN (or run the `out/index.html` locally)
32 |
33 | ## How to Use
34 |
35 | ### 💬 Changing the Title/Date/etc
36 |
37 | The default theme includes a title, date, author (and link to the author's website) in of the `` component. You can edit this data inside the `site.config.js` file.
38 |
39 | ### ✍️ Writing JSX
40 |
41 | You can use JSX in [a few ways](https://mdxjs.com/getting-started) in your MDX files:
42 |
43 | - You can use the syntax with HTML (`