2025-01-15 17:58:02 +07:00
|
|
|
import { Plugin, ButtonView } from 'ckeditor5';
|
|
|
|
|
|
|
|
import ckeditor5Icon from '../theme/icons/ckeditor.svg';
|
2025-01-22 17:53:18 +07:00
|
|
|
import ckeditor5Iconn from '../theme/icons/ckeditorr.svg';
|
2025-01-15 17:58:02 +07:00
|
|
|
|
2025-01-19 21:23:13 +07:00
|
|
|
// import { asBlob } from 'html-docx-js-typescript';
|
|
|
|
// import { saveAs } from 'file-saver';
|
2025-01-15 17:58:02 +07:00
|
|
|
|
2025-01-19 21:23:13 +07:00
|
|
|
// import Temml from 'temml';
|
|
|
|
import { MathMLToLaTeX } from 'mathml-to-latex';
|
2025-01-15 17:58:02 +07:00
|
|
|
|
|
|
|
export default class Import extends Plugin {
|
|
|
|
public static get pluginName() {
|
|
|
|
return 'Import' as const;
|
|
|
|
}
|
|
|
|
|
|
|
|
public init(): void {
|
|
|
|
const editor = this.editor;
|
|
|
|
const t = editor.t;
|
|
|
|
const model = editor.model;
|
|
|
|
|
|
|
|
// Add the "importButton" to feature components.
|
2025-01-22 16:17:17 +07:00
|
|
|
editor.ui.componentFactory.add( 'importDocx', locale => {
|
2025-01-15 17:58:02 +07:00
|
|
|
const view = new ButtonView( locale );
|
|
|
|
|
|
|
|
view.set( {
|
2025-01-22 16:17:17 +07:00
|
|
|
label: t( 'Import Docx' ),
|
2025-01-22 17:53:18 +07:00
|
|
|
icon: ckeditor5Iconn,
|
2025-01-15 17:58:02 +07:00
|
|
|
tooltip: true
|
|
|
|
} );
|
|
|
|
|
2025-01-19 21:13:39 +07:00
|
|
|
// POST fetch html from docx
|
2025-01-15 17:58:02 +07:00
|
|
|
this.listenTo( view, 'execute', async () => {
|
2025-01-19 21:13:39 +07:00
|
|
|
// 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}`);
|
2025-01-15 17:58:02 +07:00
|
|
|
}
|
|
|
|
|
2025-01-19 21:13:39 +07:00
|
|
|
const htmlText = await response.text();
|
|
|
|
// console.log("Converted HTML:", htmlText);
|
|
|
|
const htmlDoc = new DOMParser().parseFromString(htmlText, "text/html");
|
|
|
|
const body = htmlDoc.body;
|
2025-01-19 21:23:13 +07:00
|
|
|
// 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;
|
2025-01-22 13:11:41 +07:00
|
|
|
const spanElement = document.createElement("span");
|
|
|
|
spanElement.appendChild(scriptElement);
|
|
|
|
mathmlElement.replaceWith(spanElement);
|
2025-01-19 21:23:13 +07:00
|
|
|
});
|
2025-01-20 20:06:29 +07:00
|
|
|
// font-family for <span> concat with , serif
|
|
|
|
const spans = body.querySelectorAll("span");
|
|
|
|
spans.forEach(span => {
|
2025-01-20 21:45:18 +07:00
|
|
|
// span.style.fontFamily += ", serif";
|
2025-01-20 20:06:29 +07:00
|
|
|
// 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;
|
2025-01-22 15:30:29 +07:00
|
|
|
if (fontSize && span.style.fontSize === "") {
|
2025-01-20 21:45:18 +07:00
|
|
|
span.setAttribute("style", `${span.getAttribute("style")}; font-size: ${fontSize}`);
|
2025-01-20 20:06:29 +07:00
|
|
|
}
|
|
|
|
}
|
2025-01-22 15:02:22 +07:00
|
|
|
const li = span.parentElement;
|
|
|
|
if (li && li.tagName === "LI") {
|
2025-01-22 15:30:29 +07:00
|
|
|
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}`);
|
|
|
|
}
|
|
|
|
}
|
2025-01-22 15:02:22 +07:00
|
|
|
const fontSize = li.style.fontSize;
|
2025-01-22 15:30:29 +07:00
|
|
|
if (fontSize && span.style.fontSize === "") {
|
2025-01-22 15:02:22 +07:00
|
|
|
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;
|
2025-01-22 15:30:29 +07:00
|
|
|
if (fontSize && span.style.fontSize === "") {
|
2025-01-22 15:02:22 +07:00
|
|
|
span.setAttribute("style", `${span.getAttribute("style")}; font-size: ${fontSize}`);
|
|
|
|
}
|
|
|
|
}
|
2025-01-20 20:06:29 +07:00
|
|
|
});
|
|
|
|
console.log("Converted HTML:", body.innerHTML);
|
2025-01-19 21:13:39 +07:00
|
|
|
this.editor.setData(body.innerHTML);
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Error uploading file:", error);
|
|
|
|
throw error;
|
2025-01-15 17:58:02 +07:00
|
|
|
}
|
2025-01-19 21:13:39 +07:00
|
|
|
|
|
|
|
editor.editing.view.focus();
|
2025-01-15 17:58:02 +07:00
|
|
|
});
|
|
|
|
} );
|
|
|
|
|
|
|
|
return view;
|
|
|
|
} );
|
2025-01-22 16:17:17 +07:00
|
|
|
|
|
|
|
// Add the "insertButton" to feature components.
|
|
|
|
editor.ui.componentFactory.add( 'insertDocx', locale => {
|
|
|
|
const view = new ButtonView( locale );
|
|
|
|
|
|
|
|
view.set( {
|
|
|
|
label: t( 'Insert 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);
|
|
|
|
const viewFragment = editor.data.processor.toView(body.innerHTML);
|
|
|
|
const modelFragment = editor.data.toModel(viewFragment);
|
|
|
|
model.insertContent(modelFragment);
|
|
|
|
} catch (error) {
|
|
|
|
console.error("Error uploading file:", error);
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
|
|
|
|
editor.editing.view.focus();
|
|
|
|
});
|
|
|
|
} );
|
|
|
|
|
|
|
|
return view;
|
|
|
|
} );
|
2025-01-15 17:58:02 +07:00
|
|
|
}
|
|
|
|
}
|