Skip to content

Instantly share code, notes, and snippets.

@IvanaGyro
Last active February 26, 2023 10:57
Show Gist options
  • Save IvanaGyro/6e4eea78d24760211d127e9c67b2f335 to your computer and use it in GitHub Desktop.
Save IvanaGyro/6e4eea78d24760211d127e9c67b2f335 to your computer and use it in GitHub Desktop.
Insert math equations into the online version of PowerPoint.
name: Formulas for PowerPoint
description: Insert math equations into the online version of PowerPoint.
host: POWERPOINT
api_set: {}
script:
content: |
var editor;
const MathJax = window["MathJax"];
const ace = window["ace"];
const toastr = window["toastr"];
/**
* The new task passed to `PowerPoint.run` will interrupt the previous one.
* Lock this flag to prevent the process of changing name after inserting
* from being interrupted.
*/
var insertionLocked = false;
init();
$("#insert").click(() => tryCatch(insertEquation));
subscribe();
/** 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.
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/textmate");
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];
newImage(svg.outerHTML);
// await wait(500); // Wait for the inserted image to be selected.
toastr.warning("DO NOT click anything until the LaTex is stored.", "Processing...");
await setSelectedShapeName(`\$\$${latex}\$\$`);
toastr.success("", "Successfully insert the equation!");
insertionLocked = false;
}
function newImage(svgImage) {
Office.context.document.setSelectedDataAsync(
svgImage,
{
coercionType: Office.CoercionType.XmlSvg,
imageLeft: 50,
imageTop: 50
},
function(asyncResult) {
if (asyncResult.status === Office.AsyncResultStatus.Failed) {
toastr.error("asyncResult.error.message", "Fail to insert the equation.");
console.error(asyncResult);
}
}
);
}
async function setSelectedShapeName(name) {
await PowerPoint.run(async (context) => {
let shapes;
let shapeCount;
const isOneShapeSelected = async () => {
shapes = context.presentation.getSelectedShapes();
shapeCount = shapes.getCount();
await context.sync();
return shapeCount.value === 1;
};
await waitUntil(isOneShapeSelected, 50000);
if (shapeCount.value !== 1) {
return;
}
shapes.load("items");
await context.sync();
shapes.items[0].name = name;
await context.sync();
});
}
async function getSelectedShapeName() {
let name = undefined;
await PowerPoint.run(async (context) => {
const shapes = context.presentation.getSelectedShapes();
const shapeCount = shapes.getCount();
await context.sync();
if (shapeCount.value !== 1) {
return;
}
shapes.load("items");
await context.sync();
const selectedShape = shapes.items[0];
selectedShape.load("name");
await context.sync();
name = selectedShape.name;
});
return name;
}
async function subscribe() {
// Some old handlers may not be removed after reloading.
await Office.context.document.removeHandlerAsync(Office.EventType.DocumentSelectionChanged);
Office.context.document.addHandlerAsync(Office.EventType.DocumentSelectionChanged, handler);
}
async function handler(something) {
if (insertionLocked) {
return;
}
const selectedShapeName = await getSelectedShapeName();
console.debug(`selectedShapeName: ${selectedShapeName}`);
if (selectedShapeName != null && selectedShapeName.startsWith("$$") && selectedShapeName.endsWith("$$")) {
editor.setValue(selectedShapeName.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// 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>"
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