Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: feat(docs): ACT-1532 - Create Pages for the Linea JSON-RPC methods dynamically #1559

Closed
wants to merge 35 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6c7815f
feat(docs): finished adding dynamic pages and sidebar
aednikanov Sep 18, 2024
d94e724
feat(docs): made improvements and added input for key
aednikanov Sep 18, 2024
95c99ab
feat(docs): added condition for showing input
aednikanov Sep 18, 2024
3a30d78
feat(docs): added condition for color theme
aednikanov Sep 18, 2024
b7ab9f5
feat(docs): added console.log
aednikanov Sep 18, 2024
21ac68c
feat(docs): added new custom field
aednikanov Sep 18, 2024
43cdfa5
Merge branch 'main' into ACT-1532-create-linea-pages-dynamically
aednikanov Sep 18, 2024
1f74dd0
feat(docs): added dependency to useEffect
aednikanov Sep 18, 2024
cfc5fb5
feat(docs): added small improvement
aednikanov Sep 18, 2024
5622de0
feat(docs): changed sidebar labels to the right ones
aednikanov Sep 19, 2024
1a201e6
feat(docs): made improvements in code
aednikanov Sep 19, 2024
095aff6
feat(docs): added needed categories to sidebar
aednikanov Sep 19, 2024
1adab32
feat(docs): optimized code
aednikanov Sep 20, 2024
dce12cf
feat(docs): added handling for category links
aednikanov Sep 23, 2024
886a775
feat(docs): made fix for ACT-1539
aednikanov Sep 23, 2024
773c6be
resolved conflicts with main
aednikanov Sep 27, 2024
3834b9d
feat(docs): improved logic
aednikanov Sep 27, 2024
68f3e53
feat(docs): extended .gitignore
aednikanov Sep 27, 2024
3910801
feat(docs): removed yarn cache
aednikanov Sep 27, 2024
a4a787b
Merge branch 'main' into ACT-1532-create-linea-pages-dynamically
TrofimovAnton85 Sep 30, 2024
8d4e94e
fix(ref): fixes for render schema description
TrofimovAnton85 Sep 30, 2024
527b34e
feat(docs): fixed two bugs
aednikanov Sep 30, 2024
b60eab7
feat(docs): fixed styles for sidebar
aednikanov Sep 30, 2024
b3b78fb
feat(docs): removed log
aednikanov Sep 30, 2024
e2bb6a8
feat(docs): added sidebar to category page
aednikanov Sep 30, 2024
ca51a6b
Merge branch 'main' into ACT-1532-create-linea-pages-dynamically
aednikanov Sep 30, 2024
f3a4e26
feat(docs): added extra checks
aednikanov Sep 30, 2024
616863a
feat(docs): added extra types
aednikanov Sep 30, 2024
8626f91
Merge branch 'main' into ACT-1532-create-linea-pages-dynamically
TrofimovAnton85 Oct 1, 2024
a8e7c0d
feat(docs): added handle errors & loading
TrofimovAnton85 Oct 1, 2024
b2e7bae
feat(docs): minor style fixes
TrofimovAnton85 Oct 1, 2024
50c9383
feat(docs): added em styles for markdown
TrofimovAnton85 Oct 1, 2024
108d571
Merge branch 'main' into ACT-1532-create-linea-pages-dynamically
TrofimovAnton85 Oct 2, 2024
b7b3921
feat(docs): added summary
TrofimovAnton85 Oct 2, 2024
59ad386
feat(docs): added list handle
TrofimovAnton85 Oct 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Note: type annotations allow type checking and IDEs autocompletion

