Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fluent-editor): add before-editor-init props #2670

Merged
merged 4 commits into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions examples/sites/demos/apis/fluent-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,19 @@ export default {
name: 'fluent-editor',
type: 'component',
props: [
{
name: 'before-editor-init',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新添加的属性,需要标注下在什么版本添加的哈

type: '(FluentEditor) => void',
defaultValue: '',
meta: {
stable: '3.21.0'
},
desc: {
'zh-CN': 'FluentEditor 初始化之前执行的钩子,用于注册自定义 FluentEditor 模块和格式。',
'en-US': ''
},
pcDemo: 'before-editor-init'
},
{
name: 'data-type',
type: 'boolean',
Expand Down Expand Up @@ -64,7 +77,7 @@ export default {
{
name: 'options',
type: 'object',
defaultValue: "",
defaultValue: '',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix inconsistent default values for options and zIndex.

The default values should match their respective types:

  • options should be an empty object ({}) as its type is 'object'
  • zIndex should be a number as its type is 'number'

Apply this diff:

  {
    name: 'options',
    type: 'object',
-   defaultValue: '',
+   defaultValue: '{}',
    // ...
  }
  // ...
  {
    name: 'zIndex',
    type: 'number',
-   defaultValue: '',
+   defaultValue: '1000',
    // ...
  }

Also applies to: 91-91

desc: {
'zh-CN': '编辑器配置项,参考 Quill 文档:https://quilljs.com/docs/configuration#options',
'en-US': ''
Expand All @@ -75,7 +88,7 @@ export default {
{
name: 'zIndex',
type: 'number',
defaultValue: "",
defaultValue: '',
desc: {
'zh-CN': '编辑器的 z-index',
'en-US': ''
Expand Down Expand Up @@ -105,6 +118,6 @@ interface IImageUploadOptions {
fail: (serverError: string) => void // 上传失败回调信息
}
`
},
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<div>
<tiny-fluent-editor
ref="fluentEditorRef"
v-model="content"
:before-editor-init="beforeEditorInit"
:options="editorOptions"
></tiny-fluent-editor>
内容:<br />
{{ content }}
</div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import { TinyFluentEditor } from '@opentiny/vue'

const content = ref('{"ops":[{"insert":"Hello "},{"attributes":{"bold":true},"insert":"FluentEditor"},{"insert":"!"}]}')

const fluentEditorRef = ref()

const beforeEditorInit = (FluentEditor) => {
const goodIcon = `<svg t="1734490908682" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5918" width="200" height="200"><path d="M881.066667 394.666667c-21.333333-23.466667-51.2-36.266667-81.066667-36.266667H618.666667v-117.333333c0-44.8-29.866667-85.333333-87.466667-117.333334-17.066667-10.666667-38.4-12.8-57.6-8.533333-19.2 4.266667-36.266667 17.066667-46.933333 34.133333-2.133333 2.133333-2.133333 4.266667-4.266667 6.4l-125.866667 281.6H204.8c-59.733333 0-108.8 46.933333-108.8 106.666667v258.133333c0 57.6 49.066667 106.666667 108.8 106.666667h544c53.333333 0 98.133333-38.4 106.666667-89.6l51.2-337.066667c4.266667-34.133333-6.4-64-25.6-87.466666z m-593.066667 448H204.8c-25.6 0-44.8-19.2-44.8-42.666667v-256c0-23.466667 19.2-42.666667 44.8-42.666667h83.2v341.333334z m554.666667-373.333334L789.333333 806.4c-4.266667 21.333333-21.333333 36.266667-42.666666 36.266667H352V471.466667l130.133333-290.133334c2.133333-4.266667 4.266667-4.266667 6.4-4.266666 2.133333 0 4.266667 0 8.533334 2.133333 25.6 14.933333 55.466667 38.4 55.466666 64v149.333333c0 17.066667 14.933333 32 32 32h213.333334c12.8 0 25.6 4.266667 34.133333 14.933334 8.533333 6.4 12.8 19.2 10.666667 29.866666z" fill="#666666" p-id="5919"></path></svg>`
const badIcon = `<svg t="1734491308472" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3380" width="200" height="200"><path d="M904.533333 522.666667L853.333333 185.6c-8.533333-51.2-55.466667-89.6-106.666666-89.6H204.8c-59.733333 0-108.8 46.933333-108.8 106.666667v258.133333c0 57.6 49.066667 106.666667 108.8 106.666667h91.733333l125.866667 281.6c2.133333 2.133333 2.133333 4.266667 4.266667 6.4 14.933333 23.466667 38.4 36.266667 64 36.266666 12.8 0 25.6-4.266667 38.4-10.666666 57.6-34.133333 87.466667-72.533333 87.466666-117.333334v-117.333333h183.466667c32 0 59.733333-12.8 81.066667-36.266667 19.2-25.6 29.866667-55.466667 23.466666-87.466666z m-616.533333-21.333334H204.8c-25.6 0-44.8-19.2-44.8-42.666666v-256c0-23.466667 19.2-42.666667 44.8-42.666667h83.2v341.333333zM832 567.466667c-8.533333 8.533333-21.333333 14.933333-34.133333 14.933333h-213.333334c-17.066667 0-32 14.933333-32 32v149.333333c0 25.6-29.866667 49.066667-55.466666 64-4.266667 2.133333-10.666667 2.133333-14.933334-4.266666L352 533.333333V160H746.666667c21.333333 0 40.533333 14.933333 42.666666 36.266667L842.666667 533.333333c2.133333 10.666667-2.133333 23.466667-10.666667 34.133334z" fill="#666666" p-id="3381"></path></svg>`
const icons = FluentEditor.import('ui/icons')
icons.good = goodIcon
icons.bad = badIcon

const Parchment = FluentEditor.import('parchment')

const GoodStyle = new Parchment.StyleAttributor('good', 'color', {
scope: Parchment.Scope.INLINE
})

const BadStyle = new Parchment.StyleAttributor('bad', 'color', {
scope: Parchment.Scope.INLINE
})

FluentEditor.register('formats/good', GoodStyle)
FluentEditor.register('formats/bad', BadStyle)
}

const editorOptions = {
modules: {
// 工具栏
toolbar: [
['undo', 'redo', 'clean', 'format-painter'],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'bullet' }, { list: 'ordered' }],
[{ align: '' }, { align: 'center' }, { align: 'right' }],
['better-table'],
['fullscreen'],
['good', 'bad']
]
}
}

onMounted(() => {
const fluentEditor = fluentEditorRef.value.state.quill
const toolbar = fluentEditor.getModule('toolbar')

toolbar.addHandler('good', function (value) {
this.quill.format('good', value ? '#5cb300' : '')
})

toolbar.addHandler('bad', function (value) {
this.quill.format('bad', value ? '#f23030' : '')
})
})
</script>
75 changes: 75 additions & 0 deletions examples/sites/demos/pc/app/fluent-editor/before-editor-init.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<template>
<div>
<tiny-fluent-editor
ref="fluentEditorRef"
v-model="content"
:before-editor-init="beforeEditorInit"
:options="editorOptions"
></tiny-fluent-editor>
内容:<br />
{{ content }}
</div>
</template>

<script>
import { TinyFluentEditor } from '@opentiny/vue'

export default {
components: {
TinyFluentEditor
},
data() {
return {
content: '{"ops":[{"insert":"Hello "},{"attributes":{"bold":true},"insert":"FluentEditor"},{"insert":"!"}]}',
editorOptions: {
modules: {
// 工具栏
toolbar: [
['undo', 'redo', 'clean', 'format-painter'],
['bold', 'italic', 'underline', 'strike'],
[{ list: 'bullet' }, { list: 'ordered' }],
[{ align: '' }, { align: 'center' }, { align: 'right' }],
['better-table'],
['fullscreen'],
['good', 'bad']
]
}
}
}
},
methods: {
beforeEditorInit(FluentEditor) {
const goodIcon = `<svg t="1734490908682" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5918" width="200" height="200"><path d="M881.066667 394.666667c-21.333333-23.466667-51.2-36.266667-81.066667-36.266667H618.666667v-117.333333c0-44.8-29.866667-85.333333-87.466667-117.333334-17.066667-10.666667-38.4-12.8-57.6-8.533333-19.2 4.266667-36.266667 17.066667-46.933333 34.133333-2.133333 2.133333-2.133333 4.266667-4.266667 6.4l-125.866667 281.6H204.8c-59.733333 0-108.8 46.933333-108.8 106.666667v258.133333c0 57.6 49.066667 106.666667 108.8 106.666667h544c53.333333 0 98.133333-38.4 106.666667-89.6l51.2-337.066667c4.266667-34.133333-6.4-64-25.6-87.466666z m-593.066667 448H204.8c-25.6 0-44.8-19.2-44.8-42.666667v-256c0-23.466667 19.2-42.666667 44.8-42.666667h83.2v341.333334z m554.666667-373.333334L789.333333 806.4c-4.266667 21.333333-21.333333 36.266667-42.666666 36.266667H352V471.466667l130.133333-290.133334c2.133333-4.266667 4.266667-4.266667 6.4-4.266666 2.133333 0 4.266667 0 8.533334 2.133333 25.6 14.933333 55.466667 38.4 55.466666 64v149.333333c0 17.066667 14.933333 32 32 32h213.333334c12.8 0 25.6 4.266667 34.133333 14.933334 8.533333 6.4 12.8 19.2 10.666667 29.866666z" fill="#666666" p-id="5919"></path></svg>`
const badIcon = `<svg t="1734491308472" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3380" width="200" height="200"><path d="M904.533333 522.666667L853.333333 185.6c-8.533333-51.2-55.466667-89.6-106.666666-89.6H204.8c-59.733333 0-108.8 46.933333-108.8 106.666667v258.133333c0 57.6 49.066667 106.666667 108.8 106.666667h91.733333l125.866667 281.6c2.133333 2.133333 2.133333 4.266667 4.266667 6.4 14.933333 23.466667 38.4 36.266667 64 36.266666 12.8 0 25.6-4.266667 38.4-10.666666 57.6-34.133333 87.466667-72.533333 87.466666-117.333334v-117.333333h183.466667c32 0 59.733333-12.8 81.066667-36.266667 19.2-25.6 29.866667-55.466667 23.466666-87.466666z m-616.533333-21.333334H204.8c-25.6 0-44.8-19.2-44.8-42.666666v-256c0-23.466667 19.2-42.666667 44.8-42.666667h83.2v341.333333zM832 567.466667c-8.533333 8.533333-21.333333 14.933333-34.133333 14.933333h-213.333334c-17.066667 0-32 14.933333-32 32v149.333333c0 25.6-29.866667 49.066667-55.466666 64-4.266667 2.133333-10.666667 2.133333-14.933334-4.266666L352 533.333333V160H746.666667c21.333333 0 40.533333 14.933333 42.666666 36.266667L842.666667 533.333333c2.133333 10.666667-2.133333 23.466667-10.666667 34.133334z" fill="#666666" p-id="3381"></path></svg>`
const icons = FluentEditor.import('ui/icons')
icons.good = goodIcon
icons.bad = badIcon

const Parchment = FluentEditor.import('parchment')

const GoodStyle = new Parchment.StyleAttributor('good', 'color', {
scope: Parchment.Scope.INLINE
})

const BadStyle = new Parchment.StyleAttributor('bad', 'color', {
scope: Parchment.Scope.INLINE
})

FluentEditor.register('formats/good', GoodStyle)
FluentEditor.register('formats/bad', BadStyle)
}
Comment on lines +41 to +60
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve maintainability and error handling

  1. The SVG icons should be moved to separate files or a dedicated icons module.
  2. Add error handling for FluentEditor import operations.
   beforeEditorInit(FluentEditor) {
+    try {
+      const { goodIcon, badIcon } = await import('./icons')
       const icons = FluentEditor.import('ui/icons')
       icons.good = goodIcon
       icons.bad = badIcon

       const Parchment = FluentEditor.import('parchment')
       // ... rest of the code
+    } catch (error) {
+      console.error('Failed to initialize editor:', error)
+    }
   }

Committable suggestion skipped: line range outside the PR's diff.

},
mounted() {
const fluentEditor = this.$refs.fluentEditorRef.state.quill
const toolbar = fluentEditor.getModule('toolbar')

toolbar.addHandler('good', function (value) {
this.quill.format('good', value ? '#5cb300' : '')
})

toolbar.addHandler('bad', function (value) {
this.quill.format('bad', value ? '#f23030' : '')
})
}
Comment on lines +62 to +73
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add cleanup and error handling

  1. Add cleanup in the unmounted/beforeDestroy hook
  2. Add error handling for toolbar operations
  3. Consider using a more stable way to access the Quill instance
   mounted() {
+    try {
       const fluentEditor = this.$refs.fluentEditorRef.state.quill
       const toolbar = fluentEditor.getModule('toolbar')
 
       toolbar.addHandler('good', function (value) {
         this.quill.format('good', value ? '#5cb300' : '')
       })
 
       toolbar.addHandler('bad', function (value) {
         this.quill.format('bad', value ? '#f23030' : '')
       })
+    } catch (error) {
+      console.error('Failed to setup toolbar handlers:', error)
+    }
   },
+  beforeDestroy() {
+    try {
+      const toolbar = this.$refs.fluentEditorRef?.state.quill?.getModule('toolbar')
+      if (toolbar) {
+        toolbar.addHandler('good', null)
+        toolbar.addHandler('bad', null)
+      }
+    } catch (error) {
+      console.error('Failed to cleanup toolbar handlers:', error)
+    }
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
mounted() {
const fluentEditor = this.$refs.fluentEditorRef.state.quill
const toolbar = fluentEditor.getModule('toolbar')
toolbar.addHandler('good', function (value) {
this.quill.format('good', value ? '#5cb300' : '')
})
toolbar.addHandler('bad', function (value) {
this.quill.format('bad', value ? '#f23030' : '')
})
}
mounted() {
try {
const fluentEditor = this.$refs.fluentEditorRef.state.quill
const toolbar = fluentEditor.getModule('toolbar')
toolbar.addHandler('good', function (value) {
this.quill.format('good', value ? '#5cb300' : '')
})
toolbar.addHandler('bad', function (value) {
this.quill.format('bad', value ? '#f23030' : '')
})
} catch (error) {
console.error('Failed to setup toolbar handlers:', error)
}
},
beforeDestroy() {
try {
const toolbar = this.$refs.fluentEditorRef?.state.quill?.getModule('toolbar')
if (toolbar) {
toolbar.addHandler('good', null)
toolbar.addHandler('bad', null)
}
} catch (error) {
console.error('Failed to cleanup toolbar handlers:', error)
}
}

}
</script>
13 changes: 13 additions & 0 deletions examples/sites/demos/pc/app/fluent-editor/webdoc/fluent-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,19 @@ export default {
'en-US': ''
},
codeFiles: ['data-switch.vue']
},
{
demoId: 'before-editor-init',
name: {
'zh-CN': '初始化前的钩子',
'en-US': ''
},
desc: {
'zh-CN':
'<p>通过 <code>before-editor-init</code> 设置 FluentEditor 初始化前的钩子函数,主要用于注册 FluentEditor 自定义格式和模块。<br>这个示例增加了两个新的格式:good / bad,并在工具栏增加了对应的图标用于设置这两种格式。<br>选中一段文本,点击点赞图标,会将文本色设置成绿色;点击点踩图标,会将文本色设置成红色。</p>',
'en-US': ''
},
codeFiles: ['before-editor-init.vue']
}
]
}
26 changes: 14 additions & 12 deletions packages/renderless/src/fluent-editor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const init =
state.innerOptions.modules.toolbar = simpleToolbar
}

props.beforeEditorInit?.(FluentEditor)

const quill = new FluentEditor(vm.$refs.editor, state.innerOptions)
quill.emitter.on('file-change', api.fileOperationToSev)
state.quill = Object.freeze(quill)
Expand Down Expand Up @@ -194,8 +196,8 @@ export const handleCompositionend =
state.quill.root.classList.add('ql-blank')
}
} else {
let data = state.quill.container.innerHTML,
range = state.quill.getSelection(true)
let data = state.quill.container.innerHTML
let range = state.quill.getSelection(true)
const [mentionItem, offset] = state.quill.getLeaf(range.index)

if (mentionItem.statics.blotName === 'break' || (mentionItem.statics.blotName === 'text' && offset === 0)) {
Expand All @@ -204,8 +206,8 @@ export const handleCompositionend =
if (mentionItem.statics.blotName === 'break') {
state.quill.setSelection(range.index + event.data.length)
} else {
let pattern = /[\u4E00-\u9FA5\uf900-\ufa2d]/,
flag
let pattern = /[\u4E00-\u9FA5\uF900-\uFA2D]/
let flag

if (pattern.test(event.data)) {
flag = true
Expand Down Expand Up @@ -298,8 +300,8 @@ export const inputFileHandler =
fileInput.setAttribute('accept', mimeTypes)

if (
(UploaderDfls.enableMultiUpload['file'] && type === 'file') ||
(UploaderDfls.enableMultiUpload['image'] && type === 'image')
(UploaderDfls.enableMultiUpload.file && type === 'file') ||
(UploaderDfls.enableMultiUpload.image && type === 'image')
) {
fileInput.setAttribute('multiple', '')
}
Expand Down Expand Up @@ -343,12 +345,12 @@ export const uploaderDflsHandler =
export const handleUploadFile =
({ api, UploaderDfls }) =>
(range, files, hasRejectedFile) => {
const fileEnableMultiUpload = UploaderDfls.enableMultiUpload === true || UploaderDfls.enableMultiUpload['file']
const fileEnableMultiUpload = UploaderDfls.enableMultiUpload === true || UploaderDfls.enableMultiUpload.file

api.fileOperationToSev({
operation: 'upload',
data: fileEnableMultiUpload ? { files } : { file: files[0] },
hasRejectedFile: hasRejectedFile,
hasRejectedFile,
callback: (res) => {
if (!res) {
return
Expand Down Expand Up @@ -490,11 +492,11 @@ export const handleUploadImage =
(range, { file, files }, hasRejectedImage) => {
if (state.quill.options.uploadOption.imageUploadToServer) {
const index = state.promisesData.length
const imageEnableMultiUpload = UploaderDfls.enableMultiUpload['image']
const imageEnableMultiUpload = UploaderDfls.enableMultiUpload.image
const result = {
file,
data: { files: [file] },
hasRejectedImage: hasRejectedImage,
hasRejectedImage,
callback: (res) => {
if (!res) {
return
Expand Down Expand Up @@ -526,7 +528,7 @@ export const handleUploadImage =
}

if (imageEnableMultiUpload) {
result['data'] = { files }
result.data = { files }
}

state.promisesData.push({
Expand Down Expand Up @@ -863,7 +865,7 @@ export const handleDblclick =
props.picPreview &&
e &&
e.type === 'dblclick' &&
[...e.target.classList].indexOf('blot-formatter__overlay') > -1 &&
[...e.target.classList].includes('blot-formatter__overlay') &&
e.target.dataset.image
) {
api.doPreview(e.target)
Expand Down
4 changes: 4 additions & 0 deletions packages/vue/src/fluent-editor/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ export const fluentEditorProps = {
imagePasteFailCallback: {
type: Function,
default: () => {}
},
beforeEditorInit: {
type: Function,
default: () => {}
}
}

Expand Down
3 changes: 2 additions & 1 deletion packages/vue/src/fluent-editor/src/mobile-first.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,8 @@ export default defineComponent({
'dataType',
'dataUpgrade',
'zIndex',
'imagePasteFailCallback'
'imagePasteFailCallback',
'beforeEditorInit'
],
setup(props, context) {
return setup({
Expand Down
3 changes: 2 additions & 1 deletion packages/vue/src/fluent-editor/src/pc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ export default defineComponent({
'dataType',
'dataUpgrade',
'zIndex',
'imagePasteFailCallback'
'imagePasteFailCallback',
'beforeEditorInit'
],
setup(props, context): any {
return setup({
Expand Down
Loading