Skip to content

Commit

Permalink
Merge pull request #46 from hunghg255/feat-attachment
Browse files Browse the repository at this point in the history
  • Loading branch information
hunghg255 authored Sep 26, 2024
2 parents a4acaa7 + b38b9d7 commit 6443b44
Show file tree
Hide file tree
Showing 17 changed files with 457 additions and 18 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,10 @@ export function getLocaleConfig(lang: string) {
text: 'Mention',
link: '/extensions/Mention/index.md',
},
{
text: 'Attachment',
link: '/extensions/Attachment/index.md',
},
],
},
]
Expand Down
23 changes: 23 additions & 0 deletions docs/extensions/Attachment/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
description: Attachment
---

# Attachment

Attachment is a node extension that allows you to add an Attachment to your editor.

## Usage

```tsx
import { Attachment } from 'reactjs-tiptap-editor'; // [!code ++]

const extensions = [
...,
// Import Extensions Here
Attachment.configure({// [!code ++]
upload: (file: any) => {// [!code ++]
// upload file to server return url
},// [!code ++]
}),// [!code ++]
];
```
20 changes: 4 additions & 16 deletions docs/extensions/Mention/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
---
description: Mention

next:
text: Attachment
link: /extensions/Attachment/index.md
---

# Mention
Expand All @@ -19,19 +23,3 @@ const extensions = [
Mention // [!code ++]
];
```

## Configuration

```tsx
import { TextDirection } from 'reactjs-tiptap-editor'; // [!code ++]

const extensions = [
...,
// Import Extensions Here
TextDirection.configure({ // [!code ++]
types: ['heading', 'paragraph', 'blockquote', 'list_item'], // [!code ++]
directions: ['ltr', 'rtl'], // [!code ++]
defaultDirection: 'ltr', // [!code ++]
}) // [!code ++]
];
```
29 changes: 28 additions & 1 deletion playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useCallback, useState } from 'react'

import RichTextEditor, {
Attachment,
BaseKit,
Blockquote,
Bold,
Expand Down Expand Up @@ -49,6 +50,18 @@ import RichTextEditor, {

import 'reactjs-tiptap-editor/style.css'

function convertBase64ToBlob(base64: string) {
const arr = base64.split(',')
const mime = arr[0].match(/:(.*?);/)![1]
const bstr = atob(arr[1])
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}

const extensions = [
BaseKit.configure({
placeholder: {
Expand Down Expand Up @@ -131,9 +144,23 @@ const extensions = [
Excalidraw,
TextDirection,
Mention,
Attachment.configure({
upload: (file: any) => {
// fake upload return base 64
const reader = new FileReader()
reader.readAsDataURL(file)

return new Promise((resolve) => {
setTimeout(() => {
const blob = convertBase64ToBlob(reader.result as string)
resolve(URL.createObjectURL(blob))
}, 300)
})
},
}),
]

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><div class="excalidraw" defaultshowpicker="false" width="800" height="199" data="{&quot;elements&quot;:[{&quot;type&quot;:&quot;rectangle&quot;,&quot;version&quot;:15,&quot;versionNonce&quot;:1501503441,&quot;isDeleted&quot;:false,&quot;id&quot;:&quot;pjx5DYxD23xDHXQ85nOSw&quot;,&quot;fillStyle&quot;:&quot;cross-hatch&quot;,&quot;strokeWidth&quot;:2,&quot;strokeStyle&quot;:&quot;solid&quot;,&quot;roughness&quot;:1,&quot;opacity&quot;:100,&quot;angle&quot;:0,&quot;x&quot;:289.74224853515625,&quot;y&quot;:183.94967651367188,&quot;strokeColor&quot;:&quot;#e03131&quot;,&quot;backgroundColor&quot;:&quot;#ffc9c9&quot;,&quot;width&quot;:334.83209228515625,&quot;height&quot;:176.76864624023438,&quot;seed&quot;:1327620497,&quot;groupIds&quot;:[],&quot;frameId&quot;:null,&quot;roundness&quot;:{&quot;type&quot;:3},&quot;boundElements&quot;:[],&quot;updated&quot;:1725207092250,&quot;link&quot;:null,&quot;locked&quot;:false},{&quot;type&quot;:&quot;ellipse&quot;,&quot;version&quot;:18,&quot;versionNonce&quot;:1802132881,&quot;isDeleted&quot;:false,&quot;id&quot;:&quot;pHn8celdAeDKdhKVI6hCR&quot;,&quot;fillStyle&quot;:&quot;cross-hatch&quot;,&quot;strokeWidth&quot;:2,&quot;strokeStyle&quot;:&quot;solid&quot;,&quot;roughness&quot;:1,&quot;opacity&quot;:100,&quot;angle&quot;:0,&quot;x&quot;:706.022216796875,&quot;y&quot;:146.41253662109375,&quot;strokeColor&quot;:&quot;#e03131&quot;,&quot;backgroundColor&quot;:&quot;#ffc9c9&quot;,&quot;width&quot;:200.49169921875,&quot;height&quot;:213.36334228515625,&quot;seed&quot;:487390641,&quot;groupIds&quot;:[],&quot;frameId&quot;:null,&quot;roundness&quot;:{&quot;type&quot;:2},&quot;boundElements&quot;:[],&quot;updated&quot;:1725207094676,&quot;link&quot;:null,&quot;locked&quot;:false},{&quot;id&quot;:&quot;il96EMYE5vxuyhlTjVGCI&quot;,&quot;type&quot;:&quot;diamond&quot;,&quot;x&quot;:573.7800827026367,&quot;y&quot;:355.6528778076172,&quot;width&quot;:198.252685546875,&quot;height&quot;:149.70916748046875,&quot;angle&quot;:0,&quot;strokeColor&quot;:&quot;#1e1e1e&quot;,&quot;backgroundColor&quot;:&quot;transparent&quot;,&quot;fillStyle&quot;:&quot;solid&quot;,&quot;strokeWidth&quot;:2,&quot;strokeStyle&quot;:&quot;solid&quot;,&quot;roughness&quot;:1,&quot;opacity&quot;:100,&quot;groupIds&quot;:[],&quot;frameId&quot;:null,&quot;roundness&quot;:{&quot;type&quot;:2},&quot;seed&quot;:434826577,&quot;version&quot;:24,&quot;versionNonce&quot;:190824561,&quot;isDeleted&quot;:false,&quot;boundElements&quot;:null,&quot;updated&quot;:1725207125935,&quot;link&quot;:null,&quot;locked&quot;:false}],&quot;appState&quot;:{&quot;isLoading&quot;:false},&quot;files&quot;:{}}"></div><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><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</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>`
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"></div><p dir="auto"></p>`

function debounce(func: any, wait: number) {
let timeout: NodeJS.Timeout
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

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

3 changes: 3 additions & 0 deletions src/components/icons/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
PaintRoller,
PanelLeft,
PanelRight,
Paperclip,
Pencil,
Plus,
Quote,
Expand Down Expand Up @@ -188,4 +189,6 @@ export const icons = {
TextDirection: Direction,
LeftToRight,
RightToLeft,

Attachment: Paperclip,
} as any
106 changes: 106 additions & 0 deletions src/extensions/Attachment/Attachment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Node, mergeAttributes } from '@tiptap/core'
import { ReactNodeViewRenderer } from '@tiptap/react'

import { getDatasetAttribute } from '@/utils/dom-dataset'
import { NodeViewAttachment } from '@/extensions/Attachment/components/NodeViewAttachment/NodeViewAttachment'
import { ActionButton } from '@/components'
import type { GeneralOptions } from '@/types'

declare module '@tiptap/core' {
interface Commands<ReturnType> {
attachment: {
setAttachment: (attrs?: unknown) => ReturnType
}
}
}

export interface AttachmentOptions extends GeneralOptions<AttachmentOptions> {
/** Function for uploading files */
upload?: (file: File) => Promise<string>
}

export const Attachment = Node.create<AttachmentOptions>({
name: 'attachment',
content: '',
marks: '',
group: 'block',
selectable: true,
atom: true,
draggable: true,

addOptions() {
return {
...this.parent?.(),
HTMLAttributes: {
class: 'attachment',
},
button: ({ editor, t }: any) => ({
component: ActionButton,
componentProps: {
action: () => editor.chain().focus().setAttachment().run(),
isActive: () => false,
disabled: false,
icon: 'Attachment',
tooltip: t('editor.attachment.tooltip'),
},
}),
}
},

parseHTML() {
return [{ tag: 'div[class=attachment]' }]
},

renderHTML({ HTMLAttributes }) {
// @ts-expect-error
return ['div', mergeAttributes(this.options.HTMLAttributes, HTMLAttributes)]
},

addAttributes() {
return {
fileName: {
default: null,
parseHTML: getDatasetAttribute('filename'),
},
fileSize: {
default: null,
parseHTML: getDatasetAttribute('filesize'),
},
fileType: {
default: null,
parseHTML: getDatasetAttribute('filetype'),
},
fileExt: {
default: null,
parseHTML: getDatasetAttribute('fileext'),
},
url: {
default: null,
parseHTML: getDatasetAttribute('url'),
},
hasTrigger: {
default: false,
parseHTML: element => getDatasetAttribute('hastrigger')(element) === 'true',
},
error: {
default: null,
parseHTML: getDatasetAttribute('error'),
},
}
},

addCommands() {
return {
setAttachment:
(attrs = {}) =>
({ chain }) => {
// @ts-expect-error
return chain().insertContent({ type: this.name, attrs }).run()
},
}
},

addNodeView() {
return ReactNodeViewRenderer(NodeViewAttachment)
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { LucideAudioLines, LucideFile, LucideImage, LucideSheet, LucideTableProperties, LucideVideo } from 'lucide-react'
import { normalizeFileType } from '@/utils/file'
import { ExportPdf } from '@/components/icons/ExportPdf'
import ExportWord from '@/components/icons/ExportWord'

export function getFileTypeIcon(fileType: string) {
const type = normalizeFileType(fileType)

switch (type) {
case 'audio':
return <LucideAudioLines />

case 'video':
return <LucideVideo />

case 'file':
return <LucideFile />

case 'image':
return <LucideImage />

case 'pdf':
return <ExportPdf />

case 'word':
return <ExportWord />

case 'excel':
return <LucideSheet />

case 'ppt':
return <LucideTableProperties />

default: {
return <></>
}
}
}
Loading

0 comments on commit 6443b44

Please sign in to comment.