feat: Support LaTeX and AsciiMath via MathJax on stem.excalidraw.com
|
@ -31,6 +31,12 @@ Please add the latest change on the top under the correct section.
|
|||
|
||||
#### Features
|
||||
|
||||
- Render math notation using the MathJax library. Both standard Latex input and simplified AsciiMath input are supported.
|
||||
|
||||
Also added plugin-like subtypes for `ExcalidrawElement`. These allow easily supporting custom extensions of `ExcalidrawElement`s such as for MathJax, Markdown, or inline code.
|
||||
|
||||
Also created an `@excalidraw/extensions` package. This package holds the MathJax extension to make it completely decoupled from `@excalidraw/excalidraw`. The MathJax extension is implemented as a `math` subtype of `ExcalidrawTextElement`. [#2993](https://github.com/excalidraw/excalidraw/pull/2993).
|
||||
|
||||
- `restoreElements()` now takes an optional parameter to indicate whether we should also recalculate text element dimensions. Defaults to `true`, but since this is a potentially costly operation, you may want to disable it if you restore elements in tight loops, such as during collaboration [#5432](https://github.com/excalidraw/excalidraw/pull/5432).
|
||||
- Support rendering custom sidebar using [`renderSidebar`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#renderSidebar) prop ([#5663](https://github.com/excalidraw/excalidraw/pull/5663)).
|
||||
- Add [`toggleMenu`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#toggleMenu) prop to toggle specific menu open/close state ([#5663](https://github.com/excalidraw/excalidraw/pull/5663)).
|
||||
|
|
|
@ -94,7 +94,7 @@ const COMMENT_INPUT_WIDTH = 150;
|
|||
const renderTopRightUI = () => {
|
||||
return (
|
||||
<button
|
||||
onClick={() => alert("This is dummy top right UI")}
|
||||
onClick={() => alert("This is an empty top right UI")}
|
||||
style={{ height: "2.5rem" }}
|
||||
>
|
||||
{" "}
|
||||
|
@ -103,7 +103,13 @@ const renderTopRightUI = () => {
|
|||
);
|
||||
};
|
||||
|
||||
export default function App() {
|
||||
export interface AppProps {
|
||||
appTitle: string;
|
||||
useCustom: (api: ExcalidrawImperativeAPI | null, customArgs?: any[]) => void;
|
||||
customArgs?: any[];
|
||||
}
|
||||
|
||||
export default function App({ appTitle, useCustom, customArgs }: AppProps) {
|
||||
const appRef = useRef<any>(null);
|
||||
const [viewModeEnabled, setViewModeEnabled] = useState(false);
|
||||
const [zenModeEnabled, setZenModeEnabled] = useState(false);
|
||||
|
@ -130,6 +136,8 @@ export default function App() {
|
|||
const [excalidrawAPI, setExcalidrawAPI] =
|
||||
useState<ExcalidrawImperativeAPI | null>(null);
|
||||
|
||||
useCustom(excalidrawAPI, customArgs);
|
||||
|
||||
useHandleLibrary({ excalidrawAPI });
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -137,7 +145,7 @@ export default function App() {
|
|||
return;
|
||||
}
|
||||
const fetchData = async () => {
|
||||
const res = await fetch("/rocket.jpeg");
|
||||
const res = await fetch("/images/rocket.jpeg");
|
||||
const imageData = await res.blob();
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(imageData);
|
||||
|
@ -397,7 +405,7 @@ export default function App() {
|
|||
}}
|
||||
>
|
||||
<div className="comment-avatar">
|
||||
<img src="doremon.png" alt="doremon" />
|
||||
<img src="images/doremon.png" alt="doremon" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -497,7 +505,7 @@ export default function App() {
|
|||
|
||||
return (
|
||||
<div className="App" ref={appRef}>
|
||||
<h1> Excalidraw Example</h1>
|
||||
<h1>{appTitle}</h1>
|
||||
<ExampleSidebar>
|
||||
<div className="button-wrapper">
|
||||
<button onClick={loadSceneOrLibrary}>Load Scene or Library</button>
|
||||
|
@ -583,15 +591,15 @@ export default function App() {
|
|||
const collaborators = new Map();
|
||||
collaborators.set("id1", {
|
||||
username: "Doremon",
|
||||
avatarUrl: "doremon.png",
|
||||
avatarUrl: "images/doremon.png",
|
||||
});
|
||||
collaborators.set("id2", {
|
||||
username: "Excalibot",
|
||||
avatarUrl: "excalibot.png",
|
||||
avatarUrl: "images/excalibot.png",
|
||||
});
|
||||
collaborators.set("id3", {
|
||||
username: "Pika",
|
||||
avatarUrl: "pika.jpeg",
|
||||
avatarUrl: "images/pika.jpeg",
|
||||
});
|
||||
collaborators.set("id4", {
|
||||
username: "fallback",
|
||||
|
|
|
@ -8,6 +8,9 @@ const root = createRoot(rootElement);
|
|||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
<App
|
||||
appTitle={"Excalidraw Example"}
|
||||
useCustom={(api: any, args?: any[]) => {}}
|
||||
/>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 197 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
|
@ -10,8 +10,8 @@ export default function Sidebar({ children }: { children: React.ReactNode }) {
|
|||
x
|
||||
</button>
|
||||
<div className="sidebar-links">
|
||||
<button>Dummy Home</button>
|
||||
<button>Dummy About</button>{" "}
|
||||
<button>Empty Home</button>
|
||||
<button>Empty About</button>{" "}
|
||||
</div>
|
||||
</div>
|
||||
<div className={`${open ? "sidebar-open" : ""}`}>
|
||||
|
|
|
@ -2,6 +2,12 @@ import { ENV } from "../../constants";
|
|||
if (process.env.NODE_ENV !== ENV.TEST) {
|
||||
/* eslint-disable */
|
||||
/* global __webpack_public_path__:writable */
|
||||
if (process.env.NODE_ENV === ENV.DEVELOPMENT && (
|
||||
window.EXCALIDRAW_ASSET_PATH === undefined ||
|
||||
window.EXCALIDRAW_ASSET_PATH === ""
|
||||
)) {
|
||||
window.EXCALIDRAW_ASSET_PATH = "/";
|
||||
}
|
||||
__webpack_public_path__ =
|
||||
window.EXCALIDRAW_ASSET_PATH ||
|
||||
`https://unpkg.com/${process.env.PKG_NAME}@${process.env.PKG_VERSION}/dist/`;
|
||||
|
|
2
src/packages/extensions/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
dist
|
24
src/packages/extensions/CHANGELOG.md
Normal file
|
@ -0,0 +1,24 @@
|
|||
# Changelog
|
||||
|
||||
<!--
|
||||
Guidelines for changelog:
|
||||
The change should be grouped under one of the following sections and must contain a PR link.
|
||||
- Features: For new features.
|
||||
- Fixes: For bug fixes.
|
||||
- Chore: Changes for non src files example package.json.
|
||||
- Refactor: For any refactoring.
|
||||
|
||||
Please add the latest change at the top under the correct section.
|
||||
-->
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Excalidraw Extensions
|
||||
|
||||
#### Features
|
||||
|
||||
- Render math notation using the MathJax library. Both standard Latex input and simplified AsciiMath input are supported. MathJax support is implemented as a `math` subtype of `ExcalidrawTextElement`.
|
||||
|
||||
Also added plugin-like subtypes for `ExcalidrawElement`. These allow easily supporting custom extensions of `ExcalidrawElement`s such as for MathJax, Markdown, or inline code. [#5311](https://github.com/excalidraw/excalidraw/pull/5311).
|
||||
|
||||
- Provided a stub example extension (`./empty/index.ts`).
|
45
src/packages/extensions/README.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
#### Note
|
||||
|
||||
⚠️ ⚠️ ⚠️ You are viewing the docs for the **next** release, in case you want to check the docs for the stable release, you can view it [here](https://www.npmjs.com/package/@excalidraw/extensions).
|
||||
|
||||
### Extensions
|
||||
|
||||
Excalidraw extensions to be used in Excalidraw.
|
||||
|
||||
### Installation
|
||||
|
||||
You can use npm
|
||||
|
||||
```
|
||||
npm install react react-dom @excalidraw/extensions
|
||||
```
|
||||
|
||||
or via yarn
|
||||
|
||||
```
|
||||
yarn add react react-dom @excalidraw/extensions
|
||||
```
|
||||
|
||||
After installation you will see a folder `excalidraw-extensions-assets` and `excalidraw-extensions-assets-dev` in `dist` directory which contains the assets needed for this app in prod and dev mode respectively.
|
||||
|
||||
Move the folder `excalidraw-extensions-assets` and `excalidraw-extensions-assets-dev` to the path where your assets are served.
|
||||
|
||||
By default it will try to load the files from `https://unpkg.com/@excalidraw/extensions/dist/`
|
||||
|
||||
If you want to load assets from a different path you can set a variable `window.EXCALIDRAW_EXTENSIONS_ASSET_PATH` depending on environment (for example if you have different URL's for dev and prod) to the url from where you want to load the assets.
|
||||
|
||||
#### Note
|
||||
|
||||
**If you don't want to wait for the next stable release and try out the unreleased changes you can use `@excalidraw/extensions@next`.**
|
||||
|
||||
### Need help?
|
||||
|
||||
Check out the existing [Q&A](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aextensions). If you have any queries or need help, ask us [here](https://github.com/excalidraw/excalidraw/discussions?discussions_q=label%3Apackage%3Aextensions).
|
||||
|
||||
### Development
|
||||
|
||||
#### Install the dependencies
|
||||
|
||||
```bash
|
||||
yarn
|
||||
```
|
18
src/packages/extensions/env.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
const dotenv = require("dotenv");
|
||||
const { readFileSync } = require("fs");
|
||||
const pkg = require("./package.json");
|
||||
const parseEnvVariables = (filepath) => {
|
||||
const envVars = Object.entries(dotenv.parse(readFileSync(filepath))).reduce(
|
||||
(env, [key, value]) => {
|
||||
env[key] = JSON.stringify(value);
|
||||
return env;
|
||||
},
|
||||
{},
|
||||
);
|
||||
envVars.PKG_NAME = JSON.stringify(pkg.name);
|
||||
envVars.PKG_VERSION = JSON.stringify(pkg.version);
|
||||
envVars.IS_EXCALIDRAW_EXTENSIONS_NPM_PACKAGE = JSON.stringify(true);
|
||||
return envVars;
|
||||
};
|
||||
|
||||
module.exports = { parseEnvVariables };
|
23
src/packages/extensions/example/index.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import App from "../../excalidraw/example/App";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
ExcalidrawExtensionsLib: any;
|
||||
}
|
||||
}
|
||||
const { useExtensions } = window.ExcalidrawExtensionsLib;
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App
|
||||
appTitle={"Excalidraw Extensions Example"}
|
||||
useCustom={useExtensions}
|
||||
customArgs={["mathjax"]}
|
||||
/>
|
||||
</React.StrictMode>,
|
||||
rootElement,
|
||||
);
|
1
src/packages/extensions/example/public/excalidraw-assets-dev
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../excalidraw/dist/excalidraw-assets-dev/
|
1
src/packages/extensions/example/public/excalidraw.development.js
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../excalidraw/dist/excalidraw.development.js
|
1
src/packages/extensions/example/public/images
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../excalidraw/example/public/images/
|
32
src/packages/extensions/example/public/index.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
|
||||
<title>React App</title>
|
||||
<script>
|
||||
window.EXCALIDRAW_ASSET_PATH = "/";
|
||||
window.EXCALIDRAW_EXTENSIONS_ASSET_PATH = "/";
|
||||
window.name = "codesandbox";
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You need to enable JavaScript to run this app. </noscript>
|
||||
<div id="root"></div>
|
||||
<script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script>
|
||||
|
||||
<!-- This is so that we use the bundled excalidraw.development.js file instead
|
||||
of the actual source code -->
|
||||
<script src="./excalidraw.development.js"></script>
|
||||
<script src="./excalidraw-extensions.development.js"></script>
|
||||
|
||||
<script src="./bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
3
src/packages/extensions/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import "./publicPath";
|
||||
|
||||
export * from "./ts/node-main";
|
86
src/packages/extensions/package.json
Normal file
|
@ -0,0 +1,86 @@
|
|||
{
|
||||
"name": "@excalidraw/extensions",
|
||||
"version": "0.12.0",
|
||||
"main": "index.ts",
|
||||
"files": [
|
||||
"dist/*"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"description": "Excalidraw extensions",
|
||||
"repository": "https://github.com/excalidraw/excalidraw",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"excalidraw",
|
||||
"excalidraw-embed",
|
||||
"react",
|
||||
"npm",
|
||||
"npm excalidraw"
|
||||
],
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all",
|
||||
"not safari < 12",
|
||||
"not kaios <= 2.5",
|
||||
"not edge < 79",
|
||||
"not chrome < 70",
|
||||
"not and_uc < 13",
|
||||
"not samsung < 10"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.18.9",
|
||||
"@babel/plugin-transform-arrow-functions": "7.18.6",
|
||||
"@babel/plugin-transform-async-to-generator": "7.18.6",
|
||||
"@babel/plugin-transform-runtime": "7.18.9",
|
||||
"@babel/plugin-transform-typescript": "7.18.8",
|
||||
"@babel/preset-env": "7.18.6",
|
||||
"@babel/preset-react": "7.18.6",
|
||||
"@babel/preset-typescript": "7.18.6",
|
||||
"autoprefixer": "10.4.7",
|
||||
"babel-loader": "8.2.5",
|
||||
"babel-plugin-transform-class-properties": "6.24.1",
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "6.7.1",
|
||||
"dotenv": "16.0.1",
|
||||
"mini-css-extract-plugin": "2.6.1",
|
||||
"postcss-loader": "7.0.1",
|
||||
"sass-loader": "13.0.2",
|
||||
"terser-webpack-plugin": "5.3.3",
|
||||
"ts-loader": "9.3.1",
|
||||
"typescript": "4.7.4",
|
||||
"webpack": "5.73.0",
|
||||
"webpack-bundle-analyzer": "4.5.0",
|
||||
"webpack-cli": "4.10.0",
|
||||
"webpack-dev-server": "4.9.3",
|
||||
"webpack-merge": "5.8.0"
|
||||
},
|
||||
"bugs": "https://github.com/excalidraw/excalidraw/issues",
|
||||
"homepage": "https://github.com/excalidraw/excalidraw/tree/master/src/packages/extensions",
|
||||
"scripts": {
|
||||
"gen:types": "tsc --project ../../../tsconfig-types.json",
|
||||
"build:umd": "rm -rf dist && cross-env NODE_ENV=production webpack --config webpack.prod.config.js && cross-env NODE_ENV=development webpack --config webpack.dev.config.js && yarn gen:types",
|
||||
"build:umd:withAnalyzer": "cross-env NODE_ENV=production ANALYZER=true webpack --config webpack.prod.config.js",
|
||||
"pack": "yarn build:umd && yarn pack",
|
||||
"start": "webpack serve --config webpack.dev-server.config.js",
|
||||
"install:deps": "yarn install --frozen-lockfile && yarn --cwd ../../../",
|
||||
"build:deps": "yarn --cwd ../excalidraw cross-env NODE_ENV=development webpack --config webpack.dev.config.js",
|
||||
"build:example": "EXAMPLE=true webpack --config webpack.dev-server.config.js && yarn gen:types"
|
||||
},
|
||||
"dependencies": {
|
||||
"mathjax-full": "3.2.2"
|
||||
}
|
||||
}
|
14
src/packages/extensions/publicPath.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { ENV } from "../../constants";
|
||||
if (process.env.NODE_ENV !== ENV.TEST) {
|
||||
/* eslint-disable */
|
||||
/* global __webpack_public_path__:writable */
|
||||
if (process.env.NODE_ENV === ENV.DEVELOPMENT && (
|
||||
window.EXCALIDRAW_EXTENSIONS_ASSET_PATH === undefined ||
|
||||
window.EXCALIDRAW_EXTENSIONS_ASSET_PATH === ""
|
||||
)) {
|
||||
window.EXCALIDRAW_EXTENSIONS_ASSET_PATH = "/";
|
||||
}
|
||||
__webpack_public_path__ =
|
||||
window.EXCALIDRAW_EXTENSIONS_ASSET_PATH ||
|
||||
`https://unpkg.com/${process.env.PKG_NAME}@${process.env.PKG_VERSION}/dist/`;
|
||||
}
|
26
src/packages/extensions/ts/empty/index.ts
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { useEffect } from "react";
|
||||
import { ExcalidrawImperativeAPI } from "../../../../types";
|
||||
|
||||
// Extension authors: provide a extension name here like "myextension"
|
||||
export const EmptyExtension = "empty";
|
||||
|
||||
// Extension authors: provide a hook like `useMyExtension` in `myextension/index`
|
||||
export const useEmptyExtension = (api: ExcalidrawImperativeAPI | null) => {
|
||||
const enabled = emptyExtensionLoadable;
|
||||
useEffect(() => {
|
||||
if (enabled) {
|
||||
}
|
||||
}, [enabled, api]);
|
||||
};
|
||||
|
||||
// Extension authors: Use a variable like `myExtensionLoadable` to determine
|
||||
// whether or not to do anything in each of `useMyExtension` and `testMyExtension`.
|
||||
let emptyExtensionLoadable = false;
|
||||
|
||||
export const getEmptyExtensionLoadable = () => {
|
||||
return emptyExtensionLoadable;
|
||||
};
|
||||
|
||||
export const setEmptyExtensionLoadable = (loadable: boolean) => {
|
||||
emptyExtensionLoadable = loadable;
|
||||
};
|
4
src/packages/extensions/ts/global.d.ts
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
declare module SREfeature {
|
||||
function custom(locale: string): Promise<string>;
|
||||
export = custom;
|
||||
}
|
13
src/packages/extensions/ts/mathjax/icon.tsx
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { Theme } from "../../../../element/types";
|
||||
import { createIcon, iconFillColor } from "../../../../components/icons";
|
||||
|
||||
// We inline font-awesome icons in order to save on js size rather than including the font awesome react library
|
||||
export const mathSubtypeIcon = ({ theme }: { theme: Theme }) =>
|
||||
createIcon(
|
||||
<path
|
||||
fill={iconFillColor(theme)}
|
||||
// fa-square-root-variable-solid
|
||||
d="M289 24.2C292.5 10 305.3 0 320 0H544c17.7 0 32 14.3 32 32s-14.3 32-32 32H345L239 487.8c-3.2 13-14.2 22.6-27.6 24s-26.1-5.5-32.1-17.5L76.2 288H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H96c12.1 0 23.2 6.8 28.6 17.7l73.3 146.6L289 24.2zM393.4 233.4c12.5-12.5 32.8-12.5 45.3 0L480 274.7l41.4-41.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3L525.3 320l41.4 41.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L480 365.3l-41.4 41.4c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L434.7 320l-41.4-41.4c-12.5-12.5-12.5-32.8 0-45.3z"
|
||||
/>,
|
||||
{ width: 576, height: 512, mirror: true, strokeWidth: 1.25 },
|
||||
);
|
1507
src/packages/extensions/ts/mathjax/implementation.tsx
Normal file
32
src/packages/extensions/ts/mathjax/index.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { useEffect } from "react";
|
||||
import { ExcalidrawImperativeAPI } from "../../../../types";
|
||||
import { addSubtypeMethods } from "../../../../subtypes";
|
||||
import { getMathSubtypeRecord } from "./types";
|
||||
import { prepareMathSubtype } from "./implementation";
|
||||
|
||||
export const MathJaxExtension = "mathjax";
|
||||
|
||||
// Extension authors: provide a hook like `useMyExtension` in `myextension/index`
|
||||
export const useMathJaxExtension = (api: ExcalidrawImperativeAPI | null) => {
|
||||
const enabled = mathJaxExtensionLoadable;
|
||||
useEffect(() => {
|
||||
if (enabled && api) {
|
||||
const prep = api.addSubtype(getMathSubtypeRecord(), prepareMathSubtype);
|
||||
if (prep) {
|
||||
addSubtypeMethods(getMathSubtypeRecord().subtype, prep.methods);
|
||||
}
|
||||
}
|
||||
}, [enabled, api]);
|
||||
};
|
||||
|
||||
// Extension authors: Use a variable like `myExtensionLoadable` to determine
|
||||
// whether or not to do anything in each of `useMyExtension` and `testMyExtension`.
|
||||
let mathJaxExtensionLoadable = false;
|
||||
|
||||
export const getMathJaxExtensionLoadable = () => {
|
||||
return mathJaxExtensionLoadable;
|
||||
};
|
||||
|
||||
export const setMathJaxExtensionLoadable = (loadable: boolean) => {
|
||||
mathJaxExtensionLoadable = loadable;
|
||||
};
|
15
src/packages/extensions/ts/mathjax/locales/en.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"labels": {
|
||||
"changeMathOnly": "Math display",
|
||||
"mathOnlyTrue": "Math only",
|
||||
"mathOnlyFalse": "Mixed text",
|
||||
"resetUseTex": "Reset math input type",
|
||||
"useTexTrueActive": "✔ Standard input",
|
||||
"useTexTrueInactive": "Standard input",
|
||||
"useTexFalseActive": "✔ Simplified input",
|
||||
"useTexFalseInactive": "Simplified input"
|
||||
},
|
||||
"toolBar": {
|
||||
"math": "Math"
|
||||
}
|
||||
}
|
17
src/packages/extensions/ts/mathjax/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { getShortcutKey } from "../../../../utils";
|
||||
import { SubtypeRecord } from "../../../../subtypes";
|
||||
|
||||
// Exports
|
||||
export const getMathSubtypeRecord = () => mathSubtype;
|
||||
|
||||
// Use `getMathSubtype` so we don't have to export this
|
||||
const mathSubtype: SubtypeRecord = {
|
||||
subtype: "math",
|
||||
parents: ["text"],
|
||||
actionNames: ["useTexTrue", "useTexFalse", "resetUseTex", "changeMathOnly"],
|
||||
disabledNames: ["changeFontFamily"],
|
||||
shortcutMap: {
|
||||
resetUseTex: [getShortcutKey("Shift+R")],
|
||||
},
|
||||
alwaysEnabledNames: ["useTexTrue", "useTexFalse"],
|
||||
};
|
59
src/packages/extensions/ts/node-main.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { ExcalidrawImperativeAPI } from "../../../types";
|
||||
import {
|
||||
EmptyExtension,
|
||||
setEmptyExtensionLoadable,
|
||||
useEmptyExtension,
|
||||
} from "./empty";
|
||||
import {
|
||||
MathJaxExtension,
|
||||
setMathJaxExtensionLoadable,
|
||||
useMathJaxExtension,
|
||||
} from "./mathjax";
|
||||
|
||||
// Extension authors: do imports like follows:
|
||||
// ```
|
||||
// import {
|
||||
// MyExtension,
|
||||
// setMyExtensionLoadable,
|
||||
// useMyExtension,
|
||||
// } from "./myExtension";
|
||||
// ```
|
||||
|
||||
// Extension authors: include `MyExtension` in `validExtensions`
|
||||
const validExtensions: readonly string[] = [EmptyExtension, MathJaxExtension];
|
||||
const extensionsUsed: string[] = [];
|
||||
|
||||
// The main invocation hook for use in the UI
|
||||
export const useExtensions = (
|
||||
api: ExcalidrawImperativeAPI | null,
|
||||
extensions?: string[],
|
||||
) => {
|
||||
selectExtensionsToLoad(extensions);
|
||||
useEmptyExtension(api);
|
||||
useMathJaxExtension(api);
|
||||
// Extension authors: add a line here like `useMyExtension();`
|
||||
};
|
||||
|
||||
// This MUST be called before the `useExtension`/`testExtension` calls.
|
||||
const selectExtensionsToLoad = (extensions?: string[]) => {
|
||||
const extensionList: string[] = [];
|
||||
if (extensions === undefined) {
|
||||
extensionList.push(...validExtensions);
|
||||
} else {
|
||||
extensions.forEach(
|
||||
(val) => validExtensions.includes(val) && extensionList.push(val),
|
||||
);
|
||||
}
|
||||
while (extensionsUsed.length > 0) {
|
||||
extensionsUsed.pop();
|
||||
}
|
||||
extensionsUsed.push(...extensionList);
|
||||
setLoadableExtensions();
|
||||
};
|
||||
|
||||
const setLoadableExtensions = () => {
|
||||
setEmptyExtensionLoadable(extensionsUsed.includes(EmptyExtension));
|
||||
setMathJaxExtensionLoadable(extensionsUsed.includes(MathJaxExtension));
|
||||
// Extension authors: add a line here like
|
||||
// `setMyExtensionLoadable(extensionsUsed.includes(MyExtension));`
|
||||
};
|
28
src/packages/extensions/webpack.dev-server.config.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
const path = require("path");
|
||||
const { merge } = require("webpack-merge");
|
||||
|
||||
const devConfig = require("./webpack.dev.config");
|
||||
|
||||
const devServerConfig = {
|
||||
entry: {
|
||||
bundle: "./example/index.tsx",
|
||||
},
|
||||
// Server Configuration options
|
||||
devServer: {
|
||||
port: 3001,
|
||||
host: "localhost",
|
||||
hot: true,
|
||||
compress: true,
|
||||
static: {
|
||||
directory: path.join(__dirname, "./example/public"),
|
||||
},
|
||||
client: {
|
||||
progress: true,
|
||||
logging: "info",
|
||||
overlay: true, //Shows a full-screen overlay in the browser when there are compiler errors or warnings.
|
||||
},
|
||||
open: ["./"],
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = merge(devServerConfig, devConfig);
|
18
src/packages/extensions/webpack.dev.config.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
global.__childdir = __dirname;
|
||||
const path = require("path");
|
||||
const { merge } = require("webpack-merge");
|
||||
const commonConfig = require("../common.webpack.dev.config");
|
||||
|
||||
const outputDir = process.env.EXAMPLE === "true" ? "example/public" : "dist";
|
||||
const config = {
|
||||
entry: {
|
||||
"excalidraw-extensions.development": "./index.ts",
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, outputDir),
|
||||
library: "ExcalidrawExtensionsLib",
|
||||
chunkFilename: "excalidraw-extensions-assets-dev/[name]-[contenthash].js",
|
||||
assetModuleFilename: "excalidraw-extensions-assets-dev/[name][ext]",
|
||||
},
|
||||
};
|
||||
module.exports = merge(commonConfig, config);
|
17
src/packages/extensions/webpack.prod.config.js
Normal file
|
@ -0,0 +1,17 @@
|
|||
global.__childdir = __dirname;
|
||||
const path = require("path");
|
||||
const { merge } = require("webpack-merge");
|
||||
const commonConfig = require("../common.webpack.prod.config");
|
||||
|
||||
const config = {
|
||||
entry: {
|
||||
"excalidraw-extensions.production.min": "./index.ts",
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
library: "ExcalidrawExtensionsLib",
|
||||
chunkFilename: "excalidraw-extensions-assets/[name]-[contenthash].js",
|
||||
assetModuleFilename: "excalidraw-extensions-assets/[name][ext]",
|
||||
},
|
||||
};
|
||||
module.exports = merge(commonConfig, config);
|