Skip to content

Commit

Permalink
chore: update extension search, export pdf
Browse files Browse the repository at this point in the history
  • Loading branch information
hunghg255 committed Oct 11, 2024
1 parent f44069b commit 0a99773
Show file tree
Hide file tree
Showing 13 changed files with 82 additions and 29 deletions.
2 changes: 1 addition & 1 deletion playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const extensions = [
}),
]

const DEFAULT = `<div class="attachment" filename="Drought_insurance_policy_wording_Vie" filesize="547068" filetype="application/pdf" fileext="pdf" url="blob:http://localhost:5173/06d65427-5453-4902-88ec-5be6f975ef0f" hastrigger="true" error=""></div><p dir="auto"></p><div style="text-align: left;" class="image"><img height="auto" src="https://media2.giphy.com/media/qpXx6IVZ7PjHtPsDj8/giphy.gif?cid=05e2c1e1e1i06b96si957vklsjfj8vjdca1ud55j4noxc0t2&amp;ep=v1_gifs_trending&amp;rid=giphy.gif&amp;ct=g" width="273" align="left"></div><p dir="auto"></p>`
const DEFAULT = `<h1 style="text-align: center">Rich Text Editor</h1><p>A modern WYSIWYG rich text editor based on <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://github.com/scrumpy/tiptap">tiptap</a> and <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://ui.shadcn.com/">shadcn ui</a> for Reactjs</p><p></p><p style="text-align: center"></p><div style="text-align: center;" class="image"><img height="auto" src="https://picsum.photos/1920/1080.webp?t=1" align="center" width="500"></div><p></p><div data-type="horizontalRule"><hr></div><h2>Demo</h2><p>👉<a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://reactjs-tiptap-editor.vercel.app/">Demo</a></p><h2>Features</h2><ul><li><p>Use <a target="_blank" rel="noopener noreferrer nofollow" class="link" href="https://ui.shadcn.com/">shadcn ui</a> components</p></li><li><p>Markdown support</p></li><li><p>TypeScript support</p></li><li><p>I18n support (vi, en, zh, pt)</p></li><li><p>React support</p></li><li><p>Slash Commands</p></li><li><p>Multi Column</p></li><li><p>TailwindCss</p></li><li><p>Support emoji</p></li><li><p>Support iframe</p></li></ul><h2>Installation</h2><pre><code class="language-bash">pnpm add reactjs-tiptap-editor</code></pre><p></p>`

function debounce(func: any, wait: number) {
let timeout: NodeJS.Timeout
Expand Down
2 changes: 2 additions & 0 deletions src/components/icons/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Bold,
BookMarked,
ChevronDown,
ChevronUp,
ChevronsUpDown,
Clipboard,
Code,
Expand Down Expand Up @@ -193,4 +194,5 @@ export const icons = {

Attachment: Paperclip,
GifIcon,
ChevronUp,
} as any
12 changes: 11 additions & 1 deletion src/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,18 @@ export const DEFAULT_FONT_FAMILY_LIST = [
'Inter',
'Comic Sans MS, Comic Sans',
'serif',
'monospace',
'cursive',
'Arial',
'Arial Black',
'Georgia',
'Impact',
'Tahoma',
'Times New Roman',
'Verdana',
'Courier New',
'Lucida Console',
'Monaco',
'monospace',
]

export const DEFAULT_LANGUAGE_CODE_BLOCK = [
Expand Down
2 changes: 1 addition & 1 deletion src/extensions/ExportPdf/ExportPdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const ExportPdf = Extension.create<any>({
component: ActionButton,
componentProps: {
action: () => {
printEditorContent(editor.view)
printEditorContent(editor)
},
icon: 'ExportPdf',
tooltip: t('editor.exportPdf.tooltip'),
Expand Down
14 changes: 12 additions & 2 deletions src/extensions/ImportWord/components/ImportWordButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,39 @@ function ImportWordButton(props: any) {
)?.options
if (uploadOptions && typeof uploadOptions.upload === 'function') {
const files: File[] = []
// convert base64 image to blob file
for (const img of images) {
const originalSrc = img.getAttribute('src')
const blob = base64ToBlob(originalSrc, 'image/jpeg')

const file = blobToFile(blob, 'image.jpeg')
files.push(file)
}
const uploadRes = await uploadOptions.upload(files)

// images
for (let i = 0; i < images.length; i++) {
const img = images[i]
img.setAttribute('src', uploadRes[i].src)
const parent = img.parentElement
if (parent?.tagName === 'P') {
parent.insertAdjacentElement('beforebegin', img)
if (!parent.hasChildNodes() && parent.textContent === '') {
parent.remove()
}
}
}
return doc.body.innerHTML
}
else {
console.warn('Image Upload method found, skip image conversion')
return doc.body.innerHTML
}
}
else {
console.error('Image extension not found, unable to convert image')
return doc.body.innerHTML
}
}

async function importWord() {
if (props.convert) {
const result = await props.convert(file)
Expand Down
6 changes: 6 additions & 0 deletions src/extensions/SearchAndReplace/SearchAndReplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ declare module '@tiptap/core' {
replaceAll: () => ReturnType
goToPrevSearchResult: () => void
goToNextSearchResult: () => void
setCaseSensitive: (caseSensitive: boolean) => ReturnType
}
}
}
Expand Down Expand Up @@ -263,6 +264,11 @@ export const SearchAndReplace = Extension.create<SearchOptions, SearchStorage>({

return false
},
setCaseSensitive: (caseSensitive: boolean) => ({ state, dispatch }) => {
this.options.caseSensitive = caseSensitive
updateView(state, dispatch)
return false
},
replace:
() =>
({ state, dispatch }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react'
import deepEqual from 'deep-equal'
import { ActionButton, Button, IconComponent, Input, Label, Popover, PopoverContent, PopoverTrigger } from '@/components'
import { ActionButton, Button, IconComponent, Input, Label, Popover, PopoverContent, PopoverTrigger, Switch } from '@/components'
import { useLocale } from '@/locales'
import { ON_SEARCH_RESULTS, SearchAndReplace } from '@/extensions/SearchAndReplace/SearchAndReplace'

Expand All @@ -12,6 +12,7 @@ function SearchAndReplaceButton({ editor, ...props }: any) {
const [searchValue, setSearchValue] = useState('')
const [replaceValue, setReplaceValue] = useState('')
const [visible, setVisible] = useState(false)
const [caseSensitive, setCaseSensitive] = useState(false)

useEffect(() => {
if (!visible) {
Expand Down Expand Up @@ -93,7 +94,7 @@ function SearchAndReplaceButton({ editor, ...props }: any) {
side="bottom"
>

<div className="richtext-mb-[6px richtext-flex richtext-items-center richtext-justify-between">
<div className="richtext-mb-[6px] richtext-flex richtext-items-center richtext-justify-between">
<Label>
{t('editor.search.dialog.text')}
</Label>
Expand All @@ -111,11 +112,20 @@ function SearchAndReplaceButton({ editor, ...props }: any) {
value={searchValue}
onChange={e => setSearchValue(e.target.value)}
/>

<Button disabled={!results.length} className="richtext-flex-1" onClick={editor.commands.goToPrevSearchResult}>
<IconComponent name="ChevronUp" />
</Button>

<Button disabled={!results.length} className="richtext-flex-1" onClick={editor.commands.goToNextSearchResult}>
<IconComponent name="ChevronDown" />
</Button>

</div>
<Label className="richtext-mb-[6px]">
{t('editor.replace.dialog.text')}
</Label>
<div className="richtext-flex richtext-w-full richtext-max-w-sm richtext-items-center richtext-gap-1.5 richtext-mb-[16px]">
<div className="richtext-flex richtext-w-full richtext-max-w-sm richtext-items-center richtext-gap-1.5 richtext-mb-[5px]">
<div className="richtext-relative richtext-items-center richtext-w-full richtext-max-w-sm">
<Input
type="text"
Expand All @@ -128,14 +138,15 @@ function SearchAndReplaceButton({ editor, ...props }: any) {
</div>
</div>

<div className="richtext-flex richtext-items-center richtext-gap-[10px] richtext-mb-[10px]">
<Button disabled={!results.length} className="richtext-flex-1" onClick={editor.commands.goToPrevSearchResult}>
{t('editor.previous.dialog.text')}
</Button>

<Button disabled={!results.length} className="richtext-flex-1" onClick={editor.commands.goToNextSearchResult}>
{t('editor.next.dialog.text')}
</Button>
<div className="richtext-flex richtext-items-center richtext-space-x-2 richtext-mb-[10px]">
<Switch
checked={caseSensitive}
onCheckedChange={(e: any) => {
setCaseSensitive(e)
editor.commands.setCaseSensitive(e)
}}
/>
<Label>{t('editor.replace.caseSensitive')}</Label>
</div>

<div className="richtext-flex richtext-items-center richtext-gap-[10px]">
Expand Down
1 change: 1 addition & 0 deletions src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ const locale = {
'editor.attachment.uploading': 'Uploading',
'editor.attachment.please_upload': 'Please upload the file',
'editor.imageGif.tooltip': 'Gif',
'editor.replace.caseSensitive': 'Case Sensitive',
}

export default locale
1 change: 1 addition & 0 deletions src/locales/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const locale = {
'editor.attachment.uploading': 'Enviando',
'editor.attachment.please_upload': 'Por favor, envie o arquivo',
'editor.imageGif.tooltip': 'Gif',
'editor.replace.caseSensitive': 'Sensível a maiúsculas e minúsculas',
}

export default locale
1 change: 1 addition & 0 deletions src/locales/vi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const locale = {
'editor.attachment.uploading': 'Đang tải lên',
'editor.attachment.please_upload': 'Vui lòng tải lên tệp',
'editor.imageGif.tooltip': 'Gif',
'editor.replace.caseSensitive': 'Phân biệt chữ hoa chữ thường',
}

export default locale
1 change: 1 addition & 0 deletions src/locales/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ const locale = {
'editor.attachment.uploading': '上传中',
'editor.attachment.please_upload': '请上传文件',
'editor.imageGif.tooltip': 'Gif',
'editor.replace.caseSensitive': '区分大小写',
}

export default locale
1 change: 1 addition & 0 deletions src/utils/download.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function downloadFromBlob(blob: Blob, filename: string) {
a.download = filename
a.click()
window.URL.revokeObjectURL(url)
return Promise.resolve()
}

console.error('Download is not supported in Node.js')
Expand Down
35 changes: 22 additions & 13 deletions src/utils/pdf.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,33 @@
import type { Editor } from '@tiptap/core'

function printHtml(dom: Element) {
const style: string = Array.from(document.querySelectorAll('style, link')).reduce(
(str, style) => str + style.outerHTML,
'',
)

const content: string = style + dom.outerHTML

function printHtml(content: string) {
const iframe: HTMLIFrameElement = document.createElement('iframe')
iframe.setAttribute('style', 'position: absolute; width: 0; height: 0; top: 0; left: 0;')
document.body.appendChild(iframe)

iframe.textContent = `
<!DOCTYPE html>
<html lang="en">
<head>
<title>Echo Editor</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body class="is-print">
<div class="tiptap ProseMirror" translate="no" aria-expanded="false">
${content}
</div>
</body>
</html>
`

const frameWindow = iframe.contentWindow
const doc: any = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document)

// load style from CDN to iframe
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = 'https://cdn.jsdelivr.net/npm/reactjs-tiptap-editor/lib/style.css'
link.href = 'https://cdn.jsdelivr.net/npm/reactjs-tiptap-editor@latest/lib/style.css'
doc.head.appendChild(link)

if (doc) {
Expand Down Expand Up @@ -54,11 +63,11 @@ function printHtml(dom: Element) {
}
}

export function printEditorContent(view: Editor['view']) {
const editorContent = view.dom.closest('.ProseMirror')
export function printEditorContent(editor: Editor) {
const content = editor.getHTML()

if (editorContent) {
printHtml(editorContent)
if (content) {
printHtml(content)
return true
}
return false
Expand Down

0 comments on commit 0a99773

Please sign in to comment.