diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 00000000..95ac4cb2 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,9 @@ +# This configuration file was automatically generated by Gitpod. +# Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file) +# and commit this file to your remote git repository to share the goodness with others. + +tasks: + - init: yarn install && yarn run build + command: yarn run playground + + diff --git a/package.json b/package.json index 7cd6b141..f0ccc17e 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ }, "devDependencies": { "auto": "^10.18.7", - "lerna": "^4.0.0" + "lerna": "^4.0.0", + "prettier": "^2.5.1" }, "repository": "code-hike/codehike", "author": "pomber ", diff --git a/packages/highlighter/package.json b/packages/highlighter/package.json index bcaac602..5e666342 100644 --- a/packages/highlighter/package.json +++ b/packages/highlighter/package.json @@ -17,7 +17,7 @@ }, "dependencies": { "@code-hike/utils": "^0.3.0-next.0", - "shiki": "^0.9.14" + "shiki": "^0.10.1" }, "homepage": "https://codehike.org", "repository": "code-hike/codehike", diff --git a/packages/mdx/package.json b/packages/mdx/package.json index c53f9c98..aec680c0 100644 --- a/packages/mdx/package.json +++ b/packages/mdx/package.json @@ -34,7 +34,8 @@ "node-fetch": "^2.0.0", "remark-rehype": "^8.1.0", "unified": "^9.2.2", - "unist-util-visit": "^2.0.0" + "unist-util-visit": "^2.0.0", + "unist-util-visit-parents": "^3.0.0" }, "peerDependencies": { "react": "^16.8.3 || ^17 || ^18" diff --git a/packages/mdx/src/client/inline-code.tsx b/packages/mdx/src/client/inline-code.tsx index 2341b02c..1bbf355f 100644 --- a/packages/mdx/src/client/inline-code.tsx +++ b/packages/mdx/src/client/inline-code.tsx @@ -4,19 +4,28 @@ import { getColor, transparent, ColorName, + Code, } from "@code-hike/utils" export function InlineCode({ className, codeConfig, children, + code, ...rest }: { className: string + code: Code children?: React.ReactNode codeConfig: { theme: EditorTheme } }) { const { theme } = codeConfig + const { lines } = code + const allTokens = lines.flatMap(line => line.tokens) + const foreground = getColor( + theme, + ColorName.CodeForeground + ) return ( - {children} + {allTokens.map((token, j) => ( + + {token.content} + + ))} ) diff --git a/packages/mdx/src/client/scrollycoding.tsx b/packages/mdx/src/client/scrollycoding.tsx index 5807282c..26aa673e 100644 --- a/packages/mdx/src/client/scrollycoding.tsx +++ b/packages/mdx/src/client/scrollycoding.tsx @@ -9,6 +9,7 @@ import { Step as ScrollerStep, } from "@code-hike/scroller" import { Preview, PresetConfig } from "./preview" +import { LinkableSection } from "./section" export function Scrollycoding({ children, @@ -45,6 +46,19 @@ export function Scrollycoding({ setState({ ...state, step: newStep }) } + function onLinkActivation( + stepIndex: number, + filename: string | undefined, + focus: string | null + ) { + const newStep = updateEditorStep( + editorSteps[stepIndex], + filename, + focus + ) + setState({ ...state, stepIndex, step: newStep }) + } + return (
- {children} + { + onLinkActivation(i, fileName, focus) + }} + onReset={() => { + onStepChange(i) + }} + > + {children} + ))} diff --git a/packages/mdx/src/client/section.tsx b/packages/mdx/src/client/section.tsx index cf04e679..4f900181 100644 --- a/packages/mdx/src/client/section.tsx +++ b/packages/mdx/src/client/section.tsx @@ -4,17 +4,14 @@ import { InnerCode, updateEditorStep } from "./code" const SectionContext = React.createContext<{ props: EditorProps - selectedId?: string setFocus: (x: { fileName?: string focus: string | null id: string }) => void - resetFocus: () => void }>({ props: null!, setFocus: () => {}, - resetFocus: () => {}, }) export function Section({ @@ -48,23 +45,27 @@ export function Section({ const { selectedId, ...rest } = state return ( - -
{children}
-
+
+ + + {children} + + +
) } export function SectionCode() { - const { props, setFocus } = React.useContext( - SectionContext - ) + const { props, setFocus } = + React.useContext(SectionContext) const onTabClick = (filename: string) => { setFocus({ fileName: filename, focus: null, id: "" }) @@ -73,6 +74,8 @@ export function SectionCode() { return } +// --- + export function SectionLink({ focus, file, @@ -84,27 +87,79 @@ export function SectionLink({ file?: string children: React.ReactNode }) { - const { - setFocus, - resetFocus, - selectedId, - } = React.useContext(SectionContext) + const { activate, reset, activatedId } = + React.useContext(LinkableContext) - const isSelected = selectedId === id - const handleClick = isSelected - ? resetFocus - : () => setFocus({ fileName: file, focus, id }) + const isSelected = activatedId === id + // const handleClick = isSelected + // ? resetFocus + // : () => setFocus({ fileName: file, focus, id }) return ( + activate({ fileName: file, focus, id }) + } + onMouseOut={reset} /> ) } + +const LinkableContext = React.createContext<{ + activate: (x: { + fileName?: string + focus: string | null + id: string + }) => void + reset: () => void + activatedId: string | undefined +}>({ + activatedId: undefined, + activate: () => {}, + reset: () => {}, +}) + +export function LinkableSection({ + onActivation, + onReset, + children, +}: { + onActivation: (x: { + fileName?: string + focus: string | null + id: string + }) => void + onReset: () => void + children: React.ReactNode +}) { + const [activatedId, setActivatedId] = + React.useState(undefined) + + const activate = React.useCallback( + x => { + setActivatedId(x.id) + onActivation(x) + }, + [onActivation] + ) + const reset = React.useCallback(() => { + setActivatedId(undefined) + onReset() + }, [onReset]) + + return ( + + {children} + + ) +} diff --git a/packages/mdx/src/index.scss b/packages/mdx/src/index.scss index 24a15305..c394e97c 100644 --- a/packages/mdx/src/index.scss +++ b/packages/mdx/src/index.scss @@ -27,8 +27,27 @@ } .ch-inline-code > code { - padding: 0.2em 0.4em; - margin: 0.1em -0.1em; + padding: 0.2em 0.15em; + margin: 0.1em -0.05em; border-radius: 0.25em; font-size: 0.9em; } + +.ch-section-link, +.ch-section-link * { + text-decoration: underline; + text-decoration-style: dotted; + text-decoration-thickness: 1px; + text-decoration-color: var( + --ch-code-foreground, + currentColor + ); +} +.ch-section-link[data-active="true"] { + background-color: #bae6fd66; +} + +.ch-section-link[data-active="true"], +.ch-section-link[data-active="true"] * { + text-decoration-thickness: 1.5px; +} diff --git a/packages/mdx/src/plugin.ts b/packages/mdx/src/plugin.ts index 5c2851d9..0447ad85 100644 --- a/packages/mdx/src/plugin.ts +++ b/packages/mdx/src/plugin.ts @@ -34,7 +34,6 @@ export function remarkCodeHike( } }) - addConfig(tree as Parent, config) if (config.autoImport && !hasCodeHikeImport) { @@ -42,12 +41,12 @@ export function remarkCodeHike( } try { - await transformInlineCodes(tree) await transformPreviews(tree) await transformScrollycodings(tree, config) await transformSpotlights(tree, config) await transformSlideshows(tree, config) await transformSections(tree, config) + await transformInlineCodes(tree, config) await transformEditorNodes(tree, config) await transformCodeNodes(tree, config) } catch (e) { @@ -60,7 +59,11 @@ export function remarkCodeHike( function addConfigDefaults( config: Partial | undefined ): CodeHikeConfig { - return { ...config, theme: config?.theme || {}, autoImport: config?.autoImport === false ? false : true } + return { + ...config, + theme: config?.theme || {}, + autoImport: config?.autoImport === false ? false : true, + } } function addConfig(tree: Parent, config: CodeHikeConfig) { @@ -124,8 +127,7 @@ function addImportNode(tree: Parent) { type: "Literal", value: "@code-hike/mdx/dist/components.cjs.js", - raw: - '"@code-hike/mdx/dist/components.cjs.js"', + raw: '"@code-hike/mdx/dist/components.cjs.js"', }, }, ], diff --git a/packages/mdx/src/plugin/inline-code.ts b/packages/mdx/src/plugin/inline-code.ts index 62c062a3..f5b34e1e 100644 --- a/packages/mdx/src/plugin/inline-code.ts +++ b/packages/mdx/src/plugin/inline-code.ts @@ -4,15 +4,49 @@ import { CH_CODE_CONFIG_PLACEHOLDER, } from "./unist-utils" import { Node } from "unist" +import visit from "unist-util-visit" +import visitParents from "unist-util-visit-parents" +import { Parent } from "hast-util-to-estree" +import { highlight } from "@code-hike/highlighter" +import { EditorStep } from "@code-hike/mini-editor" +import { Code } from "@code-hike/utils" + +export async function transformInlineCodes( + tree: Node, + { theme }: { theme: any } +) { + // transform *`foo`* to foo + visit(tree, "emphasis", (node: Parent) => { + if ( + node.children && + node.children.length === 1 && + node.children[0].type === "inlineCode" + ) { + node.type = "mdxJsxTextElement" + node.name = "CH.InlineCode" + node.children = [ + { type: "text", value: node.children[0].value }, + ] + } + }) -export async function transformInlineCodes(tree: Node) { await visitAsync( tree, ["mdxJsxFlowElement", "mdxJsxTextElement"], - async node => { + async (node: Parent) => { if (node.name === "CH.InlineCode") { + const inlinedCode = node.children[0].value as string + const lang = node.attributes?.lang + toJSX(node, { props: { + code: await getCode( + tree, + node, + inlinedCode, + lang, + theme + ), codeConfig: CH_CODE_CONFIG_PLACEHOLDER, }, appendProps: true, @@ -21,3 +55,139 @@ export async function transformInlineCodes(tree: Node) { } ) } + +async function getCode( + tree: Node, + node: Parent, + inlinedCode: string, + lang: string | undefined, + theme: any +) { + const ancestors = getAncestors(tree, node) + const sectionNode = ancestors.find( + n => n.data?.editorStep + ) + + // if node isn't inside a section-like parent, use provided lang or "jsx" + if (!sectionNode) { + return await highlight({ + code: inlinedCode, + lang: lang || "jsx", + theme, + }) + } + + const editorStep = sectionNode.data + .editorStep as any as EditorStep + + // if the same code is present in the editor step, use it + const existingCode = getExistingCode( + editorStep.files, + inlinedCode + ) + + if (existingCode) { + return existingCode + } + + // or else, try to guess the language from somewhere + const activeFile = + editorStep.files?.find( + f => f.name === editorStep.northPanel?.active + ) || editorStep.files[0] + + const activeLang = activeFile?.code?.lang + + return await highlight({ + code: inlinedCode, + lang: lang || activeLang || "jsx", + theme, + }) +} + +function getAncestors(tree: Node, node: Node): Parent[] { + let ancestors: Parent[] = [] + visitParents(tree, node, (node, nodeAncestors) => { + ancestors = nodeAncestors + }) + return ancestors +} + +function getExistingCode( + files: EditorStep["files"] | undefined, + inlinedCode: string +): Code | undefined { + if (!files) { + return undefined + } + + for (const file of files) { + for (const line of file.code.lines) { + const lineContent = line.tokens + .map(t => t.content) + .join("") + const index = lineContent.indexOf(inlinedCode) + if (index !== -1) { + const tokens = sliceTokens( + line, + index, + inlinedCode.length + ) + return { lang: file.code.lang, lines: [{ tokens }] } + } + } + } + return undefined +} + +function sliceTokens( + line: Code["lines"][0], + start: number, + length: number +) { + const tokens = line.tokens + let currentLength = 0 + + let headTokens = [] as Code["lines"][0]["tokens"] + + for (let i = 0; i < tokens.length; i++) { + if (currentLength === start) { + headTokens = tokens.slice(i) + break + } + if (currentLength + tokens[i].content.length > start) { + const newToken = { + ...tokens[i], + content: tokens[i].content.slice( + start - currentLength + ), + } + headTokens = [newToken].concat(tokens.slice(i + 1)) + break + } + currentLength += tokens[i].content.length + } + + currentLength = 0 + for (let i = 0; i < headTokens.length; i++) { + if (currentLength === length) { + return headTokens.slice(0, i) + } + if ( + currentLength + headTokens[i].content.length > + length + ) { + const newToken = { + ...headTokens[i], + content: headTokens[i].content.slice( + 0, + length - currentLength + ), + } + + return headTokens.slice(0, i).concat([newToken]) + } + currentLength += headTokens[i].content.length + } + return [] +} diff --git a/packages/mdx/src/plugin/scrollycoding.ts b/packages/mdx/src/plugin/scrollycoding.ts index 1efc4f9b..648ac4f0 100644 --- a/packages/mdx/src/plugin/scrollycoding.ts +++ b/packages/mdx/src/plugin/scrollycoding.ts @@ -6,6 +6,7 @@ import { import { Node, Parent } from "unist" import { extractStepsInfo } from "./steps" import { getPresetConfig } from "./preview" +import { transformLinks } from "./section" export async function transformScrollycodings( tree: Node, @@ -35,6 +36,8 @@ async function transformScrollycoding( (node as any).attributes ) + transformLinks(node) + toJSX(node, { props: { codeConfig: CH_CODE_CONFIG_PLACEHOLDER, diff --git a/packages/mdx/src/plugin/section.ts b/packages/mdx/src/plugin/section.ts index 9661470b..39be3ea6 100644 --- a/packages/mdx/src/plugin/section.ts +++ b/packages/mdx/src/plugin/section.ts @@ -26,17 +26,20 @@ async function transformSection( await visitAsync( node, ["mdxJsxFlowElement", "code"], - async (node, index, parent) => { - if (isEditorNode(node)) { + async (editorNode, index, parent) => { + if (isEditorNode(editorNode)) { props = await mapAnyCodeNode( - { node, index, parent: parent! }, + { node: editorNode, index, parent: parent! }, config ) - toJSX(node, { name: "CH.SectionCode", props: {} }) + toJSX(editorNode, { + name: "CH.SectionCode", + props: {}, + }) } } ) - + node.data = { editorStep: props } transformLinks(node) if (props) { @@ -46,7 +49,7 @@ async function transformSection( } } -function transformLinks(tree: Node) { +export function transformLinks(tree: Node) { visit(tree, "link", (linkNode: any) => { const url = decodeURI(linkNode["url"]) if (url.startsWith("focus://")) { diff --git a/packages/mdx/src/plugin/steps.tsx b/packages/mdx/src/plugin/steps.tsx index 13b62386..952ae170 100644 --- a/packages/mdx/src/plugin/steps.tsx +++ b/packages/mdx/src/plugin/steps.tsx @@ -29,13 +29,11 @@ export async function extractStepsInfo( steps[stepIndex] = steps[stepIndex] || { children: [] } const step = steps[stepIndex] if (!step.editorStep && isEditorNode(child)) { - const { - codeConfig, - ...editorStep - } = await mapAnyCodeNode( - { node: child, parent, index: i }, - config - ) + const { codeConfig, ...editorStep } = + await mapAnyCodeNode( + { node: child, parent, index: i }, + config + ) if (stepIndex === 0) { // for the header props, keep it as it is @@ -58,6 +56,7 @@ export async function extractStepsInfo( return { type: "mdxJsxFlowElement", children: step.children, + data: { editorStep: step.editorStep }, } }) diff --git a/packages/mini-editor/src/editor-spring.tsx b/packages/mini-editor/src/editor-spring.tsx index 32133df1..a6a199bd 100644 --- a/packages/mini-editor/src/editor-spring.tsx +++ b/packages/mini-editor/src/editor-spring.tsx @@ -60,28 +60,73 @@ function EditorSpring({ function useStepSpring( step: EditorStep, springConfig: SpringConfig = defaultSpring -) { - const [{ target, prev, next }, setState] = React.useState( - { - target: 0, - prev: step, - next: step, - } - ) +): { prev: EditorStep; next: EditorStep; t: number } { + const [{ target, steps, index }, setState] = + React.useState({ + target: 2, + steps: [step, step, step], + index: 0, + }) React.useEffect(() => { - if (next != step) { - setState(s => ({ - target: s.target + 1, - prev: next, - next: step, - })) + const lastStep = steps[steps.length - 1] + if (lastStep != step) { + setState(s => updateStepSpring(s, step, progress)) } }, [step]) const [progress] = useSpring(target, springConfig) - const t = progress % 1 + const trioProgress = progress - index + + const result = + trioProgress <= 1 + ? { + prev: steps[0], + next: steps[1], + t: trioProgress, + } + : { + prev: steps[1], + next: steps[2], + t: trioProgress - 1, + } + + return result +} + +type StepSpringState = { + target: number + steps: [EditorStep, EditorStep, EditorStep] + index: number +} - return { prev, next, t: t || 1 } +function updateStepSpring( + state: StepSpringState, + newStep: EditorStep, + progress: number +): StepSpringState { + const { steps, target, index } = state + const stepsClone = + steps.slice() as StepSpringState["steps"] + + const trioProgress = progress - index + + if (trioProgress < 1) { + stepsClone[2] = newStep + return { + ...state, + steps: stepsClone, + } + } else { + stepsClone[0] = steps[1] + stepsClone[1] = steps[2] + stepsClone[2] = newStep + return { + ...state, + steps: stepsClone, + target: target + 1, + index: index + 1, + } + } } diff --git a/packages/playground/content/scrollycoding.mdx b/packages/playground/content/scrollycoding.mdx index 40bb91a5..5affd0a0 100644 --- a/packages/playground/content/scrollycoding.mdx +++ b/packages/playground/content/scrollycoding.mdx @@ -1,124 +1,135 @@ -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +_Based on [Next.js official docs](https://nextjs.org/docs/advanced-features/using-mdx)._ - +Start by installing next and react on an empty directory: -## Step 1 +```bash +npm install next react react-dom +``` -Lorem ipsum dolor sit amet, consectetur adipiscing something about points, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. + -Venenatis cras sed felis eget velit. Consectetur libero id faucibus nisl tincidunt. Gravida in fermentum et sollicitudin ac orci phasellus egestas tellus. Volutpat consequat mauris nunc congue nisi vitae. +Then also install the [mdx plugin for next](focus://1[13:21]), the [mdx loader](focus://1[23:36]), and [Code Hike](focus://1[38:56]). -Id aliquet risus feugiat in ante metus dictum at tempor. Sed blandit libero volutpat sed cras. Sed odio morbi quis commodo odio aenean sed adipiscing. Velit euismod in pellentesque massa placerat. Mi bibendum neque egestas congue quisque egestas diam in arcu. Nisi lacus sed viverra tellus in. Nibh cras pulvinar mattis nunc sed. Luctus accumsan tortor posuere ac ut consequat semper viverra. Fringilla ut morbi tincidunt augue interdum velit euismod. +```bash +npm install @next/mdx @mdx-js/loader @code-hike/mdx@next +``` -```jsx App.jsx -import { BasisCurve } from "curve" + -const points = [ - [10, 90], - [70, 10], - [130, 80], - [190, 20], -] +
-export default function App() { - return ( - - - - ) -} + + +Create a `next.config.js` file at the root of your project. + +Here we use the [_`require("@next/mdx")`_](focus://1,6,8) plugin to set up MDX imports. + +Also, make sure you include `"md"` and `"mdx"` on the _`pageExtensions`_ setting. + +After this step, you can use MDX files in your project, but you can't use Code Hike yet. + +{/* prettier-ignore */} +```js next.config.js +const withMDX = require("@next/mdx")({ + extension: /\.mdx?$/, + options: { + remarkPlugins: [], + }, +}) + +module.exports = withMDX({ + pageExtensions: [ + "ts", "tsx", "js", + "jsx", "md", "mdx" + ], +}) ``` --- -## Step 2 +To set up Code Hike you need to [import the @code-hike/mdx plugin](focus://1,6[1:6]), and add it to the remarkPlugins array in the next.config.js file. + +You will also want to import a theme. You can import one from shiki, or make a custom one. + +Pass the theme into Code Hike's config object, there are a few more settings you can use, like lineNumbers for example. -Venenatis cras sed felis eget velit. Consectetur libero id faucibus nisl tincidunt. Gravida in fermentum et sollicitudin ac orci phasellus egestas tellus. Volutpat consequat mauris nunc congue nisi vitae. +{/* prettier-ignore */} +```js next.config.js focus=1:2,7:9 +const { remarkCH } = require("@code-hike/mdx") +const theme = require("shiki/themes/nord.json") -Praesent elementum facilisis leo vel fringilla est ullamcorper eget. +const withMDX = require("@next/mdx")({ + extension: /\.mdx?$/, + options: { + remarkPlugins: [ + [remarkCH, { theme }] + ], + }, +}) -Id aliquet risus feugiat in ante metus dictum at tempor. Sed blandit libero volutpat sed cras. Sed odio morbi quis commodo odio aenean sed adipiscing. Velit euismod in pellentesque massa placerat. Mi bibendum neque egestas congue quisque egestas diam in arcu. Nisi lacus sed viverra tellus in. Nibh cras pulvinar mattis nunc sed. Luctus accumsan tortor posuere ac ut consequat semper viverra. Fringilla ut morbi tincidunt augue interdum velit euismod. +module.exports = withMDX({ + pageExtensions: [ + "ts", "tsx", "js", + "jsx", "md", "mdx" + ], +}) +``` -Morbi quis commodo. +--- -```jsx App.jsx -import { NaturalCurve } from "curve" +Then you need to create a `pages/_app.js` file if you don't have one. -const points = [ - [10, 90], - [70, 10], - [130, 80], - [190, 20], -] +You can find more information about the `_app.js` file in the [Next.js official docs](https://nextjs.org/docs/advanced-features/custom-app). -export default function App() { - return ( - - - - ) +{/* prettier-ignore */} +```js pages/_app.js +function MyApp({ Component, pageProps }) { + return } + +export default MyApp ``` --- -## Step 3 +The pages/\_app.js file is where you add global stylesheets in Next.js. + +Here we need to import Code Hike's CSS. -Mi bibendum neque egestas congue quisque egestas diam in arcu. Nisi lacus sed viverra tellus in. +If you want to customize Code Hike's styles with a global stylesheet make sure to import it after this import to avoid specificity issues. -```jsx App.jsx +{/* prettier-ignore */} +```js pages/_app.js focus=1 +import "@code-hike/mdx/dist/index.css" +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp ``` --- -## Step 4 - -Id aliquet risus feugiat in ante metus dictum at tempor. Sed blandit libero volutpat sed cras. Sed odio morbi quis commodo odio aenean sed adipiscing. Velit euismod in pellentesque massa placerat. Mi bibendum neque egestas congue quisque egestas diam in arcu. Nisi lacus sed viverra tellus in. Nibh cras pulvinar mattis nunc sed. Luctus accumsan tortor posuere ac ut consequat semper viverra. Fringilla ut morbi tincidunt augue interdum velit euismod. - -```jsx App.jsx -import { NaturalCurve } from "curve" - -const points = [ - [10, 90], - [70, 10], - [130, 80], - [190, 20], -] - -export default function App() { - return ( - - - - ) -} +Now you can create mdx files using codehike. + +Markdown (.md) files should also work. + +{/* prettier-ignore */} +~~~md pages/my.mdx +# Hello + +Lorem ipsum dolor sit amet. + +```python hello.py +print("Rendered with Code Hike") ``` - +Lorem ipsum dolor sit amet. +~~~ -Lorem ipsum dolor sit amet, consectetur adipiscing something about points, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. + -Venenatis cras sed felis eget velit. Consectetur libero id faucibus nisl tincidunt. Gravida in fermentum et sollicitudin ac orci phasellus egestas tellus. Volutpat consequat mauris nunc congue nisi vitae. +Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies. -Id aliquet risus feugiat in ante metus dictum at tempor. Sed blandit libero volutpat sed cras. Sed odio morbi quis commodo odio aenean sed adipiscing. Velit euismod in pellentesque massa placerat. Mi bibendum neque egestas congue quisque egestas diam in arcu. Nisi lacus sed viverra tellus in. Nibh cras pulvinar mattis nunc sed. Luctus accumsan tortor posuere ac ut consequat semper viverra. Fringilla ut morbi tincidunt augue interdum velit euismod. +Consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. At imperdiet dui accumsan sit amet nulla facilities morbi tempus. Praesent elementum facilisis leo vel fringilla. Congue mauris rhoncus aenean vel. Egestas sed tempus urna et pharetra pharetra massa massa ultricies. diff --git a/packages/playground/content/section.mdx b/packages/playground/content/section.mdx index 13fdc784..e18218d4 100644 --- a/packages/playground/content/section.mdx +++ b/packages/playground/content/section.mdx @@ -5,6 +5,7 @@ Lorem ipsum dolor sit amet. Consectetur adipiscing elit, sed do eiusmod tempor [incididunt](focus://4:7) ut labore et dolore magna aliqua. Praesent elementum facilisis leo vel fringilla est ullamcorper eget. +_`orem`_, _`sum`_ ```js function lorem(ipsum, dolor) { @@ -45,4 +46,6 @@ function lorem(ipsum, dolor) { +_`ackground-color: va`_ + diff --git a/packages/playground/content/test.mdx b/packages/playground/content/test.mdx index bb652611..4602d195 100644 --- a/packages/playground/content/test.mdx +++ b/packages/playground/content/test.mdx @@ -1,14 +1,15 @@ -```js lorem.js -// mark[1:8] -function lorem(ipsum, dolor = 1) { - // mark[24:32] - const sit = ipsum == undefined - // mark[8:16] - if ("consectur") { - // mark[20:27] - return ipsum + 12345678 - } - // mark[3:12] - adipiscing(dolor + amet) -} -``` +Lorem ipsum dolor sit amet. _`console.log("foo bar")`_. + +Lorem ipsum dolor sit amet. `console.log("foo bar")`. + +_`console.log("foo bar")`_ + +_`console.log("foo bar")`_ + +X console.log("foo bar") + +Lorem ipsum `var x = 1` + +_Lorem ipsum `var x = 1`_ + +**`console.log("foo bar")`** diff --git a/packages/smooth-code/src/code-spring.tsx b/packages/smooth-code/src/code-spring.tsx index 9950fed8..86bc1ad2 100644 --- a/packages/smooth-code/src/code-spring.tsx +++ b/packages/smooth-code/src/code-spring.tsx @@ -44,23 +44,70 @@ function useStepSpring( step: CodeStep, springConfig: SpringConfig = defaultSpring ): { tween: FullTween; t: number } { - const [{ target, tween }, setState] = React.useState({ - target: 0, - tween: { prev: step, next: step }, - }) + const [{ target, steps, index }, setState] = + React.useState({ + target: 2, + steps: [step, step, step], + index: 0, + }) React.useEffect(() => { - if (tween.next != step) { - setState(s => ({ - target: s.target + 1, - tween: { prev: tween.next, next: step }, - })) + const lastStep = steps[steps.length - 1] + if (lastStep != step) { + setState(s => updateStepSpring(s, step, progress)) } }, [step]) const [progress] = useSpring(target, springConfig) - const t = progress % 1 + const trioProgress = progress - index - return { tween, t: t || 1 } + const result = + trioProgress <= 1 + ? { + tween: { prev: steps[0], next: steps[1] }, + t: trioProgress, + } + : { + tween: { prev: steps[1], next: steps[2] }, + t: trioProgress - 1, + } + + return result +} + +type StepSpringState = { + target: number + steps: [CodeStep, CodeStep, CodeStep] + index: number +} + +function updateStepSpring( + state: StepSpringState, + newStep: CodeStep, + progress: number +): StepSpringState { + const { steps, target, index } = state + const stepsClone = + steps.slice() as StepSpringState["steps"] + + const trioProgress = progress - index + + if (trioProgress < 1) { + stepsClone[2] = newStep + return { + ...state, + steps: stepsClone, + } + } else { + stepsClone[0] = steps[1] + stepsClone[1] = steps[2] + stepsClone[2] = newStep + return { + ...state, + steps: stepsClone, + target: target + 1, + index: index + 1, + } + } } diff --git a/yarn.lock b/yarn.lock index 7dbbdb6c..0a5e914a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15110,6 +15110,11 @@ prelude-ls@~1.1.2: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= +prettier@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== + prettier@~2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5" @@ -16791,10 +16796,10 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -shiki@^0.9.14: - version "0.9.14" - resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.9.14.tgz#6b3e369edf76049ae7ad7c2b0498c35c200b8dd7" - integrity sha512-uLHjjyJdNsMzF9GOF8vlOuZ8BwigiYPraMN5yjC826k8K7Xu90JQcC5GUNrzRibLgT2EOk9597I1IX+jRdA8nw== +shiki@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/shiki/-/shiki-0.10.1.tgz#6f9a16205a823b56c072d0f1a0bcd0f2646bef14" + integrity sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng== dependencies: jsonc-parser "^3.0.0" vscode-oniguruma "^1.6.1"