Skip to content

Commit

Permalink
Merge pull request #4396 from thematters/fix/clipboard
Browse files Browse the repository at this point in the history
feat: revise `stripHtml` and make plain text as fallback of clipboard content
  • Loading branch information
robertu7 authored May 17, 2024
2 parents 70b45f0 + 98df063 commit 22ec110
Show file tree
Hide file tree
Showing 17 changed files with 66 additions and 59 deletions.
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@artsy/fresnel": "^6.1.0",
"@ensdomains/content-hash": "^2.5.7",
"@matters/apollo-upload-client": "^11.1.0",
"@matters/matters-editor": "^0.2.3-alpha.4",
"@matters/matters-editor": "^0.2.3",
"@next/bundle-analyzer": "^13.4.9",
"@reach/alert": "^0.18.0",
"@reach/dialog": "^0.18.0",
Expand Down
28 changes: 11 additions & 17 deletions src/common/utils/text/article.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { describe, expect, it } from 'vitest'

import {
collapseContent,
countChars,
makeSummary,
measureDiffs,
Expand All @@ -11,29 +10,24 @@ import {
} from './article'

describe('utils/text/article/stripHtml', () => {
it('should remove HTML tags and replace with spaces', () => {
expect(stripHtml('<p>Hello, <strong>world</strong>!</p>')).toBe(
' Hello, world ! '
)
it('should remove HTML tags', () => {
expect(stripHtml('')).toBe('')
})

it('should remove HTML tags and replace with custom string', () => {
expect(stripHtml('<p>Hello, <strong>world</strong>!</p>', '-')).toBe(
'-Hello, -world-!-'
)
expect(stripHtml('<p>Hello, <strong>world</strong>!</p>', '')).toBe(
expect(stripHtml('<p>Hello, <strong>world</strong>!</p>')).toBe(
'Hello, world!'
)

expect(
stripHtml(
'<p>Hello, <strong>world</strong>!</p><p>Hello, <strong>world</strong>!</p><blockquote>Hello, <br>world!</blockquote>'
)
).toBe('Hello, world!\nHello, world!\nHello, \nworld!')
})
})

describe('utils/text/article/collapseContent', () => {
it('should collapse content correctly', () => {
expect(collapseContent('<p>Hello, \n<strong>world</strong>!</p>')).toBe(
'Hello,world!'
it('should remove HTML tags and custom replacement', () => {
expect(stripHtml('<p>Hello, <strong>world</strong>!</p>', ' ')).toBe(
'Hello, world !'
)
expect(collapseContent('')).toBe('')
})
})

Expand Down
36 changes: 24 additions & 12 deletions src/common/utils/text/article.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,34 @@ import { distance } from 'fastest-levenshtein'

import { toSizedImageURL } from '../url'

// via https://github.com/thematters/ipns-site-generator/blob/main/src/utils/index.ts

/**
* Strip html tags from html string to get text.
* Strip HTML tags from HTML string to get plain text.
* @param html - html string
* @param replacement - string to replace tags
* @param tagReplacement - string to replace tags
* @param lineReplacement - string to replace tags
*
* @see {@url https://github.com/thematters/ipns-site-generator/blob/main/src/utils/index.ts}
*/
export const stripHtml = (html: string, replacement = ' ') =>
(String(html) || '')
.replace(/(<\/p><p>|&nbsp;)/g, ' ') // replace line break and space first
.replace(/(<([^>]+)>)/gi, replacement)
export const stripHtml = (
html: string,
tagReplacement = '',
lineReplacement = '\n'
) => {
html = String(html) || ''

export const collapseContent = (content?: string | null) => {
return stripHtml(content ? content.replace(/\r?\n|\r|\s\s/g, '') : '', '')
}
html = html.replace(/\&nbsp\;/g, ' ')

// Replace block-level elements with newlines
html = html.replace(/<(\/?p|\/?blockquote|br\/?)>/gi, lineReplacement)

// Remove remaining HTML tags
let plainText = html.replace(/<\/?[^>]+(>|$)/g, tagReplacement)

// Normalize multiple newlines and trim the result
plainText = plainText.replace(/\n\s*\n/g, '\n').trim()

return plainText
}
/**
* Return beginning of text in html as summary, split on sentence break within buffer range.
* @param html - html string to extract summary
Expand All @@ -26,7 +38,7 @@ export const collapseContent = (content?: string | null) => {
*/
export const makeSummary = (html: string, length = 140, buffer = 20) => {
// split on sentence breaks
const sections = stripHtml(html, '')
const sections = stripHtml(html, '', ' ')
.replace(/([?!。?!]|(\.\s))\s*/g, '$1|')
.split('|')

Expand Down
4 changes: 2 additions & 2 deletions src/components/ArticleDigest/Feed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'

import { ReactComponent as IconDot } from '@/public/static/icons/dot.svg'
import { TEST_ID } from '~/common/enums'
import { stripHtml, toPath } from '~/common/utils'
import { makeSummary, toPath } from '~/common/utils'
import {
DateTime,
Icon,
Expand Down Expand Up @@ -59,7 +59,7 @@ const BaseArticleDigestFeed = ({
const { author, summary } = article
const isBanned = article.articleState === 'banned'
const cover = !isBanned ? article.cover : null
const cleanedSummary = isBanned ? '' : stripHtml(summary)
const cleanedSummary = isBanned ? '' : makeSummary(summary)

const path = toPath({
page: 'articleDetail',
Expand Down
4 changes: 2 additions & 2 deletions src/components/CollectionDigest/Feed/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { FormattedMessage } from 'react-intl'

import { ReactComponent as IconDot } from '@/public/static/icons/dot.svg'
import { TEST_ID } from '~/common/enums'
import { stripHtml, toPath } from '~/common/utils'
import { makeSummary, toPath } from '~/common/utils'
import { Book, DateTime, Icon, LinkWrapper, Media } from '~/components'
import { CollectionDigestFeedCollectionFragment } from '~/gql/graphql'

Expand All @@ -25,7 +25,7 @@ const BaseCollectionDigestFeed = ({
onClick,
}: CollectionDigestFeedProps & { Placeholder: typeof Placeholder }) => {
const { title, description, cover, author, updatedAt, articles } = collection
const cleanedDescription = stripHtml(description || '')
const cleanedDescription = makeSummary(description || '')

const path = toPath({
page: 'collectionDetail',
Expand Down
3 changes: 3 additions & 0 deletions src/components/CopyToClipboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as clipboard from 'clipboard-polyfill'
import { FormattedMessage } from 'react-intl'

import { stripHtml } from '~/common/utils'
import { toast } from '~/components'

interface CopyToClipboardProps {
Expand All @@ -24,9 +25,11 @@ export const CopyToClipboard: React.FC<CopyToClipboardProps> = ({
let item = new clipboard.ClipboardItem({
'text/plain': new Blob([text], { type: 'text/plain' }),
})

if (type === 'html') {
item = new clipboard.ClipboardItem({
'text/html': new Blob([text], { type: 'text/html' }),
'text/plain': new Blob([stripHtml(text)], { type: 'text/plain' }),
})
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,7 @@ const CommentForm: React.FC<CommentFormProps> = ({
const [content, setContent] = useState(
data?.commentDraft.content || defaultContent || ''
)
const contentCount = stripHtml(content).trim().length

const contentCount = stripHtml(content).length
const isValid = contentCount > 0 && contentCount <= MAX_ARTICLE_COMMENT_LENGTH

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ const CommentForm: React.FC<CommentFormProps> = ({
const [content, setContent] = useState(
data?.commentDraft.content || defaultContent || ''
)
const isValid = stripHtml(content).trim().length > 0
const isValid = stripHtml(content).length > 0

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
const mentions = dom.getAttributes('data-id', content)
Expand Down
4 changes: 2 additions & 2 deletions src/components/Expandable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { ReactElement, useEffect, useRef, useState } from 'react'
import { FormattedMessage } from 'react-intl'

import { ReactComponent as IconUp } from '@/public/static/icons/24px/up.svg'
import { capitalizeFirstLetter, collapseContent } from '~/common/utils'
import { capitalizeFirstLetter, stripHtml } from '~/common/utils'
import {
Button,
Icon,
Expand Down Expand Up @@ -53,7 +53,7 @@ export const Expandable: React.FC<ExpandableProps> = ({
const [firstRender, setFirstRender] = useState(true)
const [expand, setExpand] = useState(true)
const node: React.RefObject<HTMLParagraphElement> | null = useRef(null)
const collapsedContent = collapseContent(content)
const collapsedContent = stripHtml(content || '')

const contentClasses = classNames({
[styles.expandable]: true,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Forms/CommentForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export const CommentForm: React.FC<CommentFormProps> = ({
const [content, setContent] = useState(
data?.commentDraft.content || defaultContent || ''
)
const isValid = stripHtml(content).trim().length > 0
const isValid = stripHtml(content).length > 0

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
const mentions = dom.getAttributes('data-id', content)
Expand Down
2 changes: 1 addition & 1 deletion src/components/Forms/CommentFormBeta/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const CommentFormBeta: React.FC<CommentFormBetaProps> = ({
getDraft(commentDraftId) || defaultContent || ''
)

const contentCount = stripHtml(content).trim().length
const contentCount = stripHtml(content).length

const isValid = contentCount > 0 && contentCount <= MAX_ARTICLE_COMMENT_LENGTH

Expand Down
4 changes: 2 additions & 2 deletions src/components/Notice/NoticeContentDigest.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TEST_ID } from '~/common/enums'
import { collapseContent } from '~/common/utils'
import { stripHtml } from '~/common/utils'

import styles from './styles.module.css'

Expand All @@ -9,7 +9,7 @@ const NoticeContentDigest = ({ content }: { content: string }) => {
className={styles.noticeContentDigest}
data-test-id={TEST_ID.NOTICE_COMMENT_CONTENT}
>
{collapseContent(content)}
{stripHtml(content)}
</section>
)
}
Expand Down
4 changes: 2 additions & 2 deletions src/views/ArticleDetail/Edit/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _isEqual from 'lodash/isEqual'
import { FormattedMessage } from 'react-intl'

import { MAX_ARTICLE_CONTENT_LENGTH } from '~/common/enums'
import { toPath } from '~/common/utils'
import { stripHtml, toPath } from '~/common/utils'
import { Button, TextIcon, toast, useMutation } from '~/components'
import {
ConfirmStepContentProps,
Expand Down Expand Up @@ -111,7 +111,7 @@ const EditModeHeader = ({
isSensitiveRevised ||
restProps.iscnPublish

const contentLength = revision.content?.length || 0
const contentLength = stripHtml(revision.content || '').length
const isOverLength = contentLength > MAX_ARTICLE_CONTENT_LENGTH

const onSave = async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/views/Follow/Feed/FollowingRecommendArticle/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'

import { stripHtml, toPath } from '~/common/utils'
import { makeSummary, toPath } from '~/common/utils'
import {
Card,
CardProps,
Expand All @@ -21,7 +21,7 @@ const RecommendArticle = ({ article, ...cardProps }: Props) => {
const { author, summary, title } = article
const isBanned = article.recommendArticleState === 'banned'
const cover = !isBanned ? article.cover : null
const cleanedSummary = isBanned ? '' : stripHtml(summary)
const cleanedSummary = isBanned ? '' : makeSummary(summary)
const path = toPath({
page: 'articleDetail',
article,
Expand Down
5 changes: 2 additions & 3 deletions src/views/Me/DraftDetail/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,7 @@ const BaseDraftDetail = () => {
)
}

const hasContent =
draft?.content && stripHtml(draft.content).trim().length > 0
const hasContent = draft?.content && stripHtml(draft.content).length > 0
const hasTitle = draft?.title && draft.title.length > 0
const isUnpublished = draft?.publishState === 'unpublished'
const publishable = !!(
Expand Down Expand Up @@ -255,7 +254,7 @@ const BaseDraftDetail = () => {
}

// check content length
const len = newDraft.content?.length || 0
const len = stripHtml(newDraft.content || '').length
setContentLength(len)
if (len > MAX_ARTICLE_CONTENT_LENGTH) {
return
Expand Down
4 changes: 2 additions & 2 deletions src/views/Me/History/Comments/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import { useIntl } from 'react-intl'

import {
analytics,
collapseContent,
filterComments,
mergeConnections,
stripHtml,
toPath,
} from '~/common/utils'
import { Head, Layout, LinkWrapper, UserDigest } from '~/components'
Expand Down Expand Up @@ -156,7 +156,7 @@ const Comments = () => {
})}
>
<section className={styles.content}>
{collapseContent(comment.content)}
{stripHtml(comment.content || '')}
</section>
</LinkWrapper>
</section>
Expand Down

0 comments on commit 22ec110

Please sign in to comment.