-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
2,754 additions
and
256 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,16 @@ | ||
<template> | ||
<NaiveUiEditor></NaiveUiEditor> | ||
<NaiveUiEditor v-model:value="content"></NaiveUiEditor> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { ref, watch } from 'vue' | ||
import { NaiveUiEditor } from 'naive-ui-editor' | ||
const content = ref('') | ||
watch(content, () => { | ||
console.log(content.value) | ||
}) | ||
</script> | ||
|
||
<style scoped></style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,16 @@ | ||
import NaiveUiEditor from './src/NaiveUiEditor.vue' | ||
|
||
import type { App } from 'vue' | ||
import type { Props, RequestFun } from './src/types/index' | ||
|
||
export type { Props, RequestFun } | ||
export { NaiveUiEditor } | ||
|
||
export default { | ||
install(app: App, option?: Props) { | ||
app.component('NaiveUiEditor', NaiveUiEditor) | ||
if (option?.requestFunc) { | ||
app.provide('requestFunc', option.requestFunc) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,51 @@ | ||
<template> | ||
<div>editor</div> | ||
</template> | ||
<script lang="ts" setup> | ||
import { NSpin } from 'naive-ui' | ||
import '@wangeditor/editor/dist/css/style.css' | ||
import { Editor, Toolbar } from '@wangeditor/editor-for-vue' | ||
import { useEditor } from './hooks' | ||
import type { Props, Emits } from './types' | ||
<script setup lang="ts"></script> | ||
const props = withDefaults(defineProps<Props>(), { | ||
mode: 'default', | ||
height: 500, | ||
editorConfig: { | ||
placeholder: '请输入内容...', | ||
MENU_CONF: {}, | ||
} | ||
}) | ||
<style scoped></style> | ||
const emits = defineEmits<Emits>() | ||
const { | ||
loading, | ||
editorRef, | ||
style, | ||
customConfig, | ||
customPaste, | ||
handleCreated, | ||
handleChange | ||
} = useEditor({ props, emits }) | ||
</script> | ||
|
||
<template> | ||
<n-spin :show="loading"> | ||
<Toolbar | ||
:editor="editorRef" | ||
:mode="mode" | ||
:defaultConfig="toolbarConfig" | ||
style="border-bottom: 1px solid #ccc" | ||
/> | ||
<Editor | ||
class="editor-content-view" | ||
:defaultConfig="customConfig" | ||
:mode="mode" | ||
:modelValue="value || ''" | ||
:style="{ height: height + 'px', overflowY: 'hidden' }" | ||
@customPaste="customPaste" | ||
@onCreated="handleCreated" | ||
@onChange="handleChange" | ||
/> | ||
</n-spin> | ||
</template> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import { ref, shallowRef, inject, computed, onBeforeUnmount } from 'vue' | ||
import { useFile } from './useFile' | ||
|
||
import type { Props, Emits, RequestFun } from '../types' | ||
|
||
export const useEditor = ({ props, emits }: { | ||
props: Props, | ||
emits: Emits | ||
}) => { | ||
const loading = ref(false) | ||
const editorRef = shallowRef() | ||
|
||
const injectRequestFunc = inject<RequestFun | undefined>('requestFunc', undefined) | ||
const requestFunc = props.requestFunc ?? injectRequestFunc | ||
|
||
if (!requestFunc) { | ||
throw new Error('requestFunc is required') | ||
} | ||
|
||
const { getElementLen, formatConfig, customPaste } = useFile({ loading, requestFunc }) | ||
|
||
const style = computed(() => ({ height: props.height + 'px', overflowY: 'hidden' })) | ||
const customConfig = computed(() => formatConfig(props.editorConfig)) | ||
|
||
// 编辑器回调函数 | ||
const handleCreated = (editor) => { | ||
editorRef.value = editor; // 记录 editor 实例 | ||
}; | ||
|
||
// 编辑器change | ||
const handleChange = (editor) => { | ||
const isEmpty = | ||
editor.isEmpty() || | ||
(!editor | ||
.getText() | ||
.replace(/[\r\n]/g, '') | ||
.replace(/ /gi, '') | ||
.trim() && | ||
!getElementLen(editor)); | ||
emits('update:value', isEmpty ? null : editor.getHtml()); | ||
}; | ||
|
||
// 组件销毁时,及时销毁编辑器 | ||
onBeforeUnmount(() => { | ||
editorRef.value?.destroy(); | ||
}); | ||
|
||
return { | ||
loading, | ||
editorRef, | ||
style, | ||
customConfig, | ||
customPaste, | ||
handleCreated, | ||
handleChange, | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { findImgFromHtml, extractImgFromRtf, transAsImgToFile, replaceAsUpdSrc } from '../utils/file' | ||
import to from 'await-to-js' | ||
|
||
import type { Ref } from 'vue' | ||
import type { InsertFnType, IEditorConfig } from '@wangeditor/editor'; | ||
import type { RequestFun } from '../types' | ||
|
||
/** | ||
* @name 富文本文件相关处理方法 | ||
* @param loading | ||
* @param requestFunc | ||
* @returns | ||
*/ | ||
export const useFile = ({ | ||
loading, | ||
requestFunc, | ||
}: { | ||
loading: Ref<boolean>, | ||
requestFunc: RequestFun, | ||
}) => { | ||
/** | ||
* 获取图片、视频、链接标签数 | ||
* @param editor | ||
* @returns | ||
*/ | ||
const getElementLen = (editor) => { | ||
return ['image', 'link', 'video'] | ||
.map((tag) => editor.getElemsByType(tag).length) | ||
.reduce((a, b) => a + b, 0); | ||
}; | ||
|
||
/** | ||
* 自定义上传 | ||
*/ | ||
const customUpload = async (file: File, insertFn?: InsertFnType) => { | ||
const [err, url] = await to(requestFunc(file)); | ||
if (err) return ''; | ||
insertFn && insertFn(url, file.name); | ||
return url; | ||
}; | ||
|
||
/** | ||
* 自定义配置 | ||
*/ | ||
const formatConfig = (editorConfig: Partial<IEditorConfig>) => { | ||
const config = { ...(editorConfig || {}) } | ||
config.MENU_CONF = { | ||
uploadImage: { | ||
customUpload, | ||
}, | ||
...(config.MENU_CONF || {}) | ||
} | ||
return config | ||
} | ||
|
||
/** | ||
* 自定义复制粘贴 | ||
* @description 获取word里的图片上传到服务器并替换图片地址 | ||
* @param editor | ||
* @param event | ||
* @returns | ||
*/ | ||
const customPaste = async (editor, event) => { | ||
// 获取粘贴的html部分,该部分包含了图片img标签 | ||
let html = event.clipboardData.getData('text/html'); | ||
|
||
// 获取rtf数据(从word、wps复制粘贴时有),复制粘贴过程中图片的数据就保存在rtf中 | ||
const rtf = event.clipboardData.getData('text/rtf'); | ||
|
||
if (html && rtf) { | ||
// 该条件分支即表示要自定义word粘贴 | ||
|
||
// 列表缩进会超出边框,直接过滤掉 | ||
html = html.replace(/text\-indent:\-(.*?)pt/gi, ''); | ||
|
||
// 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合 | ||
const imgSrcs = findImgFromHtml(html); | ||
|
||
// 如果有 | ||
if (imgSrcs.length) { | ||
// 从rtf内容中查找图片数据 | ||
const rtfImageData = extractImgFromRtf(rtf); | ||
if (rtfImageData.length) { | ||
// 阻止默认的粘贴行为 | ||
event.preventDefault(); | ||
try { | ||
loading.value = true; | ||
// 将图片转为file上传 | ||
const imgs = transAsImgToFile(imgSrcs, rtfImageData); | ||
const urls = await Promise.all(imgs.map((file) => requestFunc(file))); | ||
// 替换为上传后的url | ||
html = replaceAsUpdSrc(html, imgSrcs, rtfImageData, urls); | ||
editor.dangerouslyInsertHtml(html); | ||
return Promise.resolve(); | ||
} catch (e) { | ||
return Promise.reject(e); | ||
} finally { | ||
loading.value = false; | ||
} | ||
} | ||
} | ||
return false; | ||
} else { | ||
// 从html内容中查找粘贴内容中是否有图片元素,并返回img标签的属性src值的集合 | ||
const imgSrcs = findImgFromHtml(html); | ||
// 清除html当中的img标签(单独复制某一张图片没问题) | ||
if (imgSrcs.length && !html.includes('StartFragment--><img')) { | ||
// 阻止默认的粘贴行为 | ||
event.preventDefault(); | ||
html = html.replace(/<img [^>]*src=['"]([^'"]+)[^>]*>/g, ''); | ||
editor.dangerouslyInsertHtml(html); | ||
return false; | ||
} | ||
return true; | ||
} | ||
}; | ||
|
||
return { | ||
getElementLen, | ||
formatConfig, | ||
customPaste, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import type { PropType, ExtractPropTypes } from 'vue'; | ||
import type { IEditorConfig, IToolbarConfig } from '@wangeditor/editor'; | ||
|
||
export interface Props { | ||
value?: string | ||
mode?: 'default' | 'simple' | ||
height?: number | ||
editorConfig?: Partial<IEditorConfig> | ||
toolbarConfig?: Partial<IToolbarConfig> | ||
requestFunc?: RequestFun | ||
} | ||
export type Emits = { | ||
(e: 'update:value', value: string): void | ||
} | ||
|
||
export type Recordable<T = any> = Record<string, T> | ||
|
||
export type RequestFun = ( | ||
file: File, | ||
onProgerss?: (e: { percent: number }) => void | ||
) => Promise<string> |
Oops, something went wrong.