require("dotenv").config();
const { fetchAndGenerateSidebarItems } = require("./src/helpers/index.ts");
const capitalize = require("lodash.capitalize");
const { themes } = require("prism-react-renderer");
const codeTheme = themes.dracula;
const remarkCodesandbox = require("remark-codesandbox");
Expand Down Expand Up @@ -33,6 +35,8 @@ const config = {
customFields: {
LD_CLIENT_ID: process.env.LD_CLIENT_ID,
SENTRY_KEY: process.env.SENTRY_KEY,
sidebarData: {},
dynamicData: []
},

trailingSlash: true,
Expand Down Expand Up @@ -123,6 +127,20 @@ const config = {
editUrl: "https://github.com/MetaMask/metamask-docs/edit/main/",
sidebarPath: require.resolve("./services-sidebar.js"),
breadcrumbs: false,
sidebarItemsGenerator: async function ({ defaultSidebarItemsGenerator, ...args }) {
config.customFields.sidebarData = args;
let sidebarItems = await defaultSidebarItemsGenerator(args);
const networkName = "linea";
const dynamicSidebarItems = await fetchAndGenerateSidebarItems(networkName);
config.customFields.dynamicData = dynamicSidebarItems;
const updatedItems = sidebarItems.map(item => {
if (item?.label === capitalize(networkName) && item?.items) {
item.items = [...item.items, ...dynamicSidebarItems]
}
return item;
})
return [...updatedItems];
},
},
],
[
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"dotenv": "^16.4.5",
"js-cookie": "^3.0.5",
"launchdarkly-js-client-sdk": "^3.3.0",
"lodash.capitalize": "^4.2.1",
"lodash.debounce": "^4.0.8",
"lodash.isobject": "^3.0.2",
"node-polyfill-webpack-plugin": "^2.0.1",
Expand Down
37 changes: 32 additions & 5 deletions src/components/ParserOpenRPC/RequestBox/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, { useMemo } from "react";
import React, { useMemo, useState } from "react";
import clsx from "clsx";
import CodeBlock from "@theme/CodeBlock";
import { MethodParam } from "@site/src/components/ParserOpenRPC/interfaces";
import styles from "./styles.module.css";
import global from "../global.module.css";
import { Tooltip } from "@site/src/components/ParserOpenRPC/Tooltip";
import { useLocation } from "@docusaurus/router";
import { useColorMode } from "@docusaurus/theme-common";

interface RequestBoxProps {
isMetamaskInstalled: boolean;
Expand All @@ -15,6 +17,8 @@ interface RequestBoxProps {
openModal: () => void;
submitRequest: () => void;
isMetamaskNetwork?: boolean;
customAPIKey?: string;
setCustomAPIKey?: (key: string) => void;
}

export default function RequestBox({
Expand All @@ -26,25 +30,31 @@ export default function RequestBox({
openModal,
submitRequest,
isMetamaskNetwork = false,
customAPIKey,
setCustomAPIKey,
}: RequestBoxProps) {

const location = useLocation();
const { colorMode } = useColorMode();
const isWalletReferencePage = location.pathname.includes("/wallet/reference");
const isLineaReferencePage = location.pathname.includes("/services/reference/linea");
const exampleRequest = useMemo(() => {
const preparedParams = JSON.stringify(paramsData, null, 2);
const preparedShellParams = JSON.stringify(paramsData);
const NETWORK_URL = "https://linea-mainnet.infura.io";
const API_KEY = "<YOUR-API-KEY>";
const lineaAPIKey = customAPIKey ? customAPIKey : API_KEY;
if (isMetamaskNetwork) {
return `await window.ethereum.request({\n "method": "${method}",\n "params": ${preparedParams.replace(/"([^"]+)":/g, '$1:')},\n});`;
}
return `curl ${NETWORK_URL}/v3/${API_KEY} \\\n -X POST \\\n -H "Content-Type: application/json" \\\n -d '{\n "jsonrpc": "2.0",\n "method": "${method}",\n "params": ${preparedShellParams},\n "id": 1\n }'`;
}, [method, paramsData]);
return `curl ${NETWORK_URL}/v3/${isLineaReferencePage ? lineaAPIKey : API_KEY} \\\n -X POST \\\n -H "Content-Type: application/json" \\\n -d '{\n "jsonrpc": "2.0",\n "method": "${method}",\n "params": ${preparedShellParams},\n "id": 1\n }'`;
}, [customAPIKey, method, paramsData]);

const exampleResponse = useMemo(() => {
return JSON.stringify(response, null, 2);
}, [response]);

const methodsWithRequiredWalletConnection = ["eth_accounts", "eth_sendTransaction", "personal_sign", "eth_signTypedData_v4"];
const isRunAndCustomizeRequestDisabled = methodsWithRequiredWalletConnection.includes(method) ?
const isRunAndCustomizeRequestDisabled = isWalletReferencePage && methodsWithRequiredWalletConnection.includes(method) ?
!isMetamaskInstalled :
false;

Expand All @@ -61,6 +71,23 @@ export default function RequestBox({

return (
<>
{!isWalletReferencePage ?
<div style={{ marginBottom: "20px" }}>
<label htmlFor="custom_key">Your API Key:</label>
<input
name="custom_key"
value={customAPIKey}
onChange={(e) => setCustomAPIKey(e.target.value)}
style={{
marginLeft: "10px",
padding: "8px",
border: `1px solid ${colorMode === "dark" ? "#fff" : "#848c96"}`,
borderRadius: "8px",
width: "360px"
}} />
</div> :
null
}
<div className={styles.cardWrapper}>
<div className={styles.cardHeader}>
<strong className={styles.cardHeading}>Request</strong>
Expand Down
67 changes: 50 additions & 17 deletions src/components/ParserOpenRPC/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { createContext, useMemo, useState, useContext } from "react";
import { usePluginData } from "@docusaurus/useGlobalData";
import { ResponseItem, NETWORK_NAMES } from "@site/src/plugins/plugin-json-rpc";
import { ResponseItem } from "@site/src/plugins/plugin-json-rpc";
import { NETWORK_NAMES } from "@site/src/lib/constants";
import DetailsBox from "@site/src/components/ParserOpenRPC/DetailsBox";
import InteractiveBox from "@site/src/components/ParserOpenRPC/InteractiveBox";
import { AuthBox } from "@site/src/components/ParserOpenRPC/AuthBox";
Expand All @@ -13,6 +14,7 @@ import clsx from "clsx";
import { useColorMode } from "@docusaurus/theme-common";
import { trackClickForSegment, trackInputChangeForSegment } from "@site/src/lib/segmentAnalytics";
import { MetamaskProviderContext } from "@site/src/theme/Root";
import { useLocation } from "@docusaurus/router";

interface ParserProps {
network: NETWORK_NAMES;
Expand All @@ -39,7 +41,18 @@ export default function ParserOpenRPC({ network, method, extraContent }: ParserP
const [isDrawerContentFixed, setIsDrawerContentFixed] = useState(false);
const [drawerLabel, setDrawerLabel] = useState(null);
const [isComplexTypeView, setIsComplexTypeView] = useState(false);
const [customAPIKey, setCustomAPIKey] = useState("");
const { colorMode } = useColorMode();
const location = useLocation();
const isWalletReferencePage = location.pathname.includes("/wallet/reference");
const trackAnalyticsForRequest = (response) => {
trackClickForSegment({
eventName: "Request Sent",
clickType: "Request Sent",
userExperience: "B",
...(response?.code && { responseStatus: response.code }),
});
}
const openModal = () => {
setModalOpen(true);
trackClickForSegment({
Expand Down Expand Up @@ -132,21 +145,39 @@ export default function ParserOpenRPC({ network, method, extraContent }: ParserP
};

const onSubmitRequestHandle = async () => {
if (!metaMaskProvider) return
try {
const response = await metaMaskProvider?.request({
method: method,
params: paramsData
})
setReqResult(response);
trackClickForSegment({
eventName: "Request Sent",
clickType: "Request Sent",
userExperience: "B",
...(response?.code && { responseStatus: response.code }),
});
} catch (e) {
setReqResult(e);
if (isMetamaskNetwork) {
if (!metaMaskProvider) return
try {
const response = await metaMaskProvider?.request({
method: method,
params: paramsData
})
setReqResult(response);
trackAnalyticsForRequest(response);
} catch (e) {
setReqResult(e);
}
} else {
const NETWORK_URL = "https://linea-mainnet.infura.io";
const URL = `${NETWORK_URL}/v3/${customAPIKey}`;
let params = {
method: "POST",
"Content-Type": "application/json",
body: JSON.stringify({
jsonrpc: "2.0",
method,
params: paramsData,
id: 1,
}),
};
const res = await fetch(URL, params);
if (res.ok) {
const response = await res.json();
setReqResult(response.result);
trackAnalyticsForRequest(response.result);
} else {
console.error("error");
}
}
};

Expand Down Expand Up @@ -228,7 +259,7 @@ export default function ParserOpenRPC({ network, method, extraContent }: ParserP
</div>
<div className={global.colRight}>
<div className={global.stickyCol}>
{!metaMaskAccount && <AuthBox handleConnect={metaMaskConnectHandler} />}
{isWalletReferencePage && !metaMaskAccount && <AuthBox handleConnect={metaMaskConnectHandler} />}
<RequestBox
isMetamaskInstalled={!!metaMaskAccount}
method={method}
Expand All @@ -238,6 +269,8 @@ export default function ParserOpenRPC({ network, method, extraContent }: ParserP
openModal={openModal}
submitRequest={onSubmitRequestHandle}
isMetamaskNetwork={isMetamaskNetwork}
customAPIKey={customAPIKey}
setCustomAPIKey={setCustomAPIKey}
/>
</div>
</div>
Expand Down
25 changes: 25 additions & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { RPC_NETWORK_URL } from "../lib/constants";

export const fetchAndGenerateSidebarItems = async (networkName) => {
try {
const response = await fetch(`${RPC_NETWORK_URL}/${networkName}`);
const data = await response.json();
const dynamicItems = data.methods.map((method) => ({
type: "link",
label: method.name,
href: `/services/reference/linea/json-rpc-methods-new/${method.name}`,
})).sort((a, b) => a.label.localeCompare(b.label));
return [
{
type: "category",
label: "JSON-RPC Methods NEW",
collapsed: true,
collapsible: true,
items: dynamicItems,
},
];
} catch (error) {
console.error("Error fetching methods:", error);
return [];
}
}
7 changes: 7 additions & 0 deletions src/lib/constants.js → src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,3 +449,10 @@ export const GET_OPTIONS = {
cache: "no-cache",
headers: NO_CACHE,
};

export const RPC_NETWORK_URL = "https://sot-network-methods.vercel.app/specs";

export enum NETWORK_NAMES {
linea = "linea",
metamask = "metamask",
}
121 changes: 121 additions & 0 deletions src/pages/CustomPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import Layout from "@theme/Layout";
import { NETWORK_NAMES } from "@site/src/plugins/plugin-json-rpc";
import ParserOpenRPC from "@site/src/components/ParserOpenRPC";
import React, { useEffect, useState } from "react";
import DocSidebar from '@theme/DocSidebar';
import styles from "@site/src/theme/Layout/styles.module.css"
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import * as capitalize from "lodash.capitalize"

function generateSidebarItems(docs) {
const categories = {};

docs.forEach((doc) => {
if (doc.id === 'index') {
categories['Introduction'] = {
type: 'link',
href: '/services',
label: capitalize(doc.frontMatter?.sidebar_label || doc.title),
};
return;
}

const pathParts = doc.sourceDirName.split('/');
let currentCategory = categories;
let isIndexPage = doc.id.endsWith('/index');

pathParts.forEach((part, index) => {
if (!currentCategory[part]) {
if (isIndexPage && index === pathParts.length - 2) {
currentCategory[part] = {
type: 'category',
label: capitalize(doc.frontMatter?.sidebar_label || doc.frontMatter?.title || part),
collapsed: true,
collapsible: true,
link: {
type: 'generated-index',
slug: pathParts.slice(0, index + 1).join('/')
},
items: []
};
} else {
currentCategory[part] = {
type: 'category',
label: capitalize(part),
collapsed: true,
collapsible: true,
items: []
};
}
}

if (index === pathParts.length - 1 && !isIndexPage) {
currentCategory[part].items.push({
type: 'link',
label: capitalize(doc.frontMatter?.title || doc.title),
href: `/services/${doc.id.replace(/\/index$/, '')}`,
sidebar_position: doc.frontMatter?.sidebar_position || Number.MAX_SAFE_INTEGER
});
}
currentCategory = currentCategory[part].items;
});
});

const convertToArray = (categoryObj) => {
return Object.values(categoryObj).map((category) => {
if (category.items && typeof category.items === 'object') {
category.items = convertToArray(category.items);
if (category.items.every(item => item.sidebar_position !== undefined)) {
category.items.sort((a, b) => (a.sidebar_position || Number.MAX_SAFE_INTEGER) - (b.sidebar_position || Number.MAX_SAFE_INTEGER));
}
}
return category;
});
};
return convertToArray(categories);
}

const sidebar_wrapper_classes = "theme-doc-sidebar-container docSidebarContainer_node_modules-@docusaurus-theme-classic-lib-theme-DocRoot-Layout-Sidebar-styles-module"

const CustomPage = (props) => {
const customData = props.route.customData;
const { siteConfig } = useDocusaurusContext();
const [formattedData, setFormattedData] = useState([]);

useEffect(() => {
setFormattedData(generateSidebarItems(siteConfig.customFields.sidebarData.docs).map(item => {
if (item?.label === "Reference" && item?.items) {
return {
...item,
items: item.items.map(referenceItem => {
if (referenceItem?.label === capitalize(NETWORK_NAMES.linea) && referenceItem?.items) {
return { ...referenceItem, items: [...referenceItem.items, ...siteConfig.customFields.dynamicData] };
}
return referenceItem;
})
}
}
return item;
}));
}, []);

return formattedData ? (
<Layout>
<div className={styles.pageWrapper}>
<aside className={sidebar_wrapper_classes}>
<DocSidebar sidebar={formattedData} path="" onCollapse={() => {}} isHidden={false} />
</aside>
<div className={styles.mainContainer}>
<div className={styles.contentWrapper}>
<ParserOpenRPC
network={NETWORK_NAMES.linea}
method={customData.name}
/>
</div>
</div>
</div>
</Layout>
) : null;
};

export default CustomPage;
Loading
Loading