├── .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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |

23 | {credit}
24 |
25 | );
26 | };
27 |
28 | const credit = (
29 |
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 |
27 | );
28 | }
29 |
30 | function useInterval(callback, delay = 3000) {
31 | const savedCallback = useRef();
32 |
33 | useEffect(() => {
34 | savedCallback.current = callback;
35 | });
36 |
37 | useEffect(() => {
38 | function tick() {
39 | savedCallback.current();
40 | }
41 |
42 | let id = setInterval(tick, delay);
43 | return () => clearInterval(id);
44 | }, [delay]);
45 | }
46 |
--------------------------------------------------------------------------------
/sites/docs/src/home/docs.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Readme from "./readme.mdx";
3 | import { MDXProvider } from "@mdx-js/react";
4 | import CodeBlock from "./code-block";
5 |
6 | const components = {
7 | pre: props => ,
8 | code: CodeBlock,
9 | blockquote: props => (
10 |
18 | ),
19 | h1: props => ,
20 | h2: props => ,
21 | h3: props =>
22 | };
23 |
24 | function H(props) {
25 | const text = props.children;
26 | const id =
27 | text &&
28 | text
29 | .split(" ")
30 | .join("-")
31 | .toLowerCase();
32 | return React.createElement(props.as, { ...props, id });
33 | }
34 |
35 | export default function Docs() {
36 | return (
37 |
38 |
46 |
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 |
--------------------------------------------------------------------------------
/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 |
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 |

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