text:
import React, { useEffect, useState } from "react";
import { Widget } from "near-social-vm";
import { useParams } from "react-router-dom";
import { useQuery } from "../hooks/useQuery";
import { useHashRouterLegacy } from "../hooks/useHashRouterLegacy";

export default function ViewPage(props) {
  useHashRouterLegacy();

  const { widgetSrc } = useParams();
  const query = useQuery();
  const [widgetProps, setWidgetProps] = useState({});

  const src = widgetSrc || props.widgets.default;
  const setWidgetSrc = props.setWidgetSrc;
  const viewSourceWidget = props.widgets.viewSource;

  useEffect(() => {
    setWidgetProps(Object.fromEntries([...query.entries()]));
  }, [query]);

  useEffect(() => {
    setTimeout(() => {
      setWidgetSrc(
        src === viewSourceWidget && query.get("src")
          ? {
              edit: query.get("src"),
              view: null,
            }
          : {
              edit: src,
              view: src,
            }
      );
    }, 1);
  }, [src, query, setWidgetSrc, viewSourceWidget]);

  return (

          <Widget key={src} src={src} props={widgetProps} />{" "}

  );
}


text:
import React, { useEffect, useState } from "react";
import { Widget } from "near-social-vm";
import { useParams } from "react-router-dom";
import { useQuery } from "../hooks/useQuery";
import { useHashRouterLegacy } from "../hooks/useHashRouterLegacy";

export default function EmbedPage(props) {
  useHashRouterLegacy();

  const { widgetSrc } = useParams();
  const query = useQuery();
  const [widgetProps, setWidgetProps] = useState({});
  const src = widgetSrc || props.widgets.default;

  useEffect(() => {
    setWidgetProps(
      [...query.entries()].reduce((props, [key, value]) => {
        props[key] = value;
        return props;
      }, {})
    );
  }, [query]);

  return (
    <div className="d-inline-block position-relative overflow-hidden">
      <Widget key={src} src={src} props={widgetProps} />{" "}
    </div>
  );
}

repo_name: NearSocial/viewer

repo_language: JavaScript

mime_type: text/html


text:
import React, { useCallback, useEffect, useMemo, useState } from "react";
import ls from "local-storage";
import prettier from "prettier";
import parserBabel from "prettier/parser-babel";
import { useHistory, useParams } from "react-router-dom";
import Editor from "@monaco-editor/react";
import {
  Widget,
  useCache,
  useNear,
  CommitButton,
  useAccountId,
} from "near-social-vm";
import { Nav, OverlayTrigger, Tooltip } from "react-bootstrap";
import RenameModal from "../components/Editor/RenameModal";
import OpenModal from "../components/Editor/OpenModal";
import {
  FileTab,
  Filetype,
  StorageDomain,
  StorageType,
  toPath,
} from "../components/Editor/FileTab";
import { useHashRouterLegacy } from "../hooks/useHashRouterLegacy";

const LsKey = "social.near:v01:";
const EditorLayoutKey = LsKey + "editorLayout:";
const WidgetPropsKey = LsKey + "widgetProps:";

const DefaultEditorCode = "return
Hello World
;";

const Tab = {
  Editor: "Editor",
  Props: "Props",
  Metadata: "Metadata",
  Widget: "Widget",
};

const Layout = {
  Tabs: "Tabs",
  Split: "Split",
};

export default function EditorPage(props) {
  useHashRouterLegacy();
  const { widgetSrc } = useParams();
  const history = useHistory();
  const setWidgetSrc = props.setWidgetSrc;

  const [loading, setLoading] = useState(false);
  const [code, setCode] = useState(undefined);
  const [path, setPath] = useState(undefined);
  const [files, setFiles] = useState(undefined);
  const [lastPath, setLastPath] = useState(undefined);
  const [showRenameModal, setShowRenameModal] = useState(false);
  const [showOpenModal, setShowOpenModal] = useState(false);
  const [allSaved, setAllSaved] = useState({});

  const [renderCode, setRenderCode] = useState(code);
  const [widgetProps, setWidgetProps] = useState(
    ls.get(WidgetPropsKey) || "{}"
  );
  const [parsedWidgetProps, setParsedWidgetProps] = useState({});
  const [propsError, setPropsError] = useState(null);
  const [metadata, setMetadata] = useState(undefined);
  const near = useNear();
  const cache = useCache();
  const accountId = useAccountId();

  const [tab, setTab] = useState(Tab.Editor);
  const [layout, setLayoutState] = useState(
    ls.get(EditorLayoutKey) || Layout.Tabs
  );

  const setLayout = useCallback(
    (layout) => {
      ls.set(EditorLayoutKey, layout);
      setLayoutState(layout);
    },
    [setLayoutState]
  );

  useEffect(() => {
    setWidgetSrc({
      edit: null,
      view: widgetSrc,
    });
  }, [widgetSrc, setWidgetSrc]);

  const updateCode = useCallback(
    (path, code) => {
      cache.localStorageSet(
        StorageDomain,
        {
          path,
          type: StorageType.Code,
        },
        {
          code,
          time: Date.now(),
        }
      );
      setCode(code);
    },
    [cache, setCode]
  );

  useEffect(() => {
    ls.set(WidgetPropsKey, widgetProps);
    try {
      const parsedWidgetProps = JSON.parse(widgetProps);
      setParsedWidgetProps(parsedWidgetProps);
      setPropsError(null);
    } catch (e) {
      setParsedWidgetProps({});
      setPropsError(e.message);
    }
  }, [widgetProps]);

  const removeFromFiles = useCallback(
    (path) => {
      path = JSON.stringify(path);
      setFiles((files) =>
        files.filter((file) => JSON.stringify(file) !== path)
      );
      setLastPath(path);
    },
    [setFiles, setLastPath]
  );

  const addToFiles = useCallback(
    (path) => {
      const jpath = JSON.stringify(path);
      setFiles((files) => {
        const newFiles = [...files];
        if (!files.find((file) => JSON.stringify(file) === jpath)) {
          newFiles.push(path);
        }
        return newFiles;
      });
      setLastPath(path);
    },
    [setFiles, setLastPath]
  );

  useEffect(() => {
    if (files && lastPath) {
      cache.localStorageSet(
        StorageDomain,
        {
          type: StorageType.Files,
        },
        { files, lastPath }
      );
    }
  }, [files, lastPath, cache]);

  const openFile = useCallback(
    (path, code) => {
      setPath(path);
      addToFiles(path);
      setMetadata(undefined);
      setRenderCode(null);
      if (code !== undefined) {
        updateCode(path, code);
      } else {
        setLoading(true);
        cache
          .asyncLocalStorageGet(StorageDomain, {
            path,
            type: StorageType.Code,
          })
          .then(({ code }) => {
            updateCode(path, code);
          })
          .finally(() => {
            setLoading(false);
          });
      }
    },
    [updateCode, addToFiles]
  );

  const updateSaved = useCallback((jp, saved) => {
    setAllSaved((allSaved) => {
      return Object.assign({}, allSaved, { [jp]: saved });
    });
  }, []);

  const loadFile = useCallback(
    (nameOrPath) => {
      if (!near) {
        return;
      }
      const widgetSrc =
        nameOrPath.indexOf("/") >= 0
          ? nameOrPath
          : `${accountId}/widget/${nameOrPath}`;
      const c = () => {
        const code = cache.socialGet(
          near,
          widgetSrc,
          false,
          undefined,
          undefined,
          c
        );
        if (code) {
          const name = widgetSrc.split("/").slice(2).join("/");
          openFile(toPath(Filetype.Widget, widgetSrc), code);
        }
      };

      c();
    },
    [accountId, openFile, toPath, near, cache]
  );

  const generateNewName = useCallback(
    (type) => {
      for (let i = 0; ; i++) {
        const name = `Draft-${i}`;
        const path = toPath(type, name);
        path.unnamed = true;
        const jPath = JSON.stringify(path);
        if (!files?.find((file) => JSON.stringify(file) === jPath)) {
          return path;
        }
      }
    },
    [toPath, files]
  );

  const createFile = useCallback(
    (type) => {
      const path = generateNewName(type);
      openFile(path, DefaultEditorCode);
    },
    [generateNewName, openFile]
  );

  const renameFile = useCallback(
    (newName, code) => {
      const newPath = toPath(path.type, newName);
      const jNewPath = JSON.stringify(newPath);
      const jPath = JSON.stringify(path);
      setFiles((files) => {
        const newFiles = files.filter(
          (file) => JSON.stringify(file) !== jNewPath
        );
        const i = newFiles.findIndex((file) => JSON.stringify(file) === jPath);
        if (i >= 0) {
          newFiles[i] = newPath;
        }
        return newFiles;
      });
      setLastPath(newPath);
      setPath(newPath);
      updateCode(newPath, code);
    },
    [path, toPath, updateCode]
  );

  useEffect(() => {
    cache
      .asyncLocalStorageGet(StorageDomain, { type: StorageType.Files })
      .then((value) => {
        const { files, lastPath } = value || {};
        setFiles(files || []);
        setLastPath(lastPath);
      });
  }, [cache]);

  useEffect(() => {
    if (!near || !files) {
      return;
    }
    if (widgetSrc) {
      if (widgetSrc === "new") {
        createFile(Filetype.Widget);
      } else {
        loadFile(widgetSrc);
      }
      history.replace(`/edit/`);
    } else if (path === undefined) {
      if (files.length === 0) {
        createFile(Filetype.Widget);
      } else {
        openFile(lastPath, undefined);
      }
    }
  }, [near, createFile, lastPath, files, path, widgetSrc, openFile, loadFile]);

  const reformat = useCallback(
    (path, code) => {
      try {
        const formattedCode = prettier.format(code, {
          parser: "babel",
          plugins: [parserBabel],
        });
        updateCode(path, formattedCode);
      } catch (e) {
        console.log(e);
      }
    },
    [updateCode]
  );

  const reformatProps = useCallback(
    (props) => {
      try {
        const formattedProps = JSON.stringify(JSON.parse(props), null, 2);
        setWidgetProps(formattedProps);
      } catch (e) {
        console.log(e);
      }
    },
    [setWidgetProps]
  );

  const closeCommitted = useCallback(
    (path, allSaved) => {
      setFiles((files) => {
        files = files.filter((file) => !allSaved[JSON.stringify(file)]);
        if (allSaved[JSON.stringify(path)]) {
          if (files.length > 0) {
            openFile(files[files.length - 1], undefined);
          } else {
            createFile(Filetype.Widget);
          }
        }
        return files;
      });
    },
    [openFile, createFile]
  );

  const layoutClass = layout === Layout.Split ? "col-lg-6" : "";

  const onLayoutChange = useCallback(
    (e) => {
      const layout = e.target.value;
      if (layout === Layout.Split && tab === Tab.Widget) {
        setTab(Tab.Editor);
      }
      setLayout(layout);
    },
    [setLayout, tab, setTab]
  );

  const widgetName = path?.name;

  const commitButton = (
    <CommitButton
      className="btn btn-primary"
      disabled={!widgetName}
      near={near}
      data={{
        widget: {
          [widgetName]: {
            "": code,
            metadata,
          },
        },
      }}
    >
      Save Widget
    </CommitButton>
  );

  const widgetPath = `${accountId}/${path?.type}/${path?.name}`;
  const jpath = JSON.stringify(path);

  return (

      <RenameModal
        key={`rename-modal-${jpath}`}
        show={showRenameModal}
        name={path?.name}
        onRename={(newName) => renameFile(newName, code)}
        onHide={() => setShowRenameModal(false)}
      />
      <OpenModal
        show={showOpenModal}
        onOpen={(newName) => loadFile(newName)}
        onNew={(newName) =>
          newName
            ? openFile(toPath(Filetype.Widget, newName), DefaultEditorCode)
            : createFile(Filetype.Widget)
        }
        onHide={() => setShowOpenModal(false)}
      />

         openFile(JSON.parse(key))}
        >
          {files?.map((p, idx) => {
            const jp = JSON.stringify(p);
            const active = jp === jpath;
            return (
              <FileTab
                key={jp}
                {...{
                  p,
                  jp,
                  idx,
                  active,
                  openFile,
                  createFile,
                  removeFromFiles,
                  files,
                  code: jp === jpath ? code : undefined,
                  updateSaved,
                }}
              />
            );
          })}

             setShowOpenModal(true)}
            >
               Add

             closeCommitted(path, allSaved)}
            >
               Close unchanged

        {props.widgets.editorComponentSearch && (

            <Widget
              src={props.widgets.editorComponentSearch}
              props={useMemo(
                () => ({
                  extraButtons: ({ widgetName, widgetPath, onHide }) => (
                    <OverlayTrigger
                      placement="auto"
                      overlay={
                        <Tooltip>
                          Open "{widgetName}" component in the editor
                        </Tooltip>
                      }
                    >
                      <button
                        className="btn btn-outline-primary"
                        onClick={(e) => {
                          e.preventDefault();
                          loadFile(widgetPath);
                          onHide && onHide();
                        }}
                      >
                        Open
                      </button>
                    </OverlayTrigger>
                  ),
                }),
                [loadFile]
              )}
            />

        )}

            <input
              type="radio"
              className="btn-check"
              name="layout-radio"
              id="layout-tabs"
              autoComplete="off"
              checked={layout === Layout.Tabs}
              onChange={onLayoutChange}
              value={Layout.Tabs}
              title={"Set layout to Tabs mode"}
            />

            <input
              type="radio"
              className="btn-check"
              name="layout-radio"
              id="layout-split"
              autoComplete="off"
              checked={layout === Layout.Split}
              value={Layout.Split}
              title={"Set layout to Split mode"}
              onChange={onLayoutChange}
            />

              <ul className={`nav nav-tabs mb-2`}>

                  <button
                    className={`nav-link ${tab === Tab.Editor ? "active" : ""}`}
                    aria-current="page"
                    onClick={() => setTab(Tab.Editor)}
                  >
                    Editor
                  </button>

                  <button
                    className={`nav-link ${tab === Tab.Props ? "active" : ""}`}
                    aria-current="page"
                    onClick={() => setTab(Tab.Props)}
                  >
                    Props
                  </button>

                {props.widgets.widgetMetadataEditor && (

                    <button
                      className={`nav-link ${
                        tab === Tab.Metadata ? "active" : ""
                      }`}
                      aria-current="page"
                      onClick={() => setTab(Tab.Metadata)}
                    >
                      Metadata
                    </button>

                )}
                {layout === Layout.Tabs && (

                    <button
                      className={`nav-link ${
                        tab === Tab.Widget ? "active" : ""
                      }`}
                      aria-current="page"
                      onClick={() => {
                        setRenderCode(code);
                        setTab(Tab.Widget);
                      }}
                    >
                      Widget Preview
                    </button>

                )}

              <div className={`${tab === Tab.Editor ? "" : "visually-hidden"}`}>

                  <Editor
                    value={code}
                    path={widgetPath}
                    defaultLanguage="javascript"
                    onChange={(code) => updateCode(path, code)}
                    wrapperProps={{
                      onBlur: () => reformat(path, code),
                    }}
                  />

                  <button
                    className="btn btn-success"
                    onClick={() => {
                      setRenderCode(code);
                      if (layout === Layout.Tabs) {
                        setTab(Tab.Widget);
                      }
                    }}
                  >
                    Render preview
                  </button>
                  {!path?.unnamed && commitButton}
                  <button
                    className={`btn ${
                      path?.unnamed ? "btn-primary" : "btn-secondary"
                    }`}
                    onClick={() => {
                      setShowRenameModal(true);
                    }}
                  >
                    Rename {path?.type}
                  </button>
                  {path && accountId && (
                    <a
                      className="btn btn-outline-primary"
                      href={`/${widgetPath}`}
                      target="_blank"
                      rel="noopener noreferrer"
                    >
                      Open Component in a new tab

                  )}

              <div className={`${tab === Tab.Props ? "" : "visually-hidden"}`}>

                  <Editor
                    value={widgetProps}
                    defaultLanguage="json"
                    onChange={(props) => setWidgetProps(props)}
                    wrapperProps={{
                      onBlur: () => reformatProps(widgetProps),
                    }}
                  />

