diff --git a/config.ts b/config.ts index 8e8a7fb..3e06960 100644 --- a/config.ts +++ b/config.ts @@ -116,6 +116,10 @@ const config: Config = { label: 'Use the Faucet to Get Testnet Tokens', path: '/content/how-to/use-the-faucet.html' }, + { + label: 'Deploy a Contract to a Topos Subnet', + path: '/content/how-to/deploy-a-contract-to-topos.html' + } ] }, { diff --git a/content/how-to/deploy-a-contract-to-topos.md b/content/how-to/deploy-a-contract-to-topos.md new file mode 100644 index 0000000..7b91cd6 --- /dev/null +++ b/content/how-to/deploy-a-contract-to-topos.md @@ -0,0 +1,152 @@ +--- +title: Deploy a Contract to Topos (or Incal) +description: Learn how to deploy a smart contract to a subnet in the Topis ecosystem, such as the Topos or Incal subnets. +--- + +# Deploy a Contract to a Topos Subnet + +The Topos Ecosystem consists of several subnets, which are generally EVM (Ethereum Virtual Machine) compatible blockchains. Although Topos encompasses other components, such as [Certificate](/content/module-1/4-protocol.html#certificates) creation, the [TCE](/content/module-1/4-protocol.html#transmission-control-engine-tce-), and [ZK (Zero-Knowledge) proofs](/content/module-1/2-why-topos.html#zkps-in-general), the process of deploying contracts to one of its subnets is both straightforward and predictable. + + [Solidity](https://docs.soliditylang.org/en/latest) is the preferred programming language for developing smart contracts on the EVM. It's a compiled, object-oriented language designed for crafting programs, called _smart contracts_, that execute on the EVM. If you're unfamiliar with Solidity, we recommend familiarizing yourself with the basics of this language by exploring an [introductory guide](https://docs.soliditylang.org/en/latest/introduction-to-smart-contracts.html) before proceeding further. + +The essential steps for deploying a contract to an EVM are as follows: + +1. Compose the contract using Solidity. +1. Test your contract thoroughly prior to deploying it on the live network to ensure it operates as expected. +1. Upload the compiled version of your contract to an RPC (Remote Procedure Call) endpoint specific to the network you wish to deploy on. +1. Cover the gas fee required for the deployment of the contract. +1. Begin interacting with your deployed contract. + + +Several popular toolchains are available to assist with the development, testing, and deployment of smart contracts. Here are a few notable ones: + +1. [Hardhat](https://hardhat.org/): Hardhat is an open-source toolchain, developed in TypeScript, offering a comprehensive environment for smart contract development, testing, and deployment. +2. [Foundry](https://book.getfoundry.sh/): Foundry, another open-source toolkit written in Rust, supports smart contract development, testing, and deployment. +3. [Remix IDE](https://remix-project.org/): Remix provides a suite of tools in a complete, GUI-based environment for smart contract work. It features not just a standalone IDE and a VSCode plugin but also a fully functional development environment accessible directly within your web browser. + +In this guide, we'll demonstrate how to deploy a simple contract to the Topos testnet using the [Remix IDE](https://remix.ethereum.org) to write, compile, and deploy the contract. Remix IDE is particularly useful for those new to Solidity or conducting simple contract experiments. It simplifies many deployment complexities, allowing you to concentrate on writing, testing, and deploying your code. + +## The Contract + +If you're already familiar with Solidity, you may choose to skip ahead to [Deploy to Topos or Incal](#deploying-to-topos-or-incal-). However, if you're new to Solidity, let's begin with crafting a simple contract. + +Before deploying, you first need to create your contract. At the very least, a Solidity contract should start with a license declaration at the top, followed by a ``pragma` statement specifying the compatible Solidity version(s), and then the contract itself. Below is an example of a minimal contract. This contract can be compiled and deployed, although it doesn't perform any specific function. + + + +[Launch a Remix IDE with this code](https://remix.ethereum.org/topos-protocol/example-code-depot/blob/main/examples/docs.topos.technology/tutorials/deploy-a-contract-on-topos/memory-1.sol) + + +However, we aim to create a contract that offers interactive capabilities and possesses a simple functionality once deployed. Therefore, let's enhance our contract by introducing storage for a `string` variable and adding a function that enables us to modify the data stored in this string. + + + +[Launch a Remix IDE with this code](https://remix.ethereum.org/topos-protocol/example-code-depot/blob/main/examples/docs.topos.technology/tutorials/deploy-a-contract-on-topos/memory-2.sol) + +With these modifications, our contract now has functionality. By clicking on the link provided above to the Remix IDE, you'll be able to open and edit this code directly in the IDE's editor. + + +![Remix IDE Compilation Successful](/images/remix-compilation-successful.png) + + +In the Remix IDE, as illustrated in the image, you'll notice a green checkmark on the left side. Hovering over it will display a tooltip indicating successful compilation. This signifies that your contract is ready for deployment! + +However, before deploying your contract to a live network, including a testnet, it's prudent to conduct some basic testing. Below the icon with the green checkmark, you'll find another icon. Hovering over this will reveal the tooltip `Deploy & run transactions`. Clicking on this icon will open the deployment panel. + + +![Remix IDE Deploy Icon](/images/remix-deploy-icon.png) + + +The panel that comes up will look similar to this: + + +![Remix IDE Deploy Icon](/images/remix-deploy-panel.png) + + +Ensure `Remix VM (Shanghai)` is selected by clicking on the dropdown menu under _ENVIRONMENT_. This selection refers to an in-browser Ethereum Virtual Machine (EVM) managed by the IDE, ideal for basic testing and debugging purposes. Using this environment ensures that your contract remains within your browser. + +To deploy your contract to this in-browser EVM, click the orange ``Deploy` button. + +Upon successful deployment, the bottom of your IDE window will display a confirmation or an update reflecting the action. + + +![Remix IDE Deploy Success](/images/remix-ide-deploy-success.png) + + +And at the bottom of the _Deploy & Run Transactions_ panel, you will see something similar to this: + + +![Remix IDE Deploy Success](/images/remix-deployed-contracts.png) + + +Click on the line that reads `Memory at 0X...` + + +![Remix IDE Interact with Contract](/images/remix-interact-with-contract.png) + + +Entering text into the input box beside the orange store button and pressing it will result in a new line with a green checkmark at the bottom of your IDE window. This signifies a successful transaction, indicating that your contract executed and stored the string. + +However, unrestricted string storage in Solidity poses a risk due to the potential for strings of any size. In a production network scenario, submitting a large string to this contract could lead to prohibitive gas costs. To address this, we'll update the contract to only allow strings shorter than 256 bytes, thereby preventing excessive gas consumption. + + + +[Launch a Remix IDE with this code](https://remix.ethereum.org/topos-protocol/example-code-depot/blob/main/examples/docs.topos.technology/tutorials/deploy-a-contract-on-topos/memory-3.sol) + +In Solidity contracts, the `require` statement serves to verify conditions, reverting the transaction if the specified condition isn't met. This mechanism ensures that certain prerequisites are satisfied before proceeding with further code execution. In our scenario, if the string exceeds 256 bytes in length, the contract halts execution to prevent further action. + +Having implemented this safety measure, we'll now introduce a line to the contract enabling the retrieval of the stored data. + + + +[Launch a Remix IDE with this code](https://remix.ethereum.org/topos-protocol/example-code-depot/blob/main/examples/docs.topos.technology/tutorials/deploy-a-contract-on-topos/memory.sol) + +This contract is now complete. If you redeploy it into the Remix internal VM using the steps outlined earlier, you will be able to store arbitrary short strings and then retrieve them. + +## Deploying To Topos (or Incal) + +Now that you have verified your contract's functionality, you're prepared to deploy it to a live network. + +Deploying a contract on a live network requires you to possess tokens on that network to cover gas fees. For the Topos and Incal subnets, a [Faucet](https://faucet.testnet-1.topos.technology/) is available to supply you with tokens. If this is your first time using the faucet, it's advisable to spend a few minutes going through the guide titled [Use the Faucet to Get Testnet Tokens](/content/how-to/use-the-testnet.html). + +After securing your testnet tokens, you should also have connected your MetaMask wallet to both the Topos and Incal subnets, positioning you to deploy your contract to a live network. + +Within the _ENVIRONMENT_ dropdown menu, you'll find an option labeled `Injected Provider - MetaMask`. Please select this option. + + +![Remix IDE Injected Provider](/images/remix-injected-provider.png) + + +This setting instructs Remix to deploy your contract to the network to which your MetaMask wallet is presently connected. + +However, if you click the Deploy button immediately, you might not achieve the expected results. As of the current date (6 February 2024), Remix IDE defaults to compiling contracts with the latest version of the Solidity compiler, known as `cancun`. Yet, the EVM implementations running on the Topos and Incal subnets lag slightly behind in supporting the newest Solidity compiler versions, lacking compatibility with some of the opcodes these versions utilize. Therefore, you must recompile your contract using a compiler version supported by the Topos and Incal subnets before deployment. + +Navigate to the `Solidity Compiler` icon. In the panel that appears, titled _SOLIDITY COMPILER_, you'll find an Advanced Configurations section. Click on this to expand it. + + +![Remix IDE Advanced Configurations](/images/remix-advanced-configuration-paris.png) + + +In this section, locate the dropdown menu for _EVM VERSION_ and choose `paris`. + +Following your selection, the IDE will automatically recompile your contract. You can then return to the _Deploy & Run Transactions_ panel and click the `Deploy` button. + +Upon doing so, the MetaMask extension will prompt you with a dialog, requesting confirmation for the transaction. This is because deploying a contract incurs a gas fee, albeit a small one. + + +![MetaMask Confirm Deployment](/images/remix-metamask-deploy-confirmation.png) + + +After confirming the transaction, your Remix IDE should show a transaction record that looks something like this: + + +![Remix IDE Transaction Record](/images/remix-deployed-contract-transaction.png) + + +Congratulations! You've successfully deployed a contract to Topos (or Incal). You're now set to interact with your deployed contract—sending data to be stored and later retrieving it. A noticeable difference from your experience with the _Remix VM_ is that each time you send a string to be stored, MetaMask will prompt you to confirm the transaction. Similar to deploying a contract, transactions that store data on the blockchain require gas to cover the function call and data storage costs. However, calling the `recall` function to retrieve the stored data is free. + +## Summary and Wrapping Up + +This guide has taken you through the steps of deploying a simple contract to the Topos testnet, utilizing the Remix IDE for writing, compiling, and deploying the contract. We started with testing the contract using the IDE's in-browser EVM and concluded with deploying the contract to a live testnet subnet. + +A key takeaway when deploying contracts to the Topos or Incal subnets is to ensure your contract is compiled with the `paris` version of the Solidity compiler, given the current compatibility requirements. \ No newline at end of file diff --git a/content/how-to/images/remix-advanced-configuration-paris.png b/content/how-to/images/remix-advanced-configuration-paris.png new file mode 100644 index 0000000..c602489 Binary files /dev/null and b/content/how-to/images/remix-advanced-configuration-paris.png differ diff --git a/content/how-to/images/remix-compilation-successful.png b/content/how-to/images/remix-compilation-successful.png new file mode 100644 index 0000000..4a6cb30 Binary files /dev/null and b/content/how-to/images/remix-compilation-successful.png differ diff --git a/content/how-to/images/remix-deploy-icon.png b/content/how-to/images/remix-deploy-icon.png new file mode 100644 index 0000000..b9abd9b Binary files /dev/null and b/content/how-to/images/remix-deploy-icon.png differ diff --git a/content/how-to/images/remix-deploy-panel.png b/content/how-to/images/remix-deploy-panel.png new file mode 100644 index 0000000..379fdae Binary files /dev/null and b/content/how-to/images/remix-deploy-panel.png differ diff --git a/content/how-to/images/remix-deployed-contract-transaction.png b/content/how-to/images/remix-deployed-contract-transaction.png new file mode 100644 index 0000000..33517c5 Binary files /dev/null and b/content/how-to/images/remix-deployed-contract-transaction.png differ diff --git a/content/how-to/images/remix-deployed-contracts.png b/content/how-to/images/remix-deployed-contracts.png new file mode 100644 index 0000000..bd8f5f3 Binary files /dev/null and b/content/how-to/images/remix-deployed-contracts.png differ diff --git a/content/how-to/images/remix-ide-deploy-success.png b/content/how-to/images/remix-ide-deploy-success.png new file mode 100644 index 0000000..e0b01da Binary files /dev/null and b/content/how-to/images/remix-ide-deploy-success.png differ diff --git a/content/how-to/images/remix-injected-provider.png b/content/how-to/images/remix-injected-provider.png new file mode 100644 index 0000000..20ad83f Binary files /dev/null and b/content/how-to/images/remix-injected-provider.png differ diff --git a/content/how-to/images/remix-interact-with-contract.png b/content/how-to/images/remix-interact-with-contract.png new file mode 100644 index 0000000..5a05e78 Binary files /dev/null and b/content/how-to/images/remix-interact-with-contract.png differ diff --git a/content/how-to/images/remix-metamask-deploy-confirmation.png b/content/how-to/images/remix-metamask-deploy-confirmation.png new file mode 100644 index 0000000..273c54a Binary files /dev/null and b/content/how-to/images/remix-metamask-deploy-confirmation.png differ diff --git a/features-test/index.md b/features-test/index.md index 3aa84e4..5e73b60 100644 --- a/features-test/index.md +++ b/features-test/index.md @@ -189,6 +189,30 @@ for line in fileinput.input(inplace=1): ``` + + +--- + + + +``` +$ topos-zkevm-demo execute +> demo +> hardhat run scripts/demo.ts + + +Contract deployed at: 0x512d5c545fa66BaaA187020381876e1E368b5A08 + +Deployment transaction: 0x602540bfd101d1b02b160fe1fd84cfdb8b0fa35687fc5adc56592b931174c204 + +Ketchup transaction: 0x785102ca9881b284588452cd90685d2c713cf61f6e4f3fcc8451bb6f2a571130 (inserted in block 4) + +Mustard transaction: 0x5d98aba30400f5f0cc9c0f2d34f9f4280ec1fca88b177b3c2251ad1ea31a9af3 (inserted in block 4) +``` + + + + diff --git a/githubToken.ts b/githubToken.ts new file mode 100644 index 0000000..bde0a78 --- /dev/null +++ b/githubToken.ts @@ -0,0 +1 @@ +export const githubToken = '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' diff --git a/package-lock.json b/package-lock.json index 98ba9b6..fb46837 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "jimp": "^0.22.10", "jsx-runtime": "^1.2.0", "katex": "^0.13.24", + "prism-react-renderer": "^2.3.1", "prismjs": "^1.29.0", "react": "^18.2.0", "react-collapsed": "^4.1.2", @@ -5050,6 +5051,11 @@ "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-5.0.3.tgz", "integrity": "sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==" }, + "node_modules/@types/prismjs": { + "version": "1.26.3", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.3.tgz", + "integrity": "sha512-A0D0aTXvjlqJ5ZILMz3rNfDBOx9hHxLZYv2by47Sm/pqW35zzjusrZTryatjN/Rf8Us2gZrJD+KeHbUSTux1Cw==" + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -7194,7 +7200,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz", "integrity": "sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==", - "dev": true, "engines": { "node": ">=6" } @@ -20449,6 +20454,18 @@ "renderkid": "^2.0.4" } }, + "node_modules/prism-react-renderer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.3.1.tgz", + "integrity": "sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/prismjs": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.29.0.tgz", diff --git a/package.json b/package.json index 9dff363..8d4a6e0 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "jimp": "^0.22.10", "jsx-runtime": "^1.2.0", "katex": "^0.13.24", + "prism-react-renderer": "^2.3.1", "prismjs": "^1.29.0", "react": "^18.2.0", "react-collapsed": "^4.1.2", diff --git a/src/components/GitHubCodeBlock.tsx b/src/components/GitHubCodeBlock.tsx new file mode 100644 index 0000000..9a56cc8 --- /dev/null +++ b/src/components/GitHubCodeBlock.tsx @@ -0,0 +1,348 @@ +import React, { useState, useEffect } from 'react'; +import { Highlight, Prism, themes } from 'prism-react-renderer'; +import rangeParser from 'parse-numeric-range'; +import { twMerge } from 'tailwind-merge'; +import { githubToken } from '../../githubToken'; + +(typeof global !== 'undefined' ? global : window).Prism = Prism; + +const languages = ['solidity', 'bash']; + +const asyncImport = async (language) => { + await import(`prismjs/components/prism-${language}`); +}; + +languages.forEach((language) => { + asyncImport(language); +}); + +interface GitHubCodeBlockProps { + title: string; // The title of the code block. + language: string; // e.g. solidity, bash, rust, etc. + org: string; // The GitHub organization name. This is optional if code will not be fetched from GitHub. + repo: string; // The GitHub repository name. This is optional if code will not be fetched from GitHub. + tag: string; // The GitHub tag/branch name. This is optional if code will not be fetched from GitHub. + path: string; // The path to the file in the GitHub repository. This is optional if code will not be fetched from GitHub. + lines: string; // The lines from the fetched file to display. '1..17', '3-9, 18', etc. + highlights: string; // The lines from the fetched file to highlight. '3..5', '6, 9, 11', etc. + nofetch: boolean; // If true, the code will not be fetched from GitHub. + link: string; // The link to display. This overrides the default, which is to construct it from the github information. + nocopy: string; // An optional line or range of lines that will not be copied to the clipboard when the copy button is pressed. + copytrim: string; // An optional regular expression that will be used to trim the code before it + separator: string; // A line or lines that will be followed by a visible separator line. + nolinenumbers: boolean; // If true, line numbers will not be displayed. +} + +const calculateLines = (raw) => { + const lineNumbers = rangeParser(raw); + if (lineNumbers) { + return (index) => lineNumbers.includes(index + 1); + } else { + return () => false; + } +}; + +const trimCopiedCode = (code, separators_array, copytrim) => { + if (copytrim) { + const re = new RegExp(copytrim, 'g'); + code = code.replace(re, ''); + } + + if (separators_array(0)) { + code = code.split('\n').slice(0, separators_array(0)); + } + + return code; +}; + +const copyToClipboard = (str, separators_array, copytrim) => { + const codeToCopy = trimCopiedCode(str, separators_array, copytrim); + if (navigator.clipboard) { + navigator.clipboard.writeText(codeToCopy).then( + function () { + console.log('Copying to clipboard was successful!'); + }, + function (err) { + console.error('Could not copy text: ', err); + } + ); + } else if (window.clipboardData) { + // Internet Explorer + window.clipboardData.setData('Text', codeToCopy); + } +}; + +const assembleContentUrl = ( + org, + repo, + tag, + path, + firstLine = 0, + lastLine = 9999 +) => { + if (firstLine > 0) { + return `https://github.com/${org}/${repo}/blob/${tag}/${path}#L${firstLine}-L${lastLine}`; + } else { + return `https://github.com/${org}/${repo}/blob/${tag}/${path}`; + } +}; + +const firstAndLastLines = (code, lines) => { + if (lines === '..') { + lines = false; + } + + let firstLine = 0; + let lastLine = 0; + + if (lines) { + const lineNumbers = rangeParser(lines); + if (lineNumbers[0] > 0) { + firstLine = lineNumbers[0] - 1; + if (firstLine < 0) { + firstLine = 0; + } + } else { + firstLine = 0; + } + if (lineNumbers.slice(-1) > 0 && lineNumbers.slice(-1) > firstLine) { + lastLine = lineNumbers.slice(-1) - 1; + if (lastLine > code.split('\n').length - 1) { + lastLine = code.split('\n').length - 1; + } + } else { + lastLine = code.split('\n').length - 1; + } + } else { + firstLine = 0; + lastLine = code.split('\n').length - 1; + } + + return [firstLine, lastLine]; +}; + +const trimCode = (code, lines) => { + const [firstLine, lastLine] = firstAndLastLines(code, lines); + + return code + .split('\n') + .slice(firstLine, lastLine + 1) + .join('\n'); +}; + +export const GitHubCodeBlock: React.FC< + PropsWithChildren +> = ({ + children, + title, + language = 'text', + org, + repo, + tag = 'main', + path, + lines = '', + highlights = '', + nofetch = false, + link = null, + nocopy = '', + copytrim = '', + separator = '', + nolinenumbers = false, +}) => { + const [isCopied, setIsCopied] = React.useState(false); + const className = `language-${language}`; + const highlights_array = calculateLines(highlights); + const separators_array = calculateLines(separator); + + const [firstLine, setFirstLine] = useState(0); + const [lastLine, setLastLine] = useState(0); + const [contentUrl, setContentUrl] = useState( + assembleContentUrl(org, repo, tag, path) + ); + const childrenCode = + children?.props?.dangerouslySetInnerHTML?.__html + ?.replace( + /^
/,
+        ''
+      )
+      .replace(/<\/code><\/pre><\/div>$/, '') || '';
+
+  const [code, setCode] = useState(
+    !nofetch && org && repo && path ? '' : childrenCode
+  );
+
+  const baseUrl = assembleContentUrl(org, repo, tag, path);
+
+  useEffect(() => {
+    console.log('useEffect() called');
+
+    const doFetch = async (url, attributes) => {
+      console.log('doFetch() called');
+      console.log(`url: ${url}`);
+      console.log(`attributes: ${JSON.stringify(attributes)}`);
+      return await fetch(url, attributes)
+        .then((response) => response.json())
+        .then((data) => {
+          console.log('retrieved....')
+          console.log(data);
+          if (data?.message?.startsWith('API rate limit exceeded')) {
+            return undefined;
+          } else if (data.message == 'Not Found') {
+            return false;
+          } else {
+            return data;
+          }
+        });
+    };
+
+    console.log('doFetch() defined');
+
+    const fetchCode = async () => {
+      if (!nofetch && org && repo && path) {
+        let attributes = {
+          headers: {},
+        };
+        let number_of_attempts = 0;
+        let finished = false;
+
+        while (!finished && number_of_attempts < 2) {
+          if (number_of_attempts > 0) {
+            console.log('Retrying with authentication...');
+            attributes.headers['Authorization'] = `token ${githubToken}`;
+            attributes.headers['X-GitHub-Api-Version'] = '2022-11-28';
+          }
+
+          console.log(
+            `fetching with attributes: ${JSON.stringify(attributes)}`
+          );
+          const url = `https://api.github.com/repos/${org}/${repo}/contents/${path}?ref=${tag}`;
+          const data = await doFetch(url, attributes);
+          console.log('got data....')
+          if (data === undefined) {
+            console.log('API rate limit exceeded');
+            finished = false;
+          } else if (data === false) {
+            finished = true;
+          } else {
+            finished = true;
+            const parsed_code = trimCode(atob(data.content), lines);
+            setCode(parsed_code);
+            if (lines != '') {
+              console.log(lines);
+              const [_firstLine, _lastLine] = firstAndLastLines(
+                parsed_code,
+                lines
+              );
+              setFirstLine(_firstLine + 1);
+              setLastLine(_lastLine + 1);
+              setContentUrl(
+                assembleContentUrl(
+                  org,
+                  repo,
+                  tag,
+                  path,
+                  _firstLine + 1,
+                  _lastLine + 1
+                )
+              );
+            }
+          }
+
+          number_of_attempts++;
+        }
+      }
+    };
+
+    fetchCode();
+  }, []);
+
+  return (
+    <>
+      {title && (
+        
{title}
)} +
+
+
{`${language}`}
+
+ {!nocopy && ( + + )} +
+
+ {code ? ( + + {({ className, style, tokens, getLineProps, getTokenProps }) => ( +
+                  {tokens.map((line, i) => (
+                    
+ {!nolinenumbers && ( +
+ {i + 1} +
+ )} + {line.map((token, key) => ( + + ))} + {separators_array(i) && ( +
+ )} +
+ ))} +
+ )} +
+ ) : ( +
+ Loading{' '} + + {baseUrl} + + ... +
+ )} +
+ +
+ + ); +}; diff --git a/src/components/layout/Layout.tsx b/src/components/layout/Layout.tsx index f753cc0..8a461e7 100644 --- a/src/components/layout/Layout.tsx +++ b/src/components/layout/Layout.tsx @@ -19,7 +19,7 @@ import { MdLink } from '../mdx/MdLink'; import { Heading } from '../Heading'; import { ZoomImage } from '../mdx/ZoomImage'; import { MDXProvider } from '@mdx-js/react'; - +import { GitHubCodeBlock } from '../GitHubCodeBlock'; import Base from '../base'; import formatSlugToImageName from '../../utils/formatSlugToImageName'; @@ -66,22 +66,23 @@ const Layout: React.FC> = ({ const components = { Accordion, AccordionItem, - HighlightBox, - YoutubePlayer, - TabGroup, - TabGroupItem, - CodeBlock, - Steps, - StepItem, - Questionnaire, - ImageCarousel, Banner, BannerImage, BannerContent, ButtonLink, Card, + CodeBlock, + GitHubCodeBlock, Grid, GridItem, + HighlightBox, + ImageCarousel, + Questionnaire, + Steps, + StepItem, + TabGroup, + TabGroupItem, + YoutubePlayer, ZoomImage, h2: (props: any) => , h3: (props: any) => , diff --git a/src/styles/components/githubcodeblock.scss b/src/styles/components/githubcodeblock.scss new file mode 100644 index 0000000..e1a4e68 --- /dev/null +++ b/src/styles/components/githubcodeblock.scss @@ -0,0 +1,114 @@ +.githubblock-title { + width: 100%; + text-align: center; + margin-top: 2rem; + margin-bottom: 0; + font-weight: 700; + line-height: 1.25; +} + +.githubblock.no-title { + margin-top: 2rem; +} + +.githubblock { + @apply bg-code-top; + border-radius: 0.5rem; + margin-bottom: 2rem; + margin-top: 0; + // padding-left: 1.5rem; + + .header { + display: flex; + position: relative; + padding-left: 1.5rem; + flex-wrap: wrap; + + .language { + background-color: #ffffff; + margin-right: 1rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + text-transform: uppercase; + border-bottom-left-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; + text-align: center; + display: flex; + justify-content: center; + align-items: center; + overflow: visible; + height: 3rem; + + @media (max-width: 1536px) { + font-size: 0.85rem; + } + } + + .spacer { + flex-grow: 1; + } + + .button { + margin-right: 1.5rem; + margin-top: 0.5rem; + margin-left: 1.0rem; + padding: 8px 12px; + background-color: #00f5c426; + border: none; + border-radius: 8px; + cursor: pointer; + color: #E2E8F0; + font-size: 14px; + line-height: 1; + overflow: visible; + height: 2rem; + + @media (max-width: 1536px) { + font-size: 0.7rem; + } + } + } + + .url { + color: #9d9d9d; + font-style: italic; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + margin: 0 1rem 0 1rem; + font-size: 0.7rem; + + a { + text-decoration: none !important; + + @media (max-width: 1536px) { + font-size: 0.6rem; + } + } + } + + .code { + // @apply bg-code-top; + @apply bg-neutral-500/10; + overflow: hidden; + border-radius: 0.5rem; + + .code-wrapper { + background-color: transparent; + float: left; + min-width: 100%; + overflow: hidden; + } + + @media (max-width: 1536px) { + font-size: 0.85rem; + } + } + + .loading { + margin: 1.5rem 0 1.5rem 1.5rem; + color: #bdbdbd; + } +} + diff --git a/src/styles/global.scss b/src/styles/global.scss index 505b6b7..b7d0790 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -15,4 +15,6 @@ @import './components/images'; @import './components/image-carousel'; @import './components/mermaid'; +@import './components/githubcodeblock'; + @import './docsearch';