Embed a tweet by URL
This commit is contained in:
parent
8c537537a1
commit
82fe032cfb
|
@ -12,7 +12,8 @@
|
|||
"@tiptap/extension-link": "2.0.0-beta.43",
|
||||
"@tiptap/extension-mention": "2.0.0-beta.102",
|
||||
"@tiptap/starter-kit": "2.0.0-beta.190",
|
||||
"lodash": "4.17.21"
|
||||
"lodash": "4.17.21",
|
||||
"react-twitter-embed": "4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "4.14.178"
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
"outDir": "lib",
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"target": "es2017"
|
||||
"target": "es2017",
|
||||
"jsx": "preserve"
|
||||
},
|
||||
"include": ["**/*.ts"]
|
||||
"include": ["**/*.ts", "util/tiptap-tweet.tsx"]
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { Image } from '@tiptap/extension-image'
|
|||
import { Link } from '@tiptap/extension-link'
|
||||
import { Mention } from '@tiptap/extension-mention'
|
||||
import Iframe from './tiptap-iframe'
|
||||
import TiptapTweet from './tiptap-tweet'
|
||||
import { uniq } from 'lodash'
|
||||
|
||||
export function parseTags(text: string) {
|
||||
|
@ -94,6 +95,7 @@ export const exhibitExts = [
|
|||
Link,
|
||||
Mention,
|
||||
Iframe,
|
||||
TiptapTweet,
|
||||
]
|
||||
|
||||
export function richTextToString(text?: JSONContent) {
|
||||
|
|
37
common/util/tiptap-tweet.ts
Normal file
37
common/util/tiptap-tweet.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Node, mergeAttributes } from '@tiptap/core'
|
||||
import { ReactNodeViewRenderer } from '@tiptap/react'
|
||||
import WrappedTwitterTweetEmbed from './tweet-embed'
|
||||
|
||||
export interface TweetOptions {
|
||||
tweetId: string
|
||||
}
|
||||
|
||||
export default Node.create<TweetOptions>({
|
||||
name: 'tiptapTweet',
|
||||
group: 'block',
|
||||
atom: true,
|
||||
|
||||
addAttributes() {
|
||||
return {
|
||||
tweetId: {
|
||||
default: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
return [
|
||||
{
|
||||
tag: 'tiptap-tweet',
|
||||
},
|
||||
]
|
||||
},
|
||||
|
||||
renderHTML({ HTMLAttributes }) {
|
||||
return ['tiptap-tweet', mergeAttributes(HTMLAttributes)]
|
||||
},
|
||||
|
||||
addNodeView() {
|
||||
return ReactNodeViewRenderer(WrappedTwitterTweetEmbed)
|
||||
},
|
||||
})
|
13
common/util/tweet-embed.tsx
Normal file
13
common/util/tweet-embed.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { NodeViewWrapper } from '@tiptap/react'
|
||||
import { TwitterTweetEmbed } from 'react-twitter-embed'
|
||||
|
||||
export default function WrappedTwitterTweetEmbed(props: any): JSX.Element {
|
||||
console.log('wtwe props', props.node.attrs)
|
||||
return (
|
||||
<NodeViewWrapper className="tiptap-tweet">
|
||||
<TwitterTweetEmbed
|
||||
tweetId={props.node.attrs.tweetId || '1557429814990196736'}
|
||||
/>
|
||||
</NodeViewWrapper>
|
||||
)
|
||||
}
|
|
@ -21,12 +21,14 @@ import { useUsers } from 'web/hooks/use-users'
|
|||
import { mentionSuggestion } from './editor/mention-suggestion'
|
||||
import { DisplayMention } from './editor/mention'
|
||||
import Iframe from 'common/util/tiptap-iframe'
|
||||
import TiptapTweet from 'common/util/tiptap-tweet'
|
||||
import { CodeIcon, PhotographIcon } from '@heroicons/react/solid'
|
||||
import { Modal } from './layout/modal'
|
||||
import { Col } from './layout/col'
|
||||
import { Button } from './button'
|
||||
import { Row } from './layout/row'
|
||||
import { Spacer } from './layout/spacer'
|
||||
import { TweetModal } from './editor/tweetModal'
|
||||
|
||||
const DisplayImage = Image.configure({
|
||||
HTMLAttributes: {
|
||||
|
@ -82,6 +84,7 @@ export function useTextEditor(props: {
|
|||
suggestion: mentionSuggestion(users),
|
||||
}),
|
||||
Iframe,
|
||||
TiptapTweet,
|
||||
],
|
||||
content: defaultValue,
|
||||
},
|
||||
|
@ -139,6 +142,7 @@ export function TextEditor(props: {
|
|||
}) {
|
||||
const { editor, upload, children } = props
|
||||
const [iframeOpen, setIframeOpen] = useState(false)
|
||||
const [tweetOpen, setTweetOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -172,6 +176,26 @@ export function TextEditor(props: {
|
|||
<span className="sr-only">Embed an iframe</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setTweetOpen(true)}
|
||||
className="-m-2.5 flex h-10 w-10 items-center justify-center rounded-full text-gray-400 hover:text-gray-500"
|
||||
>
|
||||
<TweetModal
|
||||
editor={editor}
|
||||
open={tweetOpen}
|
||||
setOpen={setTweetOpen}
|
||||
/>
|
||||
<img
|
||||
className="mr-2"
|
||||
src={'/twitter-logo.svg'}
|
||||
width={15}
|
||||
height={15}
|
||||
/>
|
||||
<span className="sr-only">Embed a tweet</span>
|
||||
</button>
|
||||
</div>
|
||||
{/* Spacer that also focuses editor on click */}
|
||||
<div
|
||||
className="grow cursor-text self-stretch"
|
||||
|
|
77
web/components/editor/tweetModal.tsx
Normal file
77
web/components/editor/tweetModal.tsx
Normal file
|
@ -0,0 +1,77 @@
|
|||
import { Editor } from '@tiptap/react'
|
||||
import { useState } from 'react'
|
||||
import { TwitterTweetEmbed } from 'react-twitter-embed'
|
||||
import { Button } from '../button'
|
||||
import { Col } from '../layout/col'
|
||||
import { Modal } from '../layout/modal'
|
||||
import { Row } from '../layout/row'
|
||||
import { Spacer } from '../layout/spacer'
|
||||
|
||||
// A valid tweet URL looks like 'https://twitter.com/username/status/123456789'
|
||||
// Return the tweetId if the URL is valid, otherwise return null.
|
||||
function getTweetId(text: string) {
|
||||
const match = text.match(/^https?:\/\/twitter\.com\/.*\/status\/(\d+)$/)
|
||||
return match ? match[1] : null
|
||||
}
|
||||
|
||||
export function TweetModal(props: {
|
||||
editor: Editor | null
|
||||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
}) {
|
||||
const { editor, open, setOpen } = props
|
||||
const [input, setInput] = useState('')
|
||||
const tweetId = getTweetId(input)
|
||||
const tweetCode = `<tiptap-tweet tweetid="${tweetId}"></tiptap-tweet>`
|
||||
console.log('tweetCode', tweetCode)
|
||||
|
||||
return (
|
||||
<Modal open={open} setOpen={setOpen}>
|
||||
<Col className="gap-2 rounded bg-white p-6">
|
||||
<label
|
||||
htmlFor="embed"
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
Tweet link
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
name="embed"
|
||||
id="embed"
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
placeholder="e.g. https://twitter.com/jahooma/status/1557429814990196736"
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
/>
|
||||
|
||||
{/* Preview the embed if it's valid */}
|
||||
{tweetId ? <TwitterTweetEmbed tweetId={tweetId} /> : <Spacer h={2} />}
|
||||
|
||||
<Row className="gap-2">
|
||||
<Button
|
||||
disabled={!tweetId}
|
||||
onClick={() => {
|
||||
if (editor && tweetId) {
|
||||
editor.chain().insertContent(tweetCode).run()
|
||||
console.log('editorjson', editor.getJSON())
|
||||
setInput('')
|
||||
setOpen(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Embed
|
||||
</Button>
|
||||
<Button
|
||||
color="gray"
|
||||
onClick={() => {
|
||||
setInput('')
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Row>
|
||||
</Col>
|
||||
</Modal>
|
||||
)
|
||||
}
|
|
@ -53,6 +53,7 @@
|
|||
"react-hot-toast": "2.2.0",
|
||||
"react-instantsearch-hooks-web": "6.24.1",
|
||||
"react-query": "3.39.0",
|
||||
"react-twitter-embed": "4.0.4",
|
||||
"string-similarity": "^4.0.4",
|
||||
"tippy.js": "6.3.7"
|
||||
},
|
||||
|
|
|
@ -19,6 +19,12 @@
|
|||
"watchOptions": {
|
||||
"excludeDirectories": [".next"]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "../common/**/*.ts"],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"../common/**/*.ts",
|
||||
"../common/util/tiptap-tweet.tsx"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -9964,6 +9964,13 @@ react-textarea-autosize@^8.3.2:
|
|||
use-composed-ref "^1.3.0"
|
||||
use-latest "^1.2.1"
|
||||
|
||||
react-twitter-embed@4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/react-twitter-embed/-/react-twitter-embed-4.0.4.tgz#4a6b8354acc266876ff1110b9f648518ea20db6d"
|
||||
integrity sha512-2JIL7qF+U62zRzpsh6SZDXNI3hRNVYf5vOZ1WRcMvwKouw+xC00PuFaD0aEp2wlyGaZ+f4x2VvX+uDadFQ3HVA==
|
||||
dependencies:
|
||||
scriptjs "^2.5.9"
|
||||
|
||||
react-with-forwarded-ref@^0.3.3:
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/react-with-forwarded-ref/-/react-with-forwarded-ref-0.3.4.tgz#b1e884ea081ec3c5dd578f37889159797454c0a5"
|
||||
|
@ -10464,6 +10471,11 @@ schema-utils@^4.0.0:
|
|||
ajv-formats "^2.1.1"
|
||||
ajv-keywords "^5.0.0"
|
||||
|
||||
scriptjs@^2.5.9:
|
||||
version "2.5.9"
|
||||
resolved "https://registry.yarnpkg.com/scriptjs/-/scriptjs-2.5.9.tgz#343915cd2ec2ed9bfdde2b9875cd28f59394b35f"
|
||||
integrity sha512-qGVDoreyYiP1pkQnbnFAUIS5AjenNwwQBdl7zeos9etl+hYKWahjRTfzAZZYBv5xNHx7vNKCmaLDQZ6Fr2AEXg==
|
||||
|
||||
search-insights@^2.1.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/search-insights/-/search-insights-2.2.1.tgz#9c93344fbae5fbf2f88c1a81b46b4b5d888c11f7"
|
||||
|
|
Loading…
Reference in New Issue
Block a user