^^ Props for debugging (in JSON)

                {propsError && (

{propsError}

                )}

                  <Widget
                    src={props.widgets.widgetMetadataEditor}
                    key={`metadata-editor-${jpath}`}
                    props={useMemo(
                      () => ({
                        widgetPath,
                        onChange: setMetadata,
                      }),
                      [widgetPath]
                    )}
                  />

{commitButton}

                    {renderCode ? (
                      <Widget
                        key={`preview-${jpath}`}
                        code={renderCode}
                        props={parsedWidgetProps}
                      />
                    ) : (
                      'Click "Render preview" button to render the widget'
                    )}

                    <Widget
                      key={`metadata-${jpath}`}
                      src={props.widgets.widgetMetadata}
                      props={useMemo(
                        () => ({ metadata, accountId, widgetName }),
                        [metadata, accountId, widgetName]
                      )}
                    />

  );
}


text:
import { useHistory } from "react-router-dom";
import React, { useCallback, useEffect } from "react";

export function useHashRouterLegacy() {
  const history = useHistory();

  const onHashChange = useCallback(
    (event) => {
      let url = event.newURL.split("#").pop() ?? "/";

      if (url[0] === "/") {
        history && history.replace(url);
      }
    },
    [history]
  );

  useEffect(() => {
    window.addEventListener("hashchange", onHashChange);

    return () => {
      window.removeEventListener("hashchange", onHashChange);
    };
  }, [onHashChange]);

  useEffect(() => {
    if (!history) {
      return;
    }
    const currentUrl = window.location.href;

    if (currentUrl.includes("#")) {
      const path = currentUrl.split("#")[1];
      history.replace(path);
    }
  }, [history]);
}


text:
import { useRef } from "react";

const safeDocument = typeof document !== "undefined" ? document : {};

/**
 * Usage:
 * const [blockScroll, allowScroll] = useScrollBlock();
 */
export default () => {
  const scrollBlocked = useRef();
  const html = safeDocument.documentElement;
  const { body } = safeDocument;

  const blockScroll = () => {
    if (!body || !body.style || scrollBlocked.current) return;

    const scrollBarWidth = window.innerWidth - html.clientWidth;
    const bodyPaddingRight =
      parseInt(
        window.getComputedStyle(body).getPropertyValue("padding-right")
      ) || 0;

    /**
     * 1. Fixes a bug in iOS and desktop Safari whereby setting
     *    `overflow: hidden` on the html/body does not prevent scrolling.
     * 2. Fixes a bug in desktop Safari where `overflowY` does not prevent
     *    scroll if an `overflow-x` style is also applied to the body.
     */
    html.style.position = "relative"; /* [1] */
    html.style.overflow = "hidden"; /* [2] */
    body.style.position = "relative"; /* [1] */
    body.style.overflow = "hidden"; /* [2] */
    body.style.paddingRight = `${bodyPaddingRight + scrollBarWidth}px`;

    scrollBlocked.current = true;
  };

  const allowScroll = () => {
    if (!body || !body.style || !scrollBlocked.current) return;

    html.style.position = "";
    html.style.overflow = "";
    body.style.position = "";
    body.style.overflow = "";
    body.style.paddingRight = "";

    scrollBlocked.current = false;
  };

  return [blockScroll, allowScroll];
};


text:
import { useLocation } from "react-router-dom";
import React from "react";

export function useQuery() {
  let { search } = useLocation();
  return React.useMemo(() => new URLSearchParams(search), [search]);
}


text:
import { singletonHook } from "react-singleton-hook";
import { useEffect, useState } from "react";
import { init, useConnectWallet } from "@web3-onboard/react";
import injectedModule from "@web3-onboard/injected-wallets";
import walletConnectModule from "@web3-onboard/walletconnect";
import ledgerModule from "@web3-onboard/ledger";
import { ethers } from "ethers";
import ls from "local-storage";
import icon from "../images/near_social_icon.svg";

const web3onboardKey = "web3-onboard:connectedWallets";

const wcV1InitOptions = {
  qrcodeModalOptions: {
    mobileLinks: ["metamask", "argent", "trust"],
  },
  connectFirstChainId: true,
};

const walletConnect = walletConnectModule(wcV1InitOptions);
const ledger = ledgerModule();
const injected = injectedModule();

