diff --git a/package-lock.json b/package-lock.json index b102eea..3a3c2ac 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "react-github-permalink", - "version": "1.11.0", + "version": "1.11.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "react-github-permalink", - "version": "1.11.0", + "version": "1.11.1", "license": "MIT", "dependencies": { + "lz-string": "^1.5.0", "react-responsive": "^10.0.0", "react-syntax-highlighter": "^15.5.0" }, @@ -25,6 +26,7 @@ "@testing-library/react": "^13.0.0", "@testing-library/user-event": "^13.2.1", "@types/jest": "^27.0.1", + "@types/lz-string": "^1.3.34", "@types/node": "^20", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", @@ -5241,6 +5243,13 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/lz-string": { + "version": "1.3.34", + "resolved": "https://registry.npmjs.org/@types/lz-string/-/lz-string-1.3.34.tgz", + "integrity": "sha512-j6G1e8DULJx3ONf6NdR5JiR2ZY3K3PaaqiEuKYkLQO0Czfi1AzrtjfnfCROyWGeDd5IVMKCwsgSmMip9OWijow==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -13603,7 +13612,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, + "license": "MIT", "bin": { "lz-string": "bin/bin.js" } diff --git a/package.json b/package.json index 814330a..711c2dc 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "release": "changeset publish" }, "dependencies": { + "lz-string": "^1.5.0", "react-responsive": "^10.0.0", "react-syntax-highlighter": "^15.5.0" }, @@ -44,6 +45,7 @@ "@testing-library/react": "^13.0.0", "@testing-library/user-event": "^13.2.1", "@types/jest": "^27.0.1", + "@types/lz-string": "^1.3.34", "@types/node": "^20", "@types/react": "^18.3.0", "@types/react-dom": "^18.3.0", diff --git a/src/app/page.tsx b/src/app/page.tsx index 77ec7d0..e266868 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,5 @@ +import { TypeScriptPlayground } from "@/library/TypeScriptPlayground/TypeScriptPlayground"; +import "@/library/GithubPermalink/github-permalink.css"; export default function Home() { return ( @@ -5,8 +7,15 @@ export default function Home() {

React Github Permalink

+

This library now supports TypeScript Playground links!

- See the Storybook +

Example: TypeScript Playground

