Files
hrm/app/components/common/TextEditor.vue
2026-04-06 15:53:14 +07:00

394 lines
8.1 KiB
Vue

<template>
<ClientOnly>
<div ref="el"></div>
</ClientOnly>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import 'ckeditor5/ckeditor5.css';
import 'ckeditor5-premium-features/ckeditor5-premium-features.css';
const el = ref(null);
let editor = null;
const props = defineProps({
modelValue: {
type: String,
default: '',
},
});
const emit = defineEmits(['update:modelValue']);
const content = ref(props.modelValue || '');
onMounted(async () => {
if (!process.client) return;
const {
ClassicEditor,
AccessibilityHelp,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
BlockQuote,
BlockToolbar,
Bold,
CKBox,
CKBoxImageEdit,
CloudServices,
Essentials,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
Heading,
Highlight,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromOffice,
PictureEditing,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
GeneralHtmlSupport,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Alignment,
Undo,
} = await import('ckeditor5');
const { ExportPdf, ExportWord, ImportWord } = await import('ckeditor5-premium-features');
editor = await ClassicEditor.create(el.value, {
// licenseKey: 'GPL',
plugins: [
AccessibilityHelp,
Autoformat,
AutoImage,
AutoLink,
Autosave,
BalloonToolbar,
BlockQuote,
BlockToolbar,
Bold,
CKBox,
CKBoxImageEdit,
CloudServices,
Essentials,
ExportPdf,
ExportWord,
FontBackgroundColor,
FontColor,
FontFamily,
FontSize,
Heading,
Highlight,
ImageBlock,
ImageCaption,
ImageInline,
ImageInsert,
ImageInsertViaUrl,
ImageResize,
ImageStyle,
ImageTextAlternative,
ImageToolbar,
ImageUpload,
ImportWord,
Indent,
IndentBlock,
Italic,
Link,
LinkImage,
List,
ListProperties,
MediaEmbed,
Mention,
PageBreak,
Paragraph,
PasteFromOffice,
PictureEditing,
RemoveFormat,
SelectAll,
SpecialCharacters,
SpecialCharactersArrows,
SpecialCharactersCurrency,
SpecialCharactersEssentials,
SpecialCharactersLatin,
SpecialCharactersMathematical,
SpecialCharactersText,
Subscript,
Superscript,
Table,
TableCaption,
TableCellProperties,
TableColumnResize,
TableProperties,
TableToolbar,
TextTransformation,
TodoList,
Underline,
Alignment,
GeneralHtmlSupport,
Undo,
],
toolbar: [
'undo',
'redo',
'|',
'importWord',
'exportWord',
'exportPdf',
'|',
'heading',
'|',
'fontSize',
'fontFamily',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'underline',
'subscript',
'superscript',
'removeFormat',
'|',
'alignment', // 👈 THÊM
'|',
'specialCharacters',
'pageBreak',
'link',
'insertImage',
'insertImageViaUrl',
'ckbox',
'mediaEmbed',
'insertTable',
'highlight',
'blockQuote',
'|',
'bulletedList',
'numberedList',
'todoList',
'outdent',
'indent',
],
exportPdf: {
stylesheets: [
'https://cdn.ckeditor.com/ckeditor5/43.2.0/ckeditor5.css',
'https://cdn.ckeditor.com/ckeditor5-premium-features/43.2.0/ckeditor5-premium-features.css',
],
fileName: 'export-pdf-demo.pdf',
converterOptions: {
format: 'Tabloid',
margin_top: '20mm',
margin_bottom: '20mm',
margin_right: '24mm',
margin_left: '24mm',
page_orientation: 'portrait',
},
},
exportWord: {
stylesheets: [
'https://cdn.ckeditor.com/ckeditor5/43.2.0/ckeditor5.css',
'https://cdn.ckeditor.com/ckeditor5-premium-features/43.2.0/ckeditor5-premium-features.css',
],
fileName: 'export-word-demo.docx',
converterOptions: {
document: {
orientation: 'portrait',
size: 'Tabloid',
margins: {
top: '20mm',
bottom: '20mm',
right: '24mm',
left: '24mm',
},
},
},
},
balloonToolbar: ['bold', 'italic','fontFamily','fontSize', '|', 'alignment', 'link', 'insertImage', '|', 'bulletedList', 'numberedList'],
blockToolbar: [
'fontSize',
'fontColor',
'fontBackgroundColor',
'|',
'bold',
'italic',
'|',
'link',
'insertImage',
'insertTable',
'|',
'bulletedList',
'numberedList',
'outdent',
'indent',
],
fontFamily: {
supportAllValues: true,
},
fontSize: {
options: [10, 12,13, 14, 'default', 18, 20, 22],
supportAllValues: true,
},
heading: {
options: [
{
model: 'paragraph',
title: 'Paragraph',
class: 'ck-heading_paragraph',
},
{
model: 'heading1',
view: 'h1',
title: 'Heading 1',
class: 'ck-heading_heading1',
},
{
model: 'heading2',
view: 'h2',
title: 'Heading 2',
class: 'ck-heading_heading2',
},
{
model: 'heading3',
view: 'h3',
title: 'Heading 3',
class: 'ck-heading_heading3',
},
{
model: 'heading4',
view: 'h4',
title: 'Heading 4',
class: 'ck-heading_heading4',
},
{
model: 'heading5',
view: 'h5',
title: 'Heading 5',
class: 'ck-heading_heading5',
},
{
model: 'heading6',
view: 'h6',
title: 'Heading 6',
class: 'ck-heading_heading6',
},
],
},
image: {
toolbar: [
'toggleImageCaption',
'imageTextAlternative',
'|',
'imageStyle:inline',
'imageStyle:wrapText',
'imageStyle:breakText',
'|',
'resizeImage',
'|',
'ckboxImageEdit',
],
},
link: {
addTargetToExternalLinks: true,
defaultProtocol: 'https://',
decorators: {
toggleDownloadable: {
mode: 'manual',
label: 'Downloadable',
attributes: {
download: 'file',
},
},
},
},
list: {
properties: {
styles: true,
startIndex: true,
reversed: true,
},
},
mention: {
feeds: [
{
marker: '@',
feed: [],
},
],
},
menuBar: {
isVisible: true,
},
table: {
contentToolbar: ['tableColumn', 'tableRow', 'mergeTableCells', 'tableProperties', 'tableCellProperties'],
},
alignment: {
options: ['left', 'center', 'right', 'justify'],
},
});
// set data ban đầu
editor.setData(props.modelValue || '');
// khi thay đổi → emit ra ngoài
editor.model.document.on('change:data', () => {
const data = editor.getData();
emit('update:modelValue', data);
});
});
watch(
() => props.modelValue,
(val) => {
if (editor && val !== editor.getData()) {
editor.setData(val || '');
}
},
);
onBeforeUnmount(async () => {
if (editor) {
await editor.destroy();
editor = null;
}
});
</script>