Add support for LaTeX inline and block expressions. (#4446)

* Add support for LaTeX inline and block expressions. (#4364)

Co-authored-by: Tom Moor <tom@getoutline.com>

* tsc

* Show heading markers when LaTeX block is being edited

* Tab to space, name katex chunk

* Fork htmldiff, add support for math nodes

Co-authored-by: luisbc92 <luiscarlos.banuelos@gmail.com>
This commit is contained in:
Tom Moor
2022-11-27 06:27:56 -08:00
committed by GitHub
parent cb1b8e9764
commit fa8685d241
16 changed files with 1785 additions and 21 deletions

View File

@@ -18,6 +18,7 @@ import {
AttachmentIcon,
ClockIcon,
CalendarIcon,
MathIcon,
} from "outline-icons";
import * as React from "react";
import styled from "styled-components";
@@ -124,6 +125,12 @@ export default function blockMenuItems(dictionary: Dictionary): MenuItem[] {
shortcut: "^ ⇧ \\",
keywords: "script",
},
{
name: "math_block",
title: dictionary.mathBlock,
icon: <MathIcon />,
keywords: "math katex latex",
},
{
name: "hr",
title: dictionary.hr,

View File

@@ -69,6 +69,8 @@ export default function useDictionary() {
strong: t("Bold"),
subheading: t("Subheading"),
table: t("Table"),
mathInline: t("Math inline (LaTeX)"),
mathBlock: t("Math block (LaTeX)"),
tip: t("Tip"),
tipNotice: t("Tip notice"),
showDiagram: t("Show diagram"),

View File

@@ -339,6 +339,14 @@ function KeyboardShortcuts() {
shortcut: <Key>{"```"}</Key>,
label: t("Code block"),
},
{
shortcut: (
<>
<Key>$$</Key> <Key>Space</Key>
</>
),
label: t("LaTeX block"),
},
{
shortcut: <Key>{":::"}</Key>,
label: t("Info notice"),
@@ -359,6 +367,10 @@ function KeyboardShortcuts() {
shortcut: "`code`",
label: t("Inline code"),
},
{
shortcut: "$latex$",
label: t("Inline LaTeX"),
},
{
shortcut: "==highlight==",
label: t("Highlight"),

View File

@@ -51,6 +51,7 @@
"@babel/plugin-transform-regenerator": "^7.10.4",
"@babel/preset-env": "^7.16.0",
"@babel/preset-react": "^7.16.0",
"@benrbray/prosemirror-math": "^0.2.2",
"@bull-board/api": "^4.2.2",
"@bull-board/koa": "^4.6.2",
"@dnd-kit/core": "^6.0.5",
@@ -113,6 +114,7 @@
"json-loader": "0.5.7",
"jsonwebtoken": "^8.5.0",
"jszip": "^3.10.1",
"katex": "^0.16.3",
"kbar": "0.1.0-beta.28",
"koa": "^2.13.4",
"koa-body": "^4.2.0",
@@ -246,6 +248,7 @@
"@types/ioredis": "^4.28.1",
"@types/jest": "^28.1.6",
"@types/jsonwebtoken": "^8.5.8",
"@types/katex": "^0.14.0",
"@types/koa": "^2.13.4",
"@types/koa-compress": "^4.0.3",
"@types/koa-helmet": "^6.0.4",
@@ -309,6 +312,7 @@
"babel-plugin-transform-typescript-metadata": "^0.3.2",
"babel-plugin-tsconfig-paths-module-resolver": "^1.0.3",
"concurrently": "^7.4.0",
"css-loader": "5.2.6",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.6",
"eslint": "^7.32.0",
@@ -335,6 +339,7 @@
"prettier": "^2.0.5",
"react-refresh": "^0.14.0",
"rimraf": "^2.5.4",
"style-loader": "2.0.0",
"terser-webpack-plugin": "^4.1.0",
"typescript": "^4.7.4",
"url-loader": "^4.1.1",

View File

@@ -4,7 +4,6 @@ import {
} from "@getoutline/y-prosemirror";
import { JSDOM } from "jsdom";
import { escapeRegExp } from "lodash";
import diff from "node-htmldiff";
import { Node, DOMSerializer } from "prosemirror-model";
import * as React from "react";
import { renderToString } from "react-dom/server";
@@ -19,6 +18,7 @@ import { parser, schema } from "@server/editor";
import Logger from "@server/logging/Logger";
import Document from "@server/models/Document";
import type Revision from "@server/models/Revision";
import diff from "@server/utils/diff";
import parseAttachmentIds from "@server/utils/parseAttachmentIds";
import { getSignedUrl } from "@server/utils/s3";
import Attachment from "../Attachment";

1118
server/utils/diff.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,109 @@ export type Props = {
theme: DefaultTheme;
};
const mathStyle = (props: Props) => `
/* Based on https://github.com/benrbray/prosemirror-math/blob/master/style/math.css */
.math-node {
min-width: 1em;
min-height: 1em;
font-size: 0.95em;
font-family: ${props.theme.fontFamilyMono};
cursor: auto;
}
.math-node.empty-math .math-render::before {
content: "(empty math)";
color: ${props.theme.brand.red};
}
.math-node .math-render.parse-error::before {
content: "(math error)";
color: ${props.theme.brand.red};
cursor: help;
}
.math-node.ProseMirror-selectednode {
outline: none;
}
.math-node .math-src {
display: none;
color: ${props.theme.codeStatement};
tab-size: 4;
}
.math-node.ProseMirror-selectednode .math-src {
display: inline;
}
.math-node.ProseMirror-selectednode .math-render {
display: none;
}
math-inline {
display: inline; white-space: nowrap;
}
math-inline .math-render {
display: inline-block;
font-size: 0.85em;
}
math-inline .math-src .ProseMirror {
display: inline;
border-radius: 4px;
border: 1px solid ${props.theme.codeBorder};
padding: 3px 4px;
margin: 0px 3px;
font-family: ${props.theme.fontFamilyMono};
font-size: 80%;
}
math-block {
display: block;
}
math-block .math-render {
display: block;
}
math-block.ProseMirror-selectednode {
border-radius: 4px;
border: 1px solid ${props.theme.codeBorder};
background: ${props.theme.codeBackground};
padding: 0.75em 1em;
font-family: ${props.theme.fontFamilyMono};
font-size: 80%;
}
math-block .math-src .ProseMirror {
width: 100%;
display: block;
}
math-block .katex-display {
margin: 0;
}
p::selection, p > *::selection {
background-color: #c0c0c0;
}
.katex-html *::selection {
background-color: none !important;
}
.math-node.math-select .math-render {
background-color: #c0c0c0ff;
}
math-inline.math-select .math-render {
padding-top: 2px;
}
`;
const style = (props: Props) => `
flex-grow: ${props.grow ? 1 : 0};
justify-content: start;
@@ -329,6 +432,7 @@ h6:not(.placeholder):before {
content: "H6";
}
.ProseMirror[contenteditable="true"]:focus-within,
.ProseMirror-focused {
h1,
h2,
@@ -1254,6 +1358,7 @@ table {
}
}
.ProseMirror[contenteditable="true"]:focus-within,
.ProseMirror-focused .block-menu-trigger,
.block-menu-trigger:active,
.block-menu-trigger:focus {
@@ -1344,6 +1449,7 @@ del[data-operation-index] {
const EditorContainer = styled.div<Props>`
${style};
${mathStyle};
`;
export default EditorContainer;

View File

@@ -0,0 +1,86 @@
import {
mathBackspaceCmd,
insertMathCmd,
makeInlineMathInputRule,
REGEX_INLINE_MATH_DOLLARS,
mathSchemaSpec,
} from "@benrbray/prosemirror-math";
import { PluginSimple } from "markdown-it";
import {
chainCommands,
deleteSelection,
selectNodeBackward,
joinBackward,
} from "prosemirror-commands";
import {
NodeSpec,
NodeType,
Schema,
Node as ProsemirrorNode,
} from "prosemirror-model";
import { EditorState, Plugin } from "prosemirror-state";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import MathPlugin from "../plugins/Math";
import mathRule from "../rules/math";
import { Dispatch } from "../types";
import Node from "./Node";
export default class Math extends Node {
get name() {
return "math_inline";
}
get schema(): NodeSpec {
return mathSchemaSpec.nodes.math_inline;
}
commands({ type }: { type: NodeType }) {
return () => (state: EditorState, dispatch: Dispatch) => {
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
return true;
};
}
inputRules({ schema }: { schema: Schema }) {
return [
makeInlineMathInputRule(
REGEX_INLINE_MATH_DOLLARS,
schema.nodes.math_inline
),
];
}
keys({ type }: { type: NodeType }) {
return {
"Mod-Space": insertMathCmd(type),
Backspace: chainCommands(
deleteSelection,
mathBackspaceCmd,
joinBackward,
selectNodeBackward
),
};
}
get plugins(): Plugin[] {
return [MathPlugin];
}
get rulePlugins(): PluginSimple[] {
return [mathRule];
}
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
state.write("$");
state.text(node.textContent, false);
state.write("$");
}
parseMarkdown() {
return {
node: "math_inline",
block: "math_inline",
noCloseToken: true,
};
}
}

View File

@@ -0,0 +1,53 @@
import {
makeBlockMathInputRule,
REGEX_BLOCK_MATH_DOLLARS,
mathSchemaSpec,
} from "@benrbray/prosemirror-math";
import { PluginSimple } from "markdown-it";
import { NodeSpec, NodeType, Node as ProsemirrorNode } from "prosemirror-model";
import { EditorState } from "prosemirror-state";
import { MarkdownSerializerState } from "../lib/markdown/serializer";
import mathRule from "../rules/math";
import { Dispatch } from "../types";
import Node from "./Node";
export default class MathBlock extends Node {
get name() {
return "math_block";
}
get schema(): NodeSpec {
return mathSchemaSpec.nodes.math_display;
}
get rulePlugins(): PluginSimple[] {
return [mathRule];
}
commands({ type }: { type: NodeType }) {
return () => (state: EditorState, dispatch: Dispatch) => {
dispatch(state.tr.replaceSelectionWith(type.create()).scrollIntoView());
return true;
};
}
inputRules({ type }: { type: NodeType }) {
return [makeBlockMathInputRule(REGEX_BLOCK_MATH_DOLLARS, type)];
}
toMarkdown(state: MarkdownSerializerState, node: ProsemirrorNode) {
state.write("$$\n");
state.text(node.textContent, false);
state.ensureNewLine();
state.write("$$");
state.closeBlock(node);
}
parseMarkdown() {
return {
node: "math_block",
block: "math_block",
noCloseToken: true,
};
}
}

View File

@@ -13,6 +13,8 @@ import Embed from "../nodes/Embed";
import Heading from "../nodes/Heading";
import HorizontalRule from "../nodes/HorizontalRule";
import ListItem from "../nodes/ListItem";
import Math from "../nodes/Math";
import MathBlock from "../nodes/MathBlock";
import Node from "../nodes/Node";
import Notice from "../nodes/Notice";
import OrderedList from "../nodes/OrderedList";
@@ -36,6 +38,8 @@ const fullPackage: (typeof Node | typeof Mark | typeof Extension)[] = [
OrderedList,
Embed,
ListItem,
Math,
MathBlock,
Attachment,
Notice,
Heading,

View File

@@ -0,0 +1,75 @@
import { MathView } from "@benrbray/prosemirror-math";
import { Node as ProseNode } from "prosemirror-model";
import { Plugin, PluginKey, PluginSpec } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
export interface IMathPluginState {
macros: { [cmd: string]: string };
activeNodeViews: MathView[];
prevCursorPos: number;
}
const MATH_PLUGIN_KEY = new PluginKey<IMathPluginState>("prosemirror-math");
export function createMathView(displayMode: boolean) {
return (
node: ProseNode,
view: EditorView,
getPos: boolean | (() => number)
): MathView => {
// dynamically load katex styles and fonts
import(
/* webpackChunkName: "katex" */
"katex/dist/katex.min.css"
);
const pluginState = MATH_PLUGIN_KEY.getState(view.state);
if (!pluginState) {
throw new Error("no math plugin!");
}
const nodeViews = pluginState.activeNodeViews;
// set up NodeView
const nodeView = new MathView(
node,
view,
getPos as () => number,
{ katexOptions: { displayMode, macros: pluginState.macros } },
MATH_PLUGIN_KEY,
() => {
nodeViews.splice(nodeViews.indexOf(nodeView));
}
);
nodeViews.push(nodeView);
return nodeView;
};
}
const mathPluginSpec: PluginSpec<IMathPluginState> = {
key: MATH_PLUGIN_KEY,
state: {
init() {
return {
macros: {},
activeNodeViews: [],
prevCursorPos: 0,
};
},
apply(tr, value, oldState) {
return {
activeNodeViews: value.activeNodeViews,
macros: value.macros,
prevCursorPos: oldState.selection.from,
};
},
},
props: {
nodeViews: {
math_inline: createMathView(false),
math_block: createMathView(true),
},
},
};
export default new Plugin(mathPluginSpec);

180
shared/editor/rules/math.ts Normal file
View File

@@ -0,0 +1,180 @@
import MarkdownIt from "markdown-it";
import StateBlock from "markdown-it/lib/rules_block/state_block";
import StateInline from "markdown-it/lib/rules_inline/state_inline";
// test if potential opening or closing delimiter
// assumes that there is a "$" at state.src[pos]
function isValidDelimiter(state: StateInline, pos: number) {
const max = state.posMax;
let canOpen = true,
canClose = true;
const prevChar = pos > 0 ? state.src.charCodeAt(pos - 1) : -1;
const nextChar = pos + 1 <= max ? state.src.charCodeAt(pos + 1) : -1;
// check non-whitespace conditions for open/close, and
// check that closing delimiter isn't followed by a number
if (
prevChar === 0x20 || // " "
prevChar === 0x09 || // "\t"
(nextChar >= 0x30 && nextChar <= 0x39) // "0" - "9"
) {
canClose = false;
}
if (nextChar === 0x20 || nextChar === 0x09) {
canOpen = false;
}
return { canOpen, canClose };
}
function mathInline(state: StateInline, silent: boolean): boolean {
let match, token, res, pos;
if (state.src[state.pos] !== "$") {
return false;
}
res = isValidDelimiter(state, state.pos);
if (!res.canOpen) {
if (!silent) {
state.pending += "$";
}
state.pos += 1;
return true;
}
// first check for and bypass all properly escaped delimiters
// this loop will assume that the first leading backtick can not
// be the first character in state.src, which is known since
// we have found an opening delimiter already
const start = state.pos + 1;
match = start;
while ((match = state.src.indexOf("$", match)) !== 1) {
// found potential $, look for escapes, pos will point to
// first non escape when complete
pos = match - 1;
while (state.src[pos] === "\\") {
pos -= 1;
}
// even number of escapes, potential closing delimiter found
if ((match - pos) % 2 === 1) {
break;
}
match += 1;
}
// no closing delimiter found, consume $ and continue
if (match === -1) {
if (!silent) {
state.pending += "$";
}
state.pos = start;
return true;
}
// check if we have empty content (ex. $$) do not parse
if (match - start === 0) {
if (!silent) {
state.pending += "$$";
}
state.pos = start + 1;
return true;
}
// check for valid closing delimiter
res = isValidDelimiter(state, match);
if (!res.canClose) {
if (!silent) {
state.pending += "$";
}
state.pos = start;
return true;
}
if (!silent) {
token = state.push("math_inline", "math", 0);
token.markup = "$";
token.content = state.src.slice(start, match);
}
state.pos = match + 1;
return true;
}
function mathDisplay(
state: StateBlock,
start: number,
end: number,
silent: boolean
) {
let firstLine,
lastLine,
next,
lastPos,
found = false,
pos = state.bMarks[start] + state.tShift[start],
max = state.eMarks[start];
if (pos + 2 > max) {
return false;
}
if (state.src.slice(pos, pos + 2) !== "$$") {
return false;
}
pos += 2;
firstLine = state.src.slice(pos, max);
if (silent) {
return true;
}
if (firstLine.trim().slice(-2) === "$$") {
// Single line expression
firstLine = firstLine.trim().slice(0, -2);
found = true;
}
for (next = start; !found; ) {
next++;
if (next >= end) {
break;
}
pos = state.bMarks[next] + state.tShift[next];
max = state.eMarks[next];
if (pos < max && state.tShift[next] < state.blkIndent) {
// non-empty line with negative indent should stop the list:
break;
}
if (state.src.slice(pos, max).trim().slice(-2) === "$$") {
lastPos = state.src.slice(0, max).lastIndexOf("$$");
lastLine = state.src.slice(pos, lastPos);
found = true;
}
}
state.line = next + 1;
const token = state.push("math_block", "math", 0);
token.block = true;
token.content =
(firstLine && firstLine.trim() ? firstLine + "\n" : "") +
state.getLines(start + 1, next, state.tShift[start], true) +
(lastLine && lastLine.trim() ? lastLine : "");
token.map = [start, state.line];
token.markup = "$$";
return true;
}
export default function markdownMath(md: MarkdownIt) {
md.inline.ruler.after("escape", "math_inline", mathInline);
md.block.ruler.after("blockquote", "math_block", mathDisplay, {
alt: ["paragraph", "reference", "blockquote", "list"],
});
}

View File

@@ -277,6 +277,8 @@
"Bold": "Bold",
"Subheading": "Subheading",
"Table": "Table",
"Math inline (LaTeX)": "Math inline (LaTeX)",
"Math block (LaTeX)": "Math block (LaTeX)",
"Tip": "Tip",
"Tip notice": "Tip notice",
"Show diagram": "Show diagram",
@@ -571,7 +573,9 @@
"Numbered list": "Numbered list",
"Blockquote": "Blockquote",
"Horizontal divider": "Horizontal divider",
"LaTeX block": "LaTeX block",
"Inline code": "Inline code",
"Inline LaTeX": "Inline LaTeX",
"Sign In": "Sign In",
"Continue with Email": "Continue with Email",
"Continue with {{ authProviderName }}": "Continue with {{ authProviderName }}",

1
shared/typings/styles.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
declare module "*.css";

View File

@@ -17,27 +17,32 @@ module.exports = {
module: {
rules: [
{
test: /\.[jt]sx?$/,
loader: 'babel-loader',
exclude: [
path.join(__dirname, 'node_modules')
],
include: [
path.join(__dirname, 'app'),
path.join(__dirname, 'shared'),
],
options: {
cacheDirectory: true
}
test: /\.[jt]sx?$/,
loader: 'babel-loader',
exclude: [
path.join(__dirname, 'node_modules')
],
include: [
path.join(__dirname, 'app'),
path.join(__dirname, 'shared'),
],
options: {
cacheDirectory: true
}
},
// inline base64 URLs for <=8k images, direct URLs for the rest
{ test: /\.(png|jpg|svg)$/, loader: 'url-loader' },
{
test: /\.woff$/,
loader: 'url-loader?limit=1&mimetype=application/font-woff&name=public/fonts/[name].[ext]',
test: /\.(woff|woff2|ttf|eot)$/,
loader:
'url-loader?limit=1&mimetype=application/font-woff&name=public/fonts/[name].[ext]',
},
{ test: /\.md/, loader: 'raw-loader' },
]
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
],
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.json'],

116
yarn.lock
View File

@@ -1057,6 +1057,11 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@benrbray/prosemirror-math@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@benrbray/prosemirror-math/-/prosemirror-math-0.2.2.tgz#5b2aeb7c8c5a41d2e39317fe1fbd7040fd347cc9"
integrity sha512-n+V8MNKaQ9HtA1IASzoBFwthFY55kpu2I+0aF103AbqUw5eM8YlxHeltnLqjnYRVY4/a6A9t9YlBMBQOli5jgw==
"@braintree/sanitize-url@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.0.tgz#fe364f025ba74f6de6c837a84ef44bdb1d61e68f"
@@ -2903,6 +2908,11 @@
dependencies:
"@types/node" "*"
"@types/katex@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.14.0.tgz#b84c0afc3218069a5ad64fe2a95321881021b5fe"
integrity sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==
"@types/keygrip@*":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/keygrip/-/keygrip-1.0.2.tgz#513abfd256d7ad0bf1ee1873606317b33b1b2a72"
@@ -5407,7 +5417,7 @@ commander@^6.1.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75"
integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==
commander@^8.3.0:
commander@^8.0.0, commander@^8.3.0:
version "8.3.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66"
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
@@ -5752,6 +5762,22 @@ css-color-names@1.0.1:
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-1.0.1.tgz#6ff7ee81a823ad46e020fa2fd6ab40a887e2ba67"
integrity sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==
css-loader@5.2.6:
version "5.2.6"
resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.6.tgz#c3c82ab77fea1f360e587d871a6811f4450cc8d1"
integrity sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w==
dependencies:
icss-utils "^5.1.0"
loader-utils "^2.0.0"
postcss "^8.2.15"
postcss-modules-extract-imports "^3.0.0"
postcss-modules-local-by-default "^4.0.0"
postcss-modules-scope "^3.0.0"
postcss-modules-values "^4.0.0"
postcss-value-parser "^4.1.0"
schema-utils "^3.0.0"
semver "^7.3.5"
css-rules@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/css-rules/-/css-rules-1.1.0.tgz#404b8b1f77bd775f6c6902b7a7b534f5c016b07f"
@@ -5815,6 +5841,11 @@ css-what@^6.1.0:
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
cssesc@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
cssom@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36"
@@ -8638,6 +8669,11 @@ iconv-lite@0.6, iconv-lite@0.6.3:
dependencies:
safer-buffer ">= 2.1.2 < 3.0.0"
icss-utils@^5.0.0, icss-utils@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
idb@^6.1.4:
version "6.1.5"
resolved "https://registry.yarnpkg.com/idb/-/idb-6.1.5.tgz#dbc53e7adf1ac7c59f9b2bf56e00b4ea4fce8c7b"
@@ -9974,6 +10010,13 @@ jws@^3.2.2:
jwa "^1.4.1"
safe-buffer "^5.0.1"
katex@^0.16.3:
version "0.16.3"
resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.3.tgz#29640560b8fa0403e45f3aa20da5fdbb6d2b83a8"
integrity sha512-3EykQddareoRmbtNiNEDgl3IGjryyrp2eg/25fHDEnlHymIDi33bptkMv6K4EOC2LZCybLW/ZkEo6Le+EM9pmA==
dependencies:
commander "^8.0.0"
kbar@0.1.0-beta.28:
version "0.1.0-beta.28"
resolved "https://registry.yarnpkg.com/kbar/-/kbar-0.1.0-beta.28.tgz#35bcf1d45996f5b0cf32f4f70f673c97dc67a1f8"
@@ -11117,6 +11160,11 @@ nan@^2.12.1, nan@^2.16.0:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.17.0.tgz#c0150a2368a182f033e9aa5195ec76ea41a199cb"
integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==
nanoid@^3.3.4:
version "3.3.4"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
nanomatch@^1.2.9:
version "1.2.13"
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
@@ -12106,10 +12154,55 @@ posix-character-classes@^0.1.0:
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
postcss-value-parser@^4.0.2:
version "4.1.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
postcss-modules-extract-imports@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d"
integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==
postcss-modules-local-by-default@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c"
integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser "^6.0.2"
postcss-value-parser "^4.1.0"
postcss-modules-scope@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06"
integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==
dependencies:
postcss-selector-parser "^6.0.4"
postcss-modules-values@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c"
integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==
dependencies:
icss-utils "^5.0.0"
postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.10"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d"
integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==
dependencies:
cssesc "^3.0.0"
util-deprecate "^1.0.2"
postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^8.2.15:
version "8.4.18"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2"
integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==
dependencies:
nanoid "^3.3.4"
picocolors "^1.0.0"
source-map-js "^1.0.2"
postgres-array@~2.0.0:
version "2.0.0"
@@ -13906,6 +13999,11 @@ source-list-map@~0.1.7:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106"
integrity sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
source-map-resolve@^0.5.0:
version "0.5.3"
resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
@@ -14296,6 +14394,14 @@ style-data@^2.0.0:
mediaquery-text "^1.2.0"
pick-util "^1.1.4"
style-loader@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c"
integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==
dependencies:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
style-value-types@4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/style-value-types/-/style-value-types-4.1.4.tgz#80f37cb4fb024d6394087403dfb275e8bb627e75"