+ + +
+
+ + See the Storybook for more examples
); diff --git a/src/library/TypeScriptPlayground/TypeScriptPlayground.stories.tsx b/src/library/TypeScriptPlayground/TypeScriptPlayground.stories.tsx new file mode 100644 index 0000000..dd012a7 --- /dev/null +++ b/src/library/TypeScriptPlayground/TypeScriptPlayground.stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { TypeScriptPlayground } from "./TypeScriptPlayground"; +import { GithubPermalinkProvider } from "../config/GithubPermalinkContext"; +import "../GithubPermalink/github-permalink.css"; + +const meta: Meta = { + component: TypeScriptPlayground, +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: () => ( + + ), +}; + +export const SimpleCode: Story = { + render: () => ( + + ), +}; + +export const WithProvider: Story = { + render: () => ( + + + + ), +}; diff --git a/src/library/TypeScriptPlayground/TypeScriptPlayground.tsx b/src/library/TypeScriptPlayground/TypeScriptPlayground.tsx new file mode 100644 index 0000000..419dd66 --- /dev/null +++ b/src/library/TypeScriptPlayground/TypeScriptPlayground.tsx @@ -0,0 +1,30 @@ +"use client" + +import { useContext, useEffect, useState } from "react"; +import { TypeScriptPlaygroundDataResponse, GithubPermalinkContext } from "../config/GithubPermalinkContext"; +import { TypeScriptPlaygroundBase, TypeScriptPlaygroundBaseProps } from "./TypeScriptPlaygroundBase"; + +type TypeScriptPlaygroundProps = Omit & { playgroundUrl: string }; + +export function TypeScriptPlayground(props: TypeScriptPlaygroundProps) { + const { playgroundUrl } = props; + const [data, setData] = useState(null as null | TypeScriptPlaygroundDataResponse); + const { getTypeScriptPlaygroundFn, githubToken, onError } = useContext(GithubPermalinkContext); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + getTypeScriptPlaygroundFn(playgroundUrl, githubToken, onError).then((v) => { + setIsLoading(false); + setData(v); + }) + }, [getTypeScriptPlaygroundFn, githubToken, onError, playgroundUrl]) + + if (isLoading) { + return null; + } + if (!data) { + throw new Error("Loading is complete, but no data was returned.") + } + + return +} diff --git a/src/library/TypeScriptPlayground/TypeScriptPlaygroundBase.stories.tsx b/src/library/TypeScriptPlayground/TypeScriptPlaygroundBase.stories.tsx new file mode 100644 index 0000000..b8a093d --- /dev/null +++ b/src/library/TypeScriptPlayground/TypeScriptPlaygroundBase.stories.tsx @@ -0,0 +1,98 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { TypeScriptPlaygroundBase } from "./TypeScriptPlaygroundBase"; +import "../GithubPermalink/github-permalink.css"; + +const meta: Meta = { + component: TypeScriptPlaygroundBase, +}; + +export default meta; + +type Story = StoryObj; + +export const WithData: Story = { + render: () => ( + + ), +}; + +export const ErrorState: Story = { + render: () => ( + + ), +}; + +export const LongCode: Story = { + render: () => ( + & { id: string };", + "", + "type ColumnData = {", + " key: TKey", + " renderData: (value: TRowData[TKey]) => string;", + "}", + "", + "function processTable(row: Array, column: Array>) {", + "", + "}", + "", + "", + "processTable(", + " [{", + " id: \"123\",", + " a: 1,", + " b: {", + " x: 1,", + " y: 2", + " }", + " }], [{", + " key: \"a\",", + " renderData: (value) => {", + " // (parameter) value: number | {", + " // x: number;", + " // y: number;", + " // }", + " return `${value}`;", + " }", + " },", + " {", + " key: \"b\",", + " renderData: (value) => {", + "", + " // (parameter) value: number | {", + " // x: number;", + " // y: number;", + " // }", + " //Property 'x' does not exist on type 'number | { x: number; y: number; }'.", + " return `${value.x},${value.y}`;", + " }", + " },", + "]);" + ], + startLine: 44, + endLine: 8, + status: "ok" + }} + /> + ), +}; diff --git a/src/library/TypeScriptPlayground/TypeScriptPlaygroundBase.tsx b/src/library/TypeScriptPlayground/TypeScriptPlaygroundBase.tsx new file mode 100644 index 0000000..1eb1c40 --- /dev/null +++ b/src/library/TypeScriptPlayground/TypeScriptPlaygroundBase.tsx @@ -0,0 +1,67 @@ +import { TypeScriptPlaygroundDataResponse } from "../config/GithubPermalinkContext"; +import { ErrorMessages } from "../ErrorMessages/ErrorMessages"; +import { PropsWithChildren } from "react"; +import { SyntaxHighlight } from "../SyntaxHighlight/SyntaxHighlight"; +import { CopyButton } from "../common/CopyButton/CopyButton"; +import { AvailableLanguagesPrism } from "../SyntaxHighlight/availableLanguagesPrism"; + +export type TypeScriptPlaygroundBaseProps = { + className?: string; + playgroundUrl: string; + data: TypeScriptPlaygroundDataResponse; + language?: AvailableLanguagesPrism; +} + +export function TypeScriptPlaygroundBase(props: TypeScriptPlaygroundBaseProps) { + const { data, playgroundUrl } = props; + + if (data.status === "ok") { + const language = props.language ?? "typescript"; + const clipboard = data.lines.join("\n"); + + // Determine which lines to highlight based on startLine and endLine + const startLineNumber = data.startLine ?? 1; + + return + TypeScript Playground + {data.startLine != null && data.endLine != null && ( +

Lines {data.startLine} to {data.endLine}

+ )} + }> + +
+ } + + return + + +} + +function TypeScriptPlaygroundInner(props: PropsWithChildren<{ + header?: React.ReactNode + clipboard?: string; +} & TypeScriptPlaygroundBaseProps>) { + const { clipboard } = props; + + return
+
+
+ +
+
+ {props.header ?? {props.playgroundUrl}} +
+ + {clipboard &&
+ +
} +
+ {props.children} +
+} diff --git a/src/library/TypeScriptPlayground/TypeScriptPlaygroundRsc.stories.tsx b/src/library/TypeScriptPlayground/TypeScriptPlaygroundRsc.stories.tsx new file mode 100644 index 0000000..1601edb --- /dev/null +++ b/src/library/TypeScriptPlayground/TypeScriptPlaygroundRsc.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { TypeScriptPlaygroundRsc } from "./TypeScriptPlaygroundRsc"; +import "../GithubPermalink/github-permalink.css"; + +const meta: Meta = { + component: TypeScriptPlaygroundRsc, +}; + +export default meta; + +type Story = StoryObj; + +export const Primary: Story = { + render: () => ( + + ), +}; diff --git a/src/library/TypeScriptPlayground/TypeScriptPlaygroundRsc.tsx b/src/library/TypeScriptPlayground/TypeScriptPlaygroundRsc.tsx new file mode 100644 index 0000000..c2e635a --- /dev/null +++ b/src/library/TypeScriptPlayground/TypeScriptPlaygroundRsc.tsx @@ -0,0 +1,15 @@ +import { TypeScriptPlaygroundBase, TypeScriptPlaygroundBaseProps } from "./TypeScriptPlaygroundBase"; +import { githubPermalinkRscConfig } from "../config/GithubPermalinkRscConfig"; + +type TypeScriptPlaygroundRscProps = Omit & { playgroundUrl: string }; + +export async function TypeScriptPlaygroundRsc(props: TypeScriptPlaygroundRscProps) { + const { playgroundUrl } = props; + const getTypeScriptPlaygroundFn = githubPermalinkRscConfig.getTypeScriptPlaygroundFn(); + const githubToken = githubPermalinkRscConfig.getGithubToken(); + const onError = githubPermalinkRscConfig.getOnError(); + + const data = await getTypeScriptPlaygroundFn(playgroundUrl, githubToken, onError); + + return +} diff --git a/src/library/config/BaseConfiguration.ts b/src/library/config/BaseConfiguration.ts index f75651f..b451d77 100644 --- a/src/library/config/BaseConfiguration.ts +++ b/src/library/config/BaseConfiguration.ts @@ -1,5 +1,6 @@ import { defaultGetPermalinkFn } from "./defaultFunctions"; import { defaultGetIssueFn } from "./defaultFunctions"; +import { defaultGetTypeScriptPlaygroundFn } from "./defaultFunctions"; export type BaseConfiguration = { @@ -11,6 +12,9 @@ export type BaseConfiguration = { /** Function to provide issue data payload */ getIssueFn: typeof defaultGetIssueFn; + /** Function to provide TypeScript playground data payload */ + getTypeScriptPlaygroundFn: typeof defaultGetTypeScriptPlaygroundFn; + /** * A github personal access token - will be passed to the data fetching functions */ diff --git a/src/library/config/GithubPermalinkContext.tsx b/src/library/config/GithubPermalinkContext.tsx index c9b7f57..e426ba7 100644 --- a/src/library/config/GithubPermalinkContext.tsx +++ b/src/library/config/GithubPermalinkContext.tsx @@ -3,6 +3,7 @@ import { PropsWithChildren, createContext } from "react"; import { BaseConfiguration } from "./BaseConfiguration"; import { defaultGetIssueFn } from "./defaultFunctions"; import { defaultGetPermalinkFn } from "./defaultFunctions"; +import { defaultGetTypeScriptPlaygroundFn } from "./defaultFunctions"; // Thanks ChatGPT export type GithubPermalinkUrlInfo = { @@ -56,6 +57,17 @@ export type GithubIssueLinkDataResponse = { } } | ErrorResponses; +export type TypeScriptPlaygroundSuccessData = { + lines: Array; + startLine?: number; + startColumn?: number; + endLine?: number; + endColumn?: number; + status: "ok"; +} + +export type TypeScriptPlaygroundDataResponse = TypeScriptPlaygroundSuccessData | ErrorResponses; + @@ -63,12 +75,14 @@ export type GithubIssueLinkDataResponse = { export const GithubPermalinkContext = createContext({ getDataFn: defaultGetPermalinkFn, getIssueFn: defaultGetIssueFn, + getTypeScriptPlaygroundFn: defaultGetTypeScriptPlaygroundFn, }); export function GithubPermalinkProvider(props: PropsWithChildren>) { return diff --git a/src/library/config/GithubPermalinkRscConfig.ts b/src/library/config/GithubPermalinkRscConfig.ts index 0e66ded..2a123b7 100644 --- a/src/library/config/GithubPermalinkRscConfig.ts +++ b/src/library/config/GithubPermalinkRscConfig.ts @@ -1,9 +1,10 @@ import { BaseConfiguration } from "./BaseConfiguration"; -import { defaultGetIssueFn, defaultGetPermalinkFn } from "./defaultFunctions"; +import { defaultGetIssueFn, defaultGetPermalinkFn, defaultGetTypeScriptPlaygroundFn } from "./defaultFunctions"; const defaultConfiguration = { getDataFn: defaultGetPermalinkFn, getIssueFn: defaultGetIssueFn, + getTypeScriptPlaygroundFn: defaultGetTypeScriptPlaygroundFn, }; class GithubPermalinkRscConfig { @@ -23,6 +24,10 @@ class GithubPermalinkRscConfig { return this.baseConfiguration.getIssueFn; } + public getTypeScriptPlaygroundFn() { + return this.baseConfiguration.getTypeScriptPlaygroundFn; + } + public getGithubToken() { return this.baseConfiguration.githubToken; } diff --git a/src/library/config/defaultFunctions.ts b/src/library/config/defaultFunctions.ts index a057fed..e7c1755 100644 --- a/src/library/config/defaultFunctions.ts +++ b/src/library/config/defaultFunctions.ts @@ -1,7 +1,9 @@ import { GithubIssueLinkDataResponse } from "./GithubPermalinkContext"; -import { parseGithubIssueLink, parseGithubPermalinkUrl } from "../utils/urlParsers"; +import { parseGithubIssueLink, parseGithubPermalinkUrl, parseTypeScriptPlaygroundUrl } from "../utils/urlParsers"; import { GithubPermalinkDataResponse } from "./GithubPermalinkContext"; import { ErrorResponses } from "./GithubPermalinkContext"; +import { TypeScriptPlaygroundDataResponse } from "./GithubPermalinkContext"; +import * as LZString from "lz-string"; /** * This is AI generated code from GitHub Copilot. @@ -133,3 +135,32 @@ export function handleResponse(response: Response): ErrorResponses { }; } +export async function defaultGetTypeScriptPlaygroundFn(playgroundUrl: string, _githubToken?: string, onError?: (err: unknown) => void): Promise { + try { + const config = parseTypeScriptPlaygroundUrl(playgroundUrl); + + // Decompress the code using lz-string + const decodedCode = LZString.decompressFromEncodedURIComponent(config.code); + + if (!decodedCode) { + const error = "Failed to decompress TypeScript playground code: invalid or corrupted data"; + onError?.(error); + return { status: "other-error" }; + } + + // Split the code into lines + const lines = decodedCode.split("\n"); + + return { + lines, + startLine: config.startLine, + startColumn: config.startColumn, + endLine: config.endLine, + endColumn: config.endColumn, + status: "ok" + }; + } catch (error) { + onError?.(error); + return { status: "other-error" }; + } +} diff --git a/src/library/export.ts b/src/library/export.ts index 31db22b..942a37d 100644 --- a/src/library/export.ts +++ b/src/library/export.ts @@ -3,4 +3,6 @@ export * from "./GithubPermalink/GithubPermalink"; export * from "./GithubPermalink/GithubPermalinkBase"; export * from "./GithubIssueLink/GithubIssueLink"; export * from "./GithubIssueLink/GithubIssueLinkBase" +export * from "./TypeScriptPlayground/TypeScriptPlayground"; +export * from "./TypeScriptPlayground/TypeScriptPlaygroundBase"; export * from "./config/GithubPermalinkContext"; diff --git a/src/library/rsc.ts b/src/library/rsc.ts index ce35b3d..b565007 100644 --- a/src/library/rsc.ts +++ b/src/library/rsc.ts @@ -1,3 +1,4 @@ export * from "./GithubPermalink/GithubPermalinkRsc"; export * from "./config/GithubPermalinkRscConfig"; -export * from "./GithubIssueLink/GithubIssueLinkRsc"; \ No newline at end of file +export * from "./GithubIssueLink/GithubIssueLinkRsc"; +export * from "./TypeScriptPlayground/TypeScriptPlaygroundRsc"; \ No newline at end of file diff --git a/src/library/utils/urlParsers.test.ts b/src/library/utils/urlParsers.test.ts index b8376ba..0e9bcf7 100644 --- a/src/library/utils/urlParsers.test.ts +++ b/src/library/utils/urlParsers.test.ts @@ -1,5 +1,5 @@ import { expect, test, it, describe } from 'vitest' -import { parseGithubPermalinkUrl } from "./urlParsers"; +import { parseGithubPermalinkUrl, parseTypeScriptPlaygroundUrl } from "./urlParsers"; describe(parseGithubPermalinkUrl, () => { it("behaves correctly for correct urls", () => { @@ -32,3 +32,45 @@ describe(parseGithubPermalinkUrl, () => { }) }) }); + +describe(parseTypeScriptPlaygroundUrl, () => { + it("parses TypeScript playground URL with line numbers", () => { + expect( + parseTypeScriptPlaygroundUrl( + "https://www.typescriptlang.org/play/?ssl=44&ssc=1&pln=8&pc=1#code/C4TwDgpgBASg9gdwCIENgqgXlhAxnAJwBMAeAZ2AIEsA7AcwBooBXGgaxsRoD4oAyKAG8oVIgC4oFavSgBfANwAoRaEhQAwnAA2zALY1U6EgBV4yNBggAPYBBpEysRIZRNjAaQggo12-cdsXnAAZlCmzha82IKKUHFQgSASHl6x8QR2RBAELhIAFABuKDoQyWYuANopIAC6AJRYvFK0dEqyysGsuMBUcDRQYARwuBBkZMYoAEZaECblFj42mY7z6G6e3r7LCUGh4ebo3HlDCBIAggQEKCBzEYdM+Dr655fXJJpPBha3B65hG9xuA0Yop2spBsNRuMpjM8mk4hUYvFkSJxFAAEQARgATABmdEMeEolASTGElHxSYSJEUlFWUnk2nIpJQbFE5HtDk1JiI9nxRISdEoAl8uIZezZXJQQrFZgQBqYXg0plxAD0qulYBQV10EFsBAaRRKEhoekm2SgAB8hKKUeq6SazdklCr4vbmY7dOaCC7XVB7Zy-RlgMwCP0AAYAEkERrlsnDvopgbiskZcWVKIFGMmIqZ4qyOQs+Vj8saNttyPteS1Or12UNstKUFNXot1ozTPd8XpzadPorbo1Ht7rf7fv9GuTKvVAAUhpACKAoAByKzLqBEOCjZtwYCLKgUKB9KCqaDLlveq1CKA9i-OqAsu8+uTLgB0A7FetDEejJdfVlTX9G1fEB40TFEp1TRR6iUZRlCAA" + ) + ).toEqual({ + code: "C4TwDgpgBASg9gdwCIENgqgXlhAxnAJwBMAeAZ2AIEsA7AcwBooBXGgaxsRoD4oAyKAG8oVIgC4oFavSgBfANwAoRaEhQAwnAA2zALY1U6EgBV4yNBggAPYBBpEysRIZRNjAaQggo12-cdsXnAAZlCmzha82IKKUHFQgSASHl6x8QR2RBAELhIAFABuKDoQyWYuANopIAC6AJRYvFK0dEqyysGsuMBUcDRQYARwuBBkZMYoAEZaECblFj42mY7z6G6e3r7LCUGh4ebo3HlDCBIAggQEKCBzEYdM+Dr655fXJJpPBha3B65hG9xuA0Yop2spBsNRuMpjM8mk4hUYvFkSJxFAAEQARgATABmdEMeEolASTGElHxSYSJEUlFWUnk2nIpJQbFE5HtDk1JiI9nxRISdEoAl8uIZezZXJQQrFZgQBqYXg0plxAD0qulYBQV10EFsBAaRRKEhoekm2SgAB8hKKUeq6SazdklCr4vbmY7dOaCC7XVB7Zy-RlgMwCP0AAYAEkERrlsnDvopgbiskZcWVKIFGMmIqZ4qyOQs+Vj8saNttyPteS1Or12UNstKUFNXot1ozTPd8XpzadPorbo1Ht7rf7fv9GuTKvVAAUhpACKAoAByKzLqBEOCjZtwYCLKgUKB9KCqaDLlveq1CKA9i-OqAsu8+uTLgB0A7FetDEejJdfVlTX9G1fEB40TFEp1TRR6iUZRlCAA", + startLine: 44, + startColumn: 1, + endLine: 8, + endColumn: 1, + }); + }); + + it("parses TypeScript playground URL without line numbers", () => { + expect( + parseTypeScriptPlaygroundUrl( + "https://www.typescriptlang.org/play/#code/PTAEAEFMCdoe2gZwFygEwGY" + ) + ).toEqual({ + code: "PTAEAEFMCdoe2gZwFygEwGY", + startLine: undefined, + startColumn: undefined, + endLine: undefined, + endColumn: undefined, + }); + }); + + it("throws error for invalid TypeScript playground URL", () => { + expect(() => parseTypeScriptPlaygroundUrl( + "https://example.com/play/#code/abc123" + )).toThrow("Invalid TypeScript playground URL"); + }); + + it("throws error for TypeScript playground URL without code", () => { + expect(() => parseTypeScriptPlaygroundUrl( + "https://www.typescriptlang.org/play/" + )).toThrow("No code found in TypeScript playground URL"); + }); +}); diff --git a/src/library/utils/urlParsers.ts b/src/library/utils/urlParsers.ts index 34f203b..3910dd2 100644 --- a/src/library/utils/urlParsers.ts +++ b/src/library/utils/urlParsers.ts @@ -36,3 +36,46 @@ export function parseGithubIssueLink(url: string): { owner: string, repo: string throw new Error("Invalid issue link URL"); } } + +export type TypeScriptPlaygroundUrlInfo = { + code: string; + startLine?: number; + startColumn?: number; + endLine?: number; + endColumn?: number; +}; + +export function parseTypeScriptPlaygroundUrl(playgroundUrl: string): TypeScriptPlaygroundUrlInfo { + // TypeScript playground URL format: + // https://www.typescriptlang.org/play/?ssl=44&ssc=1&pln=8&pc=1#code/{compressed_code} + + const url = new URL(playgroundUrl); + + // Validate it's a TypeScript playground URL + if ((url.hostname !== 'www.typescriptlang.org' && url.hostname !== 'typescriptlang.org') || !url.pathname.includes('/play')) { + throw new Error("Invalid TypeScript playground URL"); + } + + // Extract code from hash + const codeMatch = url.hash.match(/#code\/(.*)/); + if (!codeMatch) { + throw new Error("No code found in TypeScript playground URL"); + } + + const compressedCode = codeMatch[1]; + + // Parse query parameters for line/column information + const params = new URLSearchParams(url.search); + const ssl = params.get('ssl'); // start line + const ssc = params.get('ssc'); // start column + const pln = params.get('pln'); // panel line (end line) + const pc = params.get('pc'); // panel column (end column) + + return { + code: compressedCode, + startLine: ssl ? parseInt(ssl, 10) : undefined, + startColumn: ssc ? parseInt(ssc, 10) : undefined, + endLine: pln ? parseInt(pln, 10) : undefined, + endColumn: pc ? parseInt(pc, 10) : undefined, + }; +}