// initialize Onboard
export const onboard = init({
  wallets: [injected, walletConnect, ledger],
  chains: [
    {
      id: 1,
      token: "ETH",
      label: "Ethereum Mainnet",
      rpcUrl: "https://rpc.ankr.com/eth",
    },
    {
      id: 3,
      token: "ETH",
      label: "Ropsten - Ethereum Testnet",
      rpcUrl: "https://rpc.ankr.com/eth_ropsten",
    },
    {
      id: 5,
      token: "ETH",
      label: "Goerli - Ethereum Testnet",
      rpcUrl: "https://rpc.ankr.com/eth_goerli",
    },
    {
      id: 10,
      token: "ETH",
      label: "Optimism",
      rpcUrl: "https://rpc.ankr.com/optimism",
    },
    {
      id: 420,
      token: "ETH",
      label: "Optimism Goerli Testnet",
      rpcUrl: "https://optimism-goerli.publicnode.com",
    },
    {
      id: 56,
      token: "BNB",
      label: "Binance Smart Chain Mainnet",
      rpcUrl: "https://bsc.publicnode.com",
    },
    {
      id: 97,
      token: "tBNB",
      label: "Binance Smart Chain Testnet",
      rpcUrl: "https://bsc-testnet.publicnode.com",
    },
    {
      id: 1313161554,
      token: "ETH",
      label: "Aurora Mainnet",
      rpcUrl: "https://mainnet.aurora.dev",
    },
    {
      id: 1313161555,
      token: "ETH",
      label: "Aurora Testnet",
      rpcUrl: "https://testnet.aurora.dev",
    },
    {
      id: 137,
      token: "MATIC",
      label: "Polygon Mainnet",
      rpcUrl: "https://rpc.ankr.com/polygon",
    },
    {
      id: 80001,
      token: "MATIC",
      label: "Polygon Testnet Mumbai",
      rpcUrl: "https://rpc.ankr.com/polygon_mumbai",
    },
    {
      id: 280,
      token: "ETH",
      label: "zkSync Era Testnet",
      rpcUrl: "https://testnet.era.zksync.dev",
    },
    {
      id: 324,
      token: "ETH",
      label: "zkSync Era Mainnet",
      rpcUrl: "https://zksync2-mainnet.zksync.io",
    },
    {
      id: 1101,
      token: "ETH",
      label: "Polygon zkEVM",
      rpcUrl: "https://zkevm-rpc.com",
    },
    {
      id: 1442,
      token: "ETH",
      label: "Polygon zkEVM Testnet",
      rpcUrl: "https://rpc.public.zkevm-test.net",
    },
    {
      id: 42161,
      token: "ETH",
      label: "Arbitrum One Mainnet",
      rpcUrl: "https://arb1.arbitrum.io/rpc",
    },
    {
      id: 42170,
      token: "ETH",
      label: "Arbitrum Nova",
      rpcUrl: "https://nova.arbitrum.io/rpc",
    },
    {
      id: 421613,
      token: "AGOR",
      label: "Arbitrum Goerli",
      rpcUrl: "https://goerli-rollup.arbitrum.io/rpc",
    },
    {
      id: 25,
      token: "CRO",
      label: "Cronos Mainnet Beta",
      rpcUrl: "https://evm.cronos.org",
    },
    {
      id: 338,
      token: "TCRO",
      label: "Cronos Testnet",
      rpcUrl: "https://evm-t3.cronos.org",
    },
    {
      id: 100,
      token: "XDAI",
      label: "Gnosis",
      rpcUrl: "https://rpc.ankr.com/gnosis",
    },
    {
      id: 10200,
      token: "XDAI",
      label: "Gnosis Chiado Testnet",
      rpcUrl: "https://rpc.chiadochain.net",
    },
    {
      id: 42220,
      token: "CELO",
      label: "Celo Mainnet",
      rpcUrl: "https://rpc.ankr.com/celo",
    },
    {
      id: 44787,
      token: "CELO",
      label: "Celo Alfajores Testnet",
      rpcUrl: "https://alfajores-forno.celo-testnet.org",
    },
    {
      id: 43114,
      token: "AVAX",
      label: "Avalanche C-Chain",
      rpcUrl: "https://rpc.ankr.com/avalanche",
    },
    {
      id: 43113,
      token: "AVAX",
      label: "Avalanche Fuji Testnet",
      rpcUrl: "https://rpc.ankr.com/avalanche_fuji",
    },
    {
      id: 250,
      token: "FTM",
      label: "Fantom Opera",
      rpcUrl: "https://rpc.ankr.com/fantom",
    },
    {
      id: 4002,
      token: "FTM",
      label: "Fantom Testnet",
      rpcUrl: "https://rpc.ankr.com/fantom_testnet",
    },
    {
      id: 1284,
      token: "GLMR",
      label: "Moonbeam",
      rpcUrl: "https://rpc.ankr.com/moonbeam",
    },
    {
      id: 61,
      token: "ETC",
      label: "Ethereum Classic Mainnet",
      rpcUrl: "https://etc.rivet.link",
    },
    {
      id: 84531,
      token: "ETH",
      label: "Base Goerli Testnet",
      rpcUrl: "https://goerli.base.org",
    },
    {
      id: 8453,
      token: "ETH",
      label: "Base Mainnet",
      rpcUrl: "https://mainnet.base.org",
    },
  ],
  appMetadata: {
    name: "NEAR Social",
    icon,
    description: "NEAR Social",
  },
  theme: "dark",
  containerElements: {
    // connectModal: '#near-social-navigation-bar',
    // accountCenter: "#near-social-web3-account",
  },
});

const defaultEthersProviderContext = {
  useConnectWallet,
  setChain: onboard.setChain,
};

export const useEthersProviderContext = singletonHook(
  defaultEthersProviderContext,
  () => {
    const [{ wallet }] = useConnectWallet();
    const [ethersProvider, setEthersProvider] = useState(
      defaultEthersProviderContext
    );

    useEffect(() => {
      (async () => {
        const walletsSub = onboard.state.select("wallets");
        const { unsubscribe } = walletsSub.subscribe((wallets) => {
          const connectedWallets = wallets.map(({ label }) => label);
          ls.set(web3onboardKey, connectedWallets);
        });

        const previouslyConnectedWallets = ls.get(web3onboardKey) || [];

        if (previouslyConnectedWallets) {
          // You can also auto connect "silently" and disable all onboard modals to avoid them flashing on page load
          await onboard.connectWallet({
            autoSelect: {
              label: previouslyConnectedWallets[0],
              disableModals: true,
            },
          });
        }
      })();
    }, []);

    useEffect(() => {
      setEthersProvider({
        provider: wallet?.provider,
        useConnectWallet,
        setChain: onboard.setChain,
      });
    }, [wallet]);

    return ethersProvider;
  }
);


text:
const TestnetDomains = {
  "test.near.social": true,
  "127.0.0.1": true,
};

export const NetworkId =
  window.location.hostname in TestnetDomains ? "testnet" : "mainnet";
const TestnetWidgets = {
  image: "eugenethedream/widget/Image",
  default: "eugenethedream/widget/Welcome",
  viewSource: "eugenethedream/widget/WidgetSource",
  widgetMetadataEditor: "eugenethedream/widget/WidgetMetadataEditor",
  widgetMetadata: "eugenethedream/widget/WidgetMetadata",
  profileImage: "eugenethedream/widget/ProfileImage",
  profilePage: "eugenethedream/widget/Profile",
  profileName: "eugenethedream/widget/ProfileName",
  notificationButton: "eugenethedream/widget/NotificationButton",
};

const MainnetWidgets = {
  image: "mob.near/widget/Image",
  default: "mob.near/widget/Homepage",
  viewSource: "mob.near/widget/WidgetSource",
  widgetMetadataEditor: "mob.near/widget/WidgetMetadataEditor",
  widgetMetadata: "mob.near/widget/WidgetMetadata",
  profileImage: "mob.near/widget/ProfileImage",
  notificationButton: "mob.near/widget/NotificationButton",
  profilePage: "mob.near/widget/ProfilePage",
  profileName: "patrick.near/widget/ProfileName",
  editorComponentSearch: "mob.near/widget/Editor.ComponentSearch",
  profileInlineBlock: "mob.near/widget/Profile.InlineBlock",
  viewHistory: "bozon.near/widget/WidgetHistory",
};

export const Widgets =
  NetworkId === "testnet" ? TestnetWidgets : MainnetWidgets;


text:
import { Nav } from "react-bootstrap";
import React, { useEffect, useState } from "react";
import { useAccountId, useCache, useNear } from "near-social-vm";

export const Filetype = {
  Widget: "widget",
  Module: "module",
};

export const StorageDomain = {
  page: "editor",
};

export const StorageType = {
  Code: "code",
  Files: "files",
};

export function toPath(type, nameOrPath) {
  const name =
    nameOrPath.indexOf("/") >= 0
      ? nameOrPath.split("/").slice(2).join("/")
      : nameOrPath;
  return { type, name };
}

export function FileTab(props) {
  const {
    files,
    p,
    active,
    idx,
    removeFromFiles,
    openFile,
    createFile,
    code,
    updateSaved,
  } = props;
  const cache = useCache();
  const near = useNear();
  const accountId = useAccountId();
  const [localCode, setLocalCode] = useState(null);
  const [chainCode, setChainCode] = useState(null);
  const [saved, setSaved] = useState(false);

  const jp = JSON.stringify(p);

  useEffect(() => {
    if (code !== undefined) {
      setLocalCode(code);
      return;
    }
    cache
      .asyncLocalStorageGet(StorageDomain, {
        path: p,
        type: StorageType.Code,
      })
      .then(({ code }) => {
        setLocalCode(code);
      });
  }, [code, cache, p]);

  useEffect(() => {
    const widgetSrc = `${accountId}/${p?.type}/${p?.name}`;
    const c = () => {
      const code = cache.socialGet(
        near,
        widgetSrc,
        false,
        undefined,
        undefined,
        c
      );
      setChainCode(code);
    };
    c();
  }, [cache, near, p, accountId]);

  useEffect(() => {
    const unsaved = localCode !== chainCode;
    setSaved(unsaved);
  }, [localCode, chainCode]);

  useEffect(() => {
    updateSaved && updateSaved(jp, !saved);
  }, [saved, updateSaved]);

  return (

        {p.name}
        {saved && (

        )}
        <button
          className={`btn btn-sm border-0 py-0 px-1 ms-1 rounded-circle ${
            active ? "btn-outline-light" : "btn-outline-secondary"
          }`}
          onClick={(e) => {
            e.preventDefault();
            e.stopPropagation();
            removeFromFiles(p);
            if (active) {
              if (files.length > 1) {
                openFile(files[idx - 1] || files[idx + 1]);
              } else {
                createFile(Filetype.Widget);
              }
            }
          }}
        >

        </button>

  );
}


text:
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";

export default function RenameModal(props) {
  const onHide = props.onHide;
  const name = props.name;
  const onRename = props.onRename;
  const show = props.show;

  const [newName, setNewName] = useState(name);

  return (
    <Modal centered scrollable show={show} onHide={onHide}>
      <Modal.Header closeButton>
        <Modal.Title>Rename</Modal.Title>
      </Modal.Header>
      <Modal.Body>
        <label htmlFor="rename-input" className="form-label">
          New name
        </label>
        <input
          className="form-control"
          id="rename-input"
          type="text"
          value={newName}
          onChange={(e) =>
            setNewName(e.target.value.replaceAll(/[^a-zA-Z0-9_.\-]/g, ""))
          }
        />
      </Modal.Body>
      <Modal.Footer>
        <button
          className="btn btn-success"
          disabled={!newName || newName === name}
          onClick={(e) => {
            e.preventDefault();
            onRename(newName);
            onHide();
          }}
        >
          Confirm
        </button>
        <button className="btn btn-secondary" onClick={onHide}>
          Close
        </button>
      </Modal.Footer>
    </Modal>
  );
}


text:
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";

export default function OpenModal(props) {
  const onHide = props.onHide;
  const onOpen = props.onOpen;
  const onNew = props.onNew;
  const show = props.show;

  const [widgetSrc, setWidgetSrc] = useState("");

  return (
    <Modal centered scrollable show={show} onHide={onHide}>
      <Modal.Header closeButton>
        <Modal.Title>Open widget</Modal.Title>
      </Modal.Header>
      <Modal.Body>

          Widget name (or path)

        <input
          className="form-control"
          id="widget-src-input"
          type="text"
          value={widgetSrc}
          onChange={(e) =>
            setWidgetSrc(e.target.value.replaceAll(/[^a-zA-Z0-9_.\-\/]/g, ""))
          }
        />
      </Modal.Body>
      <Modal.Footer>
        <button
          className="btn btn-success"
          disabled={!widgetSrc}
          onClick={(e) => {
            e.preventDefault();
            onOpen(widgetSrc);
            setWidgetSrc("");
            onHide();
          }}
        >
          Open
        </button>
        <button
          className="btn btn-outline-success"
          disabled={widgetSrc && widgetSrc.indexOf("/") !== -1}
          onClick={(e) => {
            e.preventDefault();
            onNew(widgetSrc);
            setWidgetSrc("");
            onHide();
          }}
        >
          Create New
        </button>
        <button className="btn btn-secondary" onClick={onHide}>
          Close
        </button>
      </Modal.Footer>
    </Modal>
  );
}


text:
import React from "react";

export function Withdraw() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        strokeWidth="0.3px"
        d="M14.9634 10.8766C15.2234 11.1366 15.2234 11.5567 14.9634 11.8167L12.4701 14.3067C12.2101 14.5701 11.79 14.5701 11.5301 14.3067L9.03676 11.8167C8.77679 11.5567 8.77679 11.1366 9.03676 10.8766C9.30016 10.6133 9.72011 10.6133 9.98008 10.8766L11.3333 12.2267V6.66667C11.3333 5.1933 12.5266 4 14 4H17.3333C18.8067 4 20 5.1933 20 6.66667V17.3333C20 18.8067 18.8067 20 17.3333 20H6.66667C5.1933 20 4 18.8067 4 17.3333V6.66667C4 5.1933 5.1933 4 6.66667 4H9.33333C9.7 4 10 4.3 10 4.66667C10 5.03333 9.7 5.33333 9.33333 5.33333H6.66667C5.93006 5.33333 5.33333 5.93006 5.33333 6.66667V17.3333C5.33333 18.0699 5.93006 18.6667 6.66667 18.6667H17.3333C18.0699 18.6667 18.6667 18.0699 18.6667 17.3333V6.66667C18.6667 5.93006 18.0699 5.33333 17.3333 5.33333H14C13.2634 5.33333 12.6667 5.93006 12.6667 6.66667V12.2267L14.0199 10.8766C14.28 10.6134 14.7 10.6134 14.9634 10.8766Z"
        fill="#697177"
      />
    </svg>
  );
}


text:
import React from "react";

export function ArrowUpRight() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className="arrow-up-right"
    >
      <path
        d="M17.25 15.25V6.75H8.75"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M17 7L6.75 17.25"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function Pretend() {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="16"
      height="16"
      fill="none"
      viewBox="0 0 16 16"
    >
      <path
        strokeWidth="0.3px"
        fill="#697177"
        d="M1.5 1a.5.5 0 0 0-.5.5v3a.5.5 0 0 1-1 0v-3A1.5 1.5 0 0 1 1.5 0h3a.5.5 0 0 1 0 1h-3zM11 .5a.5.5 0 0 1 .5-.5h3A1.5 1.5 0 0 1 16 1.5v3a.5.5 0 0 1-1 0v-3a.5.5 0 0 0-.5-.5h-3a.5.5 0 0 1-.5-.5zM.5 11a.5.5 0 0 1 .5.5v3a.5.5 0 0 0 .5.5h3a.5.5 0 0 1 0 1h-3A1.5 1.5 0 0 1 0 14.5v-3a.5.5 0 0 1 .5-.5zm15 0a.5.5 0 0 1 .5.5v3a1.5 1.5 0 0 1-1.5 1.5h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 0 .5-.5v-3a.5.5 0 0 1 .5-.5z"
      />
      <path
        strokeWidth="0.3px"
        fill="#697177"
        d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm8-9a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"
      />
    </svg>
  );
}


text:
import React from "react";

export function Book() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M19.25 5.75C19.25 5.19772 18.8023 4.75 18.25 4.75H14C12.8954 4.75 12 5.64543 12 6.75V19.25L12.8284 18.4216C13.5786 17.6714 14.596 17.25 15.6569 17.25H18.25C18.8023 17.25 19.25 16.8023 19.25 16.25V5.75Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M4.75 5.75C4.75 5.19772 5.19772 4.75 5.75 4.75H10C11.1046 4.75 12 5.64543 12 6.75V19.25L11.1716 18.4216C10.4214 17.6714 9.40401 17.25 8.34315 17.25H5.75C5.19772 17.25 4.75 16.8023 4.75 16.25V5.75Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function StopPretending() {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="16"
      height="16"
      fill="none"
      viewBox="0 0 16 16"
    >
      <path
        strokeWidth="0.3px"
        fill="#697177"
        d="M13.879 10.414a2.501 2.501 0 0 0-3.465 3.465l3.465-3.465Zm.707.707-3.465 3.465a2.501 2.501 0 0 0 3.465-3.465Zm-4.56-1.096a3.5 3.5 0 1 1 4.949 4.95 3.5 3.5 0 0 1-4.95-4.95ZM11 5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm-9 8c0 1 1 1 1 1h5.256A4.493 4.493 0 0 1 8 12.5a4.49 4.49 0 0 1 1.544-3.393C9.077 9.038 8.564 9 8 9c-5 0-6 3-6 4Z"
      />
    </svg>
  );
}


text:
import React from "react";

export function Fork() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M9.25 7C9.25 8.24264 8.24264 9.25 7 9.25C5.75736 9.25 4.75 8.24264 4.75 7C4.75 5.75736 5.75736 4.75 7 4.75C8.24264 4.75 9.25 5.75736 9.25 7Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M19.25 7C19.25 8.24264 18.2426 9.25 17 9.25C15.7574 9.25 14.75 8.24264 14.75 7C14.75 5.75736 15.7574 4.75 17 4.75C18.2426 4.75 19.25 5.75736 19.25 7Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M14.25 17C14.25 18.2426 13.2426 19.25 12 19.25C10.7574 19.25 9.75 18.2426 9.75 17C9.75 15.7574 10.7574 14.75 12 14.75C13.2426 14.75 14.25 15.7574 14.25 17Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M6.75 9.5V10.25C6.75 11.3546 7.64543 12.25 8.75 12.25H12M17.25 9.5V10.25C17.25 11.3546 16.3546 12.25 15.25 12.25H12M12 12.25V14.5"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function Close() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M17.25 6.75L6.75 17.25"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M6.75 6.75L17.25 17.25"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function Diff() {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      width="24"
      height="24"
      fill="none"
      viewBox="-4 -4 24 24"
    >
      <path d="M8.515 1.019A7 7 0 0 0 8 1V0a8 8 0 0 1 .589.022l-.074.997zm2.004.45a7.003 7.003 0 0 0-.985-.299l.219-.976c.383.086.76.2 1.126.342l-.36.933zm1.37.71a7.01 7.01 0 0 0-.439-.27l.493-.87a8.025 8.025 0 0 1 .979.654l-.615.789a6.996 6.996 0 0 0-.418-.302zm1.834 1.79a6.99 6.99 0 0 0-.653-.796l.724-.69c.27.285.52.59.747.91l-.818.576zm.744 1.352a7.08 7.08 0 0 0-.214-.468l.893-.45a7.976 7.976 0 0 1 .45 1.088l-.95.313a7.023 7.023 0 0 0-.179-.483zm.53 2.507a6.991 6.991 0 0 0-.1-1.025l.985-.17c.067.386.106.778.116 1.17l-1 .025zm-.131 1.538c.033-.17.06-.339.081-.51l.993.123a7.957 7.957 0 0 1-.23 1.155l-.964-.267c.046-.165.086-.332.12-.501zm-.952 2.379c.184-.29.346-.594.486-.908l.914.405c-.16.36-.345.706-.555 1.038l-.845-.535zm-.964 1.205c.122-.122.239-.248.35-.378l.758.653a8.073 8.073 0 0 1-.401.432l-.707-.707z" />
      <path d="M8 1a7 7 0 1 0 4.95 11.95l.707.707A8.001 8.001 0 1 1 8 0v1z" />
      <path d="M7.5 3a.5.5 0 0 1 .5.5v5.21l3.248 1.856a.5.5 0 0 1-.496.868l-3.5-2A.5.5 0 0 1 7 9V3.5a.5.5 0 0 1 .5-.5z" />
    </svg>
  );
}


text:
import React from "react";

export function NearSocialLogo() {
  return (
    <svg
      width="29"
      height="20"
      viewBox="0 0 29 20"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M9.55396 17.509L2 9.99996L9.55396 2.49097"
        stroke="#3D7FFF"
        strokeWidth="3"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M19.536 2.49097L27 9.99996L19.536 17.509"
        stroke="#3D7FFF"
        strokeWidth="3"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function User() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M15.25 8C15.25 9.79493 13.7949 11.25 12 11.25C10.2051 11.25 8.75 9.79493 8.75 8C8.75 6.20507 10.2051 4.75 12 4.75C13.7949 4.75 15.25 6.20507 15.25 8Z"
        stroke="#697177"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M6.84751 19.25H17.1525C18.2944 19.25 19.174 18.2681 18.6408 17.2584C17.8563 15.7731 16.068 14 12 14C7.93201 14 6.14367 15.7731 5.35924 17.2584C4.82597 18.2681 5.70559 19.25 6.84751 19.25Z"
        stroke="#697177"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function LogOut() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M15.75 8.75L19.25 12L15.75 15.25"
        stroke="#697177"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M19 12H10.75"
        stroke="#697177"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M15.25 4.75H6.75C5.64543 4.75 4.75 5.64543 4.75 6.75V17.25C4.75 18.3546 5.64543 19.25 6.75 19.25H15.25"
        stroke="#697177"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function Code() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M4.75 6.75C4.75 5.64543 5.64543 4.75 6.75 4.75H17.25C18.3546 4.75 19.25 5.64543 19.25 6.75V17.25C19.25 18.3546 18.3546 19.25 17.25 19.25H6.75C5.64543 19.25 4.75 18.3546 4.75 17.25V6.75Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M8.75 10.75L11.25 13L8.75 15.25"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function Home() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M6.75024 19.2502H17.2502C18.3548 19.2502 19.2502 18.3548 19.2502 17.2502V9.75025L12.0002 4.75024L4.75024 9.75025V17.2502C4.75024 18.3548 5.64568 19.2502 6.75024 19.2502Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M9.74963 15.7493C9.74963 14.6447 10.6451 13.7493 11.7496 13.7493H12.2496C13.3542 13.7493 14.2496 14.6447 14.2496 15.7493V19.2493H9.74963V15.7493Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React from "react";

export function UserCircle() {
  return (
    <svg
      width="24"
      height="24"
      viewBox="0 0 24 24"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
    >
      <path
        d="M19.25 12C19.25 16.0041 16.0041 19.25 12 19.25C7.99594 19.25 4.75 16.0041 4.75 12C4.75 7.99594 7.99594 4.75 12 4.75C16.0041 4.75 19.25 7.99594 19.25 12Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M14.25 10C14.25 11.2426 13.2426 12.25 12 12.25C10.7574 12.25 9.75 11.2426 9.75 10C9.75 8.75736 10.7574 7.75 12 7.75C13.2426 7.75 14.25 8.75736 14.25 10Z"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
      <path
        d="M7.19745 17C8.34392 15.625 10.0698 14.75 12 14.75C13.9302 14.75 15.6561 15.625 16.8025 17"
        stroke="#9BA1A6"
        strokeWidth="1.5"
        strokeLinecap="round"
        strokeLinejoin="round"
      />
    </svg>
  );
}


text:
import React, { useState, useEffect } from "react";
import { DesktopNavigation } from "./desktop/DesktopNavigation";
import { MobileNavigation } from "./mobile/MobileNavigation";

export function NavigationWrapper(props) {
  const [matches, setMatches] = useState(
    window.matchMedia("(min-width: 992px)").matches
  );

  useEffect(() => {
    window
      .matchMedia("(min-width: 992px)")
      .addEventListener("change", (e) => setMatches(e.matches));
  }, []);
  return (
    <>
      {matches && <DesktopNavigation {...props} />}
      {!matches && <MobileNavigation {...props} />}
    </>
  );
}


text:
import React from "react";
import { GrayBorderButton } from "../common/buttons/GrayBorderButton";

export function SignInButton(props) {
  return (
    <GrayBorderButton className="nav-sign-in-btn" onClick={props.onSignIn}>
      Sign In
    </GrayBorderButton>
  );
}


text:
import React from "react";
import styled from "styled-components";
import { NavLink } from "react-router-dom";

const StyledNavigationButton = styled.div`
  a {
    color: var(--slate-dark-11);
    font-size: 16px;
    padding: 10px;
    border-radius: 8px;
    font-weight: var(--font-weight-bold);
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;

    &:hover,
    &.active {
      color: white;
      text-decoration: none;
      background-color: var(--slate-dark-6);
    }
  }
  &.disabled {
    opacity: 0.5;
  }
`;

export function NavigationButton(props) {
  return (
    <StyledNavigationButton className={props.disabled ? "disabled" : ""}>
      {props.route ? (
        <NavLink
          onClick={(e) => {
            if (props.disabled) {
              e.preventDefault();
            }
          }}
          to={props.route}
          exact={true}
        >
          {props.children}
        </NavLink>
      ) : (
        <a href={props.href} target="_blank" rel="noopener noreferrer">
          {props.children}
        </a>
      )}
    </StyledNavigationButton>
  );
}


text:
import React, { useState } from "react";
import Modal from "react-bootstrap/Modal";
import { Widget, useAccount } from "near-social-vm";

export default function PretendModal(props) {
  const account = useAccount();
  const onHide = props.onHide;
  const show = props.show;

  const [accountId, setAccountId] = useState("");

  return (
    <Modal centered show={show} onHide={onHide}>
      <Modal.Header closeButton>
        <Modal.Title>Pretend to be another account</Modal.Title>
      </Modal.Header>
      <Modal.Body>

            Pretend to be account ID:

          <input
            className="form-control"
            id="widget-src-input"
            type="text"
            value={accountId}
            onChange={(e) =>
              setAccountId(
                e.target.value.toLowerCase().replaceAll(/[^a-z0-9_.\-]/g, "")
              )
            }
          />

          <Widget
            src={props.widgets.profileInlineBlock}
            props={{ accountId }}
          />

      </Modal.Body>
      <Modal.Footer>
        <button
          className="btn btn-success"
          disabled={!accountId || !account.startPretending}
          onClick={(e) => {
            e.preventDefault();
            account.startPretending(accountId);
            setAccountId("");
            onHide();
          }}
        >
          Pretend
        </button>
        <button className="btn btn-secondary" onClick={onHide}>
          Cancel
        </button>
      </Modal.Footer>
    </Modal>
  );
}


text:
import React from "react";
import styled from "styled-components";
import { Widget } from "near-social-vm";

const StyledNotificationWidget = styled.div`
  margin: 0 15px;
  background-color: var(--slate-dark-5);
  height: 40px;
  width: 40px;
  border-radius: 50%;

  > div,
  a {
    width: 100%;
    height: 100%;
  }

  a {
    color: var(--slate-dark-11) !important;
    display: flex;
    align-items: center;
    justify-content: center;

    i {
      font-size: 18px !important;
    }
  }

  :hover {
    a,
    i {
      color: white;
    }
  }
`;

export function NotificationWidget({ notificationButtonSrc }) {
  return (
    <StyledNotificationWidget className="nav-notification-widget">
      <Widget src={notificationButtonSrc} />
    </StyledNotificationWidget>
  );
}


text:
import React, { useCallback } from "react";
import { Widget, useNear, useAccount } from "near-social-vm";
import styled from "styled-components";
import { User } from "../../icons/User";
import { LogOut } from "../../icons/LogOut";
import { Withdraw } from "../../icons/Withdraw";
import { NavLink } from "react-router-dom";
import PretendModal from "../PretendModal";
import { Pretend } from "../../icons/Pretend";
import { StopPretending } from "../../icons/StopPretending";

const StyledDropdown = styled.div`
  button,
  a {
    font-weight: var(--font-weight-medium);
  }
  .dropdown-toggle {
    display: flex;
    align-items: center;
    text-align: left;
    background-color: var(--slate-dark-5);
    border-radius: 50px;
    outline: none;
    border: 0;

    &:after {
      margin: 0 15px;
      border-top-color: var(--slate-dark-11);
    }

    img {
      border-radius: 50% !important;
    }

    .profile-info {
      margin: 5px 10px;
      line-height: normal;
      max-width: 140px;

      .profile-name,
      .profile-username {
        text-overflow: ellipsis;
        overflow: hidden;
      }

      .profile-name {
        color: var(--slate-dark-12);
      }
      .profile-username {
        color: var(--slate-dark-11);
      }
    }
  }

  ul {
    background-color: var(--slate-dark-5);
    width: 100%;

    li {
      padding: 0 6px;
    }

    button,
    a {
      color: var(--slate-dark-11);
      display: flex;
      align-items: center;
      border-radius: 8px;
      padding: 12px;

      :hover,
      :focus {
        text-decoration: none;
        background-color: var(--slate-dark-1);
        color: white;

        svg {
          path {
            stroke: white;
          }
        }
      }

      svg {
        margin-right: 7px;
        min-width: 24px;
        path {
          stroke: var(--slate-dark-9);
        }
      }
    }
  }
`;

export function UserDropdown(props) {
  const near = useNear();
  const account = useAccount();

  const withdrawStorage = useCallback(async () => {
    await near.contract.storage_withdraw({}, undefined, "1");
  }, [near]);

  const [showPretendModal, setShowPretendModal] = React.useState(false);

  return (
    <>

      <PretendModal
        show={showPretendModal}
        onHide={() => setShowPretendModal(false)}
        widgets={props.widgets}
      />
    </>
  );
}


text:
import React from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { Fork } from "../../icons/Fork";
import { Code } from "../../icons/Code";
import { useAccount } from "near-social-vm";
import { Diff } from "../../icons/Diff";

const StyledDropdown = styled.div`
  .dropdown-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: var(--slate-dark-5);
    border-radius: 50px;
    outline: none;
    border: 0;
    width: 40px;
    height: 40px;

    &:after {
      display none;
    }

    .menu {
      width: 18px;
      height: 24px;
      display: flex;
      flex-direction: column;
      justify-content: space-evenly;

      div {
        background-color: var(--slate-dark-11);;
        height: 2px;
        width: 100%;
        border-radius: 30px;
      }
    }

    :hover {
      .menu {
        div {
          background-color: white;
        }
      }
    }
  }

  ul {
    background-color: var(--slate-dark-5);
    width: 100%;

    li {
      padding: 0 6px;
    }

    button,
    a {
      color: var(--slate-dark-11);
      display: flex;
      align-items: center;
      border-radius: 8px;
      padding: 12px;

      :hover,
      :focus {
        text-decoration: none;
        background-color: var(--slate-dark-1);
        color: white;

        svg {
          path {
            stroke: white;
          }
        }
      }

      svg {
        margin-right: 7px;
        path {
          stroke: var(--slate-dark-9);
        }
      }
    }
  }
`;

export function DevActionsDropdown(props) {
  const account = useAccount();

  if (props.widgetSrc?.edit || props.widgetSrc?.view) {
    return (

    );
  } else {
    return null;
  }
}


text:
import React from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { Logotype } from "../Logotype";
import { NavigationButton } from "../NavigationButton";
import { ArrowUpRight } from "../../icons/ArrowUpRight";
import { SignInButton } from "../SignInButton";
import { UserDropdown } from "./UserDropdown";
import { DevActionsDropdown } from "./DevActionsDropdown";
import { NotificationWidget } from "../NotificationWidget";

const StyledNavigation = styled.div`
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  width: 100%;
  background-color: var(--slate-dark-1);
  z-index: 1000;
  padding: 12px 0;

  .user-section {
    margin-left: auto;
    > button {
      font-size: 14px;
    }
  }

  .container {
    display: flex;
    align-items: center;

    .navigation-section {
      margin-left: 50px;
      display: flex;

      > div {
        > a {
          margin-right: 20px;
        }
      }
    }

    .user-section {
      display: flex;
      align-items: center;

      .nav-create-btn {
        margin-left: 10px;
      }

      .nav-sign-in-btn {
        margin-left: 10px;
      }
    }

    .arrow-up-right {
      margin-left: 4px;
    }
  }
`;

export function DesktopNavigation(props) {
  return (

  );
}


text:
import React from "react";
import styled from "styled-components";

const StyledMobileMenuButton = styled.button`
  background-color: transparent;
  border: none;
  display: flex;
  align-items: center;
  color: white;
  font-weight: var(--font-weight-bold);
  padding: 0;

  .menu {
    width: 18px;
    height: 24px;
    display: flex;
    flex-direction: column;
    justify-content: space-evenly;
    margin-right: 10px;

    div {
      background-color: white;
      height: 2px;
      width: 100%;
      border-radius: 30px;
    }
  }
`;

export function MobileMenuButton(props) {
  return (

  );
}


text:
import React, { useState, useEffect } from "react";
import { Navigation } from "./Navigation";
import { Menu } from "./Menu";
import { useLocation } from "react-router-dom";
import useScrollBlock from ".././../../hooks/useScrollBlock";

export function MobileNavigation(props) {
  const [showMenu, setShowMenu] = useState(false);
  const [currentPage, setCurrentPage] = useState("");
  const location = useLocation();
  const [blockScroll, allowScroll] = useScrollBlock();

  useEffect(() => {
    setShowMenu(false);
    getCurrentPage();
    allowScroll();
  }, [location.pathname]);

  const getCurrentPage = () => {
    switch (location.pathname) {
      case "/":
        return setCurrentPage("Home");
      case `/${props.widgets.profilePage}`:
        return setCurrentPage("Profile");
      case "/edit":
        return setCurrentPage("Create");
      default:
        return setCurrentPage("");
    }
  };

  return (
    <>
      <Navigation
        {...props}
        currentPage={currentPage}
        onClickShowMenu={() => {
          setShowMenu(true);
          blockScroll();
        }}
      />
      <Menu
        {...props}
        showMenu={showMenu}
        onCloseMenu={() => {
          setShowMenu(false);
          allowScroll();
        }}
      />
    </>
  );
}


text:
import React from "react";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { MobileMenuButton } from "./MobileMenuButton";
import { NearSocialLogo } from "../../icons/NearSocialLogo";
import { NotificationWidget } from "../NotificationWidget";
import { SignInButton } from "../SignInButton";

const StyledNavigation = styled.div`
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  width: 100%;
  background-color: var(--slate-dark-1);
  z-index: 1000;
  padding: 16px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;

  .logo-link {
    position: absolute;
    left: 0;
    right: 0;
    margin: auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: fit-content;
  }

  .nav-notification-widget {
    margin: 0;
  }

  .nav-sign-in-btn {
    background: none;
    border: none;
    padding-right: 0;
  }
`;

export function Navigation(props) {
  return (
    <StyledNavigation>
      <MobileMenuButton
        onClick={props.onClickShowMenu}
        currentPage={props.currentPage}
      />
      <Link to="/" className="logo-link">
        <NearSocialLogo />
      </Link>
      {props.signedIn ? (
        <NotificationWidget
          notificationButtonSrc={props.widgets.notificationButton}
        />
      ) : (
        <SignInButton onSignIn={() => props.requestSignIn()} />
      )}
    </StyledNavigation>
  );
}


text:
import React from "react";
import styled from "styled-components";
import { Close } from "../../icons/Close";
import { Home } from "../../icons/Home";
import { Book } from "../../icons/Book";
import { Code } from "../../icons/Code";
import { LogOut } from "../../icons/LogOut";
import { Fork } from "../../icons/Fork";
import { UserCircle } from "../../icons/UserCircle";
import { Widget } from "near-social-vm";
import { NavigationButton } from "../NavigationButton";
import { SignInButton } from "../SignInButton";
import { Link } from "react-router-dom";

const StyledMenu = styled.div`
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  display: flex;
  transition: 350ms;
  transform: translateX(-100%);

  &.show {
    transform: translateX(0);
  }

  ul {
    list-style-type: none;
    margin: 0;
    padding: 0;
  }

  .left-side {
    flex: 80;
    background-color: var(--slate-dark-1);
    position: relative;
    display: flex;
    flex-direction: column;
    padding: 25px;
    overflow-x: auto;

    .nav-sign-in-btn {
      width: fit-content;
    }

    .profile-link {
      max-width: 100%;
      white-space: nowrap;

      :hover {
        text-decoration: none;
      }
    }

    img {
      border-radius: 50% !important;
    }

    .profile-name {
      color: var(--slate-dark-12);
      font-weight: var(--font-weight-bold);
      margin-top: 10px;
    }

    .profile-username {
      color: var(--slate-dark-11);
    }

    .profile-name,
    .profile-username {
      text-overflow: ellipsis;
      overflow: hidden;
    }
  }

  .top-links,
  .bottom-links {
    a,
    button {
      justify-content: flex-start;
      padding: 28px 0;
      display: flex;
      align-items: center;
      color: var(--slate-dark-11);
      font-weight: var(--font-weight-bold);

      svg {
        margin-right: 12px;
      }

      &.active,
      &:hover,
      &:focus {
        background-color: transparent;
        color: white;
        text-decoration: none;
        svg {
          path {
            stroke: white;
          }
        }
      }
    }
  }

  .top-links {
    margin-top: 40px;
  }

  .bottom-links {
    margin-top: auto;

    a,
    button {
      padding: 14px 0;
    }
  }

  .log-out-button {
    background: none;
    border: none;
    color: var(--slate-dark-11);
    font-weight: var(--font-weight-bold);
    padding: 28px 0;

    svg {
      path {
        stroke: #9ba1a6;
      }
    }
  }

  .close-button {
    background: none;
    border: none;
    position: absolute;
    right: 16px;
    top: 16px;
    padding: 10px;

    svg {
      margin: 0;
    }
  }

  .right-side {
    flex: 20;
    opacity: 0.8;
    background-color: var(--slate-dark-1);
  }
`;

export function Menu(props) {
  return (

  );
}


text:
import React from "react";
import { Button } from "./Button";
import styled from "styled-components";

const StyledButton = styled(Button)`
  background-color: var(--slate-dark-6);
  border-color: var(--slate-dark-8);
  color: white;
`;

export function GrayBorderButton(props) {
  return <StyledButton {...props}>{props.children}</StyledButton>;
}


text:
import React from "react";
import { Button } from "./Button";
import styled from "styled-components";

const StyledButton = styled(Button)`
  background-color: var(--blue-light-9);
  border-color: var(--blue-light-9);
  color: white;
`;

export function BlueButton(props) {
  return <StyledButton {...props}>{props.children}</StyledButton>;
}


text:
import React from "react";
import styled from "styled-components";

const StyledButton = styled.button`
  border-radius: 8px;
  border-style: solid;
  border-width: 1px;
  border-color: transparent;
  padding: 8px 16px;
  font-weight: var(--font-weight-bold);
  display: inline-block;
  height: 40px;
`;

export function Button(props) {
  return (
    <StyledButton
      className={props.className}
      onClick={props.onClick}
      title={props.title}
      disabled={props.disabled}
    >
      {props.children}
    </StyledButton>
  );
}


text:
import { Buffer } from "node:buffer";

export async function socialIndex(action, key, options) {
  const request = await fetch("https://api.near.social/index", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      action,
      key,
      options,
    }),
  });
  return await request.json();
}

export async function socialKeys(keys, blockHeight, options) {
  const request = await fetch("https://api.near.social/keys", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      keys: [keys],
      blockHeight,
      options,
    }),
  });
  return await request.json();
}

export async function socialGet(keys, blockHeight, parse) {
  const request = await fetch("https://api.near.social/get", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      keys: [keys],
      blockHeight,
    }),
  });
  let data = await request.json();
  const parts = keys.split("/");
  for (let i = 0; i < parts.length; i++) {
    const part = parts[i];
    if (part === "*" || part === "**") {
      break;
    }
    data = data?.[part];
  }
  if (parse) {
    try {
      data = JSON.parse(data);
    } catch (e) {
      return null;
    }
  }
  return data;
}

export async function viewCall({ contractId, method, args }) {
  const res = await fetch("https://rpc.mainnet.near.org", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "dontcare",
      method: "query",
      params: {
        request_type: "call_function",
        finality: "final",
        account_id: contractId,
        method_name: method,
        args_base64: btoa(JSON.stringify(args)),
      },
    }),
  });
  const json = await res.json();
  const result = Buffer.from(json.result.result).toString("utf-8");
  return JSON.parse(result);
}

export async function nftToImageUrl({ contractId, tokenId }) {
  const [token, nftMetadata] = await Promise.all([
    viewCall({
      contractId,
      method: "nft_token",
      args: { token_id: tokenId },
    }),
    viewCall({
      contractId,
      method: "nft_metadata",
      args: {},
    }),
  ]);

  const tokenMetadata = token?.metadata || {};
  const tokenMedia = tokenMetadata.media || "";

  let imageUrl =
    tokenMedia.startsWith("https://") ||
    tokenMedia.startsWith("http://") ||
    tokenMedia.startsWith("data:image")
      ? tokenMedia
      : nftMetadata.base_uri
      ? `${nftMetadata.base_uri}/${tokenMedia}`
      : tokenMedia.startsWith("Qm") || tokenMedia.startsWith("ba")
      ? `https://ipfs.near.social/ipfs/${tokenMedia}`
      : tokenMedia;

  if (!tokenMedia && tokenMetadata.reference) {
    const metadataUrl =
      nftMetadata.base_uri === "https://arweave.net" &&
      !tokenMetadata.reference.startsWith("https://")
        ? `${nftMetadata.base_uri}/${tokenMetadata.reference}`
        : tokenMetadata.reference.startsWith("https://") ||
          tokenMetadata.reference.startsWith("http://")
        ? tokenMetadata.reference
        : tokenMetadata.reference.startsWith("ar://")
        ? `https://arweave.net/${tokenMetadata.reference.split("//")[1]}`
        : null;
    if (metadataUrl) {
      const res = await fetch(metadataUrl);
      const json = await res.json();
      imageUrl = json.media;
    }
  }

  return imageUrl;
}

export function wrapImage(url) {
  return url ? `https://i.near.social/large/${url}` : null;
}

export async function internalImageToUrl(env, image) {
  if (image?.url) {
    return image.url;
  } else if (image?.ipfs_cid) {
    return `https://ipfs.near.social/ipfs/${image.ipfs_cid}`;
  } else if (image?.nft) {
    try {
      const { contractId, tokenId } = image.nft;
      const NftKV = env.NftKV;

      let imageUrl = await NftKV.get(`${contractId}/${tokenId}`);
      if (!imageUrl) {
        imageUrl = await nftToImageUrl({ contractId, tokenId });
        if (imageUrl) {
          await NftKV.put(`${contractId}/${tokenId}`, imageUrl);
        }
      }
      return imageUrl;
    } catch (e) {
      console.log(e);
    }
  }
  return null;
}

export async function imageToUrl(env, image) {
  return wrapImage(await internalImageToUrl(env, image));
}

export const DefaultProfileImage =
  "https://ipfs.near.social/ipfs/bafkreibmiy4ozblcgv3fm3gc6q62s55em33vconbavfd2ekkuliznaq3zm";


text:
import {
  DefaultProfileImage,
  internalImageToUrl,
  socialGet,
} from "../../../common";

export async function onRequest({ request, next, env }) {
  const url = new URL(request.url);
  const parts = url.pathname.split("/");
  if (parts.length !== 5) {
    return next();
  }
  const accountId = parts[4];
  const image = await socialGet(`${accountId}/profile/image/**`);

  const destinationURL =
    (await internalImageToUrl(env, image)) || DefaultProfileImage;

  return new Response(destinationURL, {
    headers: {
      "content-type": "text/plain;charset=UTF-8",
    },
  });
}


text:
import { internalImageToUrl } from "../../../common";

export async function onRequest({ request, next, env }) {
  const url = new URL(request.url);
  const parts = url.pathname.split("/");
  if (parts.length !== 6) {
    return next();
  }
  const contractId = parts[4];
  const tokenId = parts[5];

  const destinationURL = await internalImageToUrl(env, {
    nft: {
      contractId,
      tokenId,
    },
  });

  return destinationURL
    ? new Response(destinationURL, {
        headers: {
          "content-type": "text/plain;charset=UTF-8",
        },
      })
    : next();
}


text:
export async function onRequest({ request, next, env }) {
  return new Response(
    `<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <sitemap>
        <loc>https://near.social/sitemap/posts/0</loc>
    </sitemap>
    <sitemap>
        <loc>https://near.social/sitemap/widgets/</loc>
    </sitemap>
    <sitemap>
        <loc>https://near.social/sitemap/profiles/</loc>
    </sitemap>
    <sitemap>
        <loc>https://near.social/sitemap/sources/0</loc>
    </sitemap>
    <sitemap>
        <loc>https://near.social/sitemap/sources/50000</loc>
    </sitemap>
</sitemapindex>`,
    {
      headers: {
        "content-type": "application/xml;charset=UTF-8",
      },
    }
  );
}


text:
import { socialKeys } from "../../common";

const MinBlockHeight = 75942518;
const LIMIT = 50000;

export const generateSitemapSources = async (env, offset) => {
  const data = await socialKeys("*/widget/*", null, {
    return_type: "History",
  });
  const urls = Object.entries(data)
    .map(([accountId, widget]) =>
      Object.entries(widget.widget)
        .map(([widgetId, blockHeights]) =>
          blockHeights
            .filter((blockHeight) => blockHeight >= MinBlockHeight)
            .map(
              (blockHeight) =>
                `  <url>
    <loc>https://near.social/mob.near/widget/WidgetSource?src=${accountId}/widget/${widgetId}&blockHeight=${blockHeight}</loc>
    <changefreq>never</changefreq>
  </url>`
            )
        )
        .flat()
    )
    .flat();
  console.log("urls count", urls.length);
  return urls.slice(offset, offset + LIMIT).join("\n");
};

export async function onRequest({ env, request, next }) {
  const url = new URL(request.url);
  const parts = url.pathname.split("/");
  if (parts.length !== 4) {
    return next();
  }
  const offset = parseInt(parts[3]);

  return new Response(
    `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${await generateSitemapSources(env, offset)}
</urlset>`,
    {
      headers: {
        "content-type": "application/xml;charset=UTF-8",
      },
    }
  );
}


text:
import { socialKeys } from "../../common";

export const generateSitemapWidgets = async (env) => {
  const data = await socialKeys("*/widget/*/metadata");
  return Object.entries(data)
    .map(([accountId, widget]) =>
      Object.keys(widget.widget).map(
        (widgetId) =>
          `  <url>
    <loc>https://near.social/${accountId}/widget/${widgetId}</loc>
    <changefreq>monthly</changefreq>
  </url>`
      )
    )
    .flat()
    .join("\n");
};

export async function onRequest({ env }) {
  return new Response(
    `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${await generateSitemapWidgets(env)}
</urlset>`,
    {
      headers: {
        "content-type": "application/xml;charset=UTF-8",
      },
    }
  );
}


text:
import { socialKeys } from "../../common";

export const generateSitemapProfiles = async (env) => {
  const data = await socialKeys("*/profile");
  const accountIds = Object.keys(data);
  return accountIds
    .map(
      (accountId) =>
        `  <url>
    <loc>https://near.social/mob.near/widget/ProfilePage?accountId=${accountId}</loc>
    <changefreq>monthly</changefreq>
  </url>`
    )
    .join("\n");
};

export async function onRequest({ env }) {
  return new Response(
    `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${await generateSitemapProfiles(env)}
</urlset>`,
    {
      headers: {
        "content-type": "application/xml;charset=UTF-8",
      },
    }
  );
}


text:
import { socialIndex } from "../../common";

const Limit = 50000;

export const generateSitemapPosts = async (env, offset) => {
  const posts = await socialIndex("post", "main", {});
  const urls = posts.map(
    (post) =>
      `  <url>
    <loc>https://near.social/mob.near/widget/MainPage.Post.Page?accountId=${post.accountId}&blockHeight=${post.blockHeight}</loc>
    <changefreq>never</changefreq>
  </url>`
  );
  console.log("urls count", urls.length);
  return urls.slice(offset, offset + Limit).join("\n");
};

export async function onRequest({ request, env, next }) {
  const url = new URL(request.url);
  const parts = url.pathname.split("/");
  if (parts.length !== 4) {
    return next();
  }
  const offset = parseInt(parts[3]);

  return new Response(
    `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${await generateSitemapPosts(env, offset)}
</urlset>`,
    {
      headers: {
        "content-type": "application/xml;charset=UTF-8",
      },
    }
  );
}


text:
import {
  socialGet,
  imageToUrl,
  wrapImage,
  DefaultProfileImage,
} from "../../common";

class MetaTitleInjector {
  constructor({ title }) {
    this.title = title;
  }

  element(element) {
    element.setAttribute("content", this.title);
  }
}

class MetaImageInjector {
  constructor({ image, authorImage }) {
    this.image = image;
    this.authorImage = authorImage;
  }

  element(element) {
    if (this.image) {
      element.setAttribute("content", this.image);
    } else if (this.authorImage) {
      element.setAttribute("content", this.authorImage);
    }
  }
}

class MetaTwitterCardInjector {
  constructor({ image }) {
    this.image = image;
  }

  element(element) {
    if (!this.image) {
      element.setAttribute("content", "summary");
    }
  }
}

class MetaDescriptionInjector {
  constructor({ shortDescription }) {
    this.shortDescription = shortDescription;
  }

  element(element) {
    element.setAttribute(
      "content",
      this.shortDescription?.replaceAll("\n", " ")
    );
  }
}

class TitleInjector {
  constructor({ title }) {
    this.title = title;
  }

  element(element) {
    element.setInnerContent(this.title);
  }
}

class NoscriptDescriptionInjector {
  constructor({ description }) {
    this.description = description;
  }

  element(element) {
    element.setInnerContent(this.description);
  }
}

function defaultData() {
  const image = "https://near.social/assets/logo.png";
  const title = "Near Social";
  const description = "Social data protocol built on NEAR";
  return {
    image,
    title,
    description,
  };
}

async function postData(env, url, data, isPost) {
  const accountId = url.searchParams.get("accountId");
  const blockHeight = url.searchParams.get("blockHeight");
  const [content, name, authorImage] = await Promise.all([
    socialGet(
      `${accountId}/post/${isPost ? "main" : "comment"}`,
      blockHeight,
      true
    ),
    socialGet(`${accountId}/profile/name`),
    socialGet(`${accountId}/profile/image/**`),
  ]);

  data.raw = content;
  data.description = content?.text || "";
  data.image = await imageToUrl(env, content?.image);
  if (!data.image) {
    data.authorImage = await imageToUrl(env, authorImage);
  }
  data.title = isPost
    ? `Post by ${name ?? accountId} | Near Social`
    : `Comment by ${name ?? accountId} | Near Social`;
  data.accountName = name;
  data.accountId = accountId;
}

async function profileData(env, url, data) {
  const accountId = url.searchParams.get("accountId");
  const profile = await socialGet(`${accountId}/profile/**`);

  const name = profile?.name;
  data.raw = profile;
  data.description =
    profile?.description || `Profile of ${accountId} on Near Social`;
  data.image = await imageToUrl(env, profile?.image);
  data.authorImage = data.image || wrapImage(DefaultProfileImage);
  data.title = name
    ? `${name} (${accountId}) | Near Social`
    : `${accountId} | Near Social`;
  data.accountName = name;
  data.accountId = accountId;
}

async function widgetData(env, url, data) {
  const parts = url.pathname.split("/");
  const accountId = parts[1];
  const widgetId = parts[3];
  const metadata = await socialGet(
    `${accountId}/widget/${widgetId}/metadata/**`
  );

  const name = metadata?.name || widgetId;
  data.raw = metadata;
  data.description =
    metadata?.description || `Component ${name} created by ${accountId}`;
  data.image = await imageToUrl(env, metadata?.image);
  data.title = `${name} by ${accountId} | Near Social`;
  data.accountName = name;
  data.accountId = accountId;
}

async function sourceData(env, url, data) {
  const key = url.searchParams.get("src");
  const parts = key.split("/");
  const accountId = parts[0];
  const blockHeight = url.searchParams.get("blockHeight");
  const [source, image] = await Promise.all([
    socialGet(key, blockHeight),
    socialGet(`${key}/metadata/image/**`),
  ]);

  data.raw = source;
  data.description = source;
  data.image = null;
  data.authorImage = await imageToUrl(env, image);
  data.title = `Source code of ${key} at block height ${blockHeight} | Near Social`;
  data.accountId = accountId;
}

async function generateData(env, url) {
  const data = defaultData();
  try {
    if (url.pathname === "/mob.near/widget/MainPage.Post.Page") {
      await postData(env, url, data, true);
    } else if (url.pathname === "/mob.near/widget/MainPage.Comment.Page") {
      await postData(env, url, data, false);
    } else if (url.pathname === "/mob.near/widget/ProfilePage") {
      await profileData(env, url, data);
    } else if (url.pathname === "/mob.near/widget/WidgetSource") {
      await sourceData(env, url, data);
    } else {
      await widgetData(env, url, data);
    }
  } catch (e) {
    console.error(e);
  }
  data.shortDescription = data.description.slice(0, 250);

  return data;
}

export async function onRequest({ request, next, env }) {
  const url = new URL(request.url);
  if (url.pathname.split("/").length < 4) {
    return next();
  }
  const data = await generateData(env, url);
  return (
    new HTMLRewriter()
      .on('meta[property="og:title"]', new MetaTitleInjector(data))
      .on('meta[property="og:image"]', new MetaImageInjector(data))
      .on('meta[name="twitter:card"]', new MetaTwitterCardInjector(data))
      .on('meta[property="og:description"]', new MetaDescriptionInjector(data))
      .on('meta[name="description"]', new MetaDescriptionInjector(data))
      // .on("head", new MetaTagInjector(data))
      .on("title", new TitleInjector(data))
      .on("noscript", new NoscriptDescriptionInjector(data))
      .transform(await next())
  );
}


text:
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const path = require("path");

module.exports = () => {
  return {
    output: {
      path: path.resolve(__dirname, "../dist"),
      publicPath: "./",
      filename: "[name].[contenthash].bundle.js",
    },
    devtool: false,
    module: {
      rules: [
        // {
        //   test: /\.(css)$/,
        //   use: [MiniCssExtractPlugin.loader, "css-loader"],
        //   //   options: {
        //   //     sourceMap: false,
        //   //   },
        // },
        {
          test: /\.(scss|css)$/,
          use: [
            {
              // inject CSS to page
              loader: "style-loader",
            },
            {
              // translates CSS into CommonJS modules
              loader: "css-loader",
            },
            {
              // Run postcss actions
              loader: "postcss-loader",
              options: {
                // `postcssOptions` is needed for postcss 8.x;
                // if you use postcss 7.x skip the key
                postcssOptions: {
                  // postcss plugins, can be exported to postcss.config.js
                  plugins: function () {
                    return [require("autoprefixer")];
                  },
                },
              },
            },
            {
              // compiles Sass to CSS
              loader: "sass-loader",
            },
          ],
        },
      ],
    },
    plugins: [
      new MiniCssExtractPlugin({
        filename: "styles/[name].[contenthash].css",
        chunkFilename: "[id].css",
      }),
    ],
    optimization: {
      minimize: true,
      minimizer: [new CssMinimizerPlugin(), "..."],
      runtimeChunk: {
        name: "runtime",
      },
    },
    performance: {
      hints: false,
      maxEntrypointSize: 512000,
      maxAssetSize: 512000,
    },
  };
};


text:
const path = require("path");

const srcPath = path.resolve(__dirname, "../src");
const distPath = path.resolve(__dirname, "../dist");
const publicPath = path.resolve(__dirname, "../public");
const nodeModulesPath = path.resolve(__dirname, "../node_modules");

module.exports = {
  srcPath,
  distPath,
  publicPath,
  nodeModulesPath,
};


text:
const path = require("path");
const { HotModuleReplacementPlugin } = require("webpack");

module.exports = () => ({
  devtool: false,
  module: {
    rules: [
      {
        test: /\.(scss|css)$/,
        use: [
          {
            // inject CSS to page
            loader: "style-loader",
          },
          {
            // translates CSS into CommonJS modules
            loader: "css-loader",
          },
          {
            // Run postcss actions
            loader: "postcss-loader",
            options: {
              // `postcssOptions` is needed for postcss 8.x;
              // if you use postcss 7.x skip the key
              postcssOptions: {
                // postcss plugins, can be exported to postcss.config.js
                plugins: function () {
                  return [require("autoprefixer")];
                },
              },
            },
          },
          {
            // compiles Sass to CSS
            loader: "sass-loader",
          },
        ],
      },
    ],
  },
  devServer: {
    open: true,
    static: path.resolve(__dirname, "../dist"),
    port: 3000,
    compress: true,
    historyApiFallback: {
      disableDotRule: true,
    },
  },
  plugins: [new HotModuleReplacementPlugin()],
});


text:
const WebpackBundleAnalyzer =
  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;

module.exports = () => ({
  plugins: [new WebpackBundleAnalyzer()],
});


text:
const { merge } = require("webpack-merge");

const loadPresets = (env = { presets: [] }) => {
  const presets = env.presets || [];
  /** @type {string[]} */
  const mergedPresets = [].concat(...[presets]);
  const mergedConfigs = mergedPresets.map((presetName) =>
    require(`./webpack.${presetName}.js`)(env)
  );

  return merge({}, ...mergedConfigs);
};
module.exports = loadPresets;


text: near.social


text:
# https://www.robotstxt.org/robotstxt.html
Sitemap: https://near.social/sitemap/
User-agent: *
Disallow:


text: /*    /index.html   200


text:
<!DOCTYPE html>

    <meta charset="utf-8" />
    <link rel="icon" href="/favicon.png" />

    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="apple-touch-icon" href="/favicon.png" />

    <link rel="manifest" href="/manifest.json" />
    <meta
        name="description"
        content="Social data protocol built on NEAR"
    />
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:site" content="@NearSocial_">
    <meta property="og:image" content="https://near.social/assets/logo.png">
    <meta property="og:type" content="website">
    <meta property="og:title" content="Near Social" />
    <meta property="og:description" content="Social data protocol built on NEAR" />
    <title>Near Social</title>

      You need to enable JavaScript to run this app.


text:
#!/usr/bin/env python3
# Author: AnalogMan
# Modified Date: 2018-10-08
# Purpose: Splits Nintendo Switch NSP files into parts for installation on FAT32

import os
import argparse
import shutil
from datetime import datetime
startTime = datetime.now()

splitSize = 0xFFFF0000 # 4,294,901,760 bytes
chunkSize = 0x8000 # 32,768 bytes

def splitQuick(filepath):
    fileSize = os.path.getsize(filepath)
    info = shutil.disk_usage(os.path.dirname(os.path.abspath(filepath)))
    if info.free < splitSize:
        print('Not enough temporary space. Needs 4GiB of free space\n')
        return
    print('Calculating number of splits...\n')
    splitNum = int(fileSize/splitSize)
    if splitNum == 0:
        print('This NSP is under 4GiB and does not need to be split.\n')
        return

    print('Splitting NSP into {0} parts...\n'.format(splitNum + 1))

    # Create directory, delete if already exists
    dir = filepath[:-4] + '_split.nsp'
    if os.path.exists(dir):
        shutil.rmtree(dir)
    os.makedirs(dir)

    # Move input file to directory and rename it to first part
    filename = os.path.basename(filepath)
    shutil.move(filepath, os.path.join(dir, '00'))
    filepath = os.path.join(dir, '00')

    # Calculate size of final part to copy first
    finalSplitSize = fileSize - (splitSize * splitNum)

    # Copy final part and trim from main file
    with open(filepath, 'r+b') as nspFile:
        nspFile.seek(finalSplitSize * -1, os.SEEK_END)
        outFile = os.path.join(dir, '{:02}'.format(splitNum))
        partSize = 0
        print('Starting part {:02}'.format(splitNum))
        with open(outFile, 'wb') as splitFile:
            while partSize < finalSplitSize:
                splitFile.write(nspFile.read(chunkSize))
                partSize += chunkSize
        nspFile.seek(finalSplitSize * -1, os.SEEK_END)
        nspFile.truncate()
        print('Part {:02} complete'.format(splitNum))

    # Loop through additional parts and trim
    with open(filepath, 'r+b') as nspFile:
        for i in range(splitNum - 1):
            nspFile.seek(splitSize * -1, os.SEEK_END)
            outFile = os.path.join(dir, '{:02}'.format(splitNum - (i + 1)))
            partSize = 0
            print('Starting part {:02}'.format(splitNum - (i + 1)))
            with open(outFile, 'wb') as splitFile:
                 while partSize < splitSize:
                    splitFile.write(nspFile.read(chunkSize))
                    partSize += chunkSize
            nspFile.seek(splitSize * -1, os.SEEK_END)
            nspFile.truncate()
            print('Part {:02} complete'.format(splitNum - (i + 1)))

    # Print assurance statement for user
    print('Starting part 00\nPart 00 complete')

    print('\nNSP successfully split!\n')

def splitCopy(filepath, output_dir=""):
    fileSize = os.path.getsize(filepath)
    info = shutil.disk_usage(os.path.dirname(os.path.abspath(filepath)))
    if info.free < fileSize*2:
        print('Not enough free space to run. Will require twice the space as the NSP file\n')
        return
    print('Calculating number of splits...\n')
    splitNum = int(fileSize/splitSize)
    if splitNum == 0:
        print('This NSP is under 4GiB and does not need to be split.\n')
        return

    print('Splitting NSP into {0} parts...\n'.format(splitNum + 1))

    # Create directory, delete if already exists
    if output_dir == "":
        dir = filepath[:-4] + '_split.nsp'
    else:
        if output_dir[-4:] != '.nsp':
            output_dir+= ".nsp"
        dir = output_dir
    if os.path.exists(dir):
        shutil.rmtree(dir)
    os.makedirs(dir)

    remainingSize = fileSize

    # Open source file and begin writing to output files stoping at splitSize
    with open(filepath, 'rb') as nspFile:
        for i in range(splitNum + 1):
            partSize = 0
            print('Starting part {:02}'.format(i))
            outFile = os.path.join(dir, '{:02}'.format(i))
            with open(outFile, 'wb') as splitFile:
                if remainingSize > splitSize:
                    while partSize < splitSize:
                        splitFile.write(nspFile.read(chunkSize))
                        partSize += chunkSize
                    remainingSize -= splitSize
                else:
                    while partSize < remainingSize:
                        splitFile.write(nspFile.read(chunkSize))
                        partSize += chunkSize
            print('Part {:02} complete'.format(i))
    print('\nNSP successfully split!\n')

def main():
    print('\n========== NSP Splitter ==========\n')

    # Arg parser for program options
    parser = argparse.ArgumentParser(description='Split NSP files into FAT32 compatible sizes')
    parser.add_argument('filepath', help='Path to NSP file')
    group = parser.add_mutually_exclusive_group()
    group.add_argument('-q', '--quick', action='store_true', help='Splits file in-place without creating a copy. Only requires 4GiB free space to run')
    group.add_argument('-o', '--output-dir', type=str, default="",
                        help="Set alternative output dir")

    # Check passed arguments
    args = parser.parse_args()

    filepath = args.filepath

    # Check if required files exist
    if os.path.isfile(filepath) == False:
        print('NSP cannot be found\n')
        return 1

    # Split NSP file
    if args.quick:
        splitQuick(filepath)
    else:
        splitCopy(filepath, args.output_dir)

if __name__ == "__main__":
    main()

repo_language: Python

mime_type: text/plain


text:
# splitNSP

As some have become aware, it's been found out that the official Nintendo SDK contains a PowerShell script for splitting NSP files into 4GiB chunks so that they can be installed from FAT32 filesystems. Seeing as the official script cannot be shared, I re-wrote it in Python3 (which makes it useable on more than just Windows) as well as added in an additional feature.

To run it you'll need Python3 installed. Once installed, call the script from Terminal or Command Prompt with the following:

```python3 splitNSP.py filename.nsp```

By default this will make a copy of the NSP and split it up into parts. Once created, you'll need to open the folder's properties and check the Archive flag. This is easily done on Windows, I'm still working on a way to do it for macOS since file flags aren't saved when copying to FAT32. You can also set the archive flag on a folder directly on the Switch using a homebrew such as NX-Shell.

You can also activate quick mode with this command:

```python3 splitNSP.py -q filename.nsp```

This will not make a copy of the NSP and instead will split the original. This is useful if you're running low on space as it only requires that you have 4GiB of temporary space to run it. It's also much faster.

Once the folder is made and the archive flag is set copy it to your SD card (sdmc:/tinfoil/nsp/ if using tinfoil) and install it like any other NSP.

If you have any issues feel free to submit an issue and I'll try my best to work it out.