Skip to content

Commit

Permalink
WIP MarkdownToolbar extracted to seperate file
Browse files Browse the repository at this point in the history
  • Loading branch information
tomrule007 committed Aug 8, 2021
1 parent 8212ec3 commit 8dbd623
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 199 deletions.
148 changes: 148 additions & 0 deletions components/MarkdownToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React from 'react'
import IconButton from './theme/IconButton'
import {
HeadingIcon,
BoldIcon,
ItalicIcon,
QuoteIcon,
CodeIcon,
LinkIcon,
ListUnorderedIcon,
ListOrderedIcon,
TasklistIcon
// TypographyIcon,
// ChevronDownIcon,
// ChevronUpIcon
} from '@primer/octicons-react'
import { ButtonToolbar, ButtonToolbarProps } from 'react-bootstrap'

// TODO: Better name for utility
const applyMarkdown = (
inputRef: React.RefObject<HTMLTextAreaElement>,
left: string,
right: string = ''
): void => {
// TODO: add expand single select to word functionality and support for one sided and multiline operations
if (!inputRef.current) return
const { value, selectionStart, selectionEnd } = inputRef.current

const selection = value.slice(selectionStart, selectionEnd)

const hasLeft =
selectionStart >= left.length &&
value.slice(selectionStart - left.length, selectionStart) === left

const hasRight =
selectionEnd + right.length <= value.length &&
value.slice(selectionEnd, selectionEnd + right.length) === right

inputRef.current.focus()

if (hasLeft && hasRight) {
// Modification detected around selection, remove it!

// expand selection to include left and right
inputRef.current.setSelectionRange(
selectionStart - left.length,
selectionEnd + right.length
)
// Replace with old selection
document.execCommand('insertText', false, selection)

// update selection with removed left
inputRef.current.setSelectionRange(
selectionStart - left.length,
selectionEnd - left.length
)
} else {
// No modification detected, apply modifiers
document.execCommand('insertText', false, left + selection + right)

/// update selection with added left
inputRef.current.setSelectionRange(
selectionStart + left.length,
selectionEnd + left.length
)
}
}

// TODO: Add hotkey support!
const buttons = [
{
tooltipTitle: 'Add header text',
togglerParams: ['### ', ''],
Icon: HeadingIcon
},
{
tooltipTitle: 'Add bold text <ctrl+b>',
togglerParams: ['**', '**'],
Icon: BoldIcon
},
{
tooltipTitle: 'Add italic text <ctrl+i>',
togglerParams: ['_', '_'],
Icon: ItalicIcon
},
{
tooltipTitle: 'Insert a quote',
togglerParams: ['\n> ', ''],
Icon: QuoteIcon
},
{
tooltipTitle: 'Insert code <ctrl+e>',
togglerParams: ['`', '`'],
Icon: CodeIcon
},
{
tooltipTitle: 'Add a link <ctrl+k>',
togglerParams: ['[', '](URL)'],
Icon: LinkIcon
},
{
tooltipTitle: 'Add a bulleted list',
togglerParams: ['\n- ', ''],
Icon: ListUnorderedIcon
},
{
tooltipTitle: 'Add a numbered list',
togglerParams: ['\n1. ', ''],
Icon: ListOrderedIcon
},
{
tooltipTitle: 'Add a task list',
togglerParams: ['\n- [] ', ''],
Icon: TasklistIcon
}
]

type Props = {
inputRef: React.RefObject<HTMLTextAreaElement>
}

const MarkdownToolbar: React.FC<Props & ButtonToolbarProps> = ({
inputRef,
...buttonProps
}) => {
return (
<ButtonToolbar {...buttonProps}>
{buttons.map(({ tooltipTitle, togglerParams, Icon }, i) => (
<IconButton
key={i}
className={i === 2 || i === 5 ? 'mr-3' : ''}
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle={tooltipTitle}
aria-label={tooltipTitle}
placement="bottom-end"
onClick={() =>
applyMarkdown(inputRef, ...(togglerParams as [string, string]))
}
icon={<Icon size={'small'} />}
/>
))}
</ButtonToolbar>
)
}

export default MarkdownToolbar
212 changes: 13 additions & 199 deletions components/MdInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,10 @@ import React, { useState, useEffect, useRef } from 'react'
import Markdown from 'markdown-to-jsx'
import noop from '../helpers/noop'
import styles from '../scss/mdInput.module.scss'
import {
HeadingIcon,
BoldIcon,
ItalicIcon,
QuoteIcon,
CodeIcon,
LinkIcon,
ListUnorderedIcon,
ListOrderedIcon,
TasklistIcon,
TypographyIcon,
ChevronDownIcon,
ChevronUpIcon
} from '@primer/octicons-react'

import IconButton from './theme/IconButton'
import { Nav } from 'react-bootstrap'
import { useBreakpoint } from '../helpers/useBreakpoint'
import MarkdownToolbar from './MarkdownToolbar'

type MdInputProps = {
onChange?: Function
placeHolder?: string
Expand Down Expand Up @@ -75,190 +61,16 @@ export const MdInput: React.FC<MdInputProps> = ({
return () => window.removeEventListener('mouseup', updateHeight, false)
}, [])

const previewBtnColor = preview ? 'black' : 'lightgrey'
const writeBtnColor = preview ? 'lightgrey' : 'black'

const onChangeWithAutoSize = e => {
if (!height && textareaRef.current) autoSize(textareaRef.current)
onChange(e.target.value)
}

// TODO: Export markdownToolbar and associated helpers to separate file
const markdownInputToggler = (
textareaRef,
left: string,
right: string = ''
): void => {
// TODO: add expand single select to word functionality and support for one sided and multiline operations
if (!textareaRef.current) return
const { value, selectionStart, selectionEnd } = textareaRef.current

const selection = value.slice(selectionStart, selectionEnd)

const hasLeft =
selectionStart >= left.length &&
value.slice(selectionStart - left.length, selectionStart) === left
const hasRight =
selectionEnd + right.length <= value.length &&
value.slice(selectionEnd, selectionEnd + right.length) === right
console.log({
selection,
value,
selectionEnd,
selectionStart,
left,
hasLeft,
right,
hasRight
})
// TODO: Convert tabs to old styles
// const previewBtnColor = preview ? 'black' : 'lightgrey'
// const writeBtnColor = preview ? 'lightgrey' : 'black'

// const fakeEvent = { target: { value: 'remove!' } }
textareaRef.current.focus()
if (hasLeft && hasRight) {
// expand selection to include left and right
textareaRef.current.setSelectionRange(
selectionStart - left.length,
selectionEnd + right.length
)
// Replace with old selection
document.execCommand('insertText', false, selection)

// update selection
// expand selection to include left and right
textareaRef.current.setSelectionRange(
selectionStart - left.length,
selectionEnd - left.length
)
} else {
document.execCommand('insertText', false, left + selection + right)
textareaRef.current.setSelectionRange(
selectionStart + left.length,
selectionEnd + left.length
)
// onChangeWithAutoSize({ target: { value: 'add!' } })
const onChangeWithAutoSize: React.ChangeEventHandler<HTMLTextAreaElement> =
e => {
if (!height && textareaRef.current) autoSize(textareaRef.current)
onChange(e.target.value)
}
}

const markdownToolbar = (
<div className="ml-auto">
{/* <IconButton
tabIndex="-1"
color="black"
aria-label="Toggle text tools"
aria-expanded="true"
onClick={e => console.log("I'm clicked")}
icon={
<>
<TypographyIcon size={'small'} />
<ChevronDownIcon size={'small'} />
</>
}
/> */}
<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Add header text"
aria-label="Add header text"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '### ', '')}
icon={<HeadingIcon size={'small'} />}
/>
<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Add bold text <ctrl+b>"
aria-label="Add bold text <ctrl+b>"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '**', '**')}
icon={<BoldIcon size={'small'} />}
/>
<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Add italic text <ctrl+i>"
aria-label="Add italic text <ctrl+i>"
role="button"
placement="bottom-end"
onClick={() => {
markdownInputToggler(textareaRef, '_', '_')
}}
icon={<ItalicIcon size={'small'} />}
/>

<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Insert a quote"
aria-label="Insert a quote"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '\n> ', '')}
icon={<QuoteIcon size={'small'} />}
/>
<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Insert code <ctrl+e>"
aria-label="Insert code <ctrl+e>"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '`', '`')}
icon={<CodeIcon size={'small'} />}
/>
<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Add a link <ctrl+k>"
aria-label="Add a link <ctrl+k>"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '[', '](URL)')}
icon={<LinkIcon size={'small'} />}
/>

<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Add a bulleted list"
aria-label="Add a bulleted list"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '\n- ', '')}
icon={<ListUnorderedIcon size={'small'} />}
/>
<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Add a numbered list"
aria-label="Add a numbered list"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '\n1. ', '')}
icon={<ListOrderedIcon size={'small'} />}
/>
<IconButton
tabIndex="-1"
color="black"
delay={{ show: 250 }}
tooltipTitle="Add a task list"
aria-label="Add a task list"
role="button"
placement="bottom-end"
onClick={() => markdownInputToggler(textareaRef, '\n- [] ', '')}
icon={<TasklistIcon size={'small'} />}
/>
</div>
)
return (
<div style={{ backgroundColor: bgColor }}>
<Nav
Expand All @@ -273,9 +85,11 @@ export const MdInput: React.FC<MdInputProps> = ({
<Nav.Item>
<Nav.Link eventKey="Preview">Preview</Nav.Link>
</Nav.Item>
{!lessThanMd && !preview && markdownToolbar}
{!lessThanMd && !preview && (
<MarkdownToolbar inputRef={textareaRef} className="ml-auto" />
)}
</Nav>
{lessThanMd && !preview && markdownToolbar}
{lessThanMd && !preview && <MarkdownToolbar inputRef={textareaRef} />}
{preview && (
<>
<Markdown
Expand Down

0 comments on commit 8dbd623

Please sign in to comment.