Last active
February 26, 2023 10:56
-
-
Save IvanaGyro/b4ed136dbfaf2330f71f8fa75475a258 to your computer and use it in GitHub Desktop.
Insert math equations into the online version of Word.
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
name: Formulas for Word | |
description: Insert math equations into the online version of Word. | |
host: WORD | |
api_set: {} | |
script: | |
content: > | |
var editor; | |
const MathJax = window["MathJax"]; | |
const ace = window["ace"]; | |
const toastr = window["toastr"]; | |
const Canvg = window["Canvg"]; | |
init(); | |
$("#insert").click(() => tryCatch(insertEquation)); | |
subscribeSelectionChanged(); | |
/** Default helper for invoking an action and handling errors. */ | |
async function tryCatch(callback) { | |
try { | |
await callback(); | |
} catch (error) { | |
// Note: In a production add-in, you'd want to notify the user through your add-in's UI. | |
toastr.error("", error); | |
console.error(error); | |
} | |
} | |
async function wait(ms) { | |
return new Promise((resolve) => { | |
setTimeout(resolve, ms); | |
}); | |
} | |
async function waitUntilInternal(condition, deadline, intervalMs) { | |
if (Date.now() >= deadline) { | |
console.log("timeout"); | |
return; | |
} | |
if (await condition()) { | |
return; | |
} | |
await wait(intervalMs); | |
return waitUntilInternal(condition, deadline, intervalMs); | |
} | |
async function waitUntil(condition, timeoutMs, intervalMs = 100) { | |
return waitUntilInternal(condition, Date.now() + timeoutMs, intervalMs); | |
} | |
function init() { | |
editor = ace.edit("editor"); | |
editor.setTheme("ace/theme/clouds"); | |
editor.getSession().setMode("ace/mode/latex"); | |
editor.$blockScrolling = Infinity; | |
editor.getSession().setUseWrapMode(true); | |
// editor.setTheme("ace/theme/chrome"); | |
editor.setOption("minLines", 10); | |
// editor.setOption("maxLines", Infinity); | |
// editor.setOption("maxLines", 15); | |
editor.setAutoScrollEditorIntoView(true); | |
editor.setShowPrintMargin(false); | |
// editor.setPrintMarginColumn(100);\ | |
editor.on("change", () => { | |
let backup = editor.getValue(); | |
typeset(() => { | |
const math = document.getElementById("equation-output"); | |
math.innerHTML = `\$\$${backup}\$\$`; | |
return [math]; | |
}); | |
}); | |
toastr.options.positionClass = "toast-bottom-right"; | |
} | |
let promise = Promise.resolve(); // Used to hold chain of typesetting calls | |
function typeset(code) { | |
promise = promise | |
.then(() => MathJax.typesetPromise(code())) | |
.catch((err) => { | |
console.log(err); | |
}); | |
return promise; | |
} | |
async function insertEquation() { | |
// Inserts an image anchored to the last paragraph. | |
const latex = editor.getValue().replace(/ +(?= )/g, ""); | |
// insertionLocked = true; | |
const svg = document.getElementById("equation-output").getElementsByTagName("svg")[0]; | |
const canvas = document.createElement("canvas"); | |
const ctx = canvas.getContext("2d"); | |
let v = await Canvg.fromString(ctx, svg.outerHTML, { emSize: 40 }); | |
// Start SVG rendering with animations and mouse handling. | |
v.start(); | |
let img = canvas.toDataURL("image/png"); | |
img = img.substring(img.indexOf("base64,") + 7); | |
// Inserts an image anchored to the last paragraph. | |
await Word.run(async (context) => { | |
const picture = context.document.body.paragraphs | |
.getLast() | |
.insertParagraph("", "After") | |
.insertInlinePictureFromBase64(img, "End"); | |
picture.altTextDescription = `\$\$${latex}\$\$`; | |
await context.sync(); | |
}); | |
toastr.success("", "Successfully insert the equation!"); | |
} | |
async function subscribeSelectionChanged() { | |
// Some old handlers may not be removed after reloading. | |
await Office.context.document.removeHandlerAsync(Office.EventType.DocumentSelectionChanged); | |
Office.context.document.addHandlerAsync(Office.EventType.DocumentSelectionChanged, loadLatex); | |
} | |
async function loadLatex(something: Office.AsyncResult<void>): Promise<void> | |
{ | |
Word.run(async (context) => { | |
const range = context.document.getSelection(); | |
await context.sync(); | |
const images = range.inlinePictures; | |
await context.load(images); | |
await context.sync(); | |
if (images.items.length !== 1) { | |
return; | |
} | |
const image = images.items[0]; | |
const latex = image.altTextDescription; | |
if (latex.startsWith("$$") && latex.endsWith("$$")) { | |
editor.setValue(latex.slice(2, -2)); | |
toastr.success("Successfully load the LaTex!"); | |
} | |
}); | |
} | |
language: typescript | |
template: | |
content: "<div class=\"equation-container\">\n\t<div id=\"editor\"></div>\n\t<div id=\"equation-output\"></div>\n</div>\n<div class=\"button-container\">\n\t<button id=\"insert\" class=\"ms-Button\">\n <span class=\"ms-Button-label\">Insert</span>\n </button>\n</div>\n\n<script>\n\t// To use physics and colorv2 package, braket and color have to be removed.\n\t\t// Refer to https://docs.mathjax.org/en/latest/web/components/input.html#tex-component\n\t\tMathJax = {\n\t\t\ttex: {\n\t\t\t\tpackages: {\n\t\t\t\t\t'[+]': ['physics', 'colorv2'],\n\t\t\t\t\t'[-]': ['braket', 'color']\n\t\t\t\t}\n\t\t\t}\n\t };\n</script>\n<script id=\"MathJax-script\" src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg-full.js\"></script>\n<script type=\"module\">\n\timport { Canvg } from 'https://cdn.jsdelivr.net/npm/canvg/+esm';\n window.Canvg = Canvg;\n</script>" | |
language: html | |
style: | |
content: | | |
body { | |
display: flex; | |
justify-content: space-between; | |
flex-flow: column nowrap; | |
margin: 0; | |
height: 100vh; | |
gap: 8px 0px; | |
align-content: stretch; | |
} | |
.equation-container { | |
display: flex; | |
flex-flow: column nowrap; | |
align-self: stretch; | |
flex-grow: 1; | |
gap: 8px 0px; | |
} | |
.button-container { | |
display: flex; | |
flex-flow: row nowrap; | |
padding: 0 8px 5px 8px; | |
gap: 0px 8px; | |
} | |
#editor { | |
height: 70vh; | |
} | |
#equation-output { | |
flex-grow: 1; | |
margin: 0px 8px; | |
background: rgb(244, 244, 244); | |
display: flex; | |
flex-flow: column nowrap; | |
justify-content: center; | |
} | |
.ms-Button { | |
display: block; | |
min-width: 80px; | |
} | |
language: css | |
libraries: | | |
https://appsforoffice.microsoft.com/lib/1/hosted/office.js | |
@types/office-js | |
office-ui-fabric-js@1.4.0/dist/css/fabric.min.css | |
office-ui-fabric-js@1.4.0/dist/css/fabric.components.min.css | |
core-js@2.4.1/client/core.min.js | |
@types/core-js | |
jquery@3.1.1 | |
@types/jquery@3.3.1 | |
https://cdnjs.cloudflare.com/ajax/libs/ace/1.15.2/ace.js | |
https://cdn.jsdelivr.net/npm/ace-builds@1.15.2/css/ace.min.css | |
https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css | |
https://cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/toastr.min.js |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment