Skip to content

Commit

Permalink
Merge pull request #1116 from geyserfund/feat/markdown-tables
Browse files Browse the repository at this point in the history
Feat/markdown tables
  • Loading branch information
sajald77 authored Aug 27, 2023
2 parents 267fafa + 569f1c2 commit 01b2722
Show file tree
Hide file tree
Showing 30 changed files with 983 additions and 44 deletions.
16 changes: 16 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@
a.appendChild(r);
})(window,document,'https://static.hotjar.com/c/hotjar-','.js?sv=');
</script>
<script>window.twttr = (function(d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0],
t = window.twttr || {};
if (d.getElementById(id)) return t;
js = d.createElement(s);
js.id = id;
js.src = "https://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);

t._e = [];
t.ready = function(f) {
t._e.push(f);
};

return t;
}(document, "script", "twitter-wjs"));</script>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@hookform/resolvers": "^3.1.0",
"@loadable/component": "^5.15.3",
"@react-hookz/web": "^23.0.0",
"@remirror/extension-node-formatting": "^2.0.13",
"@remirror/pm": "^2.0.5",
"@remirror/react": "^2.0.28",
"@remirror/react-components": "^2.1.12",
Expand All @@ -41,6 +42,7 @@
"bech32": "^2.0.0",
"buffer": "^6.0.3",
"classnames": "^2.3.2",
"dompurify": "^3.0.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"framer-motion": "^8.5.2",
Expand Down Expand Up @@ -97,6 +99,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.4.3",
"@types/dompurify": "^3.0.2",
"@types/loadable__component": "^5.13.4",
"@types/luxon": "^3.2.0",
"@types/node": "^18.11.18",
Expand Down
36 changes: 22 additions & 14 deletions src/api/bitcoin.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,41 @@
import { AxiosResponse } from 'axios'
import axios from 'axios'

const USD_QUOTE_KEY = 'usdQuote'
const BACKUP_QUOTE = 26088
const quoteSources = [
{
name: 'bitstamp',
url: 'https://www.bitstamp.net/api/v2/ticker/btcusd/',
lastPrice: (response: AxiosResponse) => Number(response.data.last),
name: 'blockchain.info',
url: 'https://blockchain.info/ticker',
lastPrice: (response: any) => Number(response.USD.last),
},
{
name: 'bitfinex',
url: 'https://api-pub.bitfinex.com/v2/ticker/tBTCUSD',
lastPrice: (response: AxiosResponse) => Number(response.data[6]),
name: 'bitstamp',
url: 'https://www.bitstamp.net/api/v2/ticker/btcusd/',
lastPrice: (response: any) => Number(response.data.last),
},
]

const getUsdQuote = async (): Promise<number> => {
const requests = quoteSources.map(
({ url, lastPrice }) =>
new Promise((resolve, reject) => {
axios
.get(url)
.then((response: AxiosResponse) => resolve(lastPrice(response)))
fetch(url)
.then((response: Response) => response.json())
.then((response: any) => resolve(lastPrice(response)))
.catch((error: Error) => reject(error))
}),
)

const usdQuote = (await Promise.any(requests)) as number
let usdQuote = Number(await Promise.any(requests))

if (!usdQuote) {
usdQuote = Number(localStorage.getItem(USD_QUOTE_KEY)) || 0
}

if (!usdQuote) {
alert('Failed to fetch bitcoin rates')
usdQuote = BACKUP_QUOTE
}

if (!usdQuote) throw new Error('Could not get bitcoin/usd quote')
localStorage.setItem(USD_QUOTE_KEY, String(usdQuote))

return usdQuote
}
Expand Down
14 changes: 14 additions & 0 deletions src/config/theme/popOverTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { popoverAnatomy } from '@chakra-ui/anatomy'
import { createMultiStyleConfigHelpers } from '@chakra-ui/react'

const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(popoverAnatomy.keys)

const baseStyle = definePartsStyle({
body: {
bg: 'neutral.0',
borderRadius: '8px',
},
})

export const popOverTheme = defineMultiStyleConfig({ baseStyle })
2 changes: 2 additions & 0 deletions src/config/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { alertTheme } from './alertTheme'
import { drawerTheme } from './drawerTheme'
import { menuTheme } from './menuTheme'
import { modalTheme } from './modalTheme'
import { popOverTheme } from './popOverTheme'

