implement dropdown then set tool bar name as uploadDocx and repair history for undo on import docx, got @esi_package/ckeditor5-import@0.0.6

This commit is contained in:
Sahatsawat Kanpai 2025-01-24 11:56:29 +07:00
parent c7faf3d97e
commit 062c13d0d7
3 changed files with 47 additions and 123 deletions

View File

@ -1,6 +1,6 @@
{
"name": "@esi_package/ckeditor5-import",
"version": "0.0.5",
"version": "0.0.6",
"description": "A plugin for CKEditor 5.",
"keywords": [
"ckeditor",

View File

@ -84,8 +84,7 @@ ClassicEditor
'undo',
'redo',
'|',
'importDocx',
'insertDocx',
'uploadDocx',
'mathlive',
'|',
'heading',

View File

@ -1,4 +1,4 @@
import { Plugin, ButtonView } from 'ckeditor5';
import { Plugin, ButtonView, DropdownView, DropdownButtonView, createDropdown, addToolbarToDropdown, addListToDropdown, Collection, ListDropdownItemDefinition, ListDropdownButtonDefinition } from 'ckeditor5';
import ckeditor5Icon from '../theme/icons/ckeditor.svg';
import ckeditor5Iconn from '../theme/icons/ckeditorr.svg';
@ -19,126 +19,42 @@ export default class Import extends Plugin {
const t = editor.t;
const model = editor.model;
// Add the "importButton" to feature components.
editor.ui.componentFactory.add( 'importDocx', locale => {
const view = new ButtonView( locale );
view.set( {
label: t( 'Import Docx' ),
icon: ckeditor5Iconn,
tooltip: true
// Add the "uploadDocx" to feature components.
editor.ui.componentFactory.add( 'uploadDocx', locale => {
const dropdownView = createDropdown( locale );
dropdownView.buttonView.set( {
label: t( 'Upload Docx' ),
icon: ckeditor5Icon,
tooltip: true,
} );
// POST fetch html from docx
this.listenTo( view, 'execute', async () => {
// curl -X 'POST' \
// 'http://localhost:8080/aspose/word-to-html' \
// -H 'accept: text/plain' \
// -H 'Content-Type: application/vnd.openxmlformats-officedocument.wordprocessingml.document' \
// --data-binary '@stou(207).docx'
// file accpt docx
const input = document.createElement('input');
input.type = 'file';
input.accept = '.docx';
input.click();
input.addEventListener('change', async () => {
const file = input.files![0];
const formData = new FormData();
formData.append("file", file);
try {
const response = await fetch("http://localhost:8080/aspose/word-to-html", {
method: "POST",
headers: {
// Do not set `Content-Type` manually when using FormData;
// the browser will automatically set the correct boundary for multipart/form-data.
},
body: file, // directly sending file bytes as the backend expects byte[]
});
if (!response.ok) {
throw new Error(`Failed to upload file: ${response.statusText}`);
}
const htmlText = await response.text();
// console.log("Converted HTML:", htmlText);
const htmlDoc = new DOMParser().parseFromString(htmlText, "text/html");
const body = htmlDoc.body;
// pre-process mathml to latex
// get all mathml elements
// replace all mathml element in the body with script type math/tex with inside content latex
const mathmlElements = body.querySelectorAll("math");
mathmlElements.forEach(mathmlElement => {
const latex = MathMLToLaTeX.convert(mathmlElement.outerHTML);
const scriptElement = document.createElement("script");
scriptElement.type = "math/tex";
scriptElement.textContent = latex;
const spanElement = document.createElement("span");
spanElement.appendChild(scriptElement);
mathmlElement.replaceWith(spanElement);
});
// font-family for <span> concat with , serif
const spans = body.querySelectorAll("span");
spans.forEach(span => {
// span.style.fontFamily += ", serif";
// check if it's <p> tag has font-size attribute, then apply to <span> tag
const p = span.parentElement;
if (p && p.tagName === "P") {
const fontSize = p.style.fontSize;
if (fontSize && span.style.fontSize === "") {
span.setAttribute("style", `${span.getAttribute("style")}; font-size: ${fontSize}`);
}
}
const li = span.parentElement;
if (li && li.tagName === "LI") {
const outerli = li.parentElement?.parentElement;
if (outerli && outerli.tagName === "LI") {
const fontSize = outerli.style.fontSize;
if (fontSize && li.style.fontSize === "") {
li.setAttribute("style", `${span.getAttribute("style")}; font-size: ${fontSize}`);
}
}
const fontSize = li.style.fontSize;
if (fontSize && span.style.fontSize === "") {
span.setAttribute("style", `${span.getAttribute("style")}; font-size: ${fontSize}`);
}
}
const pp = span.parentElement?.parentElement;
if (pp && span.parentElement.tagName === "A") {
const fontSize = pp.style.fontSize;
if (fontSize && span.style.fontSize === "") {
span.setAttribute("style", `${span.getAttribute("style")}; font-size: ${fontSize}`);
}
}
});
console.log("Converted HTML:", body.innerHTML);
this.editor.setData(body.innerHTML);
} catch (error) {
console.error("Error uploading file:", error);
throw error;
}
editor.editing.view.focus();
});
} );
return view;
} );
// Add the "insertButton" to feature components.
editor.ui.componentFactory.add( 'insertDocx', locale => {
const view = new ButtonView( locale );
view.set( {
const items = new Collection<ListDropdownItemDefinition>();
const importDropdownItem: ListDropdownButtonDefinition = {
type: 'button',
model: {
label: t( 'Insert Docx' ),
icon: ckeditor5Icon,
tooltip: true
} );
tooltip: true,
isOn: false,
withText: true
},
} as unknown as ListDropdownButtonDefinition;
const insertDropdownItem: ListDropdownButtonDefinition = {
type: 'button',
model: {
label: t( 'Import Docx' ),
icon: ckeditor5Iconn,
tooltip: true,
isOn: false,
withText: true
},
} as unknown as ListDropdownButtonDefinition;
items.add(importDropdownItem);
items.add(insertDropdownItem);
addListToDropdown( dropdownView, items );
// import/insert docx
// POST fetch html from docx
this.listenTo( view, 'execute', async () => {
this.listenTo( dropdownView, 'execute', async (evt) => {
// curl -X 'POST' \
// 'http://localhost:8080/aspose/word-to-html' \
// -H 'accept: text/plain' \
@ -222,9 +138,18 @@ export default class Import extends Plugin {
}
});
console.log("Converted HTML:", body.innerHTML);
// this.editor.setData(body.innerHTML);
// decided by user interaction on dropdown
const viewFragment = editor.data.processor.toView(body.innerHTML);
const modelFragment = editor.data.toModel(viewFragment);
if ((evt.source as any).label === 'Import Docx') {
// Clear current content
model.change(writer => {
writer.remove(writer.createRangeIn(model.document.getRoot()!));
});
} else if ((evt.source as any).label !== 'Insert Docx') {
throw new Error("Unknown event name");
}
model.insertContent(modelFragment);
} catch (error) {
console.error("Error uploading file:", error);
@ -235,7 +160,7 @@ export default class Import extends Plugin {
});
} );
return view;
return dropdownView;
} );
}
}