├── .github └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── .vscode └── settings.json ├── contributing.md ├── license ├── netlify.toml ├── package.json ├── packs ├── code-surfer │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── codeblock-metastring-parser.ts │ │ ├── column-layout.tsx │ │ ├── error-boundary.tsx │ │ ├── index.ts │ │ ├── layout.tsx │ │ ├── notes.js │ │ ├── presenter.tsx │ │ ├── step-reader.js │ │ ├── step.js │ │ ├── types.d.ts │ │ ├── use-spring.js │ │ ├── use-step-spring.js │ │ ├── use-steps.js │ │ └── use-window-resize.js │ ├── test │ │ └── codeblock-metastring-parser.test.ts │ └── tsconfig.json ├── standalone │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ └── animation.test.ts.snap │ │ ├── animation.test.ts │ │ ├── animation.ts │ │ ├── code-surfer.tsx │ │ ├── default-syntaxes.ts │ │ ├── dimensions.ts │ │ ├── easing.ts │ │ ├── errors.tsx │ │ ├── frame.tsx │ │ ├── index.tsx │ │ ├── lines.tsx │ │ ├── tuple.test.ts │ │ ├── tuple.ts │ │ ├── types.d.ts │ │ └── use-window-resize.ts │ └── tsconfig.json ├── step-parser │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── __snapshots__ │ │ │ ├── differ.test.ts.snap │ │ │ ├── step-parser.test.ts.snap │ │ │ └── tokenizer.test.ts.snap │ │ ├── differ.test.ts │ │ ├── differ.ts │ │ ├── focus-parser.test.ts │ │ ├── focus-parser.ts │ │ ├── index.ts │ │ ├── object-entries.ts │ │ ├── step-parser.test.ts │ │ ├── step-parser.ts │ │ ├── tokenizer.test.ts │ │ ├── tokenizer.ts │ │ └── types.d.ts │ └── tsconfig.json └── themes │ ├── package.json │ ├── src │ ├── index.ts │ ├── styles.tsx │ ├── theme.base.ts │ ├── theme.dracula.ts │ ├── theme.duotone-dark.ts │ ├── theme.duotone-light.ts │ ├── theme.github.ts │ ├── theme.night-owl.ts │ ├── theme.oceanic-next.ts │ ├── theme.shades-of-purple.ts │ ├── theme.ultramin.ts │ ├── theme.vs-dark.ts │ ├── utils.test.ts │ └── utils.ts │ └── tsconfig.json ├── readme-zh.md ├── readme.md ├── sites ├── book │ ├── .gitignore │ ├── package.json │ ├── src │ │ ├── basic.story.js │ │ ├── big.story.js │ │ ├── config.js │ │ ├── files │ │ │ ├── .prettierrc │ │ │ ├── 00.jsx │ │ │ ├── 01.jsx │ │ │ ├── 02.jsx │ │ │ ├── 03.jsx │ │ │ ├── 04.jsx │ │ │ ├── 05.jsx │ │ │ ├── 06.jsx │ │ │ ├── 07.jsx │ │ │ ├── 08.jsx │ │ │ ├── 09.jsx │ │ │ ├── 10.jsx │ │ │ ├── 11.jsx │ │ │ ├── 12.jsx │ │ │ ├── 13.jsx │ │ │ ├── 14.jsx │ │ │ ├── 15.jsx │ │ │ ├── 16.jsx │ │ │ ├── 17.jsx │ │ │ ├── 18.jsx │ │ │ ├── 19.jsx │ │ │ ├── 20.jsx │ │ │ ├── 21.jsx │ │ │ ├── 22.jsx │ │ │ ├── 23.jsx │ │ │ ├── 24.jsx │ │ │ ├── 25.jsx │ │ │ ├── 26.jsx │ │ │ ├── 27.jsx │ │ │ ├── 28.jsx │ │ │ ├── 29.jsx │ │ │ ├── 30.jsx │ │ │ ├── 31.jsx │ │ │ ├── 32.jsx │ │ │ ├── 33.jsx │ │ │ ├── 34.jsx │ │ │ ├── 35.jsx │ │ │ ├── 36.jsx │ │ │ ├── 37.jsx │ │ │ ├── 38.jsx │ │ │ ├── 39.jsx │ │ │ ├── 40.jsx │ │ │ ├── 41.jsx │ │ │ ├── 42.jsx │ │ │ ├── 43.jsx │ │ │ ├── 44.jsx │ │ │ ├── 45.jsx │ │ │ ├── 46.jsx │ │ │ ├── 47.jsx │ │ │ ├── 48.jsx │ │ │ ├── 49.jsx │ │ │ └── 50.jsx │ │ ├── focus.story.js │ │ ├── index.js │ │ ├── parsed-steps.js │ │ ├── parsed-steps.story.js │ │ ├── themed.story.js │ │ ├── title.story.js │ │ └── utils.js │ └── yarn.lock ├── build.js └── docs │ ├── .gitignore │ ├── decks │ ├── .prettierrc │ ├── code.js │ ├── custom-theme.js │ ├── demo.mdx │ ├── demo │ │ ├── image.js │ │ ├── logo.small.svg │ │ ├── logo.svg │ │ ├── surfer.jpg │ │ └── theme.js │ ├── errors.mdx │ ├── full.mdx │ ├── full │ │ ├── custom-theme.js │ │ └── my-component.js │ ├── test.mdx │ └── themes.mdx │ ├── gatsby-browser.js │ ├── gatsby-config.js │ ├── gatsby-ssr.js │ ├── package.json │ ├── src │ ├── gatsby-theme-mdx-deck │ │ └── templates │ │ │ └── decks.js │ ├── home │ │ ├── app.js │ │ ├── bright-squares.png │ │ ├── code-block.js │ │ ├── deck.js │ │ ├── docs.js │ │ ├── index.css │ │ ├── logo.small.svg │ │ ├── logos │ │ │ ├── bairesdev.png │ │ │ ├── jabci.png │ │ │ ├── mdxdeck.png │ │ │ └── ocollective.svg │ │ ├── purty-wood.png │ │ ├── readme.mdx │ │ ├── shattered.png │ │ ├── speaker.js │ │ ├── speakers │ │ │ ├── 0.png │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ ├── 4.png │ │ │ ├── 5.png │ │ │ ├── 6.png │ │ │ ├── 7.png │ │ │ ├── 8.png │ │ │ └── 9.png │ │ ├── stage.js │ │ ├── use-window-size.js │ │ ├── wip.js │ │ └── wood.png │ └── remove-overlay.css │ └── static │ ├── _redirects │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── card.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ └── site.webmanifest └── yarn.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | open_collective: code-surfer 2 | custom: https://www.paypal.me/pomber 3 | github: [pomber] 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | .rts2_cache_cjs 6 | .rts2_cache_esm 7 | .rts2_cache_umd 8 | dist 9 | build 10 | notes.md 11 | *.tgz 12 | sites/demo/public 13 | sites/docs/public -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: yarn 3 | node_js: 4 | - 8 5 | - node 6 | 7 | notifications: 8 | email: 9 | on_success: never 10 | on_failure: never 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "files.exclude": { 4 | "**/.rts2_cache*": true 5 | } 6 | } -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | 1. Fork the repo 2 | 2. Install deps: `$ yarn` 3 | 3. Run tests: `$ yarn test` 4 | 5 | - Open Storybook: `$ yarn workspace book start` 6 | - Open docs and demos: `$yarn workspace docs start` 7 | 8 | - http://localhost:8000/demo/ 9 | - http://localhost:8000/full/ 10 | - http://localhost:8000/test/ 11 | - http://localhost:8000/themes/ 12 | - http://localhost:8000/errors/ 13 | 14 | - Start the module/s you want to change 15 | - `yarn workspace @code-surfer/step-parser start` 16 | - `yarn workspace @code-surfer/standalone start` 17 | - `yarn workspace @code-surfer/themes start` 18 | - `yarn workspace code-surfer start` 19 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018-present, Rodrigo Pombo 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "" 3 | command = "yarn predeploy" 4 | publish = "sites/dist/" 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": [ 4 | "packs/step-parser", 5 | "packs/themes", 6 | "packs/standalone", 7 | "packs/code-surfer", 8 | "sites/book", 9 | "sites/docs" 10 | ], 11 | "devDependencies": { 12 | "prettier": "^1.18.2", 13 | "pretty-quick": "^1.11.1", 14 | "execa": "^2.0.1", 15 | "npm-run-all": "^4.1.5", 16 | "fs-extra": "^8.1.0" 17 | }, 18 | "scripts": { 19 | "format": "prettier --ignore-path .gitignore --write '**/*.{js,jsx,css,md,mdx}'", 20 | "format:check": "prettier --ignore-path .gitignore --check '**/*.{js,jsx,css,md,mdx}'", 21 | "build:step-parser": "yarn workspace @code-surfer/step-parser build", 22 | "build:themes": "yarn workspace @code-surfer/themes build", 23 | "build:standalone": "yarn workspace @code-surfer/standalone build", 24 | "build:codesurfer": "yarn workspace code-surfer build", 25 | "build:sites": "node sites/build", 26 | "prepare": "run-s build:step-parser build:themes build:standalone build:codesurfer", 27 | "predeploy": "run-s prepare build:sites", 28 | "test:step-parser": "yarn workspace @code-surfer/step-parser test", 29 | "test:themes": "yarn workspace @code-surfer/themes test", 30 | "test:standalone": "yarn workspace @code-surfer/standalone test", 31 | "test:codesurfer": "yarn workspace code-surfer test", 32 | "test": "run-p format:check test:step-parser test:themes test:standalone test:codesurfer" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packs/code-surfer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-surfer", 3 | "description": "Rad code slides", 4 | "version": "3.1.1", 5 | "license": "MIT", 6 | "author": "pomber", 7 | "repository": "pomber/code-surfer", 8 | "main": "dist/index.js", 9 | "module": "dist/code-surfer.esm.js", 10 | "typings": "dist/index.d.ts", 11 | "files": [ 12 | "dist" 13 | ], 14 | "engines": { 15 | "node": ">=8", 16 | "npm": ">=5" 17 | }, 18 | "scripts": { 19 | "start": "tsdx watch", 20 | "build": "tsdx build", 21 | "test": "cross-env CI=1 tsdx test --env=jsdom", 22 | "test:watch": "tsdx test --env=jsdom" 23 | }, 24 | "peerDependencies": { 25 | "mdx-deck": "3.0.10", 26 | "react": "^16.8.0" 27 | }, 28 | "dependencies": { 29 | "@code-surfer/standalone": "3.1.1", 30 | "@types/theme-ui": "^0.2.0", 31 | "array.prototype.flat": "^1.2.1", 32 | "diff": "^4.0.1", 33 | "prismjs": "^1.16.0", 34 | "rebound": "^0.1.0", 35 | "shell-quote": "^1.6.1", 36 | "use-spring": "^0.2.2" 37 | }, 38 | "devDependencies": { 39 | "@types/jest": "^24.0.15", 40 | "@types/prismjs": "^1.16.0", 41 | "@types/react": "^16.8.22", 42 | "@types/react-dom": "^16.8.4", 43 | "cross-env": "^5.2.0", 44 | "husky": "^2.7.0", 45 | "mdx-deck": "3.0.10", 46 | "prettier": "^1.18.2", 47 | "pretty-quick": "^1.11.1", 48 | "react": "^16.8.6", 49 | "react-dom": "^16.8.6", 50 | "tsdx": "^0.7.2", 51 | "tslib": "^1.10.0", 52 | "typescript": "^3.5.2" 53 | }, 54 | "keywords": [ 55 | "mdx", 56 | "mdx-deck", 57 | "slides", 58 | "react", 59 | "code", 60 | "highlight", 61 | "token", 62 | "prism", 63 | "animation", 64 | "transition", 65 | "reactjs" 66 | ], 67 | "funding": { 68 | "type": "opencollective", 69 | "url": "https://opencollective.com/code-surfer" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packs/code-surfer/src/codeblock-metastring-parser.ts: -------------------------------------------------------------------------------- 1 | import { parse } from "shell-quote"; 2 | 3 | type ParsedMetastring = { focus?: string } | { [key: string]: string }; 4 | 5 | /** 6 | * The metastring is the thing that comes after the language in markdown codeblocks 7 | * 8 | * ```js this is the metastring 9 | * code goes here 10 | * ``` 11 | */ 12 | export function parseMetastring(metastring: string): ParsedMetastring { 13 | if (!metastring) { 14 | return {}; 15 | } 16 | 17 | const argv = parse(metastring); 18 | const result: ParsedMetastring = {}; 19 | argv.forEach(arg => { 20 | if (!arg.includes("=")) { 21 | if (arg === "showNumbers") { 22 | result["showNumbers"] = true; 23 | } else { 24 | result.focus = arg; 25 | } 26 | } else { 27 | const [key, value] = arg.split(/=(.*)/); 28 | if (value === "true") { 29 | result[key] = true; 30 | } else { 31 | result[key] = value; 32 | } 33 | } 34 | }); 35 | return result; 36 | } 37 | -------------------------------------------------------------------------------- /packs/code-surfer/src/error-boundary.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { UnknownError } from "@code-surfer/standalone"; 3 | 4 | export default class ErrorBoundary extends React.Component< 5 | {}, 6 | { error?: any } 7 | > { 8 | constructor(props) { 9 | super(props); 10 | this.state = {}; 11 | } 12 | 13 | static getDerivedStateFromError(error) { 14 | return { error }; 15 | } 16 | 17 | componentDidCatch(error, info) { 18 | // console.log(error, info); 19 | } 20 | 21 | render() { 22 | if (!this.state.error) { 23 | return this.props.children; 24 | } else if (this.state.error.element) { 25 | return this.state.error.element; 26 | } else { 27 | console.error(this.state.error); 28 | return ; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packs/code-surfer/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Step } from "./step"; 2 | 3 | export { default as CodeSurfer } from "./layout"; 4 | export { default as CodeSurferColumns } from "./column-layout"; 5 | -------------------------------------------------------------------------------- /packs/code-surfer/src/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDeck } from "mdx-deck"; 3 | import { CodeSurfer } from "@code-surfer/standalone"; 4 | import { readStepFromElement } from "./step-reader"; 5 | import ErrorBoundary from "./error-boundary"; 6 | import { useStepSpring } from "./use-step-spring"; 7 | 8 | function CodeSurferLayout({ children, theme }) { 9 | const deck = useDeck(); 10 | const steps = React.useMemo(getStepsFromChildren(children), [deck.index]); 11 | 12 | // useNotes(steps.map(s => s.notesElement)); 13 | const progress = useStepSpring(steps.length); 14 | 15 | return ( 16 |
28 | 29 |
30 | ); 31 | } 32 | 33 | const getStepsFromChildren = children => () => { 34 | const steps = React.Children.map(children || [], child => 35 | readStepFromElement(child) 36 | ).filter(x => x); 37 | if (steps.length === 0) { 38 | throw Error("No codeblocks found inside ."); 39 | } 40 | return steps; 41 | }; 42 | 43 | export default props => ( 44 | 45 | 46 | 47 | ); 48 | -------------------------------------------------------------------------------- /packs/code-surfer/src/notes.js: -------------------------------------------------------------------------------- 1 | import { useDeck } from "mdx-deck"; 2 | import React from "react"; 3 | 4 | export function useNotes(notesElements) { 5 | const context = useDeck(); 6 | React.useEffect(() => { 7 | if (!context || !context.register) return; 8 | if (typeof context.index === "undefined") return; 9 | 10 | const notes = getNotesFromElements(notesElements); 11 | 12 | context.register(context.index, { 13 | notes 14 | }); 15 | }, []); 16 | } 17 | 18 | function getNotesFromElements(notesElements) { 19 | const notes = notesElements.map(element => { 20 | if (!element) { 21 | // this is a step with empty notes 22 | return null; 23 | } 24 | 25 | const { props } = element; 26 | 27 | if (props.inline) { 28 | // this is 29 | return { 30 | inline: true, 31 | text: props.children 32 | }; 33 | } 34 | 35 | // this is something 36 | // we shouldn't return an object here, 37 | // to be compatible with the default Presenter 38 | return props && props.children; 39 | }); 40 | 41 | if (notes.length) { 42 | const lastNotes = notes[notes.length - 1]; 43 | // we add an extra EOL to the last step 44 | notes[notes.length - 1] = (lastNotes || "") + "\n"; 45 | } 46 | 47 | return notes; 48 | } 49 | 50 | export function getTextFromNotes(notes) { 51 | if (notes === null) { 52 | // this is a step with empty notes 53 | // we don't add extra lines here 54 | // to allow a line of text with multiple notes 55 | return ""; 56 | } 57 | 58 | if (typeof notes === "object") { 59 | // this comes from a step with inline=true 60 | // but we check again just in case 61 | return notes.text + (notes.inline ? "" : "\n"); 62 | } else { 63 | // this could be an empty note from any slide 64 | // or a note from a step without the inline prop 65 | return notes + "\n"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packs/code-surfer/src/step-reader.js: -------------------------------------------------------------------------------- 1 | import { parseMetastring } from "./codeblock-metastring-parser"; 2 | 3 | export function isCode(element) { 4 | return element && element.props && element.props.mdxType === "pre"; 5 | } 6 | 7 | export function readStepFromElement(element) { 8 | if (!isCode(element)) { 9 | throw new Error( 10 | "Invalid element inside . Make sure to add empty lines (no spaces) before and after each codeblock." 11 | ); 12 | } 13 | 14 | const { props } = element.props.children; 15 | 16 | const className = props.className && props.className.split(" ")[0]; 17 | return { 18 | code: props.children, 19 | lang: className && className.substring("language-".length), 20 | ...parseMetastring(props.metastring) 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /packs/code-surfer/src/step.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | function Step() { 4 | return null; 5 | } 6 | 7 | export default Step; 8 | -------------------------------------------------------------------------------- /packs/code-surfer/src/types.d.ts: -------------------------------------------------------------------------------- 1 | type Maybe = T | null | undefined; 2 | 3 | declare module "code-surfer-types" { 4 | export interface InputStep { 5 | code: string; 6 | focus?: string; 7 | title?: { value: string }; 8 | subtitle?: { value: string }; 9 | lang?: string; 10 | } 11 | 12 | export interface Token { 13 | type: string; 14 | content: string; 15 | focus?: boolean; 16 | key?: number; 17 | } 18 | 19 | export interface Line { 20 | tokens: Token[]; 21 | key: Number; 22 | content: string; 23 | focus?: boolean; 24 | focusPerToken?: boolean; 25 | } 26 | 27 | export interface Step { 28 | lines: Line[]; 29 | title?: { value: string }; 30 | subtitle?: { value: string }; 31 | focusCenter: number; 32 | dimensions?: any; 33 | } 34 | 35 | type StyleItem = { 36 | types: string[]; 37 | style: React.CSSProperties; 38 | }; 39 | 40 | type Partial = { 41 | [P in keyof T]?: T[P]; 42 | }; 43 | } 44 | 45 | declare module "playhead-types" { 46 | type Animation = (prev: Maybe, next: Maybe, t: number) => R; 47 | type AnimationConfig = { 48 | when?: (prev: Maybe, next: Maybe) => boolean; 49 | stagger?: number; 50 | }; 51 | type AnimationAndConfig = { 52 | animation: Animation; 53 | } & AnimationConfig; 54 | } 55 | 56 | declare module "shell-quote" { 57 | export function parse(s: string): string[]; 58 | } 59 | -------------------------------------------------------------------------------- /packs/code-surfer/src/use-spring.js: -------------------------------------------------------------------------------- 1 | // based on https://github.com/streamich/react-use/blob/master/src/useSpring.ts 2 | // TODO remove dependency 3 | import rebound from "rebound"; 4 | import { useState, useEffect } from "react"; 5 | 6 | export default function useSpring({ 7 | target = 0, 8 | current = null, 9 | tension = 0, 10 | friction = 13, 11 | round = x => x 12 | }) { 13 | const [spring, setSpring] = useState(null); 14 | const [value, setValue] = useState(target); 15 | 16 | useEffect(() => { 17 | const listener = { 18 | onSpringUpdate: spring => { 19 | const value = spring.getCurrentValue(); 20 | setValue(round(value)); 21 | } 22 | }; 23 | 24 | if (!spring) { 25 | const newSpring = new rebound.SpringSystem().createSpring( 26 | tension, 27 | friction 28 | ); 29 | newSpring.setCurrentValue(target); 30 | setSpring(newSpring); 31 | newSpring.addListener(listener); 32 | return; 33 | } 34 | 35 | return () => { 36 | spring.removeListener(listener); 37 | setSpring(null); 38 | }; 39 | }, [tension, friction]); 40 | 41 | useEffect(() => { 42 | if (spring) { 43 | spring.setEndValue(target); 44 | if (current != null) { 45 | spring.setCurrentValue(current); 46 | } 47 | } 48 | }, [target, current]); 49 | 50 | return value; 51 | } 52 | -------------------------------------------------------------------------------- /packs/code-surfer/src/use-step-spring.js: -------------------------------------------------------------------------------- 1 | import useSteps from "./use-steps"; 2 | import { useSpring } from "use-spring"; 3 | 4 | function useStepSpring(stepsCount) { 5 | // step index according to mdx-deck 6 | const targetStepIndex = useSteps(stepsCount - 1); 7 | 8 | // real number between 0 and stepsCount - 1 9 | const [currentStepSpring] = useSpring(targetStepIndex, { 10 | decimals: 3, 11 | stiffness: 80, 12 | damping: 48, 13 | mass: 8 14 | }); 15 | 16 | return currentStepSpring; 17 | } 18 | 19 | export { useStepSpring }; 20 | -------------------------------------------------------------------------------- /packs/code-surfer/src/use-steps.js: -------------------------------------------------------------------------------- 1 | import { useSteps } from "mdx-deck"; 2 | 3 | export default function(stepsCount) { 4 | const step = useSteps(stepsCount); 5 | return step === Infinity ? 0 : step; 6 | } 7 | -------------------------------------------------------------------------------- /packs/code-surfer/src/use-window-resize.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function useWindowResize(handler, deps) { 4 | React.useEffect(() => { 5 | window.addEventListener("resize", handler); 6 | return () => { 7 | window.removeEventListener("resize", handler); 8 | }; 9 | }, deps); 10 | } 11 | -------------------------------------------------------------------------------- /packs/code-surfer/test/codeblock-metastring-parser.test.ts: -------------------------------------------------------------------------------- 1 | import { parseMetastring } from "../src/codeblock-metastring-parser"; 2 | 3 | /** 4 | * The metastring is the thing that comes after the language in markdown codeblocks 5 | * 6 | * ```js this is the metastring 7 | * code goes here 8 | * ``` 9 | */ 10 | 11 | describe("Parsing Codeblock Metastring", () => { 12 | it("return empty object when metastring is empty", () => { 13 | expect(parseMetastring(undefined)).toEqual({}); 14 | expect(parseMetastring(null)).toEqual({}); 15 | expect(parseMetastring("")).toEqual({}); 16 | expect(parseMetastring(" ")).toEqual({}); 17 | }); 18 | 19 | it("return focus by default", () => { 20 | expect(parseMetastring("12:20")).toEqual({ focus: "12:20" }); 21 | }); 22 | 23 | it("return any string property", () => { 24 | expect(parseMetastring("title=foo")).toEqual({ title: "foo" }); 25 | }); 26 | 27 | it("return properties with spaces", () => { 28 | expect(parseMetastring(`title="foo bar"`)).toEqual({ 29 | title: "foo bar" 30 | }); 31 | }); 32 | 33 | it("return properties containing the equals sign", () => { 34 | expect(parseMetastring(`title="foo=bar"`)).toEqual({ 35 | title: "foo=bar" 36 | }); 37 | }); 38 | 39 | it("return properties with quotes", () => { 40 | expect(parseMetastring(`title="foo \\"bar"`)).toEqual({ 41 | title: `foo "bar` 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packs/code-surfer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "allowJs": true, 8 | "declaration": false, 9 | "strictNullChecks": false, 10 | "importHelpers": true, 11 | "sourceMap": true, 12 | "rootDir": "./", 13 | "strict": false, 14 | "noImplicitAny": false, 15 | "moduleResolution": "node", 16 | "baseUrl": "./", 17 | "paths": { 18 | "*": ["src/*", "node_modules/*"] 19 | }, 20 | "jsx": "react", 21 | "esModuleInterop": true 22 | } 23 | } 24 | 25 | // { 26 | // "include": ["src", "types"], 27 | // "compilerOptions": { 28 | // "target": "es5", 29 | // "module": "esnext", 30 | // "lib": ["dom", "esnext"], 31 | // "importHelpers": true, 32 | // "declaration": true, 33 | // "sourceMap": true, 34 | // "rootDir": "./", 35 | // "strict": true, 36 | // "noImplicitAny": true, 37 | // "strictNullChecks": true, 38 | // "strictFunctionTypes": true, 39 | // "strictPropertyInitialization": true, 40 | // "noImplicitThis": true, 41 | // "alwaysStrict": true, 42 | // "noUnusedLocals": true, 43 | // "noUnusedParameters": true, 44 | // "noImplicitReturns": true, 45 | // "noFallthroughCasesInSwitch": true, 46 | // "moduleResolution": "node", 47 | // "baseUrl": "./", 48 | // "paths": { 49 | // "*": ["src/*", "node_modules/*"] 50 | // }, 51 | // "jsx": "react", 52 | // "esModuleInterop": true 53 | // } 54 | // } 55 | -------------------------------------------------------------------------------- /packs/standalone/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@code-surfer/standalone", 3 | "description": "Code Surfer React component", 4 | "version": "3.1.1", 5 | "private": false, 6 | "license": "MIT", 7 | "author": "pomber", 8 | "repository": "pomber/code-surfer", 9 | "main": "dist/index.js", 10 | "module": "dist/standalone.esm.js", 11 | "typings": "dist/index.d.ts", 12 | "files": [ 13 | "dist" 14 | ], 15 | "engines": { 16 | "node": ">=8", 17 | "npm": ">=5" 18 | }, 19 | "scripts": { 20 | "start": "tsdx watch", 21 | "build": "tsdx build", 22 | "test": "cross-env CI=1 tsdx test --env=jsdom", 23 | "test:watch": "tsdx test --env=jsdom -u --watch" 24 | }, 25 | "peerDependencies": { 26 | "react": "^16.8.0" 27 | }, 28 | "dependencies": { 29 | "@code-surfer/step-parser": "3.1.1", 30 | "@code-surfer/themes": "3.1.1", 31 | "@types/table": "^4.0.7", 32 | "@types/theme-ui": "^0.2.2", 33 | "array.prototype.flat": "^1.2.1", 34 | "diff": "^4.0.1", 35 | "prismjs": "^1.16.0", 36 | "rebound": "^0.1.0", 37 | "shell-quote": "^1.6.1" 38 | }, 39 | "devDependencies": { 40 | "@types/diff": "^4.0.2", 41 | "@types/jest": "^24.0.15", 42 | "@types/prismjs": "^1.16.0", 43 | "@types/react": "^16.8.22", 44 | "@types/react-dom": "^16.8.4", 45 | "cross-env": "^5.2.0", 46 | "execa": "^2.0.1", 47 | "fs-extra": "^8.1.0", 48 | "husky": "^2.7.0", 49 | "mdx-deck": "3.0.8", 50 | "npm-run-all": "^4.1.5", 51 | "prettier": "^1.18.2", 52 | "pretty-quick": "^1.11.1", 53 | "react": "^16.8.6", 54 | "react-dom": "^16.8.6", 55 | "table": "^5.4.6", 56 | "tsdx": "^0.7.2", 57 | "tslib": "^1.10.0", 58 | "typescript": "^3.5.2" 59 | }, 60 | "keywords": [ 61 | "mdx", 62 | "mdx-deck", 63 | "slides", 64 | "react", 65 | "code", 66 | "highlight", 67 | "token", 68 | "prism", 69 | "animation", 70 | "transition", 71 | "reactjs" 72 | ], 73 | "funding": { 74 | "type": "opencollective", 75 | "url": "https://opencollective.com/code-surfer" 76 | }, 77 | "jest": { 78 | "testMatch": [ 79 | "/**/?(*.)(spec|test).(ts|js)?(x)" 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packs/standalone/readme.md: -------------------------------------------------------------------------------- 1 | # @code-surfer/standalone 2 | 3 | For internal use by code-surfer, but you can use it if you want. Just be aware that **it doesn't follow semantic versioning**, so pin the version just in case. 4 | 5 | No docs, but you can check the code in `sites/book/`. 6 | 7 | ## Contributing 8 | 9 | Watch and build code: 10 | 11 | ```bash 12 | $ yarn 13 | $ yarn workspace @code-surfer/standalone start 14 | ``` 15 | 16 | Run storybook: 17 | 18 | ```bash 19 | $ yarn workspace book start 20 | ``` 21 | 22 | Watch tests: 23 | 24 | ```bash 25 | $ yarn workspace @code-surfer/standalone test:watch 26 | ``` 27 | -------------------------------------------------------------------------------- /packs/standalone/src/animation.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | tween, 3 | chain, 4 | exitLine, 5 | enterLine, 6 | stagger, 7 | fadeInFocus 8 | } from "./animation"; 9 | import { table, TableUserConfig } from "table"; 10 | import easing from "./easing"; 11 | 12 | test("Tween Easing", () => { 13 | const ts = Array(21) 14 | .fill(0) 15 | .map((_, i) => i / 20); 16 | 17 | const from = 10; 18 | const to = 20; 19 | 20 | const data = ts.map(t => { 21 | return [ 22 | t, 23 | tween(from, to, t, easing.linear), 24 | tween(from, to, t, easing.easeInQuad), 25 | tween(from, to, t, easing.easeOutQuad), 26 | tween(from, to, t, easing.easeInOutQuad) 27 | ]; 28 | }); 29 | 30 | expect( 31 | toTable( 32 | data, 33 | ["t", "Linear", "In Quad", "Out Quad", "In Out Quad"], 34 | [, 5, 5, 5, 5] 35 | ) 36 | ).toMatchSnapshot(); 37 | }); 38 | 39 | test("Chain", () => { 40 | const ts = Array(21) 41 | .fill(0) 42 | .map((_, i) => i / 20); 43 | 44 | const animation = chain<{ x?: number; y?: number }>([ 45 | [0.5, (t: number) => ({ x: t })], 46 | [0.75, undefined], 47 | [1, (t: number) => ({ y: t })] 48 | ]); 49 | 50 | const data = ts.map(t => { 51 | const { x, y } = animation(t); 52 | return [t, x, y]; 53 | }); 54 | 55 | expect(toTable(data, ["t", "x", "y"], [, 6, 6])).toMatchSnapshot(); 56 | }); 57 | 58 | test("Stagger", () => { 59 | const ts = Array(21) 60 | .fill(0) 61 | .map((_, i) => i / 20); 62 | 63 | const animation = (t: number) => tween(0, 100, t); 64 | 65 | const data = ts.map(t => { 66 | return [ 67 | t, 68 | stagger(animation, 0, 3)(t), 69 | stagger(animation, 1, 3)(t), 70 | stagger(animation, 2, 3)(t) 71 | ]; 72 | }); 73 | 74 | expect(toTable(data, ["t", "s0", "s1", "s2"], [, 6, 6, 6])).toMatchSnapshot(); 75 | }); 76 | 77 | test("Fade In Focus", () => { 78 | const ts = Array(21) 79 | .fill(0) 80 | .map((_, i) => i / 20); 81 | 82 | const data = ts.map(t => { 83 | return [ 84 | t, 85 | fadeInFocus(0, 1, 0, 3)(t).opacity, 86 | fadeInFocus(0, 1, 1, 3)(t).opacity, 87 | fadeInFocus(0, 1, 2, 3)(t).opacity 88 | ]; 89 | }); 90 | 91 | expect(toTable(data, ["t", "s0", "s1", "s2"], [, 6, 6, 6])).toMatchSnapshot(); 92 | }); 93 | 94 | test("Line Exit", () => { 95 | const ts = Array(21) 96 | .fill(0) 97 | .map((_, i) => i / 20); 98 | 99 | const animation = exitLine(0.8, 0, 0, 1, 100); 100 | const data = ts.map(t => { 101 | const { transform, height, opacity } = animation(t); 102 | return [t, transform, height, opacity]; 103 | }); 104 | 105 | expect( 106 | toTable(data, ["t", "transform", "height", "opacity"], [, 20, 6, 6]) 107 | ).toMatchSnapshot(); 108 | }); 109 | 110 | test("Line Enter", () => { 111 | const ts = Array(21) 112 | .fill(0) 113 | .map((_, i) => i / 20); 114 | 115 | const animation = enterLine(0, 0.8, 0, 1, 100); 116 | const data = ts.map(t => { 117 | const { transform, height, opacity } = animation(t); 118 | return [t, transform, height, opacity]; 119 | }); 120 | 121 | expect( 122 | toTable(data, ["t", "transform", "height", "opacity"], [, 20, 6, 6]) 123 | ).toMatchSnapshot(); 124 | }); 125 | 126 | function toTable( 127 | data: any[][], 128 | hr: string[], 129 | truncate: (number | undefined)[] = [] 130 | ) { 131 | const config: TableUserConfig = { 132 | drawHorizontalLine: (index, size) => { 133 | return index === 0 || index === 1 || index === size; 134 | } 135 | }; 136 | 137 | const newData = data.map(row => 138 | row.map((value, coli) => 139 | truncate[coli] 140 | ? (value == null ? "" : value).toString().slice(0, truncate[coli]) 141 | : value 142 | ) 143 | ); 144 | 145 | return "\n" + table([hr, ...newData], config); 146 | } 147 | -------------------------------------------------------------------------------- /packs/standalone/src/code-surfer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import useDimensions from "./dimensions"; 3 | import Frame from "./frame"; 4 | import { Step } from "code-surfer-types"; 5 | 6 | type CodeSurferProps = { 7 | steps: Step[]; 8 | progress: number; // float between [0, steps.lenght - 1] 9 | tokens: string[][]; 10 | types: string[][]; 11 | maxLineCount: number; 12 | showNumbers?: boolean; 13 | }; 14 | 15 | export function CodeSurfer({ 16 | progress, 17 | steps, 18 | tokens, 19 | types, 20 | maxLineCount, 21 | showNumbers = false 22 | }: CodeSurferProps) { 23 | const fakeSteps = React.useMemo(() => getFakeSteps(steps, tokens), [steps]); 24 | 25 | const ref = React.useRef(null); 26 | const { dimensions, steps: stepsWithDimensions } = useDimensions(ref, steps); 27 | if (!dimensions || !stepsWithDimensions) { 28 | return ( 29 |
33 | {fakeSteps.map((_step, i) => ( 34 |
42 | 50 |
51 | ))} 52 |
53 | ); 54 | } else { 55 | return ( 56 |
60 | 69 |
70 | ); 71 | } 72 | } 73 | 74 | function getFakeSteps(parsedSteps: Step[], tokens: string[][]) { 75 | let shortLineKey = 0; 76 | let length = 100; 77 | for (let i = 1; i < tokens.length; i++) { 78 | if (tokens[i].length < length) { 79 | length = tokens[i].length; 80 | shortLineKey = i; 81 | } 82 | if (length <= 1) { 83 | break; 84 | } 85 | } 86 | 87 | const fakeSteps = parsedSteps.map(step => { 88 | const fakeStep: Step = { 89 | ...step, 90 | lines: step.lines.map(_ => shortLineKey), 91 | longestLineIndex: 0 92 | }; 93 | fakeStep.lines[0] = step.lines[step.longestLineIndex]; 94 | return fakeStep; 95 | }); 96 | fakeSteps[0] = parsedSteps[0]; 97 | return fakeSteps; 98 | } 99 | -------------------------------------------------------------------------------- /packs/standalone/src/default-syntaxes.ts: -------------------------------------------------------------------------------- 1 | import "prismjs/components/prism-markup"; 2 | import "prismjs/components/prism-bash"; 3 | import "prismjs/components/prism-clike"; 4 | import "prismjs/components/prism-c"; 5 | import "prismjs/components/prism-cpp"; 6 | import "prismjs/components/prism-css"; 7 | import "prismjs/components/prism-css-extras"; 8 | import "prismjs/components/prism-javascript"; 9 | import "prismjs/components/prism-jsx"; 10 | import "prismjs/components/prism-js-extras"; 11 | import "prismjs/components/prism-coffeescript"; 12 | import "prismjs/components/prism-diff"; 13 | import "prismjs/components/prism-git"; 14 | import "prismjs/components/prism-go"; 15 | import "prismjs/components/prism-graphql"; 16 | import "prismjs/components/prism-json"; 17 | import "prismjs/components/prism-less"; 18 | import "prismjs/components/prism-makefile"; 19 | import "prismjs/components/prism-markdown"; 20 | import "prismjs/components/prism-objectivec"; 21 | import "prismjs/components/prism-ocaml"; 22 | import "prismjs/components/prism-python"; 23 | import "prismjs/components/prism-reason"; 24 | import "prismjs/components/prism-sass"; 25 | import "prismjs/components/prism-scss"; 26 | import "prismjs/components/prism-sql"; 27 | import "prismjs/components/prism-stylus"; 28 | import "prismjs/components/prism-typescript"; 29 | import "prismjs/components/prism-wasm"; 30 | import "prismjs/components/prism-yaml"; 31 | -------------------------------------------------------------------------------- /packs/standalone/src/dimensions.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Step, Dimensions } from "code-surfer-types"; 3 | import useWindowResize from "./use-window-resize"; 4 | 5 | type DimensionsResult = { steps?: Step[]; dimensions?: Dimensions }; 6 | 7 | function useDimensions( 8 | ref: React.MutableRefObject, 9 | steps: Step[] 10 | ): DimensionsResult { 11 | const [result, setResult] = React.useState(null); 12 | 13 | // TODO reset only if container size changed 14 | useWindowResize(() => setResult(null), [setResult]); 15 | 16 | React.useLayoutEffect(() => { 17 | if (!ref.current) return; 18 | if (result) return; 19 | 20 | const containers = ref.current.querySelectorAll( 21 | ".cs-container" 22 | ) as NodeListOf; 23 | 24 | const stepsDimensions = Array.from(containers).map((container, i) => 25 | getStepDimensions(container, steps[i]) 26 | ); 27 | 28 | const containerHeight = Math.max( 29 | ...stepsDimensions.map(d => d.containerHeight) 30 | ); 31 | 32 | const containerWidth = Math.max( 33 | ...stepsDimensions.map(d => d.containerWidth) 34 | ); 35 | 36 | const contentWidth = Math.max(...stepsDimensions.map(d => d.contentWidth)); 37 | 38 | const lineHeight = Math.max(...stepsDimensions.map(d => d.lineHeight)); 39 | 40 | setResult({ 41 | dimensions: { 42 | lineHeight, 43 | contentWidth, 44 | containerHeight, 45 | containerWidth, 46 | // TODO set or remove 47 | contentHeight: undefined 48 | }, 49 | steps: steps.map((step, i) => ({ 50 | ...step, 51 | dimensions: { 52 | paddingTop: stepsDimensions[i].paddingTop, 53 | paddingBottom: stepsDimensions[i].paddingBottom 54 | } 55 | })) 56 | }); 57 | }, [result]); 58 | 59 | return result || {}; 60 | } 61 | 62 | function getStepDimensions(container: HTMLElement, step: Step) { 63 | const longestLineKey = step.lines[step.longestLineIndex]; 64 | const longestLineSpan = container.querySelector(`.cs-line-${longestLineKey}`); 65 | const containerParent = container.parentElement as HTMLElement; 66 | const title = container.querySelector(".cs-title") as HTMLElement; 67 | const subtitle = container.querySelector(".cs-subtitle") as HTMLElement; 68 | 69 | const lineCount = step.lines.length; 70 | const heightOverflow = 71 | containerParent.scrollHeight - containerParent.clientHeight; 72 | const avaliableHeight = container.scrollHeight - heightOverflow; 73 | 74 | const lineHeight = longestLineSpan ? longestLineSpan.clientHeight : 0; 75 | const paddingTop = title ? outerHeight(title) : lineHeight; 76 | const paddingBottom = subtitle ? outerHeight(subtitle) : lineHeight; 77 | 78 | const codeHeight = lineCount * lineHeight * 2; 79 | // const maxContentHeight = codeHeight + paddingTop + paddingBottom; 80 | // const containerHeight = Math.min(maxContentHeight, avaliableHeight); 81 | const containerHeight = avaliableHeight; 82 | const containerWidth = container.clientWidth; 83 | const contentHeight = codeHeight + containerHeight; 84 | 85 | const contentWidth = longestLineSpan ? longestLineSpan.clientWidth : 0; 86 | 87 | return { 88 | lineHeight, 89 | contentHeight, 90 | contentWidth, 91 | paddingTop, 92 | paddingBottom, 93 | containerHeight, 94 | containerWidth 95 | }; 96 | } 97 | 98 | function outerHeight(element: HTMLElement) { 99 | var styles = window.getComputedStyle(element); 100 | var margin = 101 | parseFloat(styles["marginTop"] || "0") + 102 | parseFloat(styles["marginBottom"] || "0"); 103 | return element.offsetHeight + margin; 104 | } 105 | 106 | export default useDimensions; 107 | -------------------------------------------------------------------------------- /packs/standalone/src/easing.ts: -------------------------------------------------------------------------------- 1 | export type Easing = (t: number) => number; 2 | 3 | export default { 4 | // no easing, no acceleration 5 | linear: function(t: number) { 6 | return t; 7 | }, 8 | // accelerating from zero velocity 9 | easeInQuad: function(t: number) { 10 | return t * t; 11 | }, 12 | // decelerating to zero velocity 13 | easeOutQuad: function(t: number) { 14 | return t * (2 - t); 15 | }, 16 | // acceleration until halfway, then deceleration 17 | easeInOutQuad: function(t: number) { 18 | return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; 19 | }, 20 | // accelerating from zero velocity 21 | easeInCubic: function(t: number) { 22 | return t * t * t; 23 | }, 24 | // decelerating to zero velocity 25 | easeOutCubic: function(t: number) { 26 | return --t * t * t + 1; 27 | }, 28 | // acceleration until halfway, then deceleration 29 | easeInOutCubic: function(t: number) { 30 | return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; 31 | }, 32 | // accelerating from zero velocity 33 | easeInQuart: function(t: number) { 34 | return t * t * t * t; 35 | }, 36 | // decelerating to zero velocity 37 | easeOutQuart: function(t: number) { 38 | return 1 - --t * t * t * t; 39 | }, 40 | // acceleration until halfway, then deceleration 41 | easeInOutQuart: function(t: number) { 42 | return t < 0.5 ? 8 * t * t * t * t : 1 - 8 * --t * t * t * t; 43 | }, 44 | // accelerating from zero velocity 45 | easeInQuint: function(t: number) { 46 | return t * t * t * t * t; 47 | }, 48 | // decelerating to zero velocity 49 | easeOutQuint: function(t: number) { 50 | return 1 + --t * t * t * t * t; 51 | }, 52 | // acceleration until halfway, then deceleration 53 | easeInOutQuint: function(t: number) { 54 | return t < 0.5 ? 16 * t * t * t * t * t : 1 + 16 * --t * t * t * t * t; 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /packs/standalone/src/errors.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type UnknownErrorProps = { 4 | error: { toString: () => string }; 5 | }; 6 | 7 | export function UnknownError({ error }: UnknownErrorProps) { 8 | // TODO link to create issue 9 | return ; 10 | } 11 | 12 | export function grammarNotFound({ lang }: { lang: string }) { 13 | return { 14 | element: ( 15 | 19 | Syntax highlighter for "{lang}" not found. 20 |

21 | You can try importing it from prismjs with:
22 | import "prismjs/components/prism-{lang}" 23 |

24 | (See{" "} 25 | 29 | all the supported languages 30 | 31 | ) 32 | 33 | } 34 | /> 35 | ) 36 | }; 37 | } 38 | 39 | export function invalidFocusNumber(n: string) { 40 | return { 41 | withFocusString: (focusString: string) => ({ 42 | withStepIndex: (stepIndex: number) => ({ 43 | element: ( 44 | } 46 | body={ 47 | 48 | "{n}" isn't a valid number{" "} 49 | {n != focusString && (in "{focusString}")} 50 | 51 | } 52 | /> 53 | ) 54 | }) 55 | }) 56 | }; 57 | } 58 | 59 | export function invalidLineOrColumnNumber() { 60 | return { 61 | withFocusString: (focusString: string) => ({ 62 | withStepIndex: (stepIndex: number) => ({ 63 | element: ( 64 | } 66 | body={ 67 | 68 | Are you using "0" as a line or column number{" "} 69 | in "{focusString}"? 70 |
71 | (Line and column numbers should start at 1, not 0)
72 |
73 | } 74 | /> 75 | ) 76 | }) 77 | }) 78 | }; 79 | } 80 | 81 | type ErrorBoxProps = { 82 | header: React.ReactNode; 83 | body: React.ReactNode; 84 | }; 85 | 86 | function ErrorBox({ header, body }: ErrorBoxProps) { 87 | return ( 88 |
100 |

{header}

101 |

{body}

102 |
103 | ); 104 | } 105 | 106 | function StepErrorHeader({ stepIndex }: { stepIndex: number }) { 107 | return ( 108 | 109 | Oops, there's a problem with the{" "} 110 | 111 | {stepIndex + 1} 112 | {ordinal(stepIndex + 1)} step 113 | 114 | 115 | ); 116 | } 117 | 118 | function Mark({ children }: { children: React.ReactNode }) { 119 | return ( 120 | 121 | {children} 122 | 123 | ); 124 | } 125 | 126 | function ordinal(i: number) { 127 | var j = i % 10, 128 | k = i % 100; 129 | if (j == 1 && k != 11) { 130 | return "st"; 131 | } 132 | if (j == 2 && k != 12) { 133 | return "nd"; 134 | } 135 | if (j == 3 && k != 13) { 136 | return "rd"; 137 | } 138 | return "th"; 139 | } 140 | -------------------------------------------------------------------------------- /packs/standalone/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { InputStep, Step } from "code-surfer-types"; 3 | import { parseSteps } from "@code-surfer/step-parser"; 4 | import { StylesProvider, CodeSurferTheme, Styled } from "@code-surfer/themes"; 5 | import { UnknownError } from "./errors"; 6 | import { CodeSurfer } from "./code-surfer"; 7 | import "./default-syntaxes"; 8 | 9 | type ParsedSteps = { 10 | steps: Step[]; 11 | tokens: string[][]; 12 | types: string[][]; 13 | maxLineCount: number; 14 | showNumbers?: boolean; 15 | }; 16 | 17 | type CodeSurferProps = { 18 | steps?: InputStep[]; 19 | parsedSteps?: ParsedSteps; 20 | progress: number; 21 | theme?: CodeSurferTheme; 22 | nonblocking?: boolean; 23 | }; 24 | 25 | function InnerCodeSurfer({ 26 | progress, 27 | steps: inputSteps, 28 | parsedSteps 29 | }: CodeSurferProps) { 30 | const { 31 | steps, 32 | tokens, 33 | types, 34 | maxLineCount, 35 | showNumbers 36 | } = React.useMemo(() => { 37 | if (parsedSteps) return parsedSteps; 38 | return parseSteps(inputSteps!); 39 | }, [inputSteps, parsedSteps]); 40 | 41 | if (!steps || steps.length === 0) { 42 | throw new Error("No steps"); 43 | } 44 | 45 | return ( 46 | 54 | ); 55 | } 56 | 57 | function CodeSurferWrapper({ theme, nonblocking, ...props }: CodeSurferProps) { 58 | const [wait, setWait] = React.useState(nonblocking); 59 | 60 | React.useEffect(() => { 61 | if (!wait) return; 62 | setWait(false); 63 | }, []); 64 | 65 | if (wait) 66 | return ( 67 | 68 | 69 | 70 | ); 71 | 72 | return ( 73 | 74 | 75 | 76 | ); 77 | } 78 | 79 | export { CodeSurferWrapper as CodeSurfer, UnknownError }; 80 | -------------------------------------------------------------------------------- /packs/standalone/src/tuple.test.ts: -------------------------------------------------------------------------------- 1 | import { Tuple, ArrayTuple } from "./tuple"; 2 | 3 | describe("Tuple", () => { 4 | it("spread works", () => { 5 | expect(new Tuple(1, 2).spread()).toEqual([1, 2]); 6 | }); 7 | 8 | it("select works", () => { 9 | const tuple = new Tuple({ a: 1 }, { a: 2 }); 10 | expect(tuple.select(x => x.a).spread()).toEqual([1, 2]); 11 | }); 12 | 13 | it("select works with null", () => { 14 | const tuple = new Tuple({ a: 1 }, { a: null }); 15 | expect(tuple.select(x => x.a).spread()).toEqual([1, null]); 16 | }); 17 | 18 | it("select works with undefined", () => { 19 | const tuple = new Tuple({ a: 1 }, {}); 20 | expect(tuple.select(x => x.a).spread()).toEqual([1, undefined]); 21 | }); 22 | 23 | it("gets by key when items are lists", () => { 24 | const tuple = new ArrayTuple( 25 | [{ key: 1, a: 10 }, { key: 3, a: 30 }], 26 | [{ key: 1, a: 11 }, { key: 2, a: 21 }] 27 | ); 28 | expect(tuple.get(1)!.spread()).toEqual([ 29 | { key: 1, a: 10 }, 30 | { key: 1, a: 11 } 31 | ]); 32 | expect(tuple.get(2)!.spread()).toEqual([undefined, { key: 2, a: 21 }]); 33 | expect(tuple.get(3)!.spread()).toEqual([{ key: 3, a: 30 }, undefined]); 34 | }); 35 | 36 | it("maps entries with keys", () => { 37 | const tuple = new ArrayTuple( 38 | [{ key: 1, a: 10 }, { key: 3, a: 30 }], 39 | [{ key: 1, a: 11 }, { key: 2, a: 21 }] 40 | ); 41 | const result = tuple.map(tuple => tuple.spread()); 42 | 43 | expect(result).toEqual([ 44 | [{ key: 1, a: 10 }, { key: 1, a: 11 }], 45 | [undefined, { key: 2, a: 21 }], 46 | [{ key: 3, a: 30 }, undefined] 47 | ]); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /packs/standalone/src/tuple.ts: -------------------------------------------------------------------------------- 1 | export class Tuple { 2 | prev: Maybe; 3 | next: Maybe; 4 | 5 | constructor(prev: Maybe, next: Maybe) { 6 | this.prev = prev; 7 | this.next = next; 8 | } 9 | 10 | spread(): [Maybe, Maybe] { 11 | const prev = this.prev; 12 | const next = this.next; 13 | return [prev, next]; 14 | } 15 | 16 | select(selector: (x: T) => S) { 17 | const [prev, next] = this.spread(); 18 | const [newPrev, newNext] = [ 19 | prev === null ? null : prev === undefined ? undefined : selector(prev), 20 | next === null ? null : next === undefined ? undefined : selector(next) 21 | ]; 22 | return new Tuple(newPrev, newNext); 23 | } 24 | 25 | selectMany(selector: (x: T) => S[]) { 26 | const [prev, next] = this.spread(); 27 | const [newPrev, newNext] = [ 28 | prev === null ? null : prev === undefined ? undefined : selector(prev), 29 | next === null ? null : next === undefined ? undefined : selector(next) 30 | ]; 31 | return new ArrayTuple(newPrev, newNext); 32 | } 33 | 34 | any() { 35 | const [prev, next] = this.spread(); 36 | return prev == null ? next : prev; 37 | } 38 | 39 | get(_key: any) { 40 | throw Error("Get only supported in ArrayTuple"); 41 | } 42 | 43 | map(_mapper: any) { 44 | throw Error("Map only supported in ArrayTuple"); 45 | } 46 | } 47 | 48 | export class ArrayTuple extends Tuple { 49 | _dict?: Map>; 50 | 51 | _getChildrenMap() { 52 | if (!this._dict) { 53 | const [maybePrevs, maybeNexts] = this.spread(); 54 | const prevs: T[] = maybePrevs || []; 55 | const nexts: T[] = maybeNexts || []; 56 | 57 | const unsortedMap = new Map; next?: Maybe }>( 58 | prevs.map(prev => [prev.key, { prev }]) 59 | ); 60 | nexts.forEach(next => { 61 | const { prev } = unsortedMap.get(next.key) || { prev: undefined }; 62 | unsortedMap.set(next.key, { prev, next }); 63 | }); 64 | 65 | const sortedKeys = Array.from(unsortedMap.keys()); 66 | sortedKeys.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)); 67 | this._dict = new Map>( 68 | sortedKeys.map(key => { 69 | const { prev = undefined, next = undefined } = 70 | unsortedMap.get(key) || {}; 71 | return [key, new Tuple(prev, next)]; 72 | }) 73 | ); 74 | } 75 | return this._dict; 76 | } 77 | 78 | get(key: any): Tuple | undefined { 79 | const childrenMap = this._getChildrenMap(); 80 | return childrenMap.get(key); 81 | } 82 | 83 | map(mapper: (t: Tuple, key?: any, self?: ArrayTuple) => M) { 84 | const childrenMap = this._getChildrenMap(); 85 | const result: M[] = []; 86 | childrenMap.forEach((tuple, key) => result.push(mapper(tuple, key, this))); 87 | return result; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /packs/standalone/src/types.d.ts: -------------------------------------------------------------------------------- 1 | type Maybe = T | null | undefined; 2 | interface Flavoring { 3 | _type?: FlavorT; 4 | } 5 | type Flavor = T & Flavoring; 6 | 7 | declare module "code-surfer-types" { 8 | export interface InputStep { 9 | code: string; 10 | focus?: string; 11 | title?: string; 12 | subtitle?: string; 13 | lang?: string; 14 | showNumbers?: boolean; 15 | } 16 | 17 | type LineKey = Flavor; 18 | type LineIndex = Flavor; 19 | type StepIndex = Flavor; 20 | 21 | export interface Step { 22 | lines: LineKey[]; 23 | longestLineIndex: LineIndex; 24 | focus: Record; 25 | focusCenter: number; 26 | focusCount: number; 27 | title?: string; 28 | subtitle?: string; 29 | dimensions?: { 30 | paddingTop: number; 31 | paddingBottom: number; 32 | }; 33 | } 34 | 35 | export interface Dimensions { 36 | lineHeight: number; 37 | containerHeight: number; 38 | containerWidth: number; 39 | contentHeight?: number; 40 | contentWidth: number; 41 | } 42 | 43 | type Partial = { 44 | [P in keyof T]?: T[P]; 45 | }; 46 | } 47 | 48 | declare module "shell-quote" { 49 | export function parse(s: string): string[]; 50 | } 51 | -------------------------------------------------------------------------------- /packs/standalone/src/use-window-resize.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // addEventListener(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; 4 | // type addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; 5 | 6 | export default function useWindowResize( 7 | handler: (this: Window, ev: WindowEventMap["resize"]) => any, 8 | deps: any[] 9 | ) { 10 | React.useEffect(() => { 11 | window.addEventListener("resize", handler); 12 | return () => { 13 | window.removeEventListener("resize", handler); 14 | }; 15 | }, deps); 16 | } 17 | -------------------------------------------------------------------------------- /packs/standalone/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "baseUrl": "./", 24 | "paths": { 25 | "*": ["src/*", "node_modules/*"] 26 | }, 27 | "jsx": "react", 28 | "esModuleInterop": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packs/step-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@code-surfer/step-parser", 3 | "version": "3.1.1", 4 | "private": false, 5 | "license": "MIT", 6 | "author": "pomber", 7 | "repository": "pomber/code-surfer", 8 | "main": "dist/index.js", 9 | "module": "dist/step-parser.esm.js", 10 | "typings": "dist/index.d.ts", 11 | "files": [ 12 | "dist" 13 | ], 14 | "engines": { 15 | "node": ">=8", 16 | "npm": ">=5" 17 | }, 18 | "scripts": { 19 | "start": "tsdx watch", 20 | "build": "tsdx build", 21 | "test": "cross-env CI=1 tsdx test --env=jsdom", 22 | "test:watch": "tsdx test --env=jsdom -u --watch" 23 | }, 24 | "dependencies": { 25 | "array.prototype.flat": "^1.2.1", 26 | "diff": "^4.0.1", 27 | "prismjs": "^1.16.0", 28 | "shell-quote": "^1.6.1" 29 | }, 30 | "devDependencies": { 31 | "@types/diff": "^4.0.2", 32 | "@types/jest": "^24.0.15", 33 | "@types/prismjs": "^1.16.0", 34 | "tsdx": "^0.7.2", 35 | "tslib": "^1.10.0", 36 | "typescript": "^3.5.2" 37 | }, 38 | "jest": { 39 | "testMatch": [ 40 | "/**/?(*.)(spec|test).(ts|js)?(x)" 41 | ] 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packs/step-parser/readme.md: -------------------------------------------------------------------------------- 1 | # @code-surfer/step-parser 2 | 3 | For internal use by code-surfer, but you can use it if you want. Just be aware that **it doesn't follow semantic versioning**, so pin the version just in case. 4 | 5 | ## Contributing 6 | 7 | Watch and build code: 8 | 9 | ```bash 10 | $ yarn 11 | $ yarn workspace @code-surfer/step-parser start 12 | ``` 13 | 14 | Watch tests: 15 | 16 | ```bash 17 | $ yarn workspace @code-surfer/step-parser test:watch 18 | ``` 19 | -------------------------------------------------------------------------------- /packs/step-parser/src/__snapshots__/differ.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`differ works ? 1`] = ` 4 | Object { 5 | "lineIds": Array [ 6 | 0.2, 7 | 0.4, 8 | 0.6, 9 | 0.8, 10 | ], 11 | "steps": Array [ 12 | Array [ 13 | 0.2, 14 | 0.4, 15 | 0.6, 16 | 0.8, 17 | ], 18 | Array [], 19 | ], 20 | } 21 | `; 22 | 23 | exports[`differ works 1`] = ` 24 | Object { 25 | "lineIds": Array [ 26 | 0.25, 27 | 0.3125, 28 | 0.375, 29 | 0.5, 30 | 0.75, 31 | ], 32 | "steps": Array [ 33 | Array [ 34 | 0.25, 35 | 0.5, 36 | 0.75, 37 | ], 38 | Array [ 39 | 0.25, 40 | 0.375, 41 | ], 42 | Array [ 43 | 0.25, 44 | 0.3125, 45 | 0.375, 46 | ], 47 | Array [ 48 | 0.25, 49 | 0.3125, 50 | 0.375, 51 | ], 52 | ], 53 | } 54 | `; 55 | 56 | exports[`differ works when last line changes 1`] = ` 57 | Object { 58 | "lineIds": Array [ 59 | 0.5, 60 | 0.75, 61 | ], 62 | "steps": Array [ 63 | Array [ 64 | 0.5, 65 | ], 66 | Array [ 67 | 0.5, 68 | 0.75, 69 | ], 70 | ], 71 | } 72 | `; 73 | 74 | exports[`differ works with empty old code 1`] = ` 75 | Object { 76 | "lineIds": Array [ 77 | 0.3333333333333333, 78 | 0.6666666666666666, 79 | ], 80 | "steps": Array [ 81 | Array [], 82 | Array [ 83 | 0.3333333333333333, 84 | 0.6666666666666666, 85 | ], 86 | Array [], 87 | ], 88 | } 89 | `; 90 | -------------------------------------------------------------------------------- /packs/step-parser/src/__snapshots__/tokenizer.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`tokenizer works 1`] = ` 4 | Object { 5 | "tokens": Array [ 6 | Array [ 7 | "console", 8 | ".", 9 | "log", 10 | "(", 11 | "1", 12 | ")", 13 | ], 14 | Array [ 15 | "function", 16 | " ", 17 | "x", 18 | "(", 19 | ")", 20 | " ", 21 | "{", 22 | ], 23 | Array [ 24 | " ", 25 | "return", 26 | " ", 27 | "\\"foo\\"", 28 | ], 29 | Array [ 30 | "}", 31 | ], 32 | ], 33 | "types": Array [ 34 | Array [ 35 | "plain", 36 | "punctuation", 37 | "function", 38 | "punctuation", 39 | "number", 40 | "punctuation", 41 | ], 42 | Array [ 43 | "keyword", 44 | "plain", 45 | "function", 46 | "punctuation", 47 | "punctuation", 48 | "plain", 49 | "punctuation", 50 | ], 51 | Array [ 52 | "plain", 53 | "keyword", 54 | "plain", 55 | "string", 56 | ], 57 | Array [ 58 | "punctuation", 59 | ], 60 | ], 61 | } 62 | `; 63 | -------------------------------------------------------------------------------- /packs/step-parser/src/differ.test.ts: -------------------------------------------------------------------------------- 1 | import { linesDiff, generateIds } from "./differ"; 2 | 3 | describe("generate line ids", () => { 4 | it("works with empty array", () => { 5 | const ids: number[] = []; 6 | const newIds = generateIds(ids, undefined, 1); 7 | expect(newIds).toEqual([0.5]); 8 | expect(ids).toEqual([0.5]); 9 | }); 10 | it("works with empty array and several new", () => { 11 | const ids: number[] = []; 12 | const newIds = generateIds(ids, undefined, 3); 13 | expect(newIds).toEqual([0.25, 0.5, 0.75]); 14 | expect(ids).toEqual([0.25, 0.5, 0.75]); 15 | }); 16 | it("works before existing ids", () => { 17 | const ids = [0.5]; 18 | const newIds = generateIds(ids, undefined, 3); 19 | expect(newIds).toEqual([0.125, 0.25, 0.375]); 20 | expect(ids).toEqual([0.125, 0.25, 0.375, 0.5]); 21 | }); 22 | it("works after existing ids", () => { 23 | const ids = [0.5]; 24 | const newIds = generateIds(ids, 0.5, 3); 25 | expect(newIds).toEqual([0.625, 0.75, 0.875]); 26 | expect(ids).toEqual([0.5, 0.625, 0.75, 0.875]); 27 | }); 28 | it("works between existing ids", () => { 29 | const ids = [0.2, 0.6]; 30 | const newIds = generateIds(ids, 0.2, 3); 31 | expect(newIds).toEqual([0.3, 0.4, 0.5]); 32 | expect(ids).toEqual([0.2, 0.3, 0.4, 0.5, 0.6]); 33 | }); 34 | }); 35 | 36 | describe("differ", () => { 37 | it("works", () => { 38 | const codes = [ 39 | ` 40 | console.log(1) 41 | console.log(2) 42 | console.log(3) 43 | `.trim(), 44 | ` 45 | console.log(1) 46 | console.log(4) 47 | `.trim(), 48 | ` 49 | console.log(1) 50 | console.log(3) 51 | console.log(4) 52 | `.trim(), 53 | ` 54 | console.log(1) 55 | console.log(3) 56 | console.log(4) 57 | `.trim() 58 | ]; 59 | expect(linesDiff(codes)).toMatchSnapshot(); 60 | }); 61 | 62 | it("works with empty old code", () => { 63 | const codes = [ 64 | ``, 65 | ` 66 | console.log(1) 67 | console.log(4) 68 | `.trim(), 69 | `` 70 | ]; 71 | expect(linesDiff(codes)).toMatchSnapshot(); 72 | }); 73 | 74 | it("works when last line changes", () => { 75 | const codes = [ 76 | ` 77 | console.log(1) 78 | `.trim(), 79 | ` 80 | console.log(1) 81 | console.log(4) 82 | `.trim() 83 | ]; 84 | expect(linesDiff(codes)).toMatchSnapshot(); 85 | }); 86 | 87 | it("works ?", () => { 88 | const codes = [ 89 | `var x1 = 1 90 | 91 | console.log(x1) 92 | debugger 93 | 94 | `, 95 | "" 96 | ]; 97 | expect(linesDiff(codes)).toMatchSnapshot(); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /packs/step-parser/src/differ.ts: -------------------------------------------------------------------------------- 1 | import { diffLines } from "diff"; 2 | 3 | String.prototype.trimEnd = 4 | String.prototype.trimEnd || 5 | function(this: string) { 6 | if (String.prototype.trimRight) { 7 | return this.trimRight(); 8 | } else { 9 | const trimmed = this.trim(); 10 | const indexOfWord = this.indexOf(trimmed); 11 | 12 | return this.slice(indexOfWord, this.length); 13 | } 14 | }; 15 | 16 | function getChanges(oldCode: string, newCode: string) { 17 | const changes = diffLines(normalize(oldCode), normalize(newCode)); 18 | let index = 0; 19 | const chunks: { op: "-" | "+"; count: number; index: number }[] = []; 20 | changes.forEach(({ count = 0, removed, added }) => { 21 | if (removed) { 22 | chunks.push({ 23 | op: "-", 24 | count, 25 | index 26 | }); 27 | } 28 | 29 | if (added) { 30 | chunks.push({ 31 | op: "+", 32 | count, 33 | index 34 | }); 35 | } 36 | 37 | if (!removed) { 38 | index += count; 39 | } 40 | }); 41 | 42 | return chunks; 43 | } 44 | 45 | function normalize(text: string) { 46 | return text && text.trimEnd().concat("\n"); 47 | } 48 | 49 | export function generateIds( 50 | lineIds: number[], 51 | afterId: number = 0, 52 | count: number 53 | ) { 54 | const afterIndex = lineIds.indexOf(afterId); 55 | const beforeIndex = afterIndex + 1; 56 | const aid = afterId || 0; 57 | const bid = lineIds[beforeIndex] || 1; 58 | 59 | const newIds = Array(count) 60 | .fill(0) 61 | .map((_, i) => aid + ((bid - aid) * (i + 1)) / (count + 1)); 62 | 63 | lineIds.splice(afterIndex + 1, 0, ...newIds); 64 | return newIds; 65 | } 66 | 67 | function getStepIds( 68 | lineIds: number[], 69 | oldStepIds: number[] = [], 70 | oldStepCode: string = "", 71 | newStepCode: string = "" 72 | ): number[] { 73 | const changes = getChanges(oldStepCode, newStepCode); 74 | 75 | const newStepIds = oldStepIds.slice(0); 76 | changes.forEach(({ op, count, index }) => { 77 | if (op === "-") { 78 | newStepIds.splice(index, count); 79 | } else { 80 | const afterId = newStepIds[index - 1]; 81 | const newIds = generateIds(lineIds, afterId, count); 82 | newStepIds.splice(index, 0, ...newIds); 83 | } 84 | }); 85 | return newStepIds; 86 | } 87 | 88 | export function linesDiff(codeList: string[]) { 89 | const steps: number[][] = []; 90 | const lineIds: number[] = []; 91 | codeList.forEach((_, i) => { 92 | steps.push(getStepIds(lineIds, steps[i - 1], codeList[i - 1], codeList[i])); 93 | }); 94 | return { lineIds, steps }; 95 | } 96 | -------------------------------------------------------------------------------- /packs/step-parser/src/focus-parser.test.ts: -------------------------------------------------------------------------------- 1 | import { parseFocus } from "./focus-parser"; 2 | 3 | describe("Parsing Focus String", () => { 4 | it("it throws when string is empty", () => { 5 | expect(() => parseFocus("")).toThrow(); 6 | }); 7 | 8 | it("works with single lines", () => { 9 | const focus = "1"; 10 | const result = parseFocus(focus); 11 | expect(result[0]).toBeTruthy(); 12 | }); 13 | 14 | it("works with lists", () => { 15 | const focus = "1,5"; 16 | const result = parseFocus(focus); 17 | expect(result[0]).toBeTruthy(); 18 | expect(result[2]).toBeFalsy(); 19 | expect(result[4]).toBeTruthy(); 20 | }); 21 | 22 | it("works with ranges", () => { 23 | const focus = "2:5"; 24 | const result = parseFocus(focus); 25 | expect(result[0]).toBeFalsy(); 26 | expect(result[1]).toBeTruthy(); 27 | expect(result[2]).toBeTruthy(); 28 | expect(result[4]).toBeTruthy(); 29 | expect(result[5]).toBeFalsy(); 30 | }); 31 | 32 | it("works with lists and ranges", () => { 33 | const focus = "1,4:5,6,8"; 34 | const result = parseFocus(focus); 35 | expect(result[0]).toBeTruthy(); 36 | expect(result[3]).toBeTruthy(); 37 | expect(result[4]).toBeTruthy(); 38 | expect(result[5]).toBeTruthy(); 39 | expect(result[7]).toBeTruthy(); 40 | }); 41 | 42 | it("works with single column", () => { 43 | const focus = "1[5]"; 44 | const result = parseFocus(focus); 45 | expect(result[0]).toEqual([4]); 46 | }); 47 | 48 | it("works with column range", () => { 49 | const focus = "1[5:7],3[1:2]"; 50 | const result = parseFocus(focus); 51 | expect(result[0]).toEqual([4, 5, 6]); 52 | expect(result[2]).toEqual([0, 1]); 53 | }); 54 | 55 | it("works with column list and range", () => { 56 | const focus = "1[5:7,10,12:13],3[1:2],5:6"; 57 | const result = parseFocus(focus); 58 | expect(result[0]).toEqual([4, 5, 6, 9, 11, 12]); 59 | expect(result[2]).toEqual([0, 1]); 60 | expect(result[4]).toBeTruthy(); 61 | expect(result[5]).toBeTruthy(); 62 | }); 63 | 64 | it("throws when string is invalid", () => { 65 | const runParseFocus = (v: string) => () => parseFocus(v); 66 | expect(runParseFocus("foo")).toThrow(); 67 | expect(runParseFocus("12:foo")).toThrow(); 68 | expect(runParseFocus("1,2,3[4,-10]")).toThrow(); 69 | expect(runParseFocus("0:10")).toThrow(); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packs/step-parser/src/focus-parser.ts: -------------------------------------------------------------------------------- 1 | import flat from "array.prototype.flat"; 2 | import { fromEntries } from "./object-entries"; 3 | 4 | type LineIndex = number; 5 | type ColumnIndex = number; 6 | 7 | export function parseFocus(focus: string) { 8 | if (!focus) { 9 | throw new Error("Focus cannot be empty"); 10 | } 11 | 12 | try { 13 | const parts = focus.split(/,(?![^\[]*\])/g).map(parsePart); 14 | return fromEntries(flat(parts)); 15 | } catch (error) { 16 | // TODO enhance error 17 | throw error; 18 | } 19 | } 20 | 21 | type Part = [LineIndex, true | ColumnIndex[]]; 22 | 23 | function parsePart(part: string): Part[] { 24 | // a part could be 25 | // - a line number: "2" 26 | // - a line range: "5:9" 27 | // - a line number with a column selector: "2[1,3:5,9]" 28 | const columnsMatch = part.match(/(\d+)\[(.+)\]/); 29 | if (columnsMatch) { 30 | const [, line, columns] = columnsMatch; 31 | const columnsList = columns.split(",").map(expandString); 32 | const lineIndex = Number(line) - 1; 33 | const columnIndexes = flat(columnsList).map(c => c - 1); 34 | return [[lineIndex, columnIndexes]]; 35 | } else { 36 | return expandString(part).map(lineNumber => [lineNumber - 1, true]); 37 | } 38 | } 39 | 40 | function expandString(part: string) { 41 | // Transforms something like 42 | // - "1:3" to [1,2,3] 43 | // - "4" to [4] 44 | const [start, end] = part.split(":"); 45 | 46 | if (!isNaturalNumber(start)) { 47 | throw new FocusNumberError(start); 48 | } 49 | 50 | const startNumber = Number(start); 51 | 52 | if (startNumber < 1) { 53 | throw new LineOrColumnNumberError(); 54 | } 55 | 56 | if (!end) { 57 | return [startNumber]; 58 | } else { 59 | if (!isNaturalNumber(end)) { 60 | throw new FocusNumberError(end); 61 | } 62 | 63 | const list: number[] = []; 64 | for (let i = startNumber; i <= +end; i++) { 65 | list.push(i); 66 | } 67 | return list; 68 | } 69 | } 70 | 71 | function isNaturalNumber(n: any) { 72 | n = n.toString(); // force the value in case it is not 73 | var n1 = Math.abs(n), 74 | n2 = parseInt(n, 10); 75 | return !isNaN(n1) && n2 === n1 && n1.toString() === n; 76 | } 77 | 78 | export function getFocusSize(focus: Record) { 79 | const lineIndexList = Object.keys(focus).map(k => +k); 80 | const focusStart = Math.min.apply(Math, lineIndexList); 81 | const focusEnd = Math.max.apply(Math, lineIndexList); 82 | return { 83 | focusCenter: (focusStart + focusEnd + 1) / 2, 84 | focusCount: focusEnd - focusStart + 1 85 | }; 86 | } 87 | 88 | export class LineOrColumnNumberError extends Error { 89 | constructor() { 90 | super(`Invalid line or column number in focus string`); 91 | Object.setPrototypeOf(this, new.target.prototype); 92 | } 93 | } 94 | 95 | export class FocusNumberError extends Error { 96 | number: string; 97 | constructor(number: string) { 98 | super(`Invalid number "${number}" in focus string`); 99 | this.number = number; 100 | Object.setPrototypeOf(this, new.target.prototype); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /packs/step-parser/src/index.ts: -------------------------------------------------------------------------------- 1 | import { parseSteps } from "./step-parser"; 2 | 3 | export { parseSteps }; 4 | -------------------------------------------------------------------------------- /packs/step-parser/src/object-entries.ts: -------------------------------------------------------------------------------- 1 | export function fromEntries( 2 | pairs: [K, V][] 3 | ) { 4 | const result = {} as Record; 5 | 6 | let index = -1, 7 | length = pairs == null ? 0 : pairs.length; 8 | 9 | while (++index < length) { 10 | var pair = pairs[index]; 11 | result[pair[0]] = pair[1]; 12 | } 13 | 14 | return result; 15 | } 16 | 17 | export function toEntries( 18 | o: Record 19 | ): [K, V][] { 20 | const keys = Object.keys(o) as K[]; 21 | return keys.map(k => [k, o[k]]); 22 | } 23 | -------------------------------------------------------------------------------- /packs/step-parser/src/step-parser.test.ts: -------------------------------------------------------------------------------- 1 | import { parseSteps } from "./step-parser"; 2 | 3 | describe("Parsing steps", () => { 4 | it("works", () => { 5 | const steps = [ 6 | { 7 | code: ` 8 | console.log(1) 9 | console.log(2) 10 | console.log(3) 11 | `.trim(), 12 | lang: "javascript", 13 | focus: "1,2" 14 | }, 15 | { 16 | code: ` 17 | console.log(1) 18 | console.log(3) 19 | `.trim(), 20 | lang: "javascript", 21 | focus: "1[3:5],2" 22 | } 23 | ]; 24 | const result = parseSteps(steps); 25 | expect(result).toMatchSnapshot(); 26 | }); 27 | 28 | it("adds default focus", () => { 29 | const steps = [ 30 | { 31 | code: ` 32 | console.log(1) 33 | console.log(2) 34 | console.log(3) 35 | `.trim(), 36 | lang: "javascript" 37 | }, 38 | { 39 | code: ` 40 | console.log(1) 41 | console.log(3) 42 | 43 | console.log(4); 44 | `.trim() 45 | } 46 | ]; 47 | const result = parseSteps(steps); 48 | expect(result).toMatchSnapshot(); 49 | }); 50 | 51 | it("works with empty diff", () => { 52 | const steps = [ 53 | { 54 | code: ` 55 | console.log(1) 56 | console.log(2) 57 | console.log(3) 58 | `.trim(), 59 | lang: "javascript" 60 | }, 61 | { 62 | code: ``, 63 | lang: "diff" 64 | } 65 | ]; 66 | const result = parseSteps(steps); 67 | expect(result).toMatchSnapshot(); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /packs/step-parser/src/tokenizer.test.ts: -------------------------------------------------------------------------------- 1 | import { tokenize, MissingGrammarError } from "./tokenizer"; 2 | 3 | describe("tokenizer", () => { 4 | it("works", () => { 5 | const tokens = tokenize( 6 | ` 7 | console.log(1) 8 | function x() { 9 | return "foo" 10 | } 11 | `.trim(), 12 | "javascript" 13 | ); 14 | expect(tokens).toMatchSnapshot(); 15 | }); 16 | 17 | it("throws the correct error", () => { 18 | const action = () => tokenize(`foo bar`.trim(), "foolang"); 19 | expect(action).toThrowError(MissingGrammarError); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packs/step-parser/src/tokenizer.ts: -------------------------------------------------------------------------------- 1 | // // https://github.com/PrismJS/prism/issues/1303#issuecomment-375353987 2 | // global.Prism = { disableWorkerMessageHandler: true }; 3 | // const Prism = require("prismjs"); 4 | import Prism from "prismjs"; 5 | const newlineRe = /\r\n|\r|\n/; 6 | 7 | export function tokenize(code: string, lang: string) { 8 | const grammar = Prism.languages[lang]; 9 | if (!grammar) { 10 | throw new MissingGrammarError(lang); 11 | } 12 | 13 | const prismTokens = Prism.tokenize(code, Prism.languages[lang]); 14 | const nestedTokens = tokenizeStrings(prismTokens); 15 | const tokens = flattenTokens(nestedTokens); 16 | 17 | let currentLine: FlatToken[] = []; 18 | let currentTokenLine: string[] = []; 19 | let currentTypeLine: string[] = []; 20 | 21 | const lines = [currentLine]; 22 | const tokenLines = [currentTokenLine]; 23 | const typeLines = [currentTypeLine]; 24 | 25 | tokens.forEach(token => { 26 | const contentLines = token.content.split(newlineRe); 27 | 28 | const firstContent = contentLines.shift(); 29 | if (firstContent !== undefined && firstContent !== "") { 30 | currentLine.push({ type: token.type, content: firstContent }); 31 | currentTokenLine.push(firstContent); 32 | currentTypeLine.push(token.type); 33 | } 34 | contentLines.forEach(content => { 35 | currentLine = []; 36 | currentTokenLine = []; 37 | currentTypeLine = []; 38 | lines.push(currentLine); 39 | tokenLines.push(currentTokenLine); 40 | typeLines.push(currentTypeLine); 41 | if (content !== "") { 42 | currentLine.push({ type: token.type, content }); 43 | currentTokenLine.push(content); 44 | currentTypeLine.push(token.type); 45 | } 46 | }); 47 | }); 48 | return { 49 | tokens: tokenLines, 50 | types: typeLines 51 | }; 52 | } 53 | 54 | type NestedToken = { 55 | type: string; 56 | content: string | NestedToken[]; 57 | }; 58 | 59 | function tokenizeStrings( 60 | prismTokens: (string | Prism.Token)[], 61 | parentType = "plain" 62 | ): NestedToken[] { 63 | return prismTokens.map(prismToken => wrapToken(prismToken, parentType)); 64 | } 65 | 66 | function wrapToken( 67 | prismToken: string | Prism.Token, 68 | parentType = "plain" 69 | ): NestedToken { 70 | if (typeof prismToken === "string") { 71 | return { 72 | type: parentType, 73 | content: prismToken 74 | }; 75 | } 76 | 77 | if (Array.isArray(prismToken.content)) { 78 | return { 79 | type: prismToken.type, 80 | content: tokenizeStrings(prismToken.content, prismToken.type) 81 | }; 82 | } 83 | 84 | return wrapToken(prismToken.content, prismToken.type); 85 | } 86 | 87 | type FlatToken = { 88 | type: string; 89 | content: string; 90 | }; 91 | 92 | // Take a list of nested tokens 93 | // (token.content may contain an array of tokens) 94 | // and flatten it so content is always a string 95 | // and type the type of the leaf 96 | function flattenTokens(tokens: NestedToken[]) { 97 | const flatList: FlatToken[] = []; 98 | tokens.forEach(token => { 99 | const { type, content } = token; 100 | if (Array.isArray(content)) { 101 | flatList.push(...flattenTokens(content)); 102 | } else { 103 | flatList.push({ type, content }); 104 | } 105 | }); 106 | return flatList; 107 | } 108 | 109 | export class MissingGrammarError extends Error { 110 | lang: string; 111 | constructor(lang: string) { 112 | super(`Missing syntax highlighting for language "${lang}"`); 113 | this.lang = lang; 114 | Object.setPrototypeOf(this, new.target.prototype); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /packs/step-parser/src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module "array.prototype.flat" { 2 | export default function flat(a: T[][]): T[]; 3 | } 4 | -------------------------------------------------------------------------------- /packs/step-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "baseUrl": "./", 24 | "paths": { 25 | "*": ["src/*", "node_modules/*"] 26 | }, 27 | "jsx": "react", 28 | "esModuleInterop": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packs/themes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@code-surfer/themes", 3 | "description": "Themes for Code Surfer", 4 | "version": "3.1.1", 5 | "private": false, 6 | "license": "MIT", 7 | "author": "pomber", 8 | "repository": "pomber/code-surfer", 9 | "main": "dist/index.js", 10 | "module": "dist/themes.esm.js", 11 | "typings": "dist/index.d.ts", 12 | "files": [ 13 | "dist" 14 | ], 15 | "engines": { 16 | "node": ">=8", 17 | "npm": ">=5" 18 | }, 19 | "scripts": { 20 | "start": "tsdx watch", 21 | "build": "tsdx build", 22 | "test": "cross-env CI=1 tsdx test --env=jsdom", 23 | "test:watch": "tsdx test --env=jsdom -u --watch" 24 | }, 25 | "peerDependencies": { 26 | "react": "^16.8.0", 27 | "theme-ui": "^0.2.41" 28 | }, 29 | "devDependencies": { 30 | "@types/theme-ui": "^0.2.3", 31 | "tsdx": "^0.7.2", 32 | "tslib": "^1.10.0", 33 | "typescript": "^3.5.2" 34 | }, 35 | "jest": { 36 | "testMatch": [ 37 | "/**/?(*.)(spec|test).(ts|js)?(x)" 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packs/themes/src/index.ts: -------------------------------------------------------------------------------- 1 | export { theme as base } from "./theme.base"; 2 | export { theme as dracula } from "./theme.dracula"; 3 | export { theme as duotoneDark } from "./theme.duotone-dark"; 4 | export { theme as duotoneLight } from "./theme.duotone-light"; 5 | export { theme as github } from "./theme.github"; 6 | export { theme as nightOwl } from "./theme.night-owl"; 7 | export { theme as oceanicNext } from "./theme.oceanic-next"; 8 | export { theme as shadesOfPurple } from "./theme.shades-of-purple"; 9 | export { theme as ultramin } from "./theme.ultramin"; 10 | export { theme as vsDark } from "./theme.vs-dark"; 11 | 12 | export { CodeSurferTheme } from "./utils"; 13 | export { 14 | StylesProvider, 15 | Styled, 16 | getClassFromTokenType, 17 | useUnfocusedStyle 18 | } from "./styles"; 19 | -------------------------------------------------------------------------------- /packs/themes/src/styles.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { ThemeProvider, jsx, useThemeUI, SxStyleProp } from "theme-ui"; 3 | import { theme as baseTheme } from "./theme.base"; 4 | import { CodeSurferTheme, CodeSurferStyles } from "./utils"; 5 | import React from "react"; 6 | 7 | function StylesProvider({ 8 | theme = {}, 9 | children 10 | }: { 11 | theme?: CodeSurferTheme; 12 | children: React.ReactNode; 13 | }) { 14 | const outer = useThemeUI().theme || {}; 15 | 16 | const base = { 17 | ...baseTheme, 18 | ...outer, 19 | styles: { 20 | ...baseTheme.styles, 21 | ...outer.styles 22 | } 23 | }; 24 | 25 | return ( 26 | 27 | {children} 28 | 29 | ); 30 | } 31 | 32 | function useStyles(): CodeSurferStyles { 33 | const { theme } = useThemeUI(); 34 | return (theme as any).styles.CodeSurfer; 35 | } 36 | 37 | function getClassFromTokenType(type: string) { 38 | return "token-" + type; 39 | } 40 | 41 | function usePreStyle() { 42 | const styles = useStyles(); 43 | const preSx = React.useMemo(() => { 44 | const sx = { 45 | ...styles.pre 46 | }; 47 | Object.keys(styles.tokens).forEach(key => { 48 | const classList = key 49 | .split(/\s/) 50 | .map(type => "." + getClassFromTokenType(type)) 51 | .join(", "); 52 | sx[classList] = styles.tokens[key]; 53 | }); 54 | return sx; 55 | }, [styles]); 56 | return preSx; 57 | } 58 | 59 | const baseTitle: SxStyleProp = { 60 | position: "absolute" as "absolute", 61 | top: 0, 62 | width: "100%", 63 | margin: 0, 64 | padding: "1em 0", 65 | textAlign: "center" 66 | }; 67 | 68 | const baseSubtitle: SxStyleProp = { 69 | position: "absolute" as "absolute", 70 | bottom: 0, 71 | width: "calc(100% - 2em)", 72 | boxSizing: "border-box" as "border-box", 73 | margin: "0.3em 1em", 74 | padding: "0.5em", 75 | background: "rgba(2,2,2,0.9)", 76 | textAlign: "center" 77 | }; 78 | 79 | type HTMLProps = React.DetailedHTMLProps, T>; 80 | 81 | const Styled = { 82 | Placeholder: () => { 83 | return ( 84 |
91 | ); 92 | }, 93 | Code: (props: HTMLProps) => ( 94 | 95 | ), 96 | Pre: React.forwardRef( 97 | (props: HTMLProps, ref: React.Ref) => ( 98 |
 99 |     )
100 |   ),
101 |   Title: (props: HTMLProps) => (
102 |     

103 | ), 104 | Subtitle: (props: HTMLProps) => ( 105 |

106 | ) 107 | }; 108 | 109 | function useUnfocusedStyle() { 110 | return useStyles().unfocused || { opacity: 0.3 }; 111 | } 112 | export { StylesProvider, Styled, getClassFromTokenType, useUnfocusedStyle }; 113 | -------------------------------------------------------------------------------- /packs/themes/src/theme.base.ts: -------------------------------------------------------------------------------- 1 | import { CodeSurferTheme } from "./utils"; 2 | 3 | const theme: CodeSurferTheme = { 4 | colors: { 5 | background: "rgb(246, 248, 250)", 6 | text: "rgb(57, 58, 52)", 7 | primary: "rgb(0, 164, 219)" 8 | }, 9 | styles: { 10 | CodeSurfer: { 11 | pre: { 12 | color: "text", 13 | backgroundColor: "background" 14 | }, 15 | code: { 16 | color: "text", 17 | backgroundColor: "background" 18 | }, 19 | tokens: { 20 | "comment cdata doctype": { 21 | fontStyle: "italic" 22 | }, 23 | "builtin changed keyword punctuation operator tag deleted string attr-value char number inserted": { 24 | color: "primary" 25 | }, 26 | "line-number": { 27 | opacity: 0.65, 28 | userSelect: "none" 29 | } 30 | }, 31 | title: { 32 | backgroundColor: "background", 33 | color: "text" 34 | }, 35 | subtitle: { 36 | color: "#d6deeb", 37 | backgroundColor: "rgba(10,10,10,0.9)" 38 | }, 39 | unfocused: { 40 | opacity: 0.3 41 | } 42 | } 43 | } 44 | }; 45 | 46 | export { theme }; 47 | -------------------------------------------------------------------------------- /packs/themes/src/theme.dracula.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | color: "#F8F8F2", 8 | backgroundColor: "#282A36" 9 | }, 10 | styles: [ 11 | { 12 | types: ["prolog", "constant", "builtin"], 13 | style: { 14 | color: "rgb(189, 147, 249)" 15 | } 16 | }, 17 | { 18 | types: ["inserted", "function"], 19 | style: { 20 | color: "rgb(80, 250, 123)" 21 | } 22 | }, 23 | { 24 | types: ["deleted"], 25 | style: { 26 | color: "rgb(255, 85, 85)" 27 | } 28 | }, 29 | { 30 | types: ["changed"], 31 | style: { 32 | color: "rgb(255, 184, 108)" 33 | } 34 | }, 35 | { 36 | types: ["punctuation", "symbol"], 37 | style: { 38 | color: "rgb(248, 248, 242)" 39 | } 40 | }, 41 | { 42 | types: ["string", "char", "tag", "selector"], 43 | style: { 44 | color: "rgb(255, 121, 198)" 45 | } 46 | }, 47 | { 48 | types: ["keyword", "variable"], 49 | style: { 50 | color: "rgb(189, 147, 249)", 51 | fontStyle: "italic" 52 | } 53 | }, 54 | { 55 | types: ["comment"], 56 | style: { 57 | color: "rgb(98, 114, 164)" 58 | } 59 | }, 60 | { 61 | types: ["attr-name"], 62 | style: { 63 | color: "rgb(241, 250, 140)" 64 | } 65 | } 66 | ] 67 | }; 68 | 69 | const theme = makeTheme(prismTheme); 70 | 71 | export { theme }; 72 | -------------------------------------------------------------------------------- /packs/themes/src/theme.duotone-dark.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | backgroundColor: "#2a2734", 8 | color: "#9a86fd" 9 | }, 10 | styles: [ 11 | { 12 | types: ["comment", "prolog", "doctype", "cdata", "punctuation"], 13 | style: { 14 | color: "#6c6783" 15 | } 16 | }, 17 | { 18 | types: ["namespace"], 19 | style: { 20 | opacity: 0.7 21 | } 22 | }, 23 | { 24 | types: ["tag", "operator", "number"], 25 | style: { 26 | color: "#e09142" 27 | } 28 | }, 29 | { 30 | types: ["property", "function"], 31 | style: { 32 | color: "#9a86fd" 33 | } 34 | }, 35 | { 36 | types: ["tag-id", "selector", "atrule-id"], 37 | style: { 38 | color: "#eeebff" 39 | } 40 | }, 41 | { 42 | types: ["attr-name"], 43 | style: { 44 | color: "#c4b9fe" 45 | } 46 | }, 47 | { 48 | types: [ 49 | "boolean", 50 | "string", 51 | "entity", 52 | "url", 53 | "attr-value", 54 | "keyword", 55 | "control", 56 | "directive", 57 | "unit", 58 | "statement", 59 | "regex", 60 | "at-rule", 61 | "placeholder", 62 | "variable" 63 | ], 64 | style: { 65 | color: "#ffcc99" 66 | } 67 | }, 68 | { 69 | types: ["deleted"], 70 | style: { 71 | textDecorationLine: "line-through" 72 | } 73 | }, 74 | { 75 | types: ["inserted"], 76 | style: { 77 | textDecorationLine: "underline" 78 | } 79 | }, 80 | { 81 | types: ["italic"], 82 | style: { 83 | fontStyle: "italic" 84 | } 85 | }, 86 | { 87 | types: ["important", "bold"], 88 | style: { 89 | fontWeight: "bold" as "bold" 90 | } 91 | }, 92 | { 93 | types: ["important"], 94 | style: { 95 | color: "#c4b9fe" 96 | } 97 | } 98 | ] 99 | }; 100 | 101 | const theme = makeTheme(prismTheme); 102 | 103 | export { theme }; 104 | -------------------------------------------------------------------------------- /packs/themes/src/theme.duotone-light.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | backgroundColor: "#faf8f5", 8 | color: "#728fcb" 9 | }, 10 | styles: [ 11 | { 12 | types: ["comment", "prolog", "doctype", "cdata", "punctuation"], 13 | style: { 14 | color: "#b6ad9a" 15 | } 16 | }, 17 | { 18 | types: ["namespace"], 19 | style: { 20 | opacity: 0.7 21 | } 22 | }, 23 | { 24 | types: ["tag", "operator", "number"], 25 | style: { 26 | color: "#063289" 27 | } 28 | }, 29 | { 30 | types: ["property", "function"], 31 | style: { 32 | color: "#b29762" 33 | } 34 | }, 35 | { 36 | types: ["tag-id", "selector", "atrule-id"], 37 | style: { 38 | color: "#2d2006" 39 | } 40 | }, 41 | { 42 | types: ["attr-name"], 43 | style: { 44 | color: "#896724" 45 | } 46 | }, 47 | { 48 | types: [ 49 | "boolean", 50 | "string", 51 | "entity", 52 | "url", 53 | "attr-value", 54 | "keyword", 55 | "control", 56 | "directive", 57 | "unit", 58 | "statement", 59 | "regex", 60 | "at-rule" 61 | ], 62 | style: { 63 | color: "#728fcb" 64 | } 65 | }, 66 | { 67 | types: ["placeholder", "variable"], 68 | style: { 69 | color: "#93abdc" 70 | } 71 | }, 72 | { 73 | types: ["deleted"], 74 | style: { 75 | textDecorationLine: "line-through" 76 | } 77 | }, 78 | { 79 | types: ["inserted"], 80 | style: { 81 | textDecorationLine: "underline" 82 | } 83 | }, 84 | { 85 | types: ["italic"], 86 | style: { 87 | fontStyle: "italic" 88 | } 89 | }, 90 | { 91 | types: ["important", "bold"], 92 | style: { 93 | fontWeight: "bold" as "bold" 94 | } 95 | }, 96 | { 97 | types: ["important"], 98 | style: { 99 | color: "#896724" 100 | } 101 | } 102 | ] 103 | }; 104 | 105 | const theme = makeTheme(prismTheme); 106 | 107 | export { theme }; 108 | -------------------------------------------------------------------------------- /packs/themes/src/theme.github.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | color: "#393A34", 8 | backgroundColor: "#f6f8fa" 9 | }, 10 | styles: [ 11 | { 12 | types: ["comment", "prolog", "doctype", "cdata"], 13 | style: { 14 | color: "#999988", 15 | fontStyle: "italic" 16 | } 17 | }, 18 | { 19 | types: ["namespace"], 20 | style: { 21 | opacity: 0.7 22 | } 23 | }, 24 | { 25 | types: ["string", "attr-value"], 26 | style: { 27 | color: "#e3116c" 28 | } 29 | }, 30 | { 31 | types: ["punctuation", "operator"], 32 | style: { 33 | color: "#393A34" 34 | } 35 | }, 36 | { 37 | types: [ 38 | "entity", 39 | "url", 40 | "symbol", 41 | "number", 42 | "boolean", 43 | "variable", 44 | "constant", 45 | "property", 46 | "regex", 47 | "inserted" 48 | ], 49 | style: { 50 | color: "#36acaa" 51 | } 52 | }, 53 | { 54 | types: ["atrule", "keyword", "attr-name", "selector"], 55 | style: { 56 | color: "#00a4db" 57 | } 58 | }, 59 | { 60 | types: ["function", "deleted", "tag"], 61 | style: { 62 | color: "#d73a49" 63 | } 64 | }, 65 | { 66 | types: ["function-variable"], 67 | style: { 68 | color: "#6f42c1" 69 | } 70 | }, 71 | { 72 | types: ["tag", "selector"], 73 | style: { 74 | color: "#00009f" 75 | } 76 | } 77 | ] 78 | }; 79 | 80 | const theme = makeTheme(prismTheme); 81 | 82 | export { theme }; 83 | -------------------------------------------------------------------------------- /packs/themes/src/theme.night-owl.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | color: "#d6deeb", 8 | backgroundColor: "#011627" 9 | }, 10 | styles: [ 11 | { 12 | types: ["changed"], 13 | style: { 14 | color: "rgb(162, 191, 252)", 15 | fontStyle: "italic" 16 | } 17 | }, 18 | { 19 | types: ["deleted"], 20 | style: { 21 | color: "rgba(239, 83, 80, 0.56)", 22 | fontStyle: "italic" 23 | } 24 | }, 25 | { 26 | types: ["inserted", "attr-name"], 27 | style: { 28 | color: "rgb(173, 219, 103)", 29 | fontStyle: "italic" 30 | } 31 | }, 32 | { 33 | types: ["comment"], 34 | style: { 35 | color: "rgb(99, 119, 119)", 36 | fontStyle: "italic" 37 | } 38 | }, 39 | { 40 | types: ["string", "url"], 41 | style: { 42 | color: "rgb(173, 219, 103)" 43 | } 44 | }, 45 | { 46 | types: ["variable"], 47 | style: { 48 | color: "rgb(214, 222, 235)" 49 | } 50 | }, 51 | { 52 | types: ["number"], 53 | style: { 54 | color: "rgb(247, 140, 108)" 55 | } 56 | }, 57 | { 58 | types: ["builtin", "char", "constant", "function"], 59 | style: { 60 | color: "rgb(130, 170, 255)" 61 | } 62 | }, 63 | { 64 | // This was manually added after the auto-generation 65 | // so that punctuations are not italicised 66 | types: ["punctuation"], 67 | style: { 68 | color: "rgb(199, 146, 234)" 69 | } 70 | }, 71 | { 72 | types: ["selector", "doctype"], 73 | style: { 74 | color: "rgb(199, 146, 234)", 75 | fontStyle: "italic" 76 | } 77 | }, 78 | { 79 | types: ["class-name"], 80 | style: { 81 | color: "rgb(255, 203, 139)" 82 | } 83 | }, 84 | { 85 | types: ["tag", "operator", "keyword"], 86 | style: { 87 | color: "rgb(127, 219, 202)" 88 | } 89 | }, 90 | { 91 | types: ["boolean"], 92 | style: { 93 | color: "rgb(255, 88, 116)" 94 | } 95 | }, 96 | { 97 | types: ["property"], 98 | style: { 99 | color: "rgb(128, 203, 196)" 100 | } 101 | }, 102 | { 103 | types: ["namespace"], 104 | style: { 105 | color: "rgb(178, 204, 214)" 106 | } 107 | } 108 | ] 109 | }; 110 | 111 | const theme = makeTheme(prismTheme, { 112 | title: { background: "rgba(1, 22, 39, 0.8)", color: "#d6deeb" } 113 | }); 114 | 115 | export { theme }; 116 | -------------------------------------------------------------------------------- /packs/themes/src/theme.oceanic-next.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const colors = { 6 | char: "#D8DEE9", 7 | comment: "#999999", 8 | keyword: "#c5a5c5", 9 | primitive: "#5a9bcf", 10 | string: "#8dc891", 11 | variable: "#d7deea", 12 | boolean: "#ff8b50", 13 | punctuation: "#5FB3B3", 14 | tag: "#fc929e", 15 | function: "#79b6f2", 16 | className: "#FAC863", 17 | method: "#6699CC", 18 | operator: "#fc929e" 19 | }; 20 | 21 | const prismTheme: PrismTheme = { 22 | plain: { 23 | backgroundColor: "#282c34", 24 | color: "#ffffff" 25 | }, 26 | styles: [ 27 | { 28 | types: ["attr-name"], 29 | style: { 30 | color: colors.keyword 31 | } 32 | }, 33 | { 34 | types: ["attr-value"], 35 | style: { 36 | color: colors.string 37 | } 38 | }, 39 | { 40 | types: ["comment", "block-comment", "prolog", "doctype", "cdata"], 41 | style: { 42 | color: colors.comment 43 | } 44 | }, 45 | { 46 | types: [ 47 | "property", 48 | "number", 49 | "function-name", 50 | "constant", 51 | "symbol", 52 | "deleted" 53 | ], 54 | style: { 55 | color: colors.primitive 56 | } 57 | }, 58 | { 59 | types: ["boolean"], 60 | style: { 61 | color: colors.boolean 62 | } 63 | }, 64 | { 65 | types: ["tag"], 66 | style: { 67 | color: colors.tag 68 | } 69 | }, 70 | { 71 | types: ["string"], 72 | style: { 73 | color: colors.string 74 | } 75 | }, 76 | { 77 | types: ["punctuation"], 78 | style: { 79 | color: colors.string 80 | } 81 | }, 82 | { 83 | types: ["selector", "char", "builtin", "inserted"], 84 | style: { 85 | color: colors.char 86 | } 87 | }, 88 | { 89 | types: ["function"], 90 | style: { 91 | color: colors.function 92 | } 93 | }, 94 | { 95 | types: ["operator", "entity", "url", "variable"], 96 | style: { 97 | color: colors.variable 98 | } 99 | }, 100 | { 101 | types: ["keyword"], 102 | style: { 103 | color: colors.keyword 104 | } 105 | }, 106 | { 107 | types: ["at-rule", "class-name"], 108 | style: { 109 | color: colors.className 110 | } 111 | }, 112 | { 113 | types: ["important"], 114 | style: { 115 | fontWeight: 400 116 | } 117 | }, 118 | { 119 | types: ["bold"], 120 | style: { 121 | fontWeight: "bold" as "bold" 122 | } 123 | }, 124 | { 125 | types: ["italic"], 126 | style: { 127 | fontStyle: "italic" as "italic" 128 | } 129 | }, 130 | { 131 | types: ["namespace"], 132 | style: { 133 | opacity: 0.7 134 | } 135 | } 136 | ] 137 | }; 138 | 139 | const theme = makeTheme(prismTheme); 140 | 141 | export { theme }; 142 | -------------------------------------------------------------------------------- /packs/themes/src/theme.shades-of-purple.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | color: "#9EFEFF", 8 | backgroundColor: "#2D2A55" 9 | }, 10 | styles: [ 11 | { 12 | types: ["changed"], 13 | style: { 14 | color: "rgb(255, 238, 128)" 15 | } 16 | }, 17 | { 18 | types: ["deleted"], 19 | style: { 20 | color: "rgba(239, 83, 80, 0.56)" 21 | } 22 | }, 23 | { 24 | types: ["inserted"], 25 | style: { 26 | color: "rgb(173, 219, 103)" 27 | } 28 | }, 29 | { 30 | types: ["comment"], 31 | style: { 32 | color: "rgb(179, 98, 255)", 33 | fontStyle: "italic" 34 | } 35 | }, 36 | { 37 | types: ["punctuation"], 38 | style: { 39 | color: "rgb(255, 255, 255)" 40 | } 41 | }, 42 | { 43 | types: ["constant"], 44 | style: { 45 | color: "rgb(255, 98, 140)" 46 | } 47 | }, 48 | { 49 | types: ["string", "url"], 50 | style: { 51 | color: "rgb(165, 255, 144)" 52 | } 53 | }, 54 | { 55 | types: ["variable"], 56 | style: { 57 | color: "rgb(255, 238, 128)" 58 | } 59 | }, 60 | { 61 | types: ["number", "boolean"], 62 | style: { 63 | color: "rgb(255, 98, 140)" 64 | } 65 | }, 66 | { 67 | types: ["attr-name"], 68 | style: { 69 | color: "rgb(255, 180, 84)" 70 | } 71 | }, 72 | { 73 | types: [ 74 | "keyword", 75 | "operator", 76 | "property", 77 | "namespace", 78 | "tag", 79 | "selector", 80 | "doctype" 81 | ], 82 | style: { 83 | color: "rgb(255, 157, 0)" 84 | } 85 | }, 86 | { 87 | types: ["builtin", "char", "constant", "function", "class-name"], 88 | style: { 89 | color: "rgb(250, 208, 0)" 90 | } 91 | } 92 | ] 93 | }; 94 | 95 | const theme = makeTheme(prismTheme); 96 | 97 | export { theme }; 98 | -------------------------------------------------------------------------------- /packs/themes/src/theme.ultramin.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | color: "#282a2e", 8 | backgroundColor: "#ffffff" 9 | }, 10 | styles: [ 11 | { 12 | types: ["comment"], 13 | style: { 14 | color: "rgb(197, 200, 198)" 15 | } 16 | }, 17 | { 18 | types: ["string", "number", "builtin", "variable"], 19 | style: { 20 | color: "rgb(150, 152, 150)" 21 | } 22 | }, 23 | { 24 | types: ["class-name", "function", "tag", "attr-name"], 25 | style: { 26 | color: "rgb(40, 42, 46)" 27 | } 28 | } 29 | ] 30 | }; 31 | 32 | const theme = makeTheme(prismTheme); 33 | 34 | export { theme }; 35 | -------------------------------------------------------------------------------- /packs/themes/src/theme.vs-dark.ts: -------------------------------------------------------------------------------- 1 | import { makeTheme, PrismTheme } from "./utils"; 2 | 3 | // From: https://github.com/FormidableLabs/prism-react-renderer/blob/master/themes/ 4 | 5 | const prismTheme: PrismTheme = { 6 | plain: { 7 | color: "#9CDCFE", 8 | backgroundColor: "#1E1E1E" 9 | }, 10 | styles: [ 11 | { 12 | types: ["prolog"], 13 | style: { 14 | color: "rgb(0, 0, 128)" 15 | } 16 | }, 17 | { 18 | types: ["comment"], 19 | style: { 20 | color: "rgb(106, 153, 85)" 21 | } 22 | }, 23 | { 24 | types: ["builtin", "changed", "keyword"], 25 | style: { 26 | color: "rgb(86, 156, 214)" 27 | } 28 | }, 29 | { 30 | types: ["number", "inserted"], 31 | style: { 32 | color: "rgb(181, 206, 168)" 33 | } 34 | }, 35 | { 36 | types: ["constant"], 37 | style: { 38 | color: "rgb(100, 102, 149)" 39 | } 40 | }, 41 | { 42 | types: ["attr-name", "variable"], 43 | style: { 44 | color: "rgb(156, 220, 254)" 45 | } 46 | }, 47 | { 48 | types: ["deleted", "string", "attr-value"], 49 | style: { 50 | color: "rgb(206, 145, 120)" 51 | } 52 | }, 53 | { 54 | types: ["selector"], 55 | style: { 56 | color: "rgb(215, 186, 125)" 57 | } 58 | }, 59 | { 60 | // Fix tag color 61 | types: ["tag"], 62 | style: { 63 | color: "rgb(78, 201, 176)" 64 | } 65 | }, 66 | { 67 | // Fix tag color for HTML 68 | types: ["tag"], 69 | style: { 70 | color: "rgb(86, 156, 214)" 71 | } 72 | }, 73 | { 74 | types: ["punctuation", "operator"], 75 | style: { 76 | color: "rgb(212, 212, 212)" 77 | } 78 | }, 79 | { 80 | // Fix punctuation color for HTML 81 | types: ["punctuation"], 82 | style: { 83 | color: "#808080" 84 | } 85 | }, 86 | { 87 | types: ["function"], 88 | style: { 89 | color: "rgb(220, 220, 170)" 90 | } 91 | }, 92 | { 93 | types: ["class-name"], 94 | style: { 95 | color: "rgb(78, 201, 176)" 96 | } 97 | }, 98 | { 99 | types: ["char"], 100 | style: { 101 | color: "rgb(209, 105, 105)" 102 | } 103 | } 104 | ] 105 | }; 106 | 107 | const theme = makeTheme(prismTheme); 108 | 109 | export { theme }; 110 | -------------------------------------------------------------------------------- /packs/themes/src/utils.test.ts: -------------------------------------------------------------------------------- 1 | test("works", () => {}); 2 | -------------------------------------------------------------------------------- /packs/themes/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { SxStyleProp, Theme } from "theme-ui"; 2 | 3 | export type CodeSurferStyles = { 4 | title: SxStyleProp; 5 | subtitle: SxStyleProp; 6 | code: SxStyleProp; 7 | pre: SxStyleProp; 8 | tokens: Record; 9 | unfocused?: { 10 | opacity: number; 11 | }; 12 | }; 13 | 14 | type StyleItem = { 15 | types: string[]; 16 | style: SxStyleProp; 17 | }; 18 | 19 | export type PrismTheme = { 20 | plain: { color: string; backgroundColor: string }; 21 | styles: StyleItem[]; 22 | }; 23 | 24 | export type CodeSurferTheme = Theme & { 25 | styles?: { CodeSurfer?: CodeSurferStyles }; 26 | }; 27 | 28 | export function makeTheme( 29 | prismTheme: PrismTheme, 30 | override: Partial = {} 31 | ): CodeSurferTheme { 32 | const tokens = {} as Record; 33 | prismTheme.styles.forEach(s => { 34 | tokens[s.types.join(" ")] = s.style; 35 | }); 36 | 37 | const theme: CodeSurferTheme = { 38 | colors: { 39 | text: prismTheme.plain.color, 40 | background: prismTheme.plain.backgroundColor 41 | }, 42 | styles: { 43 | CodeSurfer: { 44 | tokens, 45 | title: { 46 | backgroundColor: prismTheme.plain.backgroundColor, 47 | color: prismTheme.plain.color 48 | }, 49 | subtitle: { 50 | color: "#d6deeb", 51 | backgroundColor: "rgba(10,10,10,0.9)" 52 | }, 53 | pre: { 54 | color: prismTheme.plain.color, 55 | backgroundColor: prismTheme.plain.backgroundColor 56 | }, 57 | code: { 58 | color: prismTheme.plain.color, 59 | backgroundColor: prismTheme.plain.backgroundColor 60 | }, 61 | ...override 62 | } 63 | } 64 | }; 65 | 66 | const stringStyle = prismTheme.styles.find(s => s.types.includes("string")); 67 | const primary = stringStyle && (stringStyle.style.color as string); 68 | if (theme.colors && primary) { 69 | theme.colors.primary = primary; 70 | } 71 | 72 | return theme; 73 | } 74 | -------------------------------------------------------------------------------- /packs/themes/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src", "types"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | "declaration": true, 9 | "sourceMap": true, 10 | "rootDir": "./", 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "strictFunctionTypes": true, 15 | "strictPropertyInitialization": true, 16 | "noImplicitThis": true, 17 | "alwaysStrict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noImplicitReturns": true, 21 | "noFallthroughCasesInSwitch": true, 22 | "moduleResolution": "node", 23 | "baseUrl": "./", 24 | "paths": { 25 | "*": ["src/*", "node_modules/*"] 26 | }, 27 | "jsx": "react", 28 | "esModuleInterop": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sites/book/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /sites/book/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "book", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "start-storybook -c ./src", 8 | "build": "build-storybook -c ./src -o ./dist" 9 | }, 10 | "dependencies": { 11 | "@code-surfer/standalone": "3.1.1", 12 | "react": "^16.9.0", 13 | "react-dom": "^16.9.0", 14 | "theme-ui": "^0.2.41", 15 | "use-spring": "^0.2.2" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.4.5", 19 | "@storybook/react": "^5.1.9", 20 | "babel-loader": "^8.0.6", 21 | "raw-loader": "^3.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /sites/book/src/basic.story.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | import { CodeSurfer } from "@code-surfer/standalone"; 6 | import { StoryWithSlider } from "./utils"; 7 | 8 | storiesOf("Basic", module) 9 | .add("Steps", () => ) 10 | .add("Parsed Steps", () => ) 11 | .add("Line Numbers", () => ); 12 | 13 | function Story() { 14 | return ( 15 | 16 | {progress => } 17 | 18 | ); 19 | } 20 | 21 | function ParsedStepsStory() { 22 | return ( 23 | 24 | {progress => } 25 | 26 | ); 27 | } 28 | function StoryWithNumbers() { 29 | return ( 30 | 31 | {progress => } 32 | 33 | ); 34 | } 35 | 36 | const steps = [ 37 | { 38 | code: `var x1 = 1 39 | debugger`, 40 | focus: "1", 41 | lang: "js" 42 | }, 43 | { 44 | code: `var x0 = 3 45 | var x1 = 1 46 | var x0 = 3`, 47 | lang: "js" 48 | } 49 | ]; 50 | const parsedSteps = { 51 | steps: [ 52 | { 53 | lines: [1, 3], 54 | focus: { "0": true }, 55 | focusCenter: 0.5, 56 | focusCount: 1, 57 | longestLineIndex: 0 58 | }, 59 | { 60 | lines: [0, 1, 2], 61 | focus: { "0": true, "2": true }, 62 | focusCenter: 1.5, 63 | focusCount: 3, 64 | longestLineIndex: 0 65 | } 66 | ], 67 | tokens: [ 68 | ["var", " x0 ", "=", " ", "3"], 69 | ["var", " x1 ", "=", " ", "1"], 70 | ["var", " x0 ", "=", " ", "3"], 71 | ["debugger"] 72 | ], 73 | types: [ 74 | ["keyword", "plain", "operator", "plain", "number"], 75 | ["keyword", "plain", "operator", "plain", "number"], 76 | ["keyword", "plain", "operator", "plain", "number"], 77 | ["keyword"] 78 | ], 79 | maxLineCount: 4 80 | }; 81 | 82 | const stepsWithNumbers = [ 83 | { 84 | code: `var x1 = 1 85 | debugger`, 86 | focus: "1", 87 | lang: "js", 88 | showNumbers: true 89 | }, 90 | { 91 | code: `var x0 = 3 92 | var x1 = 1 93 | var x0 = 3`, 94 | lang: "js" 95 | } 96 | ]; 97 | -------------------------------------------------------------------------------- /sites/book/src/big.story.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | import { CodeSurfer } from "@code-surfer/standalone"; 6 | import { StoryWithSlider } from "./utils"; 7 | 8 | storiesOf("Perf", module) 9 | .add("50 Steps", () => ) 10 | .add("50 Steps (nonblocking)", () => ); 11 | 12 | const steps = [ 13 | { 14 | code: require("!!raw-loader!./files/00.jsx").default, 15 | lang: "jsx" 16 | }, 17 | { code: require("!!raw-loader!./files/01.jsx").default }, 18 | { code: require("!!raw-loader!./files/02.jsx").default }, 19 | { code: require("!!raw-loader!./files/03.jsx").default }, 20 | { code: require("!!raw-loader!./files/04.jsx").default }, 21 | { code: require("!!raw-loader!./files/05.jsx").default }, 22 | { code: require("!!raw-loader!./files/06.jsx").default }, 23 | { code: require("!!raw-loader!./files/07.jsx").default }, 24 | { code: require("!!raw-loader!./files/08.jsx").default }, 25 | { code: require("!!raw-loader!./files/09.jsx").default }, 26 | { code: require("!!raw-loader!./files/10.jsx").default }, 27 | { code: require("!!raw-loader!./files/11.jsx").default }, 28 | { code: require("!!raw-loader!./files/12.jsx").default }, 29 | { code: require("!!raw-loader!./files/13.jsx").default }, 30 | { code: require("!!raw-loader!./files/14.jsx").default }, 31 | { code: require("!!raw-loader!./files/15.jsx").default }, 32 | { code: require("!!raw-loader!./files/16.jsx").default }, 33 | { code: require("!!raw-loader!./files/17.jsx").default }, 34 | { code: require("!!raw-loader!./files/18.jsx").default }, 35 | { code: require("!!raw-loader!./files/19.jsx").default }, 36 | { code: require("!!raw-loader!./files/20.jsx").default }, 37 | { code: require("!!raw-loader!./files/21.jsx").default }, 38 | { code: require("!!raw-loader!./files/22.jsx").default }, 39 | { code: require("!!raw-loader!./files/23.jsx").default }, 40 | { code: require("!!raw-loader!./files/24.jsx").default }, 41 | { code: require("!!raw-loader!./files/25.jsx").default }, 42 | { code: require("!!raw-loader!./files/26.jsx").default }, 43 | { code: require("!!raw-loader!./files/27.jsx").default }, 44 | { code: require("!!raw-loader!./files/28.jsx").default }, 45 | { code: require("!!raw-loader!./files/29.jsx").default }, 46 | { code: require("!!raw-loader!./files/30.jsx").default }, 47 | { code: require("!!raw-loader!./files/31.jsx").default }, 48 | { code: require("!!raw-loader!./files/32.jsx").default }, 49 | { code: require("!!raw-loader!./files/33.jsx").default }, 50 | { code: require("!!raw-loader!./files/34.jsx").default }, 51 | { code: require("!!raw-loader!./files/35.jsx").default }, 52 | { code: require("!!raw-loader!./files/36.jsx").default }, 53 | { code: require("!!raw-loader!./files/37.jsx").default }, 54 | { code: require("!!raw-loader!./files/38.jsx").default }, 55 | { code: require("!!raw-loader!./files/39.jsx").default }, 56 | { code: require("!!raw-loader!./files/40.jsx").default }, 57 | { code: require("!!raw-loader!./files/41.jsx").default }, 58 | { code: require("!!raw-loader!./files/42.jsx").default }, 59 | { code: require("!!raw-loader!./files/43.jsx").default }, 60 | { code: require("!!raw-loader!./files/44.jsx").default }, 61 | { code: require("!!raw-loader!./files/45.jsx").default }, 62 | { code: require("!!raw-loader!./files/46.jsx").default }, 63 | { code: require("!!raw-loader!./files/47.jsx").default }, 64 | { code: require("!!raw-loader!./files/48.jsx").default }, 65 | { code: require("!!raw-loader!./files/49.jsx").default }, 66 | { code: require("!!raw-loader!./files/50.jsx").default } 67 | ]; 68 | 69 | function Story() { 70 | const [shouldLoad, setLoad] = React.useState(false); 71 | 72 | if (!shouldLoad) { 73 | return ; 74 | } 75 | 76 | return ( 77 | 78 | {progress => } 79 | 80 | ); 81 | } 82 | function NonblockingStory() { 83 | const [shouldLoad, setLoad] = React.useState(false); 84 | 85 | if (!shouldLoad) { 86 | return ; 87 | } 88 | 89 | return ( 90 | 91 | {progress => ( 92 | 93 | )} 94 | 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /sites/book/src/config.js: -------------------------------------------------------------------------------- 1 | import { configure } from "@storybook/react"; 2 | 3 | function loadStories() { 4 | require("./index.js"); 5 | // You can require as many stories as you need. 6 | } 7 | 8 | configure(loadStories, module); 9 | -------------------------------------------------------------------------------- /sites/book/src/files/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "printWidth": 52, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /sites/book/src/files/00.jsx: -------------------------------------------------------------------------------- 1 | const element = ( 2 |

3 | bar 4 | 5 |
6 | ) 7 | const container = document.getElementById("root") 8 | ReactDOM.render(element, container) 9 | -------------------------------------------------------------------------------- /sites/book/src/files/01.jsx: -------------------------------------------------------------------------------- 1 | const element = React.createElement( 2 | "div", 3 | { id: "foo" }, 4 | React.createElement("a", null, "bar"), 5 | React.createElement("b") 6 | ) 7 | const container = document.getElementById("root") 8 | ReactDOM.render(element, container) 9 | -------------------------------------------------------------------------------- /sites/book/src/files/02.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children, 7 | }, 8 | } 9 | } 10 | 11 | const element = React.createElement( 12 | "div", 13 | { id: "foo" }, 14 | React.createElement("a", null, "bar"), 15 | React.createElement("b") 16 | ) 17 | const container = document.getElementById("root") 18 | ReactDOM.render(element, container) 19 | -------------------------------------------------------------------------------- /sites/book/src/files/03.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | const element = React.createElement( 26 | "div", 27 | { id: "foo" }, 28 | React.createElement("a", null, "bar"), 29 | React.createElement("b") 30 | ) 31 | const container = document.getElementById("root") 32 | ReactDOM.render(element, container) 33 | -------------------------------------------------------------------------------- /sites/book/src/files/04.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | const Didact = { 26 | createElement, 27 | } 28 | 29 | const element = Didact.createElement( 30 | "div", 31 | { id: "foo" }, 32 | Didact.createElement("a", null, "bar"), 33 | Didact.createElement("b") 34 | ) 35 | const container = document.getElementById("root") 36 | ReactDOM.render(element, container) 37 | -------------------------------------------------------------------------------- /sites/book/src/files/05.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | const Didact = { 26 | createElement, 27 | } 28 | 29 | /** @jsx Didact.createElement */ 30 | const element = ( 31 |
32 | bar 33 | 34 |
35 | ) 36 | const container = document.getElementById("root") 37 | ReactDOM.render(element, container) 38 | -------------------------------------------------------------------------------- /sites/book/src/files/06.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function render(element, container) { 26 | // TODO 27 | } 28 | 29 | const Didact = { 30 | createElement, 31 | render, 32 | } 33 | 34 | /** @jsx Didact.createElement */ 35 | const element = ( 36 |
37 | bar 38 | 39 |
40 | ) 41 | const container = document.getElementById("root") 42 | Didact.render(element, container) 43 | -------------------------------------------------------------------------------- /sites/book/src/files/07.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function render(element, container) { 26 | const dom = document.createElement(element.type) 27 | 28 | container.appendChild(dom) 29 | } 30 | 31 | const Didact = { 32 | createElement, 33 | render, 34 | } 35 | 36 | /** @jsx Didact.createElement */ 37 | const element = ( 38 |
39 | bar 40 | 41 |
42 | ) 43 | const container = document.getElementById("root") 44 | Didact.render(element, container) 45 | -------------------------------------------------------------------------------- /sites/book/src/files/08.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function render(element, container) { 26 | const dom = document.createElement(element.type) 27 | 28 | element.props.children.forEach(child => 29 | render(child, dom) 30 | ) 31 | 32 | container.appendChild(dom) 33 | } 34 | 35 | const Didact = { 36 | createElement, 37 | render, 38 | } 39 | 40 | /** @jsx Didact.createElement */ 41 | const element = ( 42 |
43 | bar 44 | 45 |
46 | ) 47 | const container = document.getElementById("root") 48 | Didact.render(element, container) 49 | -------------------------------------------------------------------------------- /sites/book/src/files/09.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function render(element, container) { 26 | const dom = 27 | element.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(element.type) 30 | 31 | element.props.children.forEach(child => 32 | render(child, dom) 33 | ) 34 | 35 | container.appendChild(dom) 36 | } 37 | 38 | const Didact = { 39 | createElement, 40 | render, 41 | } 42 | 43 | /** @jsx Didact.createElement */ 44 | const element = ( 45 |
46 | bar 47 | 48 |
49 | ) 50 | const container = document.getElementById("root") 51 | Didact.render(element, container) 52 | -------------------------------------------------------------------------------- /sites/book/src/files/10.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function render(element, container) { 26 | const dom = 27 | element.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(element.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(element.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = element.props[name] 36 | }) 37 | 38 | element.props.children.forEach(child => 39 | render(child, dom) 40 | ) 41 | 42 | container.appendChild(dom) 43 | } 44 | 45 | const Didact = { 46 | createElement, 47 | render, 48 | } 49 | 50 | /** @jsx Didact.createElement */ 51 | const element = ( 52 |
53 | bar 54 | 55 |
56 | ) 57 | const container = document.getElementById("root") 58 | Didact.render(element, container) 59 | -------------------------------------------------------------------------------- /sites/book/src/files/11.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function render(element, container) { 26 | const dom = 27 | element.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(element.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(element.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = element.props[name] 36 | }) 37 | 38 | element.props.children.forEach(child => 39 | render(child, dom) 40 | ) 41 | 42 | container.appendChild(dom) 43 | } 44 | 45 | let nextUnitOfWork = null 46 | 47 | function workLoop(deadline) { 48 | let shouldYield = false 49 | while (nextUnitOfWork && !shouldYield) { 50 | nextUnitOfWork = performUnitOfWork( 51 | nextUnitOfWork 52 | ) 53 | shouldYield = deadline.timeRemaining() < 1 54 | } 55 | requestIdleCallback(workLoop) 56 | } 57 | 58 | requestIdleCallback(workLoop) 59 | 60 | function performUnitOfWork(nextUnitOfWork) { 61 | // TODO 62 | } 63 | 64 | const Didact = { 65 | createElement, 66 | render, 67 | } 68 | 69 | /** @jsx Didact.createElement */ 70 | const element = ( 71 |
72 | bar 73 | 74 |
75 | ) 76 | const container = document.getElementById("root") 77 | Didact.render(element, container) 78 | -------------------------------------------------------------------------------- /sites/book/src/files/12.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function render(element, container) { 42 | // TODO 43 | } 44 | 45 | let nextUnitOfWork = null 46 | 47 | function workLoop(deadline) { 48 | let shouldYield = false 49 | while (nextUnitOfWork && !shouldYield) { 50 | nextUnitOfWork = performUnitOfWork( 51 | nextUnitOfWork 52 | ) 53 | shouldYield = deadline.timeRemaining() < 1 54 | } 55 | requestIdleCallback(workLoop) 56 | } 57 | 58 | requestIdleCallback(workLoop) 59 | 60 | function performUnitOfWork(fiber) { 61 | // TODO 62 | } 63 | 64 | const Didact = { 65 | createElement, 66 | render, 67 | } 68 | 69 | /** @jsx Didact.createElement */ 70 | const element = ( 71 |
72 | bar 73 | 74 |
75 | ) 76 | const container = document.getElementById("root") 77 | Didact.render(element, container) 78 | -------------------------------------------------------------------------------- /sites/book/src/files/13.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function render(element, container) { 42 | nextUnitOfWork = { 43 | dom: container, 44 | props: { 45 | children: [element], 46 | }, 47 | } 48 | } 49 | 50 | let nextUnitOfWork = null 51 | 52 | function workLoop(deadline) { 53 | let shouldYield = false 54 | while (nextUnitOfWork && !shouldYield) { 55 | nextUnitOfWork = performUnitOfWork( 56 | nextUnitOfWork 57 | ) 58 | shouldYield = deadline.timeRemaining() < 1 59 | } 60 | requestIdleCallback(workLoop) 61 | } 62 | 63 | requestIdleCallback(workLoop) 64 | 65 | function performUnitOfWork(fiber) { 66 | // TODO 67 | } 68 | 69 | const Didact = { 70 | createElement, 71 | render, 72 | } 73 | 74 | /** @jsx Didact.createElement */ 75 | const element = ( 76 |
77 | bar 78 | 79 |
80 | ) 81 | const container = document.getElementById("root") 82 | Didact.render(element, container) 83 | -------------------------------------------------------------------------------- /sites/book/src/files/14.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function render(element, container) { 42 | nextUnitOfWork = { 43 | dom: container, 44 | props: { 45 | children: [element], 46 | }, 47 | } 48 | } 49 | 50 | let nextUnitOfWork = null 51 | 52 | function workLoop(deadline) { 53 | let shouldYield = false 54 | while (nextUnitOfWork && !shouldYield) { 55 | nextUnitOfWork = performUnitOfWork( 56 | nextUnitOfWork 57 | ) 58 | shouldYield = deadline.timeRemaining() < 1 59 | } 60 | requestIdleCallback(workLoop) 61 | } 62 | 63 | requestIdleCallback(workLoop) 64 | 65 | function performUnitOfWork(fiber) { 66 | if (!fiber.dom) { 67 | fiber.dom = createDom(fiber) 68 | } 69 | 70 | if (fiber.parent) { 71 | fiber.parent.dom.appendChild(fiber.dom) 72 | } 73 | } 74 | 75 | const Didact = { 76 | createElement, 77 | render, 78 | } 79 | 80 | /** @jsx Didact.createElement */ 81 | const element = ( 82 |
83 | bar 84 | 85 |
86 | ) 87 | const container = document.getElementById("root") 88 | Didact.render(element, container) 89 | -------------------------------------------------------------------------------- /sites/book/src/files/15.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function render(element, container) { 42 | nextUnitOfWork = { 43 | dom: container, 44 | props: { 45 | children: [element], 46 | }, 47 | } 48 | } 49 | 50 | let nextUnitOfWork = null 51 | 52 | function workLoop(deadline) { 53 | let shouldYield = false 54 | while (nextUnitOfWork && !shouldYield) { 55 | nextUnitOfWork = performUnitOfWork( 56 | nextUnitOfWork 57 | ) 58 | shouldYield = deadline.timeRemaining() < 1 59 | } 60 | requestIdleCallback(workLoop) 61 | } 62 | 63 | requestIdleCallback(workLoop) 64 | 65 | function performUnitOfWork(fiber) { 66 | if (!fiber.dom) { 67 | fiber.dom = createDom(fiber) 68 | } 69 | 70 | if (fiber.parent) { 71 | fiber.parent.dom.appendChild(fiber.dom) 72 | } 73 | 74 | const elements = fiber.props.children 75 | let index = 0 76 | let prevSibling = null 77 | 78 | while (index < elements.length) { 79 | const element = elements[index] 80 | 81 | const newFiber = { 82 | type: element.type, 83 | props: element.props, 84 | parent: fiber, 85 | dom: null, 86 | } 87 | } 88 | } 89 | 90 | const Didact = { 91 | createElement, 92 | render, 93 | } 94 | 95 | /** @jsx Didact.createElement */ 96 | const element = ( 97 |
98 | bar 99 | 100 |
101 | ) 102 | const container = document.getElementById("root") 103 | Didact.render(element, container) 104 | -------------------------------------------------------------------------------- /sites/book/src/files/16.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function render(element, container) { 42 | nextUnitOfWork = { 43 | dom: container, 44 | props: { 45 | children: [element], 46 | }, 47 | } 48 | } 49 | 50 | let nextUnitOfWork = null 51 | 52 | function workLoop(deadline) { 53 | let shouldYield = false 54 | while (nextUnitOfWork && !shouldYield) { 55 | nextUnitOfWork = performUnitOfWork( 56 | nextUnitOfWork 57 | ) 58 | shouldYield = deadline.timeRemaining() < 1 59 | } 60 | requestIdleCallback(workLoop) 61 | } 62 | 63 | requestIdleCallback(workLoop) 64 | 65 | function performUnitOfWork(fiber) { 66 | if (!fiber.dom) { 67 | fiber.dom = createDom(fiber) 68 | } 69 | 70 | if (fiber.parent) { 71 | fiber.parent.dom.appendChild(fiber.dom) 72 | } 73 | 74 | const elements = fiber.props.children 75 | let index = 0 76 | let prevSibling = null 77 | 78 | while (index < elements.length) { 79 | const element = elements[index] 80 | 81 | const newFiber = { 82 | type: element.type, 83 | props: element.props, 84 | parent: fiber, 85 | dom: null, 86 | } 87 | 88 | if (index === 0) { 89 | fiber.child = newFiber 90 | } else { 91 | prevSibling.sibling = newFiber 92 | } 93 | 94 | prevSibling = newFiber 95 | index++ 96 | } 97 | } 98 | 99 | const Didact = { 100 | createElement, 101 | render, 102 | } 103 | 104 | /** @jsx Didact.createElement */ 105 | const element = ( 106 |
107 | bar 108 | 109 |
110 | ) 111 | const container = document.getElementById("root") 112 | Didact.render(element, container) 113 | -------------------------------------------------------------------------------- /sites/book/src/files/17.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function render(element, container) { 42 | nextUnitOfWork = { 43 | dom: container, 44 | props: { 45 | children: [element], 46 | }, 47 | } 48 | } 49 | 50 | let nextUnitOfWork = null 51 | 52 | function workLoop(deadline) { 53 | let shouldYield = false 54 | while (nextUnitOfWork && !shouldYield) { 55 | nextUnitOfWork = performUnitOfWork( 56 | nextUnitOfWork 57 | ) 58 | shouldYield = deadline.timeRemaining() < 1 59 | } 60 | requestIdleCallback(workLoop) 61 | } 62 | 63 | requestIdleCallback(workLoop) 64 | 65 | function performUnitOfWork(fiber) { 66 | if (!fiber.dom) { 67 | fiber.dom = createDom(fiber) 68 | } 69 | 70 | if (fiber.parent) { 71 | fiber.parent.dom.appendChild(fiber.dom) 72 | } 73 | 74 | const elements = fiber.props.children 75 | let index = 0 76 | let prevSibling = null 77 | 78 | while (index < elements.length) { 79 | const element = elements[index] 80 | 81 | const newFiber = { 82 | type: element.type, 83 | props: element.props, 84 | parent: fiber, 85 | dom: null, 86 | } 87 | 88 | if (index === 0) { 89 | fiber.child = newFiber 90 | } else { 91 | prevSibling.sibling = newFiber 92 | } 93 | 94 | prevSibling = newFiber 95 | index++ 96 | } 97 | 98 | if (fiber.child) { 99 | return fiber.child 100 | } 101 | let nextFiber = fiber 102 | while (nextFiber) { 103 | if (nextFiber.sibling) { 104 | return nextFiber.sibling 105 | } 106 | nextFiber = nextFiber.parent 107 | } 108 | } 109 | 110 | const Didact = { 111 | createElement, 112 | render, 113 | } 114 | 115 | /** @jsx Didact.createElement */ 116 | const element = ( 117 |
118 | bar 119 | 120 |
121 | ) 122 | const container = document.getElementById("root") 123 | Didact.render(element, container) 124 | -------------------------------------------------------------------------------- /sites/book/src/files/18.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function render(element, container) { 42 | nextUnitOfWork = { 43 | dom: container, 44 | props: { 45 | children: [element], 46 | }, 47 | } 48 | } 49 | 50 | let nextUnitOfWork = null 51 | 52 | function workLoop(deadline) { 53 | let shouldYield = false 54 | while (nextUnitOfWork && !shouldYield) { 55 | nextUnitOfWork = performUnitOfWork( 56 | nextUnitOfWork 57 | ) 58 | shouldYield = deadline.timeRemaining() < 1 59 | } 60 | requestIdleCallback(workLoop) 61 | } 62 | 63 | requestIdleCallback(workLoop) 64 | 65 | function performUnitOfWork(fiber) { 66 | if (!fiber.dom) { 67 | fiber.dom = createDom(fiber) 68 | } 69 | 70 | const elements = fiber.props.children 71 | let index = 0 72 | let prevSibling = null 73 | 74 | while (index < elements.length) { 75 | const element = elements[index] 76 | 77 | const newFiber = { 78 | type: element.type, 79 | props: element.props, 80 | parent: fiber, 81 | dom: null, 82 | } 83 | 84 | if (index === 0) { 85 | fiber.child = newFiber 86 | } else { 87 | prevSibling.sibling = newFiber 88 | } 89 | 90 | prevSibling = newFiber 91 | index++ 92 | } 93 | 94 | if (fiber.child) { 95 | return fiber.child 96 | } 97 | let nextFiber = fiber 98 | while (nextFiber) { 99 | if (nextFiber.sibling) { 100 | return nextFiber.sibling 101 | } 102 | nextFiber = nextFiber.parent 103 | } 104 | } 105 | 106 | const Didact = { 107 | createElement, 108 | render, 109 | } 110 | 111 | /** @jsx Didact.createElement */ 112 | const element = ( 113 |
114 | bar 115 | 116 |
117 | ) 118 | const container = document.getElementById("root") 119 | Didact.render(element, container) 120 | -------------------------------------------------------------------------------- /sites/book/src/files/19.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | // TODO 43 | } 44 | 45 | function render(element, container) { 46 | wipRoot = { 47 | dom: container, 48 | props: { 49 | children: [element], 50 | }, 51 | } 52 | nextUnitOfWork = wipRoot 53 | } 54 | 55 | let nextUnitOfWork = null 56 | let wipRoot = null 57 | 58 | function workLoop(deadline) { 59 | let shouldYield = false 60 | while (nextUnitOfWork && !shouldYield) { 61 | nextUnitOfWork = performUnitOfWork( 62 | nextUnitOfWork 63 | ) 64 | shouldYield = deadline.timeRemaining() < 1 65 | } 66 | 67 | if (!nextUnitOfWork && wipRoot) { 68 | commitRoot() 69 | } 70 | 71 | requestIdleCallback(workLoop) 72 | } 73 | 74 | requestIdleCallback(workLoop) 75 | 76 | function performUnitOfWork(fiber) { 77 | if (!fiber.dom) { 78 | fiber.dom = createDom(fiber) 79 | } 80 | 81 | const elements = fiber.props.children 82 | let index = 0 83 | let prevSibling = null 84 | 85 | while (index < elements.length) { 86 | const element = elements[index] 87 | 88 | const newFiber = { 89 | type: element.type, 90 | props: element.props, 91 | parent: fiber, 92 | dom: null, 93 | } 94 | 95 | if (index === 0) { 96 | fiber.child = newFiber 97 | } else { 98 | prevSibling.sibling = newFiber 99 | } 100 | 101 | prevSibling = newFiber 102 | index++ 103 | } 104 | 105 | if (fiber.child) { 106 | return fiber.child 107 | } 108 | let nextFiber = fiber 109 | while (nextFiber) { 110 | if (nextFiber.sibling) { 111 | return nextFiber.sibling 112 | } 113 | nextFiber = nextFiber.parent 114 | } 115 | } 116 | 117 | const Didact = { 118 | createElement, 119 | render, 120 | } 121 | 122 | /** @jsx Didact.createElement */ 123 | const element = ( 124 |
125 | bar 126 | 127 |
128 | ) 129 | const container = document.getElementById("root") 130 | Didact.render(element, container) 131 | -------------------------------------------------------------------------------- /sites/book/src/files/20.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | wipRoot = null 44 | } 45 | 46 | function commitWork(fiber) { 47 | if (!fiber) { 48 | return 49 | } 50 | const domParent = fiber.parent.dom 51 | domParent.appendChild(fiber.dom) 52 | commitWork(fiber.child) 53 | commitWork(fiber.sibling) 54 | } 55 | 56 | function render(element, container) { 57 | wipRoot = { 58 | dom: container, 59 | props: { 60 | children: [element], 61 | }, 62 | } 63 | nextUnitOfWork = wipRoot 64 | } 65 | 66 | let nextUnitOfWork = null 67 | let wipRoot = null 68 | 69 | function workLoop(deadline) { 70 | let shouldYield = false 71 | while (nextUnitOfWork && !shouldYield) { 72 | nextUnitOfWork = performUnitOfWork( 73 | nextUnitOfWork 74 | ) 75 | shouldYield = deadline.timeRemaining() < 1 76 | } 77 | 78 | if (!nextUnitOfWork && wipRoot) { 79 | commitRoot() 80 | } 81 | 82 | requestIdleCallback(workLoop) 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | 87 | function performUnitOfWork(fiber) { 88 | if (!fiber.dom) { 89 | fiber.dom = createDom(fiber) 90 | } 91 | 92 | const elements = fiber.props.children 93 | let index = 0 94 | let prevSibling = null 95 | 96 | while (index < elements.length) { 97 | const element = elements[index] 98 | 99 | const newFiber = { 100 | type: element.type, 101 | props: element.props, 102 | parent: fiber, 103 | dom: null, 104 | } 105 | 106 | if (index === 0) { 107 | fiber.child = newFiber 108 | } else { 109 | prevSibling.sibling = newFiber 110 | } 111 | 112 | prevSibling = newFiber 113 | index++ 114 | } 115 | 116 | if (fiber.child) { 117 | return fiber.child 118 | } 119 | let nextFiber = fiber 120 | while (nextFiber) { 121 | if (nextFiber.sibling) { 122 | return nextFiber.sibling 123 | } 124 | nextFiber = nextFiber.parent 125 | } 126 | } 127 | 128 | const Didact = { 129 | createElement, 130 | render, 131 | } 132 | 133 | /** @jsx Didact.createElement */ 134 | const element = ( 135 |
136 | bar 137 | 138 |
139 | ) 140 | const container = document.getElementById("root") 141 | Didact.render(element, container) 142 | -------------------------------------------------------------------------------- /sites/book/src/files/21.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | nextUnitOfWork = wipRoot 66 | } 67 | 68 | let nextUnitOfWork = null 69 | let currentRoot = null 70 | let wipRoot = null 71 | 72 | function workLoop(deadline) { 73 | let shouldYield = false 74 | while (nextUnitOfWork && !shouldYield) { 75 | nextUnitOfWork = performUnitOfWork( 76 | nextUnitOfWork 77 | ) 78 | shouldYield = deadline.timeRemaining() < 1 79 | } 80 | 81 | if (!nextUnitOfWork && wipRoot) { 82 | commitRoot() 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | 90 | function performUnitOfWork(fiber) { 91 | if (!fiber.dom) { 92 | fiber.dom = createDom(fiber) 93 | } 94 | 95 | const elements = fiber.props.children 96 | let index = 0 97 | let prevSibling = null 98 | 99 | while (index < elements.length) { 100 | const element = elements[index] 101 | 102 | const newFiber = { 103 | type: element.type, 104 | props: element.props, 105 | parent: fiber, 106 | dom: null, 107 | } 108 | 109 | if (index === 0) { 110 | fiber.child = newFiber 111 | } else { 112 | prevSibling.sibling = newFiber 113 | } 114 | 115 | prevSibling = newFiber 116 | index++ 117 | } 118 | 119 | if (fiber.child) { 120 | return fiber.child 121 | } 122 | let nextFiber = fiber 123 | while (nextFiber) { 124 | if (nextFiber.sibling) { 125 | return nextFiber.sibling 126 | } 127 | nextFiber = nextFiber.parent 128 | } 129 | } 130 | 131 | const Didact = { 132 | createElement, 133 | render, 134 | } 135 | 136 | /** @jsx Didact.createElement */ 137 | const element = ( 138 |
139 | bar 140 | 141 |
142 | ) 143 | const container = document.getElementById("root") 144 | Didact.render(element, container) 145 | -------------------------------------------------------------------------------- /sites/book/src/files/22.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | nextUnitOfWork = wipRoot 66 | } 67 | 68 | let nextUnitOfWork = null 69 | let currentRoot = null 70 | let wipRoot = null 71 | 72 | function workLoop(deadline) { 73 | let shouldYield = false 74 | while (nextUnitOfWork && !shouldYield) { 75 | nextUnitOfWork = performUnitOfWork( 76 | nextUnitOfWork 77 | ) 78 | shouldYield = deadline.timeRemaining() < 1 79 | } 80 | 81 | if (!nextUnitOfWork && wipRoot) { 82 | commitRoot() 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | 90 | function performUnitOfWork(fiber) { 91 | if (!fiber.dom) { 92 | fiber.dom = createDom(fiber) 93 | } 94 | 95 | const elements = fiber.props.children 96 | reconcileChildren(fiber, elements) 97 | 98 | if (fiber.child) { 99 | return fiber.child 100 | } 101 | let nextFiber = fiber 102 | while (nextFiber) { 103 | if (nextFiber.sibling) { 104 | return nextFiber.sibling 105 | } 106 | nextFiber = nextFiber.parent 107 | } 108 | } 109 | 110 | function reconcileChildren(wipFiber, elements) { 111 | let index = 0 112 | let prevSibling = null 113 | 114 | while (index < elements.length) { 115 | const element = elements[index] 116 | 117 | const newFiber = { 118 | type: element.type, 119 | props: element.props, 120 | parent: wipFiber, 121 | dom: null, 122 | } 123 | 124 | if (index === 0) { 125 | wipFiber.child = newFiber 126 | } else { 127 | prevSibling.sibling = newFiber 128 | } 129 | 130 | prevSibling = newFiber 131 | index++ 132 | } 133 | } 134 | 135 | const Didact = { 136 | createElement, 137 | render, 138 | } 139 | 140 | /** @jsx Didact.createElement */ 141 | const element = ( 142 |
143 | bar 144 | 145 |
146 | ) 147 | const container = document.getElementById("root") 148 | Didact.render(element, container) 149 | -------------------------------------------------------------------------------- /sites/book/src/files/23.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | nextUnitOfWork = wipRoot 66 | } 67 | 68 | let nextUnitOfWork = null 69 | let currentRoot = null 70 | let wipRoot = null 71 | 72 | function workLoop(deadline) { 73 | let shouldYield = false 74 | while (nextUnitOfWork && !shouldYield) { 75 | nextUnitOfWork = performUnitOfWork( 76 | nextUnitOfWork 77 | ) 78 | shouldYield = deadline.timeRemaining() < 1 79 | } 80 | 81 | if (!nextUnitOfWork && wipRoot) { 82 | commitRoot() 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | 90 | function performUnitOfWork(fiber) { 91 | if (!fiber.dom) { 92 | fiber.dom = createDom(fiber) 93 | } 94 | 95 | const elements = fiber.props.children 96 | reconcileChildren(fiber, elements) 97 | 98 | if (fiber.child) { 99 | return fiber.child 100 | } 101 | let nextFiber = fiber 102 | while (nextFiber) { 103 | if (nextFiber.sibling) { 104 | return nextFiber.sibling 105 | } 106 | nextFiber = nextFiber.parent 107 | } 108 | } 109 | 110 | function reconcileChildren(wipFiber, elements) { 111 | let index = 0 112 | let oldFiber = 113 | wipFiber.alternate && wipFiber.alternate.child 114 | let prevSibling = null 115 | 116 | while ( 117 | index < elements.length || 118 | oldFiber != null 119 | ) { 120 | const element = elements[index] 121 | let newFiber = null 122 | 123 | // TODO compare oldFiber to element 124 | 125 | if (oldFiber) { 126 | oldFiber = oldFiber.sibling 127 | } 128 | 129 | if (index === 0) { 130 | wipFiber.child = newFiber 131 | } else if (element) { 132 | prevSibling.sibling = newFiber 133 | } 134 | 135 | prevSibling = newFiber 136 | index++ 137 | } 138 | } 139 | 140 | const Didact = { 141 | createElement, 142 | render, 143 | } 144 | 145 | /** @jsx Didact.createElement */ 146 | const element = ( 147 |
148 | bar 149 | 150 |
151 | ) 152 | const container = document.getElementById("root") 153 | Didact.render(element, container) 154 | -------------------------------------------------------------------------------- /sites/book/src/files/24.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | nextUnitOfWork = wipRoot 66 | } 67 | 68 | let nextUnitOfWork = null 69 | let currentRoot = null 70 | let wipRoot = null 71 | 72 | function workLoop(deadline) { 73 | let shouldYield = false 74 | while (nextUnitOfWork && !shouldYield) { 75 | nextUnitOfWork = performUnitOfWork( 76 | nextUnitOfWork 77 | ) 78 | shouldYield = deadline.timeRemaining() < 1 79 | } 80 | 81 | if (!nextUnitOfWork && wipRoot) { 82 | commitRoot() 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | 90 | function performUnitOfWork(fiber) { 91 | if (!fiber.dom) { 92 | fiber.dom = createDom(fiber) 93 | } 94 | 95 | const elements = fiber.props.children 96 | reconcileChildren(fiber, elements) 97 | 98 | if (fiber.child) { 99 | return fiber.child 100 | } 101 | let nextFiber = fiber 102 | while (nextFiber) { 103 | if (nextFiber.sibling) { 104 | return nextFiber.sibling 105 | } 106 | nextFiber = nextFiber.parent 107 | } 108 | } 109 | 110 | function reconcileChildren(wipFiber, elements) { 111 | let index = 0 112 | let oldFiber = 113 | wipFiber.alternate && wipFiber.alternate.child 114 | let prevSibling = null 115 | 116 | while ( 117 | index < elements.length || 118 | oldFiber != null 119 | ) { 120 | const element = elements[index] 121 | let newFiber = null 122 | 123 | const sameType = 124 | oldFiber && 125 | element && 126 | element.type == oldFiber.type 127 | 128 | if (sameType) { 129 | // TODO update the node 130 | } 131 | if (element && !sameType) { 132 | // TODO add this node 133 | } 134 | if (oldFiber && !sameType) { 135 | // TODO delete the oldFiber's node 136 | } 137 | 138 | if (oldFiber) { 139 | oldFiber = oldFiber.sibling 140 | } 141 | 142 | if (index === 0) { 143 | wipFiber.child = newFiber 144 | } else if (element) { 145 | prevSibling.sibling = newFiber 146 | } 147 | 148 | prevSibling = newFiber 149 | index++ 150 | } 151 | } 152 | 153 | const Didact = { 154 | createElement, 155 | render, 156 | } 157 | 158 | /** @jsx Didact.createElement */ 159 | const element = ( 160 |
161 | bar 162 | 163 |
164 | ) 165 | const container = document.getElementById("root") 166 | Didact.render(element, container) 167 | -------------------------------------------------------------------------------- /sites/book/src/files/25.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | nextUnitOfWork = wipRoot 66 | } 67 | 68 | let nextUnitOfWork = null 69 | let currentRoot = null 70 | let wipRoot = null 71 | 72 | function workLoop(deadline) { 73 | let shouldYield = false 74 | while (nextUnitOfWork && !shouldYield) { 75 | nextUnitOfWork = performUnitOfWork( 76 | nextUnitOfWork 77 | ) 78 | shouldYield = deadline.timeRemaining() < 1 79 | } 80 | 81 | if (!nextUnitOfWork && wipRoot) { 82 | commitRoot() 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | 90 | function performUnitOfWork(fiber) { 91 | if (!fiber.dom) { 92 | fiber.dom = createDom(fiber) 93 | } 94 | 95 | const elements = fiber.props.children 96 | reconcileChildren(fiber, elements) 97 | 98 | if (fiber.child) { 99 | return fiber.child 100 | } 101 | let nextFiber = fiber 102 | while (nextFiber) { 103 | if (nextFiber.sibling) { 104 | return nextFiber.sibling 105 | } 106 | nextFiber = nextFiber.parent 107 | } 108 | } 109 | 110 | function reconcileChildren(wipFiber, elements) { 111 | let index = 0 112 | let oldFiber = 113 | wipFiber.alternate && wipFiber.alternate.child 114 | let prevSibling = null 115 | 116 | while ( 117 | index < elements.length || 118 | oldFiber != null 119 | ) { 120 | const element = elements[index] 121 | let newFiber = null 122 | 123 | const sameType = 124 | oldFiber && 125 | element && 126 | element.type == oldFiber.type 127 | 128 | if (sameType) { 129 | newFiber = { 130 | type: oldFiber.type, 131 | props: element.props, 132 | dom: oldFiber.dom, 133 | parent: wipFiber, 134 | alternate: oldFiber, 135 | effectTag: "UPDATE", 136 | } 137 | } 138 | if (element && !sameType) { 139 | // TODO add this node 140 | } 141 | if (oldFiber && !sameType) { 142 | // TODO delete the oldFiber's node 143 | } 144 | 145 | if (oldFiber) { 146 | oldFiber = oldFiber.sibling 147 | } 148 | 149 | if (index === 0) { 150 | wipFiber.child = newFiber 151 | } else if (element) { 152 | prevSibling.sibling = newFiber 153 | } 154 | 155 | prevSibling = newFiber 156 | index++ 157 | } 158 | } 159 | 160 | const Didact = { 161 | createElement, 162 | render, 163 | } 164 | 165 | /** @jsx Didact.createElement */ 166 | const element = ( 167 |
168 | bar 169 | 170 |
171 | ) 172 | const container = document.getElementById("root") 173 | Didact.render(element, container) 174 | -------------------------------------------------------------------------------- /sites/book/src/files/26.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | nextUnitOfWork = wipRoot 66 | } 67 | 68 | let nextUnitOfWork = null 69 | let currentRoot = null 70 | let wipRoot = null 71 | 72 | function workLoop(deadline) { 73 | let shouldYield = false 74 | while (nextUnitOfWork && !shouldYield) { 75 | nextUnitOfWork = performUnitOfWork( 76 | nextUnitOfWork 77 | ) 78 | shouldYield = deadline.timeRemaining() < 1 79 | } 80 | 81 | if (!nextUnitOfWork && wipRoot) { 82 | commitRoot() 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | 90 | function performUnitOfWork(fiber) { 91 | if (!fiber.dom) { 92 | fiber.dom = createDom(fiber) 93 | } 94 | 95 | const elements = fiber.props.children 96 | reconcileChildren(fiber, elements) 97 | 98 | if (fiber.child) { 99 | return fiber.child 100 | } 101 | let nextFiber = fiber 102 | while (nextFiber) { 103 | if (nextFiber.sibling) { 104 | return nextFiber.sibling 105 | } 106 | nextFiber = nextFiber.parent 107 | } 108 | } 109 | 110 | function reconcileChildren(wipFiber, elements) { 111 | let index = 0 112 | let oldFiber = 113 | wipFiber.alternate && wipFiber.alternate.child 114 | let prevSibling = null 115 | 116 | while ( 117 | index < elements.length || 118 | oldFiber != null 119 | ) { 120 | const element = elements[index] 121 | let newFiber = null 122 | 123 | const sameType = 124 | oldFiber && 125 | element && 126 | element.type == oldFiber.type 127 | 128 | if (sameType) { 129 | newFiber = { 130 | type: oldFiber.type, 131 | props: element.props, 132 | dom: oldFiber.dom, 133 | parent: wipFiber, 134 | alternate: oldFiber, 135 | effectTag: "UPDATE", 136 | } 137 | } 138 | if (element && !sameType) { 139 | newFiber = { 140 | type: element.type, 141 | props: element.props, 142 | dom: null, 143 | parent: wipFiber, 144 | alternate: null, 145 | effectTag: "PLACEMENT", 146 | } 147 | } 148 | if (oldFiber && !sameType) { 149 | // TODO delete the oldFiber's node 150 | } 151 | 152 | if (oldFiber) { 153 | oldFiber = oldFiber.sibling 154 | } 155 | 156 | if (index === 0) { 157 | wipFiber.child = newFiber 158 | } else if (element) { 159 | prevSibling.sibling = newFiber 160 | } 161 | 162 | prevSibling = newFiber 163 | index++ 164 | } 165 | } 166 | 167 | const Didact = { 168 | createElement, 169 | render, 170 | } 171 | 172 | /** @jsx Didact.createElement */ 173 | const element = ( 174 |
175 | bar 176 | 177 |
178 | ) 179 | const container = document.getElementById("root") 180 | Didact.render(element, container) 181 | -------------------------------------------------------------------------------- /sites/book/src/files/27.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | nextUnitOfWork = wipRoot 66 | } 67 | 68 | let nextUnitOfWork = null 69 | let currentRoot = null 70 | let wipRoot = null 71 | 72 | function workLoop(deadline) { 73 | let shouldYield = false 74 | while (nextUnitOfWork && !shouldYield) { 75 | nextUnitOfWork = performUnitOfWork( 76 | nextUnitOfWork 77 | ) 78 | shouldYield = deadline.timeRemaining() < 1 79 | } 80 | 81 | if (!nextUnitOfWork && wipRoot) { 82 | commitRoot() 83 | } 84 | 85 | requestIdleCallback(workLoop) 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | 90 | function performUnitOfWork(fiber) { 91 | if (!fiber.dom) { 92 | fiber.dom = createDom(fiber) 93 | } 94 | 95 | const elements = fiber.props.children 96 | reconcileChildren(fiber, elements) 97 | 98 | if (fiber.child) { 99 | return fiber.child 100 | } 101 | let nextFiber = fiber 102 | while (nextFiber) { 103 | if (nextFiber.sibling) { 104 | return nextFiber.sibling 105 | } 106 | nextFiber = nextFiber.parent 107 | } 108 | } 109 | 110 | function reconcileChildren(wipFiber, elements) { 111 | let index = 0 112 | let oldFiber = 113 | wipFiber.alternate && wipFiber.alternate.child 114 | let prevSibling = null 115 | 116 | while ( 117 | index < elements.length || 118 | oldFiber != null 119 | ) { 120 | const element = elements[index] 121 | let newFiber = null 122 | 123 | const sameType = 124 | oldFiber && 125 | element && 126 | element.type == oldFiber.type 127 | 128 | if (sameType) { 129 | newFiber = { 130 | type: oldFiber.type, 131 | props: element.props, 132 | dom: oldFiber.dom, 133 | parent: wipFiber, 134 | alternate: oldFiber, 135 | effectTag: "UPDATE", 136 | } 137 | } 138 | if (element && !sameType) { 139 | newFiber = { 140 | type: element.type, 141 | props: element.props, 142 | dom: null, 143 | parent: wipFiber, 144 | alternate: null, 145 | effectTag: "PLACEMENT", 146 | } 147 | } 148 | if (oldFiber && !sameType) { 149 | oldFiber.effectTag = "DELETION" 150 | deletions.push(oldFiber) 151 | } 152 | 153 | if (oldFiber) { 154 | oldFiber = oldFiber.sibling 155 | } 156 | 157 | if (index === 0) { 158 | wipFiber.child = newFiber 159 | } else if (element) { 160 | prevSibling.sibling = newFiber 161 | } 162 | 163 | prevSibling = newFiber 164 | index++ 165 | } 166 | } 167 | 168 | const Didact = { 169 | createElement, 170 | render, 171 | } 172 | 173 | /** @jsx Didact.createElement */ 174 | const element = ( 175 |
176 | bar 177 | 178 |
179 | ) 180 | const container = document.getElementById("root") 181 | Didact.render(element, container) 182 | -------------------------------------------------------------------------------- /sites/book/src/files/28.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | commitWork(wipRoot.child) 43 | currentRoot = wipRoot 44 | wipRoot = null 45 | } 46 | 47 | function commitWork(fiber) { 48 | if (!fiber) { 49 | return 50 | } 51 | const domParent = fiber.parent.dom 52 | domParent.appendChild(fiber.dom) 53 | commitWork(fiber.child) 54 | commitWork(fiber.sibling) 55 | } 56 | 57 | function render(element, container) { 58 | wipRoot = { 59 | dom: container, 60 | props: { 61 | children: [element], 62 | }, 63 | alternate: currentRoot, 64 | } 65 | deletions = [] 66 | nextUnitOfWork = wipRoot 67 | } 68 | 69 | let nextUnitOfWork = null 70 | let currentRoot = null 71 | let wipRoot = null 72 | let deletions = null 73 | 74 | function workLoop(deadline) { 75 | let shouldYield = false 76 | while (nextUnitOfWork && !shouldYield) { 77 | nextUnitOfWork = performUnitOfWork( 78 | nextUnitOfWork 79 | ) 80 | shouldYield = deadline.timeRemaining() < 1 81 | } 82 | 83 | if (!nextUnitOfWork && wipRoot) { 84 | commitRoot() 85 | } 86 | 87 | requestIdleCallback(workLoop) 88 | } 89 | 90 | requestIdleCallback(workLoop) 91 | 92 | function performUnitOfWork(fiber) { 93 | if (!fiber.dom) { 94 | fiber.dom = createDom(fiber) 95 | } 96 | 97 | const elements = fiber.props.children 98 | reconcileChildren(fiber, elements) 99 | 100 | if (fiber.child) { 101 | return fiber.child 102 | } 103 | let nextFiber = fiber 104 | while (nextFiber) { 105 | if (nextFiber.sibling) { 106 | return nextFiber.sibling 107 | } 108 | nextFiber = nextFiber.parent 109 | } 110 | } 111 | 112 | function reconcileChildren(wipFiber, elements) { 113 | let index = 0 114 | let oldFiber = 115 | wipFiber.alternate && wipFiber.alternate.child 116 | let prevSibling = null 117 | 118 | while ( 119 | index < elements.length || 120 | oldFiber != null 121 | ) { 122 | const element = elements[index] 123 | let newFiber = null 124 | 125 | const sameType = 126 | oldFiber && 127 | element && 128 | element.type == oldFiber.type 129 | 130 | if (sameType) { 131 | newFiber = { 132 | type: oldFiber.type, 133 | props: element.props, 134 | dom: oldFiber.dom, 135 | parent: wipFiber, 136 | alternate: oldFiber, 137 | effectTag: "UPDATE", 138 | } 139 | } 140 | if (element && !sameType) { 141 | newFiber = { 142 | type: element.type, 143 | props: element.props, 144 | dom: null, 145 | parent: wipFiber, 146 | alternate: null, 147 | effectTag: "PLACEMENT", 148 | } 149 | } 150 | if (oldFiber && !sameType) { 151 | oldFiber.effectTag = "DELETION" 152 | deletions.push(oldFiber) 153 | } 154 | 155 | if (oldFiber) { 156 | oldFiber = oldFiber.sibling 157 | } 158 | 159 | if (index === 0) { 160 | wipFiber.child = newFiber 161 | } else if (element) { 162 | prevSibling.sibling = newFiber 163 | } 164 | 165 | prevSibling = newFiber 166 | index++ 167 | } 168 | } 169 | 170 | const Didact = { 171 | createElement, 172 | render, 173 | } 174 | 175 | /** @jsx Didact.createElement */ 176 | const element = ( 177 |
178 | bar 179 | 180 |
181 | ) 182 | const container = document.getElementById("root") 183 | Didact.render(element, container) 184 | -------------------------------------------------------------------------------- /sites/book/src/files/29.jsx: -------------------------------------------------------------------------------- 1 | function createElement(type, props, ...children) { 2 | return { 3 | type, 4 | props: { 5 | ...props, 6 | children: children.map(child => 7 | typeof child === "object" 8 | ? child 9 | : createTextElement(child) 10 | ), 11 | }, 12 | } 13 | } 14 | 15 | function createTextElement(text) { 16 | return { 17 | type: "TEXT_ELEMENT", 18 | props: { 19 | nodeValue: text, 20 | children: [], 21 | }, 22 | } 23 | } 24 | 25 | function createDom(fiber) { 26 | const dom = 27 | fiber.type == "TEXT_ELEMENT" 28 | ? document.createTextNode("") 29 | : document.createElement(fiber.type) 30 | 31 | const isProperty = key => key !== "children" 32 | Object.keys(fiber.props) 33 | .filter(isProperty) 34 | .forEach(name => { 35 | dom[name] = fiber.props[name] 36 | }) 37 | 38 | return dom 39 | } 40 | 41 | function commitRoot() { 42 | deletions.forEach(commitWork) 43 | commitWork(wipRoot.child) 44 | currentRoot = wipRoot 45 | wipRoot = null 46 | } 47 | 48 | function commitWork(fiber) { 49 | if (!fiber) { 50 | return 51 | } 52 | const domParent = fiber.parent.dom 53 | domParent.appendChild(fiber.dom) 54 | commitWork(fiber.child) 55 | commitWork(fiber.sibling) 56 | } 57 | 58 | function render(element, container) { 59 | wipRoot = { 60 | dom: container, 61 | props: { 62 | children: [element], 63 | }, 64 | alternate: currentRoot, 65 | } 66 | deletions = [] 67 | nextUnitOfWork = wipRoot 68 | } 69 | 70 | let nextUnitOfWork = null 71 | let currentRoot = null 72 | let wipRoot = null 73 | let deletions = null 74 | 75 | function workLoop(deadline) { 76 | let shouldYield = false 77 | while (nextUnitOfWork && !shouldYield) { 78 | nextUnitOfWork = performUnitOfWork( 79 | nextUnitOfWork 80 | ) 81 | shouldYield = deadline.timeRemaining() < 1 82 | } 83 | 84 | if (!nextUnitOfWork && wipRoot) { 85 | commitRoot() 86 | } 87 | 88 | requestIdleCallback(workLoop) 89 | } 90 | 91 | requestIdleCallback(workLoop) 92 | 93 | function performUnitOfWork(fiber) { 94 | if (!fiber.dom) { 95 | fiber.dom = createDom(fiber) 96 | } 97 | 98 | const elements = fiber.props.children 99 | reconcileChildren(fiber, elements) 100 | 101 | if (fiber.child) { 102 | return fiber.child 103 | } 104 | let nextFiber = fiber 105 | while (nextFiber) { 106 | if (nextFiber.sibling) { 107 | return nextFiber.sibling 108 | } 109 | nextFiber = nextFiber.parent 110 | } 111 | } 112 | 113 | function reconcileChildren(wipFiber, elements) { 114 | let index = 0 115 | let oldFiber = 116 | wipFiber.alternate && wipFiber.alternate.child 117 | let prevSibling = null 118 | 119 | while ( 120 | index < elements.length || 121 | oldFiber != null 122 | ) { 123 | const element = elements[index] 124 | let newFiber = null 125 | 126 | const sameType = 127 | oldFiber && 128 | element && 129 | element.type == oldFiber.type 130 | 131 | if (sameType) { 132 | newFiber = { 133 | type: oldFiber.type, 134 | props: element.props, 135 | dom: oldFiber.dom, 136 | parent: wipFiber, 137 | alternate: oldFiber, 138 | effectTag: "UPDATE", 139 | } 140 | } 141 | if (element && !sameType) { 142 | newFiber = { 143 | type: element.type, 144 | props: element.props, 145 | dom: null, 146 | parent: wipFiber, 147 | alternate: null, 148 | effectTag: "PLACEMENT", 149 | } 150 | } 151 | if (oldFiber && !sameType) { 152 | oldFiber.effectTag = "DELETION" 153 | deletions.push(oldFiber) 154 | } 155 | 156 | if (oldFiber) { 157 | oldFiber = oldFiber.sibling 158 | } 159 | 160 | if (index === 0) { 161 | wipFiber.child = newFiber 162 | } else if (element) { 163 | prevSibling.sibling = newFiber 164 | } 165 | 166 | prevSibling = newFiber 167 | index++ 168 | } 169 | } 170 | 171 | const Didact = { 172 | createElement, 173 | render, 174 | } 175 | 176 | /** @jsx Didact.createElement */ 177 | const element = ( 178 |
179 | bar 180 | 181 |
182 | ) 183 | const container = document.getElementById("root") 184 | Didact.render(element, container) 185 | -------------------------------------------------------------------------------- /sites/book/src/index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | const reqs = require.context(".", true, /\.story\.js$/, "sync"); 4 | reqs.keys().forEach(filename => reqs(filename)); 5 | -------------------------------------------------------------------------------- /sites/book/src/parsed-steps.story.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | import { CodeSurfer } from "@code-surfer/standalone"; 6 | import { StoryWithSlider } from "./utils"; 7 | import parsedSteps from "./parsed-steps"; 8 | 9 | storiesOf("Perf", module).add("50 Steps Parsed", () => ); 10 | 11 | function Story() { 12 | const [shouldLoad, setLoad] = React.useState(false); 13 | 14 | if (!shouldLoad) { 15 | return ; 16 | } 17 | 18 | return ( 19 | 20 | {progress => } 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /sites/book/src/themed.story.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | import { CodeSurfer } from "@code-surfer/standalone"; 6 | import { nightOwl } from "@code-surfer/themes"; 7 | import { StoryWithSlider } from "./utils"; 8 | 9 | storiesOf("Basic", module).add("Themed", () => ); 10 | 11 | const steps = [ 12 | { 13 | code: `var x1 = 1 14 | debugger`, 15 | focus: "1", 16 | lang: "js" 17 | }, 18 | { 19 | code: `var x0 = 3 20 | var x1 = 1 21 | var x0 = 3`, 22 | lang: "js" 23 | } 24 | ]; 25 | 26 | function Story() { 27 | return ( 28 | 29 | {progress => ( 30 | 31 | )} 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /sites/book/src/title.story.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import React from "react"; 4 | import { storiesOf } from "@storybook/react"; 5 | import { CodeSurfer } from "@code-surfer/standalone"; 6 | import { StoryWithSlider } from "./utils"; 7 | 8 | storiesOf("Title & Subtitle", module) 9 | .add("Title", () => ) 10 | .add("Subtitle", () => ) 11 | .add("Fit Code", () => ); 12 | 13 | const code = `var x0 = 3 14 | var x1 = 1 15 | var x0 = 3`; 16 | 17 | function TitleStory() { 18 | const steps = [ 19 | { code, title: "Title 1", lang: "js" }, 20 | { code, title: "Title 2" }, 21 | { code, title: "Title 2" }, 22 | { code }, 23 | { code, title: "Title 3" } 24 | ]; 25 | return ( 26 | 27 | {progress => } 28 | 29 | ); 30 | } 31 | function SubtitleStory() { 32 | const steps = [ 33 | { code, subtitle: "Subtitle 1", lang: "js" }, 34 | { code, subtitle: "Subtitle 2" }, 35 | { code, subtitle: "Subtitle 2" }, 36 | { code }, 37 | { code, subtitle: "Subtitle 3" } 38 | ]; 39 | return ( 40 | 41 | {progress => } 42 | 43 | ); 44 | } 45 | 46 | function ZoomStory() { 47 | const fiveLines = ` 48 | console.log(1) 49 | console.log(1) 50 | console.log(1) 51 | console.log(1) 52 | console.log(1)`; 53 | const code = (fiveLines + fiveLines + fiveLines).trim(); 54 | const steps = [ 55 | { code, title: "title 1", subtitle: "Subtitle 1", lang: "js" }, 56 | { code, subtitle: "Subtitle 2" }, 57 | { code, title: "title 2" }, 58 | { code } 59 | ]; 60 | return ( 61 | 62 | {progress => } 63 | 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /sites/book/src/utils.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSpring } from "use-spring"; 3 | 4 | const height = 225; 5 | const width = 400; 6 | 7 | export function StoryWithSlider({ max, children }) { 8 | const [{ progress, teleport }, setProgress] = React.useState({ 9 | progress: 0, 10 | teleport: true 11 | }); 12 | const [p] = useSpring(progress, { 13 | decimals: 3, 14 | stiffness: 80, 15 | damping: 48, 16 | mass: 8, 17 | teleport 18 | }); 19 | return ( 20 |
21 |
29 | 39 | 44 | setProgress({ progress: +e.target.value, teleport: true }) 45 | } 46 | max={max} 47 | step={0.01} 48 | /> 49 | 50 | {Math.round(p * 100) / 100} 51 | 52 | 62 |
63 |
71 | {children(p)} 72 |
73 |
74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /sites/build.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require("fs-extra"); 4 | const { join } = require("path"); 5 | const execa = require("execa"); 6 | 7 | async function main() { 8 | // Remove dist dir 9 | fs.removeSync(join(__dirname, "dist")); 10 | 11 | // List of subfolder names 12 | const siteDirNames = fs 13 | .readdirSync(__dirname) 14 | .filter(fileName => isDir(join(__dirname, fileName))); 15 | 16 | // Create dist dir 17 | fs.ensureDirSync(join(__dirname, "dist")); 18 | 19 | // for each site 20 | // install dependencies, build and copy to dist 21 | const siteBuilds = siteDirNames.map(async siteDirName => { 22 | console.log(` 23 | 24 | --- building ${siteDirName} --- 25 | 26 | `); 27 | const cwd = join(__dirname, siteDirName); 28 | const { stdout, stderr } = process; 29 | 30 | execa.commandSync("yarn", { cwd, stdout, stderr }); 31 | execa.commandSync("yarn build", { cwd, stdout, stderr }); 32 | 33 | if (fs.existsSync(join(cwd, "dist"))) { 34 | await fs.copy(join(cwd, "dist"), join(__dirname, "dist", siteDirName)); 35 | } else if (fs.existsSync(join(cwd, "build"))) { 36 | await fs.copy(join(cwd, "build"), join(__dirname, "dist", siteDirName)); 37 | } else { 38 | await fs.copy(join(cwd, "public"), join(__dirname, "dist", siteDirName)); 39 | } 40 | }); 41 | 42 | await Promise.all(siteBuilds); 43 | 44 | // Move all files and folders from ./dist/docs to ./dist 45 | fs.readdirSync(join(__dirname, "dist/docs")).forEach(fileName => 46 | fs.moveSync( 47 | join(__dirname, "dist/docs", fileName), 48 | join(__dirname, "dist", fileName) 49 | ) 50 | ); 51 | fs.removeSync(join(__dirname, "dist/docs")); 52 | } 53 | 54 | main().catch(err => { 55 | console.error(err); 56 | }); 57 | 58 | // utils 59 | 60 | function isDir(source) { 61 | return fs.lstatSync(source).isDirectory(); 62 | } 63 | -------------------------------------------------------------------------------- /sites/docs/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.pnp 3 | .pnp.js 4 | /coverage 5 | /dist 6 | /public 7 | /.cache 8 | 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | -------------------------------------------------------------------------------- /sites/docs/decks/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "demo.mdx", 5 | "options": { 6 | "printWidth": 50, 7 | "semi": false 8 | } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /sites/docs/decks/code.js: -------------------------------------------------------------------------------- 1 | function hello(world) { 2 | return "hello " + world; 3 | } 4 | 5 | function hello(world) { 6 | return "hello " + world; 7 | } 8 | 9 | function hello(world) { 10 | return "hello " + world; 11 | } 12 | -------------------------------------------------------------------------------- /sites/docs/decks/custom-theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | colors: { 3 | background: "#222", 4 | text: "#ddd", 5 | primary: "#a66" 6 | }, 7 | styles: { 8 | CodeSurfer: { 9 | pre: { 10 | color: "text", 11 | backgroundColor: "background" 12 | }, 13 | code: { 14 | color: "text", 15 | backgroundColor: "background" 16 | }, 17 | tokens: { 18 | "comment cdata doctype": { 19 | fontStyle: "italic" 20 | }, 21 | "builtin changed keyword punctuation operator tag deleted string attr-value char number inserted": { 22 | color: "primary" 23 | } 24 | }, 25 | title: { 26 | backgroundColor: "background", 27 | color: "text" 28 | }, 29 | subtitle: { 30 | color: "#d6deeb", 31 | backgroundColor: "rgba(10,10,10,0.9)" 32 | } 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /sites/docs/decks/demo/image.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import img from "./surfer.jpg"; 3 | 4 | export default ({ style }) => { 5 | return ( 6 |
12 | surfer 23 | {credit} 24 |
25 | ); 26 | }; 27 | 28 | const credit = ( 29 |
30 | Photo by{" "} 31 | 36 | bady qb 37 | {" "} 38 | on Unsplash 39 |
40 | ); 41 | -------------------------------------------------------------------------------- /sites/docs/decks/demo/surfer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/decks/demo/surfer.jpg -------------------------------------------------------------------------------- /sites/docs/decks/demo/theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | colors: { text: "#0D0543" }, 3 | styles: { a: { color: "text" } } 4 | }; 5 | -------------------------------------------------------------------------------- /sites/docs/decks/errors.mdx: -------------------------------------------------------------------------------- 1 | import { CodeSurfer, CodeSurferColumns, Step } from "code-surfer"; 2 | import { nightOwl, github, oceanicNext, vsDark } from "@code-surfer/themes"; 3 | 4 | 5 | 6 | ```foo 7 | bar 8 | ``` 9 | 10 | 11 | 12 | --- 13 | 14 | 15 | 16 | ``` 17 | bar 18 | ``` 19 | 20 | 21 | 22 | --- 23 | 24 | 25 | 26 | ```js 27 | console.log(1); 28 | ``` 29 | 30 | ```js 31 | console.log(1); 32 | ``` 33 | 34 | 35 | 36 | --- 37 | 38 | 39 | 40 | ```js title="Empty Step" 41 | ``` 42 | 43 | 44 | 45 | --- 46 | 47 | 48 | 49 | 50 | 51 | --- 52 | 53 | 54 | 55 | # No code 56 | 57 | 58 | 59 | --- 60 | 61 | ```js // no empty lines ``` 62 | 63 | --- 64 | 65 | 66 | 67 | 68 | 69 | --- 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | --- 80 | 81 | 82 | 83 | 84 | 85 | ```js 86 | console.log(1) 87 | ``` 88 | 89 | Foo 90 | 91 |

92 | 93 | ```js 94 | console.log(2); 95 | ``` 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /sites/docs/decks/full/custom-theme.js: -------------------------------------------------------------------------------- 1 | export default { 2 | colors: { 3 | background: "#222", 4 | text: "#ddd", 5 | primary: "#1e9" 6 | }, 7 | styles: { 8 | CodeSurfer: { 9 | pre: { 10 | color: "text", 11 | backgroundColor: "background" 12 | }, 13 | code: { 14 | color: "text", 15 | backgroundColor: "background" 16 | }, 17 | tokens: { 18 | "comment cdata doctype": { 19 | fontStyle: "italic" 20 | }, 21 | "builtin changed keyword punctuation operator tag deleted string attr-value char number inserted attr-name": { 22 | color: "primary" 23 | }, 24 | function: { 25 | color: "text" 26 | }, 27 | "line-number": { 28 | color: "yellow", 29 | transform: "rotate(-20deg)", 30 | display: "inline-block" 31 | } 32 | }, 33 | title: { 34 | backgroundColor: "background", 35 | color: "text", 36 | border: "25px solid", 37 | borderColor: "primary", 38 | marginBottom: "30px", 39 | padding: "25px", 40 | textAlign: "right" 41 | }, 42 | subtitle: { 43 | color: "#222", 44 | backgroundColor: "#d6deeb99", 45 | transform: "rotate(-5deg) translateY(-100px)" 46 | }, 47 | unfocused: { 48 | opacity: 0.1 49 | } 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /sites/docs/decks/full/my-component.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | 3 | export default () => { 4 | const [count, setCount] = useState(1); 5 | return ( 6 |
7 | 10 |

{count}

11 | 14 |
15 | ); 16 | }; 17 | -------------------------------------------------------------------------------- /sites/docs/decks/test.mdx: -------------------------------------------------------------------------------- 1 | import { CodeSurfer, CodeSurferColumns, Step } from "code-surfer"; 2 | import { nightOwl, dracula, oceanicNext, vsDark } from "@code-surfer/themes"; 3 | 4 | import { script } from "mdx-deck/themes"; 5 | 6 | 7 | 8 | ```js 2 title="Hi" subtitle="foo class foo" 9 | console.log(1); 10 | console.log(2); 11 | console.log(3); 12 | ``` 13 | 14 | 15 | 16 | --- 17 | 18 | 19 | 20 | 21 | 22 | ```js 23 | console.log(1); 24 | console.log(1); 25 | console.log(1); 26 | console.log(1); 27 | console.log(1); 28 | console.log(1); 29 | 30 | console.log(1); 31 | console.log(1); 32 | console.log(1); 33 | console.log(1); 34 | console.log(1); 35 | console.log(1); 36 | console.log(1); 37 | console.log(1); 38 | console.log(1); 39 | ``` 40 | 41 | ```js 42 | console.log(1); 43 | console.log(1); 44 | console.log(1); 45 | console.log(1); 46 | console.log(1); 47 | console.log(1); 48 | console.log(1); 49 | console.log(1); 50 | 51 | console.log(1); 52 | console.log(1); 53 | console.log(1); 54 | console.log(1); 55 | console.log(1); 56 | console.log(1); 57 | console.log(1); 58 | ``` 59 | 60 | 61 | 62 | 63 | 64 | ```diff 65 | 66 | ``` 67 | 68 | ```diff 69 | 70 | ``` 71 | 72 | 73 | 74 | 75 | 76 | ```diff 77 | 78 | ``` 79 | 80 | ```diff 81 | 82 | ``` 83 | 84 | 85 | 86 | 87 | 88 | ```diff 89 | 90 | ``` 91 | 92 | ```diff 93 | 94 | ``` 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /sites/docs/decks/themes.mdx: -------------------------------------------------------------------------------- 1 | import { CodeSurfer } from "code-surfer"; 2 | import { 3 | dracula, 4 | duotoneDark, 5 | duotoneLight, 6 | github, 7 | nightOwl, 8 | oceanicNext, 9 | shadesOfPurple, 10 | ultramin, 11 | vsDark 12 | } from "@code-surfer/themes"; 13 | 14 | import customTheme from "./custom-theme"; 15 | 16 | ## Code Surfer 17 | 18 | # Themes 19 | 20 | --- 21 | 22 | 23 | 24 | ```js 5:7 title="Default Theme" file="./code.js" 25 | ``` 26 | 27 | 28 | 29 | --- 30 | 31 | 32 | 33 | ```js 5:7 title="Ultramin" file="./code.js" 34 | ``` 35 | 36 | 37 | 38 | --- 39 | 40 | 41 | 42 | ```js 5:7 title="Duotone Light" file="./code.js" 43 | ``` 44 | 45 | 46 | 47 | --- 48 | 49 | 50 | 51 | ```js 5:7 title="GitHub" file="./code.js" 52 | ``` 53 | 54 | 55 | 56 | --- 57 | 58 | 59 | 60 | ```js 5:7 title="Night Owl" file="./code.js" 61 | ``` 62 | 63 | 64 | 65 | --- 66 | 67 | 68 | 69 | ```js 5:7 title="Shades of Purple" file="./code.js" 70 | ``` 71 | 72 | 73 | 74 | --- 75 | 76 | 77 | 78 | ```js 5:7 title="Duotone Dark" file="./code.js" 79 | ``` 80 | 81 | 82 | 83 | --- 84 | 85 | 86 | 87 | ```js 5:7 title="Dracula" file="./code.js" 88 | ``` 89 | 90 | 91 | 92 | --- 93 | 94 | 95 | 96 | ```js 5:7 title="Oceanic Next" file="./code.js" 97 | ``` 98 | 99 | 100 | 101 | --- 102 | 103 | 104 | 105 | ```js 5:7 title="VS Dark" file="./code.js" 106 | ``` 107 | 108 | 109 | 110 | --- 111 | 112 | 113 | 114 | ```js 5:7 title="Custom Theme" file="./code.js" 115 | ``` 116 | 117 | 118 | -------------------------------------------------------------------------------- /sites/docs/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import "./src/remove-overlay.css"; 2 | -------------------------------------------------------------------------------- /sites/docs/gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ["gatsby-theme-mdx-deck", "gatsby-plugin-react-helmet"] 3 | }; 4 | -------------------------------------------------------------------------------- /sites/docs/gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | exports.wrapPageElement = ({ element, props }) => { 2 | // dont ssr errors deck 3 | if (props["*"] && props["*"].startsWith("errors")) return ""; 4 | return element; 5 | }; 6 | -------------------------------------------------------------------------------- /sites/docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "code-surfer": "3.1.1", 8 | "gatsby": "^2.15.28", 9 | "gatsby-plugin-react-helmet": "^3.1.15", 10 | "gatsby-theme-mdx-deck": "^3.0.13", 11 | "prism-react-renderer": "^1.0.2", 12 | "react": "^16.10.0", 13 | "react-dom": "^16.10.0", 14 | "react-helmet": "^5.2.1", 15 | "react-vista": "^0.1.1" 16 | }, 17 | "scripts": { 18 | "start": "gatsby develop", 19 | "build": "gatsby build", 20 | "clean": "gatsby clean" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /sites/docs/src/gatsby-theme-mdx-deck/templates/decks.js: -------------------------------------------------------------------------------- 1 | import Home from "../../home/app"; 2 | 3 | export default Home; 4 | -------------------------------------------------------------------------------- /sites/docs/src/home/app.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./index.css"; 3 | import Stage from "./stage"; 4 | import Deck from "./deck"; 5 | import Docs from "./docs"; 6 | import { Helmet } from "react-helmet"; 7 | 8 | const card = "https://codesurfer.pomb.us/card.png"; 9 | 10 | function App() { 11 | return ( 12 | 13 | 14 | 15 | Code Surfer - Rad Code Slides 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | }> 27 | 28 | 29 | 30 | ); 31 | } 32 | 33 | export default App; 34 | -------------------------------------------------------------------------------- /sites/docs/src/home/bright-squares.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/bright-squares.png -------------------------------------------------------------------------------- /sites/docs/src/home/code-block.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Highlight, { defaultProps } from "prism-react-renderer"; 3 | 4 | export default ({ children, className }) => { 5 | const language = className.replace(/language-/, ""); 6 | return ( 7 | 13 | {({ className, style, tokens, getLineProps, getTokenProps }) => ( 14 |
24 |           {tokens.map((line, i) => (
25 |             
26 | {line.map((token, key) => ( 27 | 28 | ))} 29 |
30 | ))} 31 |
32 | )} 33 |
34 | ); 35 | }; 36 | 37 | const theme = { 38 | plain: { 39 | color: "rgb(57, 58, 52)", 40 | backgroundColor: "rgb(246, 248, 250)" 41 | }, 42 | styles: [ 43 | { 44 | types: [ 45 | "builtin", 46 | "changed", 47 | "keyword", 48 | "punctuation", 49 | "operator", 50 | "tag", 51 | "deleted", 52 | "string", 53 | "attr-value", 54 | "char", 55 | "number", 56 | "inserted" 57 | ], 58 | style: { 59 | color: "rgb(0, 164, 219)" 60 | } 61 | }, 62 | { 63 | types: ["comment"], 64 | style: { 65 | fontStyle: "italic" 66 | } 67 | } 68 | ] 69 | }; 70 | -------------------------------------------------------------------------------- /sites/docs/src/home/deck.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect } from "react"; 2 | 3 | function isInViewport(element) { 4 | var rect = element.getBoundingClientRect(); 5 | return rect.bottom > 80; 6 | } 7 | 8 | export default function Deck() { 9 | const ref = useRef(); 10 | useInterval(() => { 11 | const iframe = ref.current; 12 | if (isInViewport(iframe)) { 13 | iframe.contentWindow.dispatchEvent( 14 | new KeyboardEvent("keydown", { keyCode: 39 }) 15 | ); 16 | } 17 | }); 18 | 19 | return ( 20 | 54 | 55 | 56 | 57 |

58 | 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /sites/docs/src/home/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | html, 11 | body, 12 | #___gatsby, 13 | #gatsby-focus-wrapper { 14 | height: 100%; 15 | } 16 | 17 | code { 18 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 19 | monospace; 20 | } 21 | -------------------------------------------------------------------------------- /sites/docs/src/home/logos/bairesdev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/logos/bairesdev.png -------------------------------------------------------------------------------- /sites/docs/src/home/logos/jabci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/logos/jabci.png -------------------------------------------------------------------------------- /sites/docs/src/home/logos/mdxdeck.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/logos/mdxdeck.png -------------------------------------------------------------------------------- /sites/docs/src/home/logos/ocollective.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Slice 5 | Created with Sketch. 6 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /sites/docs/src/home/purty-wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/purty-wood.png -------------------------------------------------------------------------------- /sites/docs/src/home/shattered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/shattered.png -------------------------------------------------------------------------------- /sites/docs/src/home/speaker.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import s0 from "./speakers/0.png"; 3 | import s1 from "./speakers/1.png"; 4 | import s2 from "./speakers/2.png"; 5 | import s3 from "./speakers/3.png"; 6 | import s4 from "./speakers/4.png"; 7 | import s5 from "./speakers/5.png"; 8 | import s6 from "./speakers/6.png"; 9 | import s7 from "./speakers/7.png"; 10 | import s8 from "./speakers/8.png"; 11 | import s9 from "./speakers/9.png"; 12 | 13 | const speakers = [s0, s1, s2, s3, s4, s5, s6, s7, s8, s9]; 14 | 15 | const speaker = speakers[Math.floor(Math.random() * speakers.length)]; 16 | 17 | export default () => ( 18 | speaker 19 | ); 20 | -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/0.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/1.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/2.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/3.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/4.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/5.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/6.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/7.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/8.png -------------------------------------------------------------------------------- /sites/docs/src/home/speakers/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/speakers/9.png -------------------------------------------------------------------------------- /sites/docs/src/home/use-window-size.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function useWindowSize() { 4 | const isClient = typeof window === "object"; 5 | 6 | function getSize() { 7 | return [ 8 | isClient ? window.innerWidth : undefined, 9 | isClient ? window.innerHeight : undefined 10 | ]; 11 | } 12 | 13 | const [windowSize, setWindowSize] = React.useState(getSize); 14 | 15 | React.useEffect(() => { 16 | if (!isClient) { 17 | return false; 18 | } 19 | 20 | function handleResize() { 21 | setWindowSize(getSize()); 22 | } 23 | 24 | window.addEventListener("resize", handleResize); 25 | return () => window.removeEventListener("resize", handleResize); 26 | }, []); 27 | 28 | return windowSize; 29 | } 30 | -------------------------------------------------------------------------------- /sites/docs/src/home/wip.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import logo from "./logo.small.svg"; 3 | import { Helmet } from "react-helmet"; 4 | import "./index.css"; 5 | 6 | export default function Home() { 7 | return ( 8 |
18 | 19 | Code Surfer: Rad Code Slides 20 | 21 | Code Surfer Logo 31 |

Rad Code Slides

32 |
42 |         {`npm init code-surfer-deck my-deck
43 | cd my-deck
44 | npm start`}
45 |       
46 |

47 | This site is a work in progress, here are the provisional{" "} 48 | 49 | docs for Code Surfer v3.0.0 50 | 51 |

52 |
53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /sites/docs/src/home/wood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/src/home/wood.png -------------------------------------------------------------------------------- /sites/docs/src/remove-overlay.css: -------------------------------------------------------------------------------- 1 | body > iframe { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /sites/docs/static/_redirects: -------------------------------------------------------------------------------- 1 | # Redirect domain aliases to primary domain 2 | https://codesurfer.js.org/* https://codesurfer.pomb.us/:splat 301! 3 | 4 | /:site/* /:site/index.html 200 -------------------------------------------------------------------------------- /sites/docs/static/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/static/android-chrome-192x192.png -------------------------------------------------------------------------------- /sites/docs/static/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/static/android-chrome-512x512.png -------------------------------------------------------------------------------- /sites/docs/static/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/static/apple-touch-icon.png -------------------------------------------------------------------------------- /sites/docs/static/card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/static/card.png -------------------------------------------------------------------------------- /sites/docs/static/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/static/favicon-16x16.png -------------------------------------------------------------------------------- /sites/docs/static/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/static/favicon-32x32.png -------------------------------------------------------------------------------- /sites/docs/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomber/code-surfer/948452f7c1c8adaf019e29600d7d158747a91a90/sites/docs/static/favicon.ico -------------------------------------------------------------------------------- /sites/docs/static/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Code Surfer", 3 | "short_name": "Code Surfer", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | --------------------------------------------------------------------------------