export const theme = {
initialColorMode: 'system',
Expand Down Expand Up @@ -247,6 +248,7 @@ export const theme = {
Menu: menuTheme,
Modal: modalTheme,
Drawer: drawerTheme,
Popover: popOverTheme,
Input: {
defaultProps: {
focusBorderColor: 'primary.400',
Expand Down
5 changes: 5 additions & 0 deletions src/constants/components/id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ export const ID = {
contribution: 'project-activity-list-container',
leaderboard: 'project-leaderboard-list-container',
},
story: {
markdown: {
container: 'project-story-markdown-container',
},
},
},
profile: {
tabs: 'user-profile-tab-container',
Expand Down
69 changes: 69 additions & 0 deletions src/forms/components/TableCellMenuComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ChevronDownIcon } from '@chakra-ui/icons'
import {
IconButton,
Menu,
MenuButton,
MenuItem,
MenuItemProps,
MenuList,
VStack,
} from '@chakra-ui/react'
import { useCommands } from '@remirror/react'
import { useTranslation } from 'react-i18next'

export const TableCellMenuComponent = () => {
const { t } = useTranslation()
const commands = useCommands()

return (
<Menu>
<MenuButton
mr="5px"
size="sm"
as={IconButton}
icon={<ChevronDownIcon />}
border="1px solid"
borderRadius="4px"
borderColor="neutral.200"
_hover={{ color: 'primary.400', borderColor: 'primary.400' }}
/>
<MenuList
as={VStack}
bg="neutral.0"
border="1px solid"
borderColor="neutral.200"
borderRadius={'4px'}
>
<ModifiedMenuItem onClick={() => commands.addTableRowBefore()}>
{t('Add row above')}
</ModifiedMenuItem>
<ModifiedMenuItem onClick={() => commands.addTableRowAfter()}>
{t('Add row below')}
</ModifiedMenuItem>
<ModifiedMenuItem onClick={() => commands.addTableColumnBefore()}>
{t('Add column left')}
</ModifiedMenuItem>
<ModifiedMenuItem onClick={() => commands.addTableColumnAfter()}>
{t('Add column right')}
</ModifiedMenuItem>
<ModifiedMenuItem onClick={() => commands.deleteTableColumn()}>
{t('Remove column')}
</ModifiedMenuItem>
<ModifiedMenuItem onClick={() => commands.deleteTableRow()}>
{t('Remove row')}
</ModifiedMenuItem>
</MenuList>
</Menu>
)
}

const ModifiedMenuItem = (props: MenuItemProps) => {
return (
<MenuItem
paddingX="10px"
paddingY="5px"
_hover={{ bg: 'neutral.200' }}
{...props}
/>
)
}
60 changes: 43 additions & 17 deletions src/forms/markdown/MarkdownField.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Box, Button, HStack, Text } from '@chakra-ui/react'
import { EditorComponent, Remirror, useRemirror } from '@remirror/react'
import {
EditorComponent,
Remirror,
TableComponents,
useRemirror,
} from '@remirror/react'
import { ForwardedRef, useCallback } from 'react'
import { Control } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
import { BsGear } from 'react-icons/bs'
import { InvalidContentHandler } from 'remirror'
import { AnyExtension, InvalidContentHandler } from 'remirror'
import {
BlockquoteExtension,
BoldExtension,
Expand All @@ -17,6 +22,7 @@ import {
ItalicExtension,
LinkExtension,
MarkdownExtension,
NodeFormattingExtension,
OrderedListExtension,
PlaceholderExtension,
TableExtension,
Expand All @@ -29,10 +35,14 @@ import TurndownService from 'turndown'
import { useSignedUpload } from '../../hooks'
import { useMobileMode } from '../../utils'
import { ReactHookTextArea } from '../components/ReactHookTextArea'
import { PreviewRenderer } from './helpers/PreviewRenderer'
import { SaveModule } from './helpers/SaveModule'
import { StyleProvider } from './helpers/StyleProvider'
import { imageHandler } from './helpers/typeMaps'
import { TableCellMenuComponent } from '../components/TableCellMenuComponent'
import {
FrameHandler,
imageHandler,
PreviewRenderer,
SaveModule,
StyleProvider,
} from './helpers'
import { MarkdownToolbar } from './MarkdownToolbar'

const turndownService = new TurndownService()
Expand Down Expand Up @@ -69,7 +79,6 @@ export const MarkdownField = ({
}: Props) => {
const { t } = useTranslation()
const isMobile = useMobileMode()

const onError: InvalidContentHandler = useCallback(
({ json, invalidContent, transformers }) => {
// Automatically remove all invalid nodes and marks.
Expand All @@ -80,8 +89,8 @@ export const MarkdownField = ({

const { uploadFile } = useSignedUpload()

const extensions = useCallback(
() => [
const extensions = useCallback<() => AnyExtension[]>(() => {
const exts = [
new PlaceholderExtension({ placeholder }),
new LinkExtension({
autoLink: true,
Expand All @@ -92,7 +101,6 @@ export const MarkdownField = ({
}),
new MarkdownExtension({
copyAsMarkdown: true,
htmlToMarkdown: (html) => turndownService.turndown(html),
}),
new BoldExtension(),
new UnderlineExtension(),
Expand All @@ -101,7 +109,18 @@ export const MarkdownField = ({
new BlockquoteExtension(),
new OrderedListExtension(),
new CodeExtension(),
new IframeExtension(),
new IframeExtension({
enableResizing: false,
extraAttributes: {
width: '100%',
scolling: 'no',
style: {
default: JSON.stringify({ width: '100%', height: '400px' }),
parseDOM: (domNode) => domNode.getAttribute('style'),
toDOM: (attrs) => ['style', (attrs.style as string) || ''],
},
},
}),
new HardBreakExtension(),
new TableExtension(),
new TrailingNodeExtension(),
Expand All @@ -119,9 +138,14 @@ export const MarkdownField = ({
},
enableResizing: false,
}),
],
[placeholder, uploadFile],
)
] as AnyExtension[]

if (!preview) {
exts.push(new NodeFormattingExtension())
}

return exts
}, [placeholder, uploadFile])

const { manager } = useRemirror({
extensions,
Expand All @@ -130,15 +154,13 @@ export const MarkdownField = ({
react: {
nodeViewComponents: {
image: imageHandler,
paragraph: ({ forwardRef }: { forwardRef: ForwardedRef<any> }) => (
<Box mb={4} ref={forwardRef} />
),
bulletList: ({ forwardRef }: { forwardRef: ForwardedRef<any> }) => (
<Box pl={5} ref={forwardRef} />
),
orderedList: ({ forwardRef }: { forwardRef: ForwardedRef<any> }) => (
<Box pl={5} ref={forwardRef} />
),
iframe: (props: any) => FrameHandler(props),
},
},
})
Expand All @@ -165,6 +187,7 @@ export const MarkdownField = ({
display="flex"
justifyContent="space-between"
alignItems="start"
mb={2}
sx={
stickyToolbar !== undefined && stickyToolbar !== false
? {
Expand Down Expand Up @@ -205,6 +228,9 @@ export const MarkdownField = ({
)}
<StyleProvider flex={flex} display={isEditorMode ? 'none' : undefined}>
<EditorComponent />
<TableComponents
tableCellMenuProps={{ Component: TableCellMenuComponent }}
/>
</StyleProvider>
<SaveModule name={name} control={control} />
</Remirror>
Expand Down
2 changes: 2 additions & 0 deletions src/forms/markdown/MarkdownToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ToolbarBlocks } from './toolbar/ToolbarBlocks'
import { ToolbarCommon } from './toolbar/ToolbarCommon'
import { ToolbarHeading } from './toolbar/ToolbarHeading'
import { ToolbarMedia } from './toolbar/ToolbarMedia'
import { ToolbarTable } from './toolbar/ToolbarTable'

export const MarkdownToolbar = ({ isDisabled }: { isDisabled?: boolean }) => {
return (
Expand All @@ -12,6 +13,7 @@ export const MarkdownToolbar = ({ isDisabled }: { isDisabled?: boolean }) => {
<ToolbarBlocks isDisabled={isDisabled} />
<ToolbarHeading isDisabled={isDisabled} />
<ToolbarMedia isDisabled={isDisabled} />
<ToolbarTable isDisabled={isDisabled} />
</HStack>
)
}
5 changes: 5 additions & 0 deletions src/forms/markdown/commands/ImageCommand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@ export const ImageCommand = ({ isDisabled }: { isDisabled?: boolean }) => {
const commands = useCommands()

const modal = useInsertLinkModal(({ url, label }: MarkdownImage) => {
if (!commands.insertImage) return
commands.insertHardBreak()
commands.insertImage({
src: url,
alt: label || 'image',
})

commands.insertHardBreak()

modal.onClose()
})

Expand Down
Loading

0 comments on commit 01b2722

Please sign in to comment.