Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add POC for new integrated math nodes #3025

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 23 additions & 15 deletions packages/editor/src/extensions/code-block/code-block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import stripIndent from "strip-indent";
import { nanoid } from "nanoid";
import Languages from "./languages.json";

interface Indent {
export interface Indent {
type: "tab" | "space";
amount: number;
}
Expand Down Expand Up @@ -349,7 +349,7 @@ export const CodeBlock = Node.create<CodeBlockOptions>({
if (this.options.exitOnTripleEnter && exitOnTripleEnter(editor, $from))
return true;

const indentation = parseIndentation($from.parent);
const indentation = parseIndentation($from.parent, this.name);

if (indentation) return indentOnEnter(editor, $from, indentation);
return false;
Expand Down Expand Up @@ -420,7 +420,7 @@ export const CodeBlock = Node.create<CodeBlockOptions>({
return false;
}

const indentation = parseIndentation($from.parent);
const indentation = parseIndentation($from.parent, this.name);
if (!indentation) return false;

const indentToken = indent(indentation);
Expand Down Expand Up @@ -452,7 +452,7 @@ export const CodeBlock = Node.create<CodeBlockOptions>({
if ($from.parent.type !== this.type) {
return false;
}
const indentation = parseIndentation($from.parent);
const indentation = parseIndentation($from.parent, this.name);
if (!indentation) return false;

const { lines } = $from.parent.attrs as CodeBlockAttributes;
Expand Down Expand Up @@ -518,7 +518,7 @@ export const CodeBlock = Node.create<CodeBlockOptions>({

const indent = fixIndentation(
text,
parseIndentation(view.state.selection.$from.parent)
parseIndentation(view.state.selection.$from.parent, this.name)
);

const { tr } = view.state;
Expand Down Expand Up @@ -584,11 +584,12 @@ export type CaretPosition = {
from: number;
};
export function toCaretPosition(
name: string,
selection: Selection,
lines?: CodeLine[]
): CaretPosition | undefined {
const { $from, $to, $head } = selection;
if ($from.parent.type.name !== CodeBlock.name) return;
if ($from.parent.type.name !== name) return;
lines = lines || getLines($from.parent);

for (const line of lines) {
Expand All @@ -611,7 +612,7 @@ export function getLines(node: ProsemirrorNode) {
return lines || [];
}

function exitOnTripleEnter(editor: Editor, $from: ResolvedPos) {
export function exitOnTripleEnter(editor: Editor, $from: ResolvedPos) {
const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
const endsWithDoubleNewline = $from.parent.textContent.endsWith("\n\n");

Expand All @@ -630,7 +631,11 @@ function exitOnTripleEnter(editor: Editor, $from: ResolvedPos) {
.run();
}

function indentOnEnter(editor: Editor, $from: ResolvedPos, options: Indent) {
export function indentOnEnter(
editor: Editor,
$from: ResolvedPos,
options: Indent
) {
const { indentation, newline } = getNewline($from, options) || {};
if (!newline) return false;

Expand All @@ -657,7 +662,7 @@ function getNewline($from: ResolvedPos, options: Indent) {
};
}

type CodeLine = {
export type CodeLine = {
index: number;
from: number;
to: number;
Expand Down Expand Up @@ -697,7 +702,7 @@ export function toCodeLines(code: string, pos: number): CodeLine[] {
return positions;
}

function getSelectedLines(lines: CodeLine[], selection: Selection) {
export function getSelectedLines(lines: CodeLine[], selection: Selection) {
const { $from, $to } = selection;
return lines.filter(
(line) =>
Expand All @@ -707,8 +712,11 @@ function getSelectedLines(lines: CodeLine[], selection: Selection) {
);
}

function parseIndentation(node: ProsemirrorNode): Indent | undefined {
if (node.type.name !== CodeBlock.name) return undefined;
export function parseIndentation(
node: ProsemirrorNode,
name: string
): Indent | undefined {
if (node.type.name !== name) return undefined;

const { indentType, indentLength } = node.attrs;
return {
Expand All @@ -725,12 +733,12 @@ function inRange(x: number, a: number, b: number) {
return x >= a && x <= b;
}

function indent(options: Indent) {
export function indent(options: Indent) {
const char = options.type === "space" ? " " : "\t";
return char.repeat(options.amount);
}

function compareCaretPosition(
export function compareCaretPosition(
prev: CaretPosition | undefined,
next: CaretPosition | undefined
): boolean {
Expand All @@ -744,7 +752,7 @@ function compareCaretPosition(
/**
* Persist selection between transaction steps
*/
function withSelection(
export function withSelection(
tr: Transaction,
callback: (tr: Transaction) => void
): boolean {
Expand Down
5 changes: 4 additions & 1 deletion packages/editor/src/extensions/code-block/highlighter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ export function HighlighterPlugin({
name: string;
defaultLanguage: string | null | undefined;
}) {
const HIGHLIGHTER_PLUGIN_KEY = new PluginKey<HighlighterState>("highlighter");
const HIGHLIGHTER_PLUGIN_KEY = new PluginKey<HighlighterState>(
`${name}-highlighter`
);
const HIGHLIGHTED_BLOCKS: Set<string> = new Set();

return new Plugin<HighlighterState>({
Expand Down Expand Up @@ -293,6 +295,7 @@ function updateSelection(
}

const position = toCaretPosition(
name,
newState.selection,
isDocChanged ? toCodeLines(node.textContent, pos) : undefined
);
Expand Down
183 changes: 183 additions & 0 deletions packages/editor/src/extensions/math/block.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)

Copyright (C) 2023 Streetwriters (Private) Limited

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { Box, Flex, Text } from "@theme-ui/components";
import { useEffect, useRef } from "react";
import { Button } from "../../components/button";
import { useTimer } from "../../hooks/use-timer";
import { SelectionBasedReactNodeViewProps } from "../react/types";
import { MathBlock, MathBlockAttributes } from "./math-block";
import { loadKatex } from "./plugin/renderers/katex";

export function MathBlockComponent(
props: SelectionBasedReactNodeViewProps<MathBlockAttributes>
) {
const { editor, node, forwardRef, getPos } = props;
const { indentLength, indentType, caretPosition } = node.attrs;
const toolbarRef = useRef<HTMLDivElement>(null);
const { enabled, start } = useTimer(1000);
const isActive = editor.isActive(MathBlock.name);
const elementRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (isActive) return;
(async function () {
const pos = getPos();
const node = editor.current?.state.doc.nodeAt(pos);
const text = node?.textContent;

if (text && elementRef.current) {
const katex = await loadKatex();

elementRef.current.innerHTML = katex.renderToString(text, {
displayMode: true,
globalGroup: true,
throwOnError: false
});
}
})();
}, [isActive]);

return (
<>
<Flex
sx={{
flexDirection: "column",
borderRadius: "default",
overflow: "hidden",
...(isActive ? {} : { height: "1px", width: 0, visibility: "hidden" })
}}
>
<Text
ref={forwardRef}
as="pre"
autoCorrect="off"
autoCapitalize="none"
css={{}}
sx={{
"div, span.token, span.line-number-widget, span.line-number::before":
{
fontFamily: "monospace",
fontSize: "code",
whiteSpace: "pre", // TODO !important
tabSize: 1
},
position: "relative",
lineHeight: "20px",
bg: "codeBg",
color: "static",
overflowX: "auto",
display: "flex",
px: 2,
pt: 2,
pb: 2
}}
spellCheck={false}
/>
<Flex
ref={toolbarRef}
contentEditable={false}
sx={{
bg: "codeBg",
alignItems: "center",
justifyContent: "flex-end",
borderTop: "1px solid var(--codeBorder)"
}}
>
{caretPosition ? (
<Text variant={"subBody"} sx={{ mr: 1, color: "codeFg" }}>
Line {caretPosition.line}, Column {caretPosition.column}{" "}
{caretPosition.selected
? `(${caretPosition.selected} selected)`
: ""}
</Text>
) : null}

<Button
variant={"icon"}
sx={{
p: 1,
opacity: "1 !important",
":hover": { bg: "codeSelection" }
}}
title="Toggle indentation mode"
disabled={!editor.isEditable}
onClick={() => {
if (!editor.isEditable) return;
editor.commands.changeCodeBlockIndentation({
type: indentType === "space" ? "tab" : "space",
amount: indentLength
});
}}
>
<Text variant={"subBody"} sx={{ color: "codeFg" }}>
{indentType === "space" ? "Spaces" : "Tabs"}: {indentLength}
</Text>
</Button>

<Button
variant={"icon"}
sx={{
opacity: "1 !important",
p: 1,
bg: "transparent",
":hover": { bg: "codeSelection" }
}}
disabled={true}
>
<Text
variant={"subBody"}
spellCheck={false}
sx={{ color: "codeFg" }}
>
Latex
</Text>
</Button>

{node.textContent?.length > 0 ? (
<Button
variant={"icon"}
sx={{
opacity: "1 !important",
p: 1,
mr: 1,
bg: "transparent",
":hover": { bg: "codeSelection" }
}}
onClick={() => {
editor.commands.copyToClipboard(node.textContent);
start();
}}
title="Copy to clipboard"
>
<Text
variant={"subBody"}
spellCheck={false}
sx={{ color: "codeFg" }}
>
{enabled ? "Copied" : "Copy"}
</Text>
</Button>
) : null}
</Flex>
</Flex>
<Box contentEditable={false} ref={elementRef} />
</>
);
}