diff --git a/packages/mdx/dev/content/assets/foo.js b/packages/mdx/dev/content/assets/foo.js new file mode 100644 index 00000000..c290d71a --- /dev/null +++ b/packages/mdx/dev/content/assets/foo.js @@ -0,0 +1 @@ +console.log("hello foo") diff --git a/packages/mdx/dev/content/assets/foo.py b/packages/mdx/dev/content/assets/foo.py new file mode 100644 index 00000000..aa8f6477 --- /dev/null +++ b/packages/mdx/dev/content/assets/foo.py @@ -0,0 +1 @@ +print("Hello foo") \ No newline at end of file diff --git a/packages/mdx/dev/content/external.mdx b/packages/mdx/dev/content/external.mdx new file mode 100644 index 00000000..dbc8af58 --- /dev/null +++ b/packages/mdx/dev/content/external.mdx @@ -0,0 +1,34 @@ +```js foo.js +// from ./assets/foo.js +``` + +```py foo.py +# from ./assets/foo.py +``` + +```py foo.py +print("not external") +# from ./assets/foo.py +``` + + + +```js foo.js +// from ./assets/foo.js +``` + +```py foo.py +# from ./assets/foo.py +``` + +```html index.html +

Hello

+``` + +--- + +```py another.py +# from ./assets/foo.py +``` + +
diff --git a/packages/mdx/dev/content/test.mdx b/packages/mdx/dev/content/test.mdx index 86145ff7..f7b59b45 100644 --- a/packages/mdx/dev/content/test.mdx +++ b/packages/mdx/dev/content/test.mdx @@ -1,10 +1,79 @@ +### with width + + + ```js -foo -// link(1:2) https://codehike.org -const hi = 'hi' -const hi = 'hi' -const hi = 'hi' -// link[2:18] https://codehike.org -hello world -hello +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" ``` + + + + + +```js +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +``` + + + +### with width + + + +```js +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +``` + + + + + +```js +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +``` + + + +### with zoom + + + +```js +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +``` + + + + + +```js +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +``` + + + +### with line numbers + + + +```js +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +``` + + + + + +```js +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +const = "hello" + "hello" + "hello" + "hello" + "hello" + "hello"+ "hello" + "goodbye" +``` + + diff --git a/packages/mdx/dev/files.ts b/packages/mdx/dev/files.ts index 5cc9118f..d2fd18ce 100644 --- a/packages/mdx/dev/files.ts +++ b/packages/mdx/dev/files.ts @@ -10,16 +10,14 @@ export async function getFiles() { .filter(file => file.endsWith(".mdx")) .map(filename => filename.slice(0, -4)) } -export async function getContent(filename: string) { - const file = await fs.promises.readFile( - `./dev/content/${filename}.mdx`, - "utf8" - ) +export async function getFile(filename: string) { + const path = `./dev/content/${filename}.mdx` + const file = await fs.promises.readFile(path, "utf8") - return file + return { value: file, path } } -export async function getCode(file: string, config = {}) { +export async function getCode(file: any, config = {}) { let debugLink = "" const debugCompile = withDebugger(compile, { @@ -36,6 +34,7 @@ export async function getCode(file: string, config = {}) { remarkCodeHike, { autoImport: false, + skipLanguages: ["", "mermaid"], showCopyButton: true, theme, ...config, diff --git a/packages/mdx/pages/[name].tsx b/packages/mdx/pages/[name].tsx index caf7b48e..ac1e89bf 100644 --- a/packages/mdx/pages/[name].tsx +++ b/packages/mdx/pages/[name].tsx @@ -1,7 +1,7 @@ import { runSync } from "@mdx-js/mdx" import * as runtime from "react/jsx-runtime.js" import { CH } from "../src/components" -import { getCode, getContent, getFiles } from "../dev/files" +import { getCode, getFile, getFiles } from "../dev/files" import { ClickToComponent } from "click-to-react-component" import { Layout } from "../dev/layout" @@ -17,8 +17,8 @@ export async function getStaticProps({ params }) { const { name = "test" } = params const files = await getFiles() - const content = await getContent(name) - const { code, debugLink } = await getCode(content) + const file = await getFile(name) + const { code, debugLink } = await getCode(file) return { props: { tests: files, diff --git a/packages/mdx/src/mdx-client/code.tsx b/packages/mdx/src/mdx-client/code.tsx index e19149ea..23f456f3 100644 --- a/packages/mdx/src/mdx-client/code.tsx +++ b/packages/mdx/src/mdx-client/code.tsx @@ -55,6 +55,7 @@ export function mergeCodeConfig( showExpandButton == null ? props.codeConfig?.showExpandButton : showExpandButton, + debug: props.debug ?? props.codeConfig?.debug, } return { ...rest, codeConfig } } diff --git a/packages/mdx/src/mdx-client/scrollycoding.scss b/packages/mdx/src/mdx-client/scrollycoding.scss index b0bd845f..927c5743 100644 --- a/packages/mdx/src/mdx-client/scrollycoding.scss +++ b/packages/mdx/src/mdx-client/scrollycoding.scss @@ -2,11 +2,11 @@ display: flex; position: relative; margin: 1rem 0; + gap: 1rem; } .ch-scrollycoding-content { box-sizing: border-box; - padding-right: 16px; flex: 1; } diff --git a/packages/mdx/src/mdx-client/scrollycoding.tsx b/packages/mdx/src/mdx-client/scrollycoding.tsx index f0efb6c2..d09926db 100644 --- a/packages/mdx/src/mdx-client/scrollycoding.tsx +++ b/packages/mdx/src/mdx-client/scrollycoding.tsx @@ -11,6 +11,8 @@ export function Scrollycoding({ codeConfig, presetConfig, start = 0, + className, + style, ...rest }: { children: React.ReactNode @@ -18,6 +20,8 @@ export function Scrollycoding({ codeConfig: EditorProps["codeConfig"] start?: number presetConfig?: PresetConfig + className?: string + style?: React.CSSProperties }) { const stepsChildren = React.Children.toArray(children) @@ -58,7 +62,8 @@ export function Scrollycoding({
diff --git a/packages/mdx/src/mdx-client/section.tsx b/packages/mdx/src/mdx-client/section.tsx index 6708c90b..280bede0 100644 --- a/packages/mdx/src/mdx-client/section.tsx +++ b/packages/mdx/src/mdx-client/section.tsx @@ -16,9 +16,13 @@ const SectionContext = React.createContext<{ export function Section({ children, + className, + style, ...props }: { children: React.ReactNode + className?: string + style?: React.CSSProperties }) { const [state, setState] = React.useState(props) @@ -45,7 +49,10 @@ export function Section({ const { selectedId, ...rest } = state return ( -
+
{headerElement?.props?.children ? ( @@ -83,6 +88,6 @@ export function Spotlight({ /> )}
-
+
) } diff --git a/packages/mdx/src/mini-editor/code-browser.tsx b/packages/mdx/src/mini-editor/code-browser.tsx index d29906c5..b5514ec4 100644 --- a/packages/mdx/src/mini-editor/code-browser.tsx +++ b/packages/mdx/src/mini-editor/code-browser.tsx @@ -1,6 +1,6 @@ import { CodeFile } from "./editor-shift" import { IRawTheme } from "vscode-textmate" -import { ColorName, getColor } from "utils" +import { ColorName, getColor, getColorScheme } from "utils" import React from "react" export function CodeBrowser({ @@ -189,15 +189,20 @@ function Content({ theme, ColorName.SelectionBackground ), + colorScheme: getColorScheme(theme), }} > {file.code.lines.map((line, i) => (
- {line.tokens.map((token, i) => ( - - {token.content} - - ))} + {line.tokens.length === 0 ? ( +
+ ) : ( + line.tokens.map((token, i) => ( + + {token.content} + + )) + )}
))}
diff --git a/packages/mdx/src/remark/code.ts b/packages/mdx/src/remark/code.ts index c8d406fb..ae23352b 100644 --- a/packages/mdx/src/remark/code.ts +++ b/packages/mdx/src/remark/code.ts @@ -8,15 +8,23 @@ import { extractAnnotationsFromCode, extractJSXAnnotations, } from "./annotations" -import { mergeFocus } from "../utils" +import { Code, mergeFocus } from "../utils" import { CodeNode, SuperNode } from "./nodes" import { CodeHikeConfig } from "./config" +import { getCommentData } from "./comment-data" -export function isEditorNode(node: SuperNode) { +export function isEditorNode( + node: SuperNode, + config: CodeHikeConfig +) { + if (node.type === "code") { + const lang = (node.lang as string) || "" + const shouldSkip = config.skipLanguages.includes(lang) + return !shouldSkip + } return ( - node.type === "code" || - (node.type === "mdxJsxFlowElement" && - node.name === "CH.Code") + node.type === "mdxJsxFlowElement" && + node.name === "CH.Code" ) } @@ -112,12 +120,15 @@ async function mapFile( const lang = (node.lang as string) || "text" - const code = await highlight({ + let code = await highlight({ code: node.value as string, lang, theme, }) + // if the code is a single line with a "from" annotation + code = await getCodeFromExternalFileIfNeeded(code, config) + const [commentAnnotations, commentFocus] = extractAnnotationsFromCode(code) @@ -129,12 +140,12 @@ async function mapFile( options as any ) - const linkAnnotations = extractLinks( - node, - index, - parent, - node.value as string - ) + // const linkAnnotations = extractLinks( + // node, + // index, + // parent, + // nodeValue as string + // ) const jsxAnnotations = extractJSXAnnotations( node, @@ -181,3 +192,69 @@ function parseMetastring( }) return { name: name || "", ...options } } + +async function getCodeFromExternalFileIfNeeded( + code: Code, + config: CodeHikeConfig +) { + if (code?.lines?.length != 1) { + return code + } + + const firstLine = code.lines[0] + const commentData = getCommentData(firstLine, code.lang) + + if (!commentData || commentData.key != "from") { + return code + } + + const fileText = firstLine.tokens + .map(t => t.content) + .join("") + + const codepath = commentData.data + + let fs, path + + try { + fs = (await import("fs")).default + path = (await import("path")).default + if (!fs || !fs.readFileSync || !path || !path.resolve) { + throw new Error("fs or path not found") + } + } catch (e) { + e.message = `Code Hike couldn't resolve this annotation: +${fileText} +Looks like node "fs" and "path" modules are not available.` + throw e + } + + // if we don't know the path of the mdx file: + if (config.filepath === undefined) { + throw new Error( + `Code Hike couldn't resolve this annotation: + ${fileText} + Someone is calling the mdx compile function without setting the path. + Open an issue on CodeHike's repo for help.` + ) + } + + const dir = path.dirname(config.filepath) + const absoluteCodepath = path.resolve(dir, codepath) + + let nodeValue + try { + nodeValue = fs.readFileSync(absoluteCodepath, "utf8") + } catch (e) { + e.message = `Code Hike couldn't resolve this annotation: +${fileText} +${absoluteCodepath} doesn't exist.` + throw e + } + + return await highlight({ + code: nodeValue, + lang: code.lang, + theme: config.theme, + }) +} diff --git a/packages/mdx/src/remark/comment-data.ts b/packages/mdx/src/remark/comment-data.ts index 9b8cdada..ac4728bd 100644 --- a/packages/mdx/src/remark/comment-data.ts +++ b/packages/mdx/src/remark/comment-data.ts @@ -1,7 +1,11 @@ import { Code } from "../utils" import { annotationsMap } from "../mdx-client/annotations" -const validKeys = ["focus", ...Object.keys(annotationsMap)] +const validKeys = [ + "focus", + "from", + ...Object.keys(annotationsMap), +] export function getCommentData( line: Code["lines"][0], diff --git a/packages/mdx/src/remark/config.ts b/packages/mdx/src/remark/config.ts index 59b8379f..d8fc55ac 100644 --- a/packages/mdx/src/remark/config.ts +++ b/packages/mdx/src/remark/config.ts @@ -2,20 +2,27 @@ export type CodeHikeConfig = { theme: any lineNumbers?: boolean autoImport?: boolean + skipLanguages: string[] showExpandButton?: boolean showCopyButton?: boolean + // path to the current file, internal use only + filepath?: string } /** * Add defaults and normalize config */ export function addConfigDefaults( - config: Partial | undefined + config: Partial | undefined, + cwd?: string, + filepath?: string ): CodeHikeConfig { // TODO warn when config looks weird return { ...config, theme: config?.theme || {}, autoImport: config?.autoImport === false ? false : true, + skipLanguages: config?.skipLanguages || [], + filepath, } } diff --git a/packages/mdx/src/remark/steps.tsx b/packages/mdx/src/remark/steps.tsx index 2344fe35..6c72e6d0 100644 --- a/packages/mdx/src/remark/steps.tsx +++ b/packages/mdx/src/remark/steps.tsx @@ -29,7 +29,7 @@ export async function extractStepsInfo( steps[stepIndex] = steps[stepIndex] || { children: [] } const step = steps[stepIndex] - if (!step.editorStep && isEditorNode(child)) { + if (!step.editorStep && isEditorNode(child, config)) { const editorStep = await mapAnyCodeNode( { node: child, parent, index: i }, config diff --git a/packages/mdx/src/remark/transform.code.ts b/packages/mdx/src/remark/transform.code.ts index f25727e8..7e0f7df5 100644 --- a/packages/mdx/src/remark/transform.code.ts +++ b/packages/mdx/src/remark/transform.code.ts @@ -1,5 +1,9 @@ import { NodeInfo, toJSX, visitAsync } from "./unist-utils" -import { mapAnyCodeNode, mapEditor } from "./code" +import { + isEditorNode, + mapAnyCodeNode, + mapEditor, +} from "./code" import { CodeNode, JsxNode, SuperNode } from "./nodes" import { CodeHikeConfig } from "./config" @@ -20,7 +24,10 @@ export async function transformCodes( tree, "code", async (node: CodeNode, index, parent) => { - await transformCode({ node, index, parent }, config) + // here we check if we should skip it because of the language: + if (isEditorNode(node, config)) { + await transformCode({ node, index, parent }, config) + } } ) } diff --git a/packages/mdx/src/remark/transform.section.ts b/packages/mdx/src/remark/transform.section.ts index c19596cd..5e9d4035 100644 --- a/packages/mdx/src/remark/transform.section.ts +++ b/packages/mdx/src/remark/transform.section.ts @@ -27,7 +27,7 @@ async function transformSection( node, ["mdxJsxFlowElement", "code"], async (editorNode, index, parent) => { - if (isEditorNode(editorNode)) { + if (isEditorNode(editorNode, config)) { props = await mapAnyCodeNode( { node: editorNode, index, parent }, config @@ -48,6 +48,7 @@ async function transformSection( name: "CH.Section", props: props as any, addConfigProp: true, + appendProps: true, }) } else { toJSX(node, { name: "div", props: {} }) diff --git a/packages/mdx/src/remark/transform.ts b/packages/mdx/src/remark/transform.ts index 7b5f130f..2808bae5 100644 --- a/packages/mdx/src/remark/transform.ts +++ b/packages/mdx/src/remark/transform.ts @@ -20,10 +20,15 @@ const transforms = [ transformInlineCodes, transformCodes, ] - export function transform(unsafeConfig: CodeHikeConfig) { - return async (tree: SuperNode) => { - const config = addConfigDefaults(unsafeConfig) + return async (tree: SuperNode, file: any) => { + const config = addConfigDefaults( + unsafeConfig, + file?.cwd, + file?.history + ? file.history[file.history.length - 1] + : undefined + ) try { for (const transform of transforms) { diff --git a/packages/mdx/src/smooth-code/code-tween.tsx b/packages/mdx/src/smooth-code/code-tween.tsx index f4b3d661..f56e32e7 100644 --- a/packages/mdx/src/smooth-code/code-tween.tsx +++ b/packages/mdx/src/smooth-code/code-tween.tsx @@ -46,6 +46,7 @@ export type CodeConfig = { lineNumbers?: boolean showCopyButton?: boolean showExpandButton?: boolean + debug?: boolean } function useCodeShift({ @@ -83,17 +84,12 @@ export function CodeTween({ config.lineNumbers || false, [config.parentHeight] ) - // return ( - // - // ) - return !dimensions ? ( + return !dimensions || config.debug ? ( ) : ( {element} diff --git a/packages/mdx/src/smooth-code/index.scss b/packages/mdx/src/smooth-code/index.scss index d73da873..875f7fc9 100644 --- a/packages/mdx/src/smooth-code/index.scss +++ b/packages/mdx/src/smooth-code/index.scss @@ -19,6 +19,7 @@ padding: 0; box-sizing: content-box; border: none; + overscroll-behavior-y: contain; } .ch-code-scroll-parent ::selection { diff --git a/packages/mdx/src/smooth-code/smooth-container.tsx b/packages/mdx/src/smooth-code/smooth-container.tsx index d547b6f1..69f3e74c 100644 --- a/packages/mdx/src/smooth-code/smooth-container.tsx +++ b/packages/mdx/src/smooth-code/smooth-container.tsx @@ -59,7 +59,7 @@ export function SmoothContainer({ const width = Math.max( focusWidth + leftPad, - dimensions!.containerWidth + dimensions!.contentWidth ) const startX = leftPad / zoom @@ -75,7 +75,7 @@ export function SmoothContainer({ scale={zoom} height={Math.max( focusHeight, - dimensions!.containerHeight + dimensions!.contentHeight )} width={width} > @@ -214,8 +214,10 @@ function getContentProps({ originalContentHeight: number horizontalCenter: boolean }) { - const { containerWidth, containerHeight, lineHeight } = - dimensions! + const { lineHeight } = dimensions! + const containerHeight = dimensions?.contentHeight + const containerWidth = dimensions?.contentWidth + const originalFocusHeight = (extremes[1] - extremes[0] + 3) * lineHeight diff --git a/packages/mdx/src/smooth-code/use-dimensions.tsx b/packages/mdx/src/smooth-code/use-dimensions.tsx index 11fe0460..da8ea15b 100644 --- a/packages/mdx/src/smooth-code/use-dimensions.tsx +++ b/packages/mdx/src/smooth-code/use-dimensions.tsx @@ -9,6 +9,8 @@ import { type Dimensions = { containerWidth: number containerHeight: number + contentWidth: number + contentHeight: number deps: React.DependencyList lineWidths: [number, number] lineWidth: [number, number] @@ -77,7 +79,13 @@ function useDimensions( _{lineCount} ) : undefined} -
+
{line}
@@ -99,7 +107,8 @@ function useDimensions( useLayoutEffect(() => { if (prevLineRef.current) { const pll = prevLineRef.current - const codeElement = pll?.parentElement! + const contentElement = pll?.parentElement! + const codeElement = contentElement.parentElement! // TODO is it clientWidth or clientRect? const lineContentDiv = pll?.querySelector( @@ -126,6 +135,12 @@ function useDimensions( containerHeight: getHeightWithoutPadding( codeElement.parentElement! )!, + contentWidth: getWidthWithoutPadding( + contentElement.parentElement! + ), + contentHeight: getHeightWithoutPadding( + contentElement.parentElement! + )!, lineWidths: [ plw || nlw || DEFAULT_WIDTH, nlw || plw || DEFAULT_WIDTH, @@ -146,9 +161,8 @@ function useDimensions( deps: allDeps, } setDimensions(d) - // console.log({ d }) } - }, [allDeps]) + }, allDeps) if ( !dimensions || diff --git a/playground/src/index.css b/playground/src/index.css index 6f5d1f97..221facda 100644 --- a/playground/src/index.css +++ b/playground/src/index.css @@ -182,7 +182,7 @@ main { .compile-error pre { color: #111; - white-space: normal; + white-space: pre-wrap; } .with-error {