From 22d224895164906c46950cd750bc8db9ab8efd53 Mon Sep 17 00:00:00 2001 From: Sinclair Chen Date: Mon, 12 Sep 2022 16:10:32 -0700 Subject: [PATCH] Add floating menu (bold, italic, link) (#867) * Add floating menu (bold, italic, link) * Sanitize and href-ify user input --- common/util/parse.ts | 7 ++++ web/components/editor.tsx | 68 +++++++++++++++++++++++++++++++++++ web/lib/icons/bold-icon.tsx | 20 +++++++++++ web/lib/icons/italic-icon.tsx | 21 +++++++++++ web/lib/icons/link-icon.tsx | 20 +++++++++++ 5 files changed, 136 insertions(+) create mode 100644 web/lib/icons/bold-icon.tsx create mode 100644 web/lib/icons/italic-icon.tsx create mode 100644 web/lib/icons/link-icon.tsx diff --git a/common/util/parse.ts b/common/util/parse.ts index 4fac3225..0bbd5cd9 100644 --- a/common/util/parse.ts +++ b/common/util/parse.ts @@ -23,8 +23,15 @@ import { Link } from '@tiptap/extension-link' import { Mention } from '@tiptap/extension-mention' import Iframe from './tiptap-iframe' import TiptapTweet from './tiptap-tweet-type' +import { find } from 'linkifyjs' import { uniq } from 'lodash' +/** get first url in text. like "notion.so " -> "http://notion.so"; "notion" -> null */ +export function getUrl(text: string) { + const results = find(text, 'url') + return results.length ? results[0].href : null +} + export function parseTags(text: string) { const regex = /(?:^|\s)(?:[#][a-z0-9_]+)/gi const matches = (text.match(regex) || []).map((match) => diff --git a/web/components/editor.tsx b/web/components/editor.tsx index bb947579..745fc3c5 100644 --- a/web/components/editor.tsx +++ b/web/components/editor.tsx @@ -2,6 +2,7 @@ import CharacterCount from '@tiptap/extension-character-count' import Placeholder from '@tiptap/extension-placeholder' import { useEditor, + BubbleMenu, EditorContent, JSONContent, Content, @@ -24,13 +25,19 @@ import Iframe from 'common/util/tiptap-iframe' import TiptapTweet from './editor/tiptap-tweet' import { EmbedModal } from './editor/embed-modal' import { + CheckIcon, CodeIcon, PhotographIcon, PresentationChartLineIcon, + TrashIcon, } from '@heroicons/react/solid' import { MarketModal } from './editor/market-modal' import { insertContent } from './editor/utils' import { Tooltip } from './tooltip' +import BoldIcon from 'web/lib/icons/bold-icon' +import ItalicIcon from 'web/lib/icons/italic-icon' +import LinkIcon from 'web/lib/icons/link-icon' +import { getUrl } from 'common/util/parse' const DisplayImage = Image.configure({ HTMLAttributes: { @@ -141,6 +148,66 @@ function isValidIframe(text: string) { return /^$/.test(text) } +function FloatingMenu(props: { editor: Editor | null }) { + const { editor } = props + + const [url, setUrl] = useState(null) + + if (!editor) return null + + // current selection + const isBold = editor.isActive('bold') + const isItalic = editor.isActive('italic') + const isLink = editor.isActive('link') + + const setLink = () => { + const href = url && getUrl(url) + if (href) { + editor.chain().focus().extendMarkRange('link').setLink({ href }).run() + } + } + + const unsetLink = () => editor.chain().focus().unsetLink().run() + + return ( + + {url === null ? ( + <> + + + + + ) : ( + <> + setUrl(e.target.value)} + /> + + + + )} + + ) +} + export function TextEditor(props: { editor: Editor | null upload: ReturnType @@ -155,6 +222,7 @@ export function TextEditor(props: { {/* hide placeholder when focused */}
+ {/* Toolbar, with buttons for images and embeds */}
diff --git a/web/lib/icons/bold-icon.tsx b/web/lib/icons/bold-icon.tsx new file mode 100644 index 00000000..f4fec497 --- /dev/null +++ b/web/lib/icons/bold-icon.tsx @@ -0,0 +1,20 @@ +// from Feather: https://feathericons.com/ +export default function BoldIcon(props: React.SVGProps) { + return ( + + + + + ) +} diff --git a/web/lib/icons/italic-icon.tsx b/web/lib/icons/italic-icon.tsx new file mode 100644 index 00000000..d412ed77 --- /dev/null +++ b/web/lib/icons/italic-icon.tsx @@ -0,0 +1,21 @@ +// from Feather: https://feathericons.com/ +export default function ItalicIcon(props: React.SVGProps) { + return ( + + + + + + ) +} diff --git a/web/lib/icons/link-icon.tsx b/web/lib/icons/link-icon.tsx new file mode 100644 index 00000000..6323344c --- /dev/null +++ b/web/lib/icons/link-icon.tsx @@ -0,0 +1,20 @@ +// from Feather: https://feathericons.com/ +export default function LinkIcon(props: React.SVGProps) { + return ( + + + + + ) +}