Browse Source

feat: better bundle size, new header, highlighter fixes

fix/enable-imgix
Thomas Osmonson 4 years ago
parent
commit
30d30374ac
  1. 5
      next.config.js
  2. 2
      package.json
  3. 100
      src/components/code-block/components.tsx
  4. 94
      src/components/code-block/index.tsx
  5. 88
      src/components/feedback.tsx
  6. 1
      src/components/footer.tsx
  7. 237
      src/components/header.tsx
  8. 230
      src/components/highlighter/index.tsx
  9. 117
      src/components/highlighter/language-definition.tsx
  10. 205
      src/components/highlighter/nord.css
  11. 140
      src/components/highlighter/prism-theme.ts
  12. 90
      src/components/highlighter/types.ts
  13. 3
      src/components/home/sections/hero.tsx
  14. 12
      src/components/layouts/docs-layout.tsx
  15. 2
      src/components/mdx/mdx-components.tsx
  16. 7
      src/components/side-nav.tsx
  17. 2
      src/pages/browser/todo-list.md
  18. 2
      src/pages/org/wallet-use.md
  19. 2
      yarn.lock

5
next.config.js

@ -2,6 +2,8 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({
enabled: process.env.ANALYZE === 'true', enabled: process.env.ANALYZE === 'true',
}); });
const webpack = require('webpack');
const path = require('path'); const path = require('path');
const remarkPlugins = [ const remarkPlugins = [
@ -68,6 +70,9 @@ module.exports = withBundleAnalyzer({
aliases.react = aliases['react-dom'] = 'preact/compat'; aliases.react = aliases['react-dom'] = 'preact/compat';
aliases['react-ssr-prepass'] = 'preact-ssr-prepass'; aliases['react-ssr-prepass'] = 'preact-ssr-prepass';
// to fix a dupe dependency
config.externals.push('prismjs');
// https://github.com/FormidableLabs/react-live#what-bundle-size-can-i-expect // https://github.com/FormidableLabs/react-live#what-bundle-size-can-i-expect
aliases['buble'] = '@philpl/buble'; aliases['buble'] = '@philpl/buble';
} }

2
package.json

@ -46,7 +46,7 @@
"preact-ssr-prepass": "^1.1.0", "preact-ssr-prepass": "^1.1.0",
"prettier": "^2.0.5", "prettier": "^2.0.5",
"preval.macro": "^5.0.0", "preval.macro": "^5.0.0",
"prism-react-renderer": "^1.0.2", "prism-react-renderer": "^1.1.1",
"prismjs": "^1.20.0", "prismjs": "^1.20.0",
"react-children-utilities": "^2.1.3", "react-children-utilities": "^2.1.3",
"react-gesture-responder": "^2.1.0", "react-gesture-responder": "^2.1.0",

100
src/components/code-block/components.tsx

@ -1,100 +0,0 @@
import React, { useContext } from 'react';
import { LiveProvider, LiveContext, LivePreview } from 'react-live';
import {
Flex,
Button,
Box,
CodeBlock as BaseCodeBlock,
space,
useClipboard,
BoxProps,
} from '@blockstack/ui';
import { CodeEditor } from '@components/code-editor';
import { Text } from '@components/typography';
import { border } from '@common/utils';
const Error = () => {
const { error } = useContext(LiveContext);
return error ? <BaseCodeBlock mt={space('base')} code={error} /> : null;
};
export const liveEditorStyle = {
fontSize: 14,
marginBottom: 24,
marginTop: 24,
overflowX: 'auto',
fontFamily: 'Menlo,monospace',
borderRadius: 10,
};
export const liveErrorStyle = {
fontFamily: 'Fira Mono, monospace',
fontSize: 14,
padding: '1em',
overflowX: 'auto',
color: 'white',
marginTop: '16px',
border: '1px solid var(--colors-border)',
backgroundColor: 'var(--colors-bg-light)',
};
export const LiveCodePreview = (props: any) => (
<Box fontFamily="body">
<Box
as={LivePreview}
boxShadow="mid"
border={border()}
borderRadius="6px"
p={space('base')}
mb={space('base')}
{...props}
/>
</Box>
);
const Label = (props: BoxProps) => (
<Text fontWeight={500} fontFamily="body" fontSize="12px" {...props} />
);
export const JsxEditor = ({ liveProviderProps, editorCode, handleCodeChange, language }) => {
const { hasCopied, onCopy } = useClipboard(editorCode);
return (
<LiveProvider {...liveProviderProps}>
<Box mb={space('extra-tight')}>
<Label>Preview</Label>
</Box>
<LiveCodePreview />
<Flex justify="space-between" fontFamily="'Inter'" align="flex-end" mb={space('tight')}>
<Label>Editable example</Label>
<Button onClick={onCopy} size="sm" mode="secondary">
{hasCopied ? 'Copied!' : 'Copy example code'}
</Button>
</Flex>
<CodeEditor value={editorCode} onChange={handleCodeChange} language={language} />
<Error />
</LiveProvider>
);
};
export const Preview = ({ liveProviderProps }) => (
<Box>
<LiveProvider {...liveProviderProps}>
<LiveCodePreview />
</LiveProvider>
</Box>
);
export const SimpleCodeBlock = ({ editorCode, language, ...rest }) => (
<BaseCodeBlock
borderTop={'1px solid rgb(39, 41, 46)'}
borderBottom={'1px solid rgb(39, 41, 46)'}
borderLeft={['none', '1px solid rgb(39, 41, 46)', '1px solid rgb(39, 41, 46)']}
borderRight={['none', '1px solid rgb(39, 41, 46)', '1px solid rgb(39, 41, 46)']}
borderRadius={['unset', 'unset', '12px', '12px']}
code={editorCode}
language={language}
{...rest}
/>
);

94
src/components/code-block/index.tsx

@ -7,28 +7,80 @@ import 'prismjs/components/prism-json';
import 'prismjs/components/prism-toml'; import 'prismjs/components/prism-toml';
import 'prismjs/components/prism-python'; import 'prismjs/components/prism-python';
import 'prismjs/components/prism-kotlin'; import 'prismjs/components/prism-kotlin';
import { Highlighter, HighlighterProps } from '../highlighter';
import { Box, BoxProps } from '@blockstack/ui';
import { css } from '@styled-system/css';
import { SimpleCodeBlock } from '@components/code-block/components'; interface CodeBlock {
import { useForceUpdate } from '@blockstack/ui'; live?: boolean;
highlight?: string;
export const CodeBlock = React.memo( }
({ className, live = true, isManual, render, children, ...props }: any) => {
const update = useForceUpdate(); export type CodeBlockProps = CodeBlock & HighlighterProps & BoxProps;
React.useEffect(() => {
update(); const getHighlightLineNumbers = str =>
}, []); str &&
str
const language = className && className.replace(/language-/, ''); .split(' ')
.join('')
return ( .split(',')
<SimpleCodeBlock .flatMap(s => {
editorCode={children.toString()} if (!s.includes('-')) return +s;
showLineNumbers={language !== 'bash'}
language={language} const [min, max] = s.split('-');
{...props}
/> return Array.from({ length: max - min + 1 }, (_, n) => n + +min);
); });
}
const CodeBlock = React.memo(
React.forwardRef(
(
{
code,
showLineNumbers,
hideLineHover,
style = {},
highlightedLines,
className,
live = true,
highlight,
children,
...props
}: CodeBlockProps,
ref: React.Ref<HTMLDivElement>
) => {
const language = className && className.replace(/language-/, '');
return (
<Box
className={language !== 'bash' ? 'line-numbers' : ''}
bg="ink"
borderRadius={[0, 0, '12px']}
overflowX="auto"
>
<Box
ref={ref}
css={css({
...style,
// @ts-ignore
color: 'white',
// @ts-ignore
whiteSpace: 'pre',
...props,
})}
>
<Highlighter
language={language as any}
code={children.toString().trim()}
showLineNumbers={language && language !== 'bash'}
highlightedLines={getHighlightLineNumbers(highlight)}
hideLineHover
/>
</Box>
</Box>
);
}
)
); );
export default CodeBlock; export default CodeBlock;

88
src/components/feedback.tsx

@ -1,5 +1,17 @@
import React from 'react'; import React from 'react';
import { Box, BoxProps, color, Flex, space, Stack, themeColor } from '@blockstack/ui'; import {
Box,
Button,
BoxProps,
color,
Flex,
space,
Stack,
themeColor,
transition,
SlideFade,
} from '@blockstack/ui';
import { Text } from '@components/typography';
import { MDXComponents, Link } from '@components/mdx'; import { MDXComponents, Link } from '@components/mdx';
import { SadIcon, NeutralIcon, HappyIcon } from '@components/icons/feedback'; import { SadIcon, NeutralIcon, HappyIcon } from '@components/icons/feedback';
import { useHover } from 'use-events'; import { useHover } from 'use-events';
@ -15,8 +27,63 @@ const Icon: React.FC<BoxProps & { icon: React.FC<any> }> = ({ icon: IconComponen
); );
}; };
const FeedbackCard = ({ show, onClose }) => {
return (
<SlideFade in={show}>
{styles => (
<Box ml={space('base-loose')} p={space('base')}>
<Flex
p={space('base')}
border={border()}
borderRadius="12px"
align="center"
justifyContent="center"
bg={color('bg')}
size="100%"
boxShadow="mid"
transition={transition}
_hover={{
transform: 'translateY(-5px)',
boxShadow: 'high',
}}
style={{
...styles,
}}
>
<Box>
<Button
as="a"
// @ts-ignore
href="https://forms.formium.io/f/5f174a3960b46d000139b62f"
target="_blank"
>
Leave feedback
</Button>
<Box
_hover={{ color: color('accent'), textDecoration: 'underline', cursor: 'pointer' }}
onClick={onClose}
mt={space('tight')}
textAlign="center"
mx="auto"
>
<Text color="currentColor" fontSize="14px">
Dismiss
</Text>
</Box>
</Box>
</Flex>
</Box>
)}
</SlideFade>
);
};
export const FeedbackSection: React.FC<BoxProps> = props => { export const FeedbackSection: React.FC<BoxProps> = props => {
const { pathname } = useRouter(); const { pathname } = useRouter();
const [showButton, setShowButton] = React.useState(false);
const handleShow = () => {
setShowButton(!showButton);
};
return ( return (
<Flex <Flex
flexDirection={['column', 'column', 'row']} flexDirection={['column', 'column', 'row']}
@ -24,14 +91,17 @@ export const FeedbackSection: React.FC<BoxProps> = props => {
borderTop={border()} borderTop={border()}
mt={space('extra-loose')} mt={space('extra-loose')}
> >
<Box> <Flex>
<MDXComponents.h3>Was this page helpful?</MDXComponents.h3> <Box position="relative">
<Stack isInline spacing={space('base-loose')} mt={space('base-loose')}> <MDXComponents.h4>Was this page helpful?</MDXComponents.h4>
<Icon icon={SadIcon} /> <Stack isInline spacing={space('base-loose')} mt={space('base-loose')}>
<Icon icon={NeutralIcon} /> <Icon onClick={() => handleShow()} icon={SadIcon} />
<Icon icon={HappyIcon} /> <Icon onClick={() => handleShow()} icon={NeutralIcon} />
</Stack> <Icon onClick={() => handleShow()} icon={HappyIcon} />
</Box> </Stack>
</Box>
<FeedbackCard show={showButton} onClose={() => setShowButton(false)} />
</Flex>
<Box mt={space(['extra-loose', 'extra-loose', 'base-loose'])}> <Box mt={space(['extra-loose', 'extra-loose', 'base-loose'])}>
<Link <Link
href={`https://github.com/blockstack/docs.blockstack/tree/feat/next/src/pages${pathname}.md`} href={`https://github.com/blockstack/docs.blockstack/tree/feat/next/src/pages${pathname}.md`}

1
src/components/footer.tsx

@ -3,7 +3,6 @@ import { Pagination } from '@components/pagination';
import { Section, SectionWrapper } from '@components/home/common'; import { Section, SectionWrapper } from '@components/home/common';
import { FeedbackSection } from '@components/feedback'; import { FeedbackSection } from '@components/feedback';
const Footer = ({ hidePagination, ...rest }: any) => { const Footer = ({ hidePagination, ...rest }: any) => {
return ( return (
<Section> <Section>

237
src/components/header.tsx

@ -1,6 +1,16 @@
import React from 'react'; import React from 'react';
import { Flex, Box, BlockstackIcon, Portal, Input, color, space } from '@blockstack/ui'; import {
import { Link, Text } from '@components/typography'; Flex,
Box,
BlockstackIcon,
Stack,
color,
space,
themeColor,
transition,
ChevronIcon,
} from '@blockstack/ui';
import { Link, Text, LinkProps } from '@components/typography';
import MenuIcon from 'mdi-react/MenuIcon'; import MenuIcon from 'mdi-react/MenuIcon';
import CloseIcon from 'mdi-react/CloseIcon'; import CloseIcon from 'mdi-react/CloseIcon';
import { useLockBodyScroll } from '@common/hooks/use-lock-body-scroll'; import { useLockBodyScroll } from '@common/hooks/use-lock-body-scroll';
@ -9,10 +19,12 @@ import { SideNav } from './side-nav';
import GithubIcon from 'mdi-react/GithubIcon'; import GithubIcon from 'mdi-react/GithubIcon';
import { IconButton } from '@components/icon-button'; import { IconButton } from '@components/icon-button';
import { border } from '@common/utils'; import { border } from '@common/utils';
import useSWR from 'swr'; import routes from '@common/routes';
import { useAppState } from '@common/hooks/use-app-state';
import { css } from '@styled-system/css'; import { css } from '@styled-system/css';
import NextLink from 'next/link'; import NextLink from 'next/link';
import MagnifyIcon from 'mdi-react/MagnifyIcon';
import { useRouter } from 'next/router';
const MenuButton = ({ ...rest }: any) => { const MenuButton = ({ ...rest }: any) => {
const { isOpen, handleOpen, handleClose } = useMobileMenuState(); const { isOpen, handleOpen, handleClose } = useMobileMenuState();
const Icon = isOpen ? CloseIcon : MenuIcon; const Icon = isOpen ? CloseIcon : MenuIcon;
@ -28,8 +40,50 @@ const MenuButton = ({ ...rest }: any) => {
</Flex> </Flex>
); );
}; };
const BreadCrumbs: React.FC<any> = props => {
const router = useRouter();
const [route, setRoute] = React.useState(undefined);
const [section, setSection] = React.useState(undefined);
React.useEffect(() => {
routes.forEach(_section => {
_section?.routes?.length &&
_section.routes.forEach(_route => {
if (router.route === `/${_route.path}`) {
setSection(_section);
setRoute(_route);
}
});
});
}, [router.route]);
const GithubButton = () => ( return (
<Flex align="center">
<Box>
<Text fontSize="14px" fontWeight="600">
Docs
</Text>
</Box>
<Box pt="3px" color={color('text-caption')}>
<ChevronIcon size="20px" />
</Box>
<Box>
<Text fontSize="14px" fontWeight="600">
{section?.title}
</Text>
</Box>
<Box pt="3px" color={color('text-caption')}>
<ChevronIcon size="20px" />
</Box>
<Box>
<Text fontSize="14px" fontWeight="600">
{route?.title || (route?.headings.length && route.headings[0])}
</Text>
</Box>
</Flex>
);
};
const GithubButton = (props: LinkProps) => (
<IconButton <IconButton
as="a" as="a"
href="https://github.com/blockstack/ux/tree/master/packages/ui#blockstack-ui" href="https://github.com/blockstack/ux/tree/master/packages/ui#blockstack-ui"
@ -42,6 +96,7 @@ const GithubButton = () => (
style={{ style={{
alignItems: 'center', alignItems: 'center',
}} }}
{...props}
> >
<Text position="absolute" opacity={0} as="label"> <Text position="absolute" opacity={0} as="label">
Find us on GitHub Find us on GitHub
@ -56,8 +111,8 @@ const MobileSideNav = () => {
return ( return (
<SideNav <SideNav
position="fixed" position="fixed"
top="50px" top={`${HEADER_HEIGHT}px`}
maxHeight="calc(100vh - 50px)" maxHeight={`calc(100vh - ${HEADER_HEIGHT}px)`}
width="100%" width="100%"
zIndex={99} zIndex={99}
bg={color('bg')} bg={color('bg')}
@ -67,64 +122,122 @@ const MobileSideNav = () => {
); );
}; };
const Header = ({ ...rest }: any) => { const HeaderWrapper: React.FC<any> = props => (
<Box position="fixed" zIndex={9999} width="100%" {...props} />
);
const nav = [
{
label: 'Developers',
},
{ label: 'Run a node' },
{ label: 'Build on Blockstack' },
];
const SubBar: React.FC<any> = props => (
<Flex
justifyContent="space-between"
align="center"
height="60px"
width="100%"
px={['extra-loose', 'extra-loose', 'base', 'base']}
bg="rgba(255,255,255, 0.8)"
borderBottom={border()}
style={{
backdropFilter: 'blur(5px)',
}}
>
<BreadCrumbs />
<Flex
align="center"
justifyContent="flex-end"
bg={themeColor('ink.150')}
height="32px"
width="32px"
borderRadius="32px"
transition={transition}
px={space('tight')}
_hover={{
bg: themeColor('ink.200'),
width: ['32px', '32px', '225px', '225px'],
cursor: 'pointer',
justifyContent: 'flex-end',
}}
>
<MagnifyIcon size="16px" />
</Flex>
</Flex>
);
export const HEADER_HEIGHT = 132;
const Header = ({ hideSubBar, ...rest }: any) => {
return ( return (
<> <>
<Flex <HeaderWrapper>
justifyContent="space-between" <Flex
align="center" justifyContent="space-between"
px="base" align="center"
position="fixed" px={['extra-loose', 'extra-loose', 'base', 'base']}
zIndex={9999} bg="rgba(255,255,255, 0.8)"
width="100%" borderBottom={border()}
bg="rgba(255,255,255, 0.8)" style={{
borderBottom={border()} backdropFilter: 'blur(5px)',
style={{ }}
backdropFilter: 'blur(5px)', height="72px"
}} {...rest}
height="50px" >
boxShadow="mid" <NextLink href="/" passHref>
{...rest} <Link as="a">
> <Flex align="center">
<NextLink href="/" passHref> <Box color={color('invert')} mr={space('tight')}>
<Link as="a"> <BlockstackIcon size="20px" />
<Flex align="center"> </Box>
<Box color={color('invert')} mr={space('tight')}> <Box>
<BlockstackIcon size="20px" /> <Text
</Box> color={color('invert')}
<Box> css={css({
<Text fontWeight: 500,
color={color('invert')} fontSize: '13.75px',
css={css({ lineHeight: '14px',
fontWeight: 500, padding: '0.05px 0',
fontSize: '13.75px', ':before': {
lineHeight: '14px', content: "''",
padding: '0.05px 0', marginTop: '-0.14909090909090908em',
':before': { display: 'block',
content: "''", height: 0,
marginTop: '-0.14909090909090908em', },
display: 'block', ':after': {
height: 0, content: "''",
}, marginBottom: '-0.14909090909090908em',
':after': { display: 'block',
content: "''", height: 0,
marginBottom: '-0.14909090909090908em', },
display: 'block', })}
height: 0, >
}, Blockstack
})} </Text>
> </Box>
Blockstack </Flex>
</Text> </Link>
</Box> </NextLink>
</Flex> <Flex align="center">
</Link> <Box display={['none', 'none', 'block']}>
</NextLink> <Stack mr={space('base')} isInline spacing={space('base')}>
<Flex align="center"> {nav.map(item => (
<GithubButton /> <Box>
<MenuButton /> <Text fontSize="14px" fontWeight="600">
{item.label}
</Text>
</Box>
))}
</Stack>
</Box>
<GithubButton />
<MenuButton />
</Flex>
</Flex> </Flex>
</Flex> {!hideSubBar && <SubBar />}
</HeaderWrapper>
<MobileSideNav /> <MobileSideNav />
</> </>
); );

230
src/components/highlighter/index.tsx

@ -0,0 +1,230 @@
import React from 'react';
import Highlight from 'prism-react-renderer';
import { Box, Flex, space, useTheme } from '@blockstack/ui';
import { GrammaticalToken, GetGrammaticalTokenProps, RenderProps, Language } from './types';
import Prism from 'prism-react-renderer/prism';
import { theme } from '@components/highlighter/prism-theme';
import './language-definition';
import { css } from '@styled-system/css';
const startPad = (n: number, z = 2, s = '0') =>
(n + '').length <= z ? ['', '-'][+(n < 0)] + (s.repeat(z) + Math.abs(n)).slice(-1 * z) : n + '';
const LINE_NUMBER_WIDTH = 50;
const getLineNumber = (n: number, length: number) => startPad(n + 1, length.toString().length);
const Tokens = ({
tokens,
getTokenProps,
showLineNumbers,
...rest
}: {
tokens: GrammaticalToken[];
getTokenProps: GetGrammaticalTokenProps;
showLineNumbers?: boolean;
}) => {
const bsTheme = useTheme();
const pl = showLineNumbers
? [`calc(${LINE_NUMBER_WIDTH}px + ${(bsTheme as any).sizes['base']})`]
: ['unset', 'unset', 'base', 'base'];
return (
<Box pl={pl} pr="base" position="relative" zIndex={2} {...rest}>
{tokens.map(
(token, key) =>
token.content !== '// highlight' && (
<Box py="2px" display="inline-block" {...getTokenProps({ token, key })} />
)
)}
</Box>
);
};
const LineNumber = ({ number, length, ...rest }: { number: number; length: number }) => (
<Flex
textAlign="right"
pr={space('tight')}
pl={space('tight')}
width={LINE_NUMBER_WIDTH}
borderRight="1px solid"
borderRightColor="inherit"
color="ink.400"
flexShrink={0}
style={{ userSelect: 'none' }}
position="absolute"
left={0}
height="100%"
align="baseline"
justify="center"
zIndex={1}
css={css({
color: 'rgba(255,255,255,0.6)',
whiteSpace: 'pre',
fontFamily: 'Fira Code, Consolata, monospace',
fontSize: '14.556040756914118px',
lineHeight: '24px',
padding: '2px 0',
'::before': {
content: "''",
marginTop: '-0.47483499999999995em',
display: 'block',
height: 0,
},
'::after': {
content: "''",
marginBottom: '-0.493835em',
display: 'block',
height: 0,
},
})}
{...rest}
>
{getLineNumber(number, length)}
</Flex>
);
const Line = ({
tokens,
getTokenProps,
index,
length,
showLineNumbers,
hideLineHover,
highlighted,
...rest
}: {
tokens: GrammaticalToken[];
index: number;
length: number;
getTokenProps: GetGrammaticalTokenProps;
showLineNumbers?: boolean;
hideLineHover?: boolean;
highlighted?: boolean;
}) => {
const highlightedStyle = {
bg: ['unset', 'unset', 'ink.900'],
borderColor: ['ink.900', 'ink.900', 'ink.600'],
};
const hasHighlightComment = !!tokens.find(token => token.content === '// highlight');
const isHighlighted = highlighted || hasHighlightComment;
const highlighedProps = isHighlighted ? highlightedStyle : {};
return (
<Flex
height="loose"
align="baseline"
borderColor="ink.900"
_hover={hideLineHover ? undefined : highlightedStyle}
position="relative"
{...highlighedProps}
{...rest}
>
{showLineNumbers ? <LineNumber number={index} length={length} /> : null}
<Tokens showLineNumbers={showLineNumbers} getTokenProps={getTokenProps} tokens={tokens} />
</Flex>
);
};
const Spacer = ({ showLineNumbers }: { showLineNumbers?: boolean }) => (
<Flex height="base-loose" bg="ink" width="100%">
{showLineNumbers && (
<Box
height="base-loose"
borderRight="1px solid"
borderRightColor="ink.900"
width={`${LINE_NUMBER_WIDTH}px`}
/>
)}
</Flex>
);
const Lines = ({
tokens: lines,
getLineProps,
getTokenProps,
className,
showLineNumbers,
hideLineHover,
highlightedLines,
}: {
showLineNumbers?: boolean;
hideLineHover?: boolean;
highlightedLines?: number[];
} & RenderProps) => {
return (
<Box display="block" className={className}>
<Box display="block" style={{ fontFamily: 'Fira Code' }}>
<Spacer showLineNumbers={showLineNumbers} />
{lines.map((tokens, i) => (
<Box
css={css({
'& > *': {
fontFamily: 'Fira Code, Consolata, monospace',
fontSize: '14.556040756914118px',
lineHeight: '24px',
padding: '0.05px 0',
'::before': {
content: "''",
marginTop: '-0.47483499999999995em',
display: 'block',
height: 0,
},
'::after': {
content: "''",
marginBottom: '-0.493835em',
display: 'block',
height: 0,
},
},
})}
>
<Line
index={i}
tokens={tokens}
getTokenProps={getTokenProps}
length={lines.length + 1}
showLineNumbers={showLineNumbers}
highlighted={
highlightedLines?.length && !!highlightedLines.find(lineNumber => lineNumber === i)
}
hideLineHover={hideLineHover || lines.length < 3}
{...getLineProps({ line: tokens, key: i })}
/>
</Box>
))}
<Spacer showLineNumbers={showLineNumbers} />
</Box>
</Box>
);
};
export interface HighlighterProps {
code: string;
language?: Language;
showLineNumbers?: boolean;
hideLineHover?: boolean;
highlightedLines?: number[];
}
export const Highlighter = React.memo(
({
code,
language = 'clarity',
showLineNumbers,
hideLineHover,
highlightedLines,
}: HighlighterProps) => {
return (
<Highlight Prism={Prism} theme={theme} code={code} language={language as any}>
{props => (
<Lines
showLineNumbers={showLineNumbers}
highlightedLines={highlightedLines}
hideLineHover={hideLineHover}
{...props}
/>
)}
</Highlight>
);
}
);
Highlighter.displayName = 'Highlighter';

117
src/components/highlighter/language-definition.tsx

@ -0,0 +1,117 @@
// @ts-nocheck
import Prism from 'prism-react-renderer/prism';
(function (Prism) {
// Functions to construct regular expressions
// simple form
// e.g. (interactive ... or (interactive)
function simple_form(name) {
return RegExp('(\\()' + name + '(?=[\\s\\)])');
}
// booleans and numbers
function primitive(pattern) {
return RegExp('([\\s([])' + pattern + '(?=[\\s)])');
}
// Patterns in regular expressions
// Open parenthesis for look-behind
const par = '(\\()';
const endpar = '(?=\\))';
// End the pattern with look-ahead space
const space = '(?=\\s)';
const language = {
// Three or four semicolons are considered a heading.
heading: {
pattern: /;;;.*/,
alias: ['comment', 'title'],
},
comment: /;;.*/,
string: [
{
pattern: /"(?:[^"\\]|\\.)*"/,
greedy: true,
},
{
pattern: /0x[0-9a-fA-F]*/,
greedy: true,
},
],
symbol: {
pattern: /'[^()#'\s]+/,
greedy: true,
},
keyword: [
{
pattern: RegExp(
par +
'(?:or|and|xor|not|begin|let|if|ok|err|unwrap\\!|unwrap-err\\!|unwrap-panic|unwrap-err-panic|match|try\\!|asserts\\!|\
map-get\\?|var-get|contract-map-get\\?|get|tuple|\
define-public|define-private|define-constant|define-map|define-data-var|\
define-fungible-token|define-non-fungible-token|\
define-read-only)' +
space
),
lookbehind: true,
},
{
pattern: RegExp(par + '(?:is-eq|is-some|is-none|is-ok|is-er)' + space),
lookbehind: true,
},
{
pattern: RegExp(
par +
'(?:var-set|map-set|map-delete|map-insert|\
ft-transfer\\?|nft-transfer\\?|nft-mint\\?|ft-mint\\?|nft-get-owner\\?|ft-get-balance\\?|\
contract-call\\?)' +
space
),
lookbehind: true,
},
{
pattern: RegExp(
par +
'(?:list|map|filter|fold|len|concat|append|as-max-len\\?|to-int|to-uint|\
buff|hash160|sha256|sha512|sha512/256|keccak256|true|false|none)' +
space
),
lookbehind: true,
},
{
pattern: RegExp(
par +
'(?:as-contract|contract-caller|tx-sender|block-height|at-block|get-block-info\\?)' +
space
),
lookbehind: true,
},
{
pattern: RegExp(par + '(?:is-eq|is-some|is-none|is-ok|is-err)' + space),
lookbehind: true,
},
],
boolean: /(?:false|true|none)/,
number: {
pattern: primitive('[-]?u?\\d+'),
lookbehind: true,
},
address: {
pattern: /([\s()])(?:\'[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{28,41})(?=[()\s]|$)/,
lookbehind: true,
},
operator: {
pattern: /(\()(?:[-+*\/]|[<>]=?|=>?)(?=[()\s]|$)/,
lookbehind: true,
},
function: {
pattern: /(\()[^()'\s]+(?=[()\s]|$)/,
lookbehind: true,
},
punctuation: /[()']/,
};
if (Prism && Prism.languages) {
Prism.languages.clarity = language;
}
})(Prism);

205
src/components/highlighter/nord.css

@ -0,0 +1,205 @@
code[class*="language-"],
pre[class*="language-"] {
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
color: #eee;
background: #2f2f2f;
font-family: Roboto Mono, monospace;
font-size: 1em;
line-height: 1.5em;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
code[class*="language-"]::-moz-selection,
pre[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection,
pre[class*="language-"] ::-moz-selection {
background: #363636;
}
code[class*="language-"]::selection,
pre[class*="language-"]::selection,
code[class*="language-"] ::selection,
pre[class*="language-"] ::selection {
background: #363636;
}
:not(pre) > code[class*="language-"] {
white-space: normal;
border-radius: 0.2em;
padding: 0.1em;
}
pre[class*="language-"] {
overflow: auto;
position: relative;
margin: 0.5em 0;
padding: 1.25em 1em;
}
.language-css > code,
.language-sass > code,
.language-scss > code {
color: #fd9170;
}
[class*="language-"] .namespace {
opacity: 0.7;
}
.token.atrule {
color: #c792ea;
}
.token.attr-name {
color: #ffcb6b;
}
.token.attr-value {
color: #a5e844;
}
.token.attribute {
color: #a5e844;
}
.token.boolean {
color: #c792ea;
}
.token.builtin {
color: #ffcb6b;
}
.token.cdata {
color: #80cbc4;
}
.token.char {
color: #80cbc4;
}
.token.class {
color: #ffcb6b;
}
.token.class-name {
color: #f2ff00;
}
.token.comment {
color: #616161;
}
.token.constant {
color: #c792ea;
}
.token.deleted {
color: #ff6666;
}
.token.doctype {
color: #616161;
}
.token.entity {
color: #ff6666;
}
.token.function {
color: #c792ea;
}
.token.hexcode {
color: #f2ff00;
}
.token.id {
color: #c792ea;
font-weight: bold;
}
.token.important {
color: #c792ea;
font-weight: bold;
}
.token.inserted {
color: #80cbc4;
}
.token.keyword {
color: #c792ea;
}
.token.number {
color: #fd9170;
}
.token.operator {
color: #89ddff;
}
.token.prolog {
color: #616161;
}
.token.property {
color: #80cbc4;
}
.token.pseudo-class {
color: #a5e844;
}
.token.pseudo-element {
color: #a5e844;
}
.token.punctuation {
color: #89ddff;
}
.token.regex {
color: #f2ff00;
}
.token.selector {
color: #ff6666;
}
.token.string {
color: #a5e844;
}
.token.symbol {
color: #c792ea;
}
.token.tag {
color: #ff6666;
}
.token.unit {
color: #fd9170;
}
.token.url {
color: #ff6666;
}
.token.variable {
color: #ff6666;
}

140
src/components/highlighter/prism-theme.ts

@ -0,0 +1,140 @@
import { PrismTheme } from 'prism-react-renderer';
export const theme: PrismTheme = {
plain: {
color: 'rgba(255,255,255,1)',
backgroundColor: '#0f111a',
},
styles: [
{
types: ['string'],
style: {
color: 'rgb(195, 232, 141)',
},
},
{
types: ['boolean'],
style: {
color: 'rgb(255, 156, 172)',
},
},
{
types: ['number', 'keyword', 'operator'],
style: {
color: 'rgb(247, 140, 108)',
},
},
{
types: ['comment'],
style: {
color: 'rgba(255,255,255,0.6)',
fontStyle: 'italic',
},
},
{
types: ['punctuation', 'builtin'],
style: {
color: 'rgb(137, 221, 255)',
},
},
{
types: ['tag'],
style: {
color: 'rgb(240, 113, 120)',
},
},
{
types: ['attr-name'],
style: {
color: 'rgb(255, 203, 107)',
},
},
{
types: ['function'],
style: {
color: 'rgb(130, 170, 255)',
},
},
{
types: ['constant'],
style: {
color: 'rgb(137, 221, 255)',
fontStyle: 'italic',
},
},
],
};
// export const theme: PrismTheme = {
// plain: {
// color: '#fff',
// backgroundColor: 'transparent',
// },
// styles: [
// {
// types: ['prolog'],
// style: {
// color: 'rgb(0, 0, 128)',
// },
// },
// {
// types: ['comment', 'punctuation'],
// style: {
// color: 'rgb(106, 153, 85)',
// },
// },
// {
// types: ['builtin', 'tag', 'changed', 'function', 'keyword'],
// style: {
// color: 'rgb(86, 156, 214)',
// },
// },
// {
// types: ['number', 'variable', 'inserted'],
// style: {
// color: '#A58FFF',
// },
// },
// {
// types: ['operator'],
// style: {
// color: 'rgb(212, 212, 212)',
// },
// },
// {
// types: ['constant'],
// style: {
// color: 'rgb(100, 102, 149)',
// },
// },
// {
// types: ['attr-name'],
// style: {
// color: 'rgb(156, 220, 254)',
// },
// },
// {
// types: ['car'],
// style: {
// color: 'rgb(156, 220, 254)',
// },
// },
// {
// types: ['deleted', 'string'],
// style: {
// color: '#FF7B48',
// },
// },
// {
// types: ['class-name'],
// style: {
// color: 'rgb(78, 201, 176)',
// },
// },
// {
// types: ['char'],
// style: {
// color: '#FF7B48',
// },
// },
// ],
// };

90
src/components/highlighter/types.ts

@ -0,0 +1,90 @@
import * as React from 'react';
export interface GrammaticalToken {
types: string[];
content: string;
empty?: boolean;
}
export interface StyleObj {
[key: string]: string | number | null;
}
export interface GrammaticalTokenOutputProps {
key?: React.Key;
style?: StyleObj;
className: string;
children: string;
[otherProp: string]: any;
}
export interface GrammaticalTokenInputProps {
key?: React.Key;
style?: StyleObj;
className?: string;
token: GrammaticalToken;
[otherProp: string]: any;
}
export interface LineInputProps {
key?: React.Key;
style?: StyleObj;
className?: string;
line: GrammaticalToken[];
[otherProp: string]: any;
}
export interface LineOutputProps {
key?: React.Key;
style?: StyleObj;
className: string;
[otherProps: string]: any;
}
export interface RenderProps {
tokens: GrammaticalToken[][];
className: string;
style: StyleObj;
getLineProps: (input: LineInputProps) => LineOutputProps;
getTokenProps: (input: GrammaticalTokenInputProps) => GrammaticalTokenOutputProps;
}
export type GetGrammaticalTokenProps = (
input: GrammaticalTokenInputProps
) => GrammaticalTokenOutputProps;
export type Language =
| 'markup'
| 'bash'
| 'clarity'
| 'clike'
| 'c'
| 'cpp'
| 'css'
| 'javascript'
| 'jsx'
| 'coffeescript'
| 'actionscript'
| 'css-extr'
| 'diff'
| 'git'
| 'go'
| 'graphql'
| 'handlebars'
| 'json'
| 'less'
| 'lisp'
| 'makefile'
| 'markdown'
| 'objectivec'
| 'ocaml'
| 'python'
| 'reason'
| 'sass'
| 'scss'
| 'sql'
| 'stylus'
| 'tsx'
| 'typescript'
| 'wasm'
| 'yaml';

3
src/components/home/sections/hero.tsx

@ -4,11 +4,12 @@ import { CircleIcon } from '@components/home/common';
import { CONTENT_MAX_WIDTH } from '@common/constants'; import { CONTENT_MAX_WIDTH } from '@common/constants';
import { Card } from '@components/home/card'; import { Card } from '@components/home/card';
import { Body, H1, BodyLarge, SubHeading } from '@components/home/text'; import { Body, H1, BodyLarge, SubHeading } from '@components/home/text';
import { HEADER_HEIGHT } from '@components/header';
export const Hero = ({ cards }: { cards?: any }) => { export const Hero = ({ cards }: { cards?: any }) => {
return ( return (
<> <>
<Grid pb="64px" pt="64px" style={{ placeItems: 'center' }} mt="50px"> <Grid pb="64px" pt="64px" style={{ placeItems: 'center' }}>
<Box maxWidth="62ch" textAlign="center"> <Box maxWidth="62ch" textAlign="center">
<H1 mb={space('base')}>Easily build decentralized apps</H1> <H1 mb={space('base')}>Easily build decentralized apps</H1>
<BodyLarge maxWidth="42ch" mt="64px" mx="auto"> <BodyLarge maxWidth="42ch" mt="64px" mx="auto">

12
src/components/layouts/docs-layout.tsx

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Flex, color, space, themeColor } from '@blockstack/ui'; import { Flex, color, space, themeColor, ChevronIcon } from '@blockstack/ui';
import { SideNav } from '../side-nav'; import { SideNav } from '../side-nav';
import { Header } from '../header'; import { Header, HEADER_HEIGHT } from '../header';
import { Main } from '../main'; import { Main } from '../main';
import { Footer } from '../footer'; import { Footer } from '../footer';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
@ -225,7 +225,7 @@ const styleOverwrites = {
img: { img: {
my: space('extra-loose'), my: space('extra-loose'),
}, },
'& > pre > *:not(pre)': { '& > pre > *:not(pre):not(.line-numbers)': {
border: 'none', border: 'none',
px: space(['extra-loose', 'extra-loose', 'none', 'none']), px: space(['extra-loose', 'extra-loose', 'none', 'none']),
}, },
@ -273,7 +273,7 @@ export const Contents = ({ headings, children }) => (
<TableOfContents <TableOfContents
display={['none', 'none', 'block', 'block']} display={['none', 'none', 'block', 'block']}
position="sticky" position="sticky"
top="105px" top="195px"
pl={space('extra-loose')} pl={space('extra-loose')}
headings={headings} headings={headings}
/> />
@ -292,7 +292,7 @@ const DocsLayout: React.FC<{ isHome?: boolean }> = ({ children, isHome }) => {
}); });
return ( return (
<Flex minHeight="100vh" flexDirection="column"> <Flex minHeight="100vh" flexDirection="column">
<Header /> <Header hideSubBar={isHome || isErrorPage} />
<Flex width="100%" flexGrow={1}> <Flex width="100%" flexGrow={1}>
{!isHome && <SideNav display={['none', 'none', 'block']} />} {!isHome && <SideNav display={['none', 'none', 'block']} />}
<Flex <Flex
@ -303,7 +303,7 @@ const DocsLayout: React.FC<{ isHome?: boolean }> = ({ children, isHome }) => {
`calc(100% - ${isHome ? 0 : SIDEBAR_WIDTH}px)`, `calc(100% - ${isHome ? 0 : SIDEBAR_WIDTH}px)`,
`calc(100% - ${isHome ? 0 : SIDEBAR_WIDTH}px)`, `calc(100% - ${isHome ? 0 : SIDEBAR_WIDTH}px)`,
]} ]}
mt={'50px'} mt={`${HEADER_HEIGHT}px`}
flexDirection="column" flexDirection="column"
> >
<Main mx="unset" width={'100%'}> <Main mx="unset" width={'100%'}>

2
src/components/mdx/mdx-components.tsx

@ -14,7 +14,7 @@ import { Text } from '@components/typography';
import { border } from '@common/utils'; import { border } from '@common/utils';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
const Code = dynamic(() => import('../code-block')); const Code = dynamic(() => import('../code-block/index'));
const BaseHeading: React.FC<BoxProps> = React.memo(props => ( const BaseHeading: React.FC<BoxProps> = React.memo(props => (
<Heading width="100%" mt={space('base-loose')} {...props} /> <Heading width="100%" mt={space('base-loose')} {...props} />

7
src/components/side-nav.tsx

@ -9,13 +9,14 @@ import { useMobileMenuState } from '@common/hooks/use-mobile-menu';
import dynamic from 'next/dynamic'; import dynamic from 'next/dynamic';
const SearchBox = dynamic(() => import('./search')); const SearchBox = dynamic(() => import('./search'));
import { SIDEBAR_WIDTH } from '@common/constants'; import { SIDEBAR_WIDTH } from '@common/constants';
import { HEADER_HEIGHT } from '@components/header';
const Wrapper = ({ width = `${SIDEBAR_WIDTH}px`, children, ...rest }: any) => ( const Wrapper = ({ width = `${SIDEBAR_WIDTH}px`, children, ...rest }: any) => (
<Box <Box
position="relative" position="relative"
width={width} width={width}
maxWidth={width} maxWidth={width}
height="calc(100vh - 50px)" height={`calc(100vh - ${HEADER_HEIGHT}px)`}
flexGrow={0} flexGrow={0}
flexShrink={0} flexShrink={0}
overflow="auto" overflow="auto"
@ -23,9 +24,9 @@ const Wrapper = ({ width = `${SIDEBAR_WIDTH}px`, children, ...rest }: any) => (
> >
<Box <Box
position="fixed" position="fixed"
top={50} top={HEADER_HEIGHT}
width={width} width={width}
height="calc(100vh - 50px)" height={`calc(100vh - ${HEADER_HEIGHT}px)`}
overflow="auto" overflow="auto"
borderRight={['none', border(), border()]} borderRight={['none', border(), border()]}
pb={space('base-loose')} pb={space('base-loose')}

2
src/pages/browser/todo-list.md

@ -154,7 +154,7 @@ Further down in the component we see in
that it checks upon mount to either process completion of authentication with `userSession.handlePendingSignIn()` that it checks upon mount to either process completion of authentication with `userSession.handlePendingSignIn()`
or otherwise load session data into app state as above with `userSession.isUserSignedIn()`: or otherwise load session data into app state as above with `userSession.isUserSignedIn()`:
```js ```jsx
// src/components/App.jsx // src/components/App.jsx
componentDidMount() { componentDidMount() {

2
src/pages/org/wallet-use.md

@ -33,7 +33,7 @@ You can use any of these hardware wallets with the Stacks Wallet:
- Ledger Nano S - Ledger Nano S
- Ledger Blue - Ledger Blue
-> Blockstack only supports the hardware wallets listed above. Other wallets, for example, the Trezor Model T, <strong>are not supported</strong>. If you have questions about wallet support, please <a href='emailto:support@blockstack.org' target='_blank'>contact Blockstack support</a>. -> Blockstack only supports the hardware wallets listed above. Other wallets, for example, the Trezor Model T, <strong>are not supported</strong>. If you have questions about wallet support, please <a href='emailto:support@blockstack.org' target='_blank'>contact Blockstack support</a>.
The private key on your hardware wallet is used by the Stacks Wallet software to sign send transactions. Receive transactions don't require a signature. Please consult the device's manufacturer for support in setting up and configuring your hardware device. The private key on your hardware wallet is used by the Stacks Wallet software to sign send transactions. Receive transactions don't require a signature. Please consult the device's manufacturer for support in setting up and configuring your hardware device.

2
yarn.lock

@ -7892,7 +7892,7 @@ preval.macro@^5.0.0:
dependencies: dependencies:
babel-plugin-preval "^5.0.0" babel-plugin-preval "^5.0.0"
prism-react-renderer@^1.0.1, prism-react-renderer@^1.0.2: prism-react-renderer@^1.0.1, prism-react-renderer@^1.0.2, prism-react-renderer@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz#1c1be61b1eb9446a146ca7a50b7bcf36f2a70a44" resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.1.1.tgz#1c1be61b1eb9446a146ca7a50b7bcf36f2a70a44"
integrity sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug== integrity sha512-MgMhSdHuHymNRqD6KM3eGS0PNqgK9q4QF5P0yoQQvpB6jNjeSAi3jcSAz0Sua/t9fa4xDOMar9HJbLa08gl9ug==

Loading…
Cancel
Save