Skip to content

Commit

Permalink
feat: range splitting of code block types
Browse files Browse the repository at this point in the history
  • Loading branch information
WindRunnerMax committed Jun 29, 2024
1 parent 39ff8b2 commit 981ee5c
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 112 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<span>|</span>
<a href="https://windrunnermax.github.io/DocEditor/">DEMO</a>
<span>|</span>
<a href="https://github.com/WindrunnerMax/EveryDay/blob/master/Plugin/基于slate构建文档编辑器.md">BLOG</a>
<a href="https://github.com/WindrunnerMax/DocEditor/issues/11">BLOG</a>
<span>|</span>
<a href="./NOTE.md">NOTE</a>
<span>|</span>
Expand Down
27 changes: 27 additions & 0 deletions packages/core/src/reflex/modules/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,38 @@ export class Get extends Is {
const path = [...Editor.path(this.raw, at)];
while (path.length) {
const tuple = this.getNodeTuple(path, 0);
if (!tuple) return null;
// NOTE: 理论上目标需要操作的节点不会是`Instance`节点
if (this.isInstanceNode(tuple.node)) return null;
if (tuple && this.isBlock(tuple.node)) {
return tuple as BlockNodeTuple;
}
path.pop();
}
return null;
}

/**
* 获取最近的符合条件的块级节点元组
* @description 用于检查当前`Path`到`Instance`的匹配节点
* @param key string
* @param at? Location
* @returns BlockNodeTuple | null
*/
public getClosestMatchBlockTuple(key: string, at?: Location): BlockNodeTuple | null {
const location = at || this.raw.selection;
if (!location) return null;
const path = [...Editor.path(this.raw, location)];
while (path.length) {
const tuple = Editor.node(this.raw, path);
if (!tuple) return null;
const [node, tuplePath] = tuple;
if (this.isInstanceNode(node)) return null;
if (Editor.isBlock(this.raw, node) && node[key]) {
return { node, path: tuplePath };
}
path.pop();
}
return null;
}
}
30 changes: 10 additions & 20 deletions packages/plugin/src/codeblock/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ import type {
} from "doc-editor-core";
import { BlockPlugin, LeafPlugin } from "doc-editor-core";
import type { BaseRange, BlockElement, NodeEntry, Range } from "doc-editor-delta";
import { Editor, Transforms } from "doc-editor-delta";
import { Transforms } from "doc-editor-delta";
import { ReactEditor } from "doc-editor-delta";
import { getBlockNode, getClosestBlockPath, getParentNode } from "doc-editor-utils";
import { getClosestBlockPath, getParentNode } from "doc-editor-utils";
import { isBlock, isText } from "doc-editor-utils";
import { setBlockNode } from "doc-editor-utils";

import { CODE_BLOCK_CONFIG, CODE_BLOCK_KEY, CODE_BLOCK_TYPE } from "./types";
import { codeTokenize, DEFAULT_LANGUAGE, getLanguage, SUPPORTED_LANGUAGES } from "./utils/parser";
import {
DEFAULT_LANGUAGE,
getLanguage,
parseCodeNodeRange,
SUPPORTED_LANGUAGES,
} from "./utils/parser";

class CodeBlockLeafPlugin extends LeafPlugin {
public readonly key: string = CODE_BLOCK_TYPE;
Expand Down Expand Up @@ -113,23 +118,8 @@ export class CodeBlockPlugin extends BlockPlugin {
if (!isText(textNode)) {
return ranges;
}
const codeblockNode = getBlockNode(this.raw, { at: path, key: CODE_BLOCK_KEY });
if (codeblockNode) {
const textPath = [...path, 0];
const str = Editor.string(this.raw, path);
const language = getLanguage(codeblockNode.block);
const codeRange = codeTokenize(str, language);
// TODO: 采取双迭代的方式 取较小值作为`range`
codeRange.forEach(item => {
ranges.push({
anchor: { path: textPath, offset: item.start },
focus: { path: textPath, offset: item.end },
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
[CODE_BLOCK_TYPE]: item.type,
});
});
}
const language = getLanguage(parent.node);
ranges.push(...parseCodeNodeRange(node, path, language, CODE_BLOCK_TYPE));
return ranges;
}
}
50 changes: 49 additions & 1 deletion packages/plugin/src/codeblock/utils/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import "prismjs/themes/prism.min.css";
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-java";

import type { BlockElement } from "doc-editor-delta";
import type { BlockElement, Path, Range } from "doc-editor-delta";

import { CODE_BLOCK_CONFIG } from "../types";
import { isText } from "doc-editor-utils";

export const DEFAULT_LANGUAGE = "Plain Text";
export const SUPPORTED_LANGUAGES = [DEFAULT_LANGUAGE, "JavaScript", "Java"];
Expand Down Expand Up @@ -45,3 +46,50 @@ export const codeTokenize = (code: string, language: string) => {
export const getLanguage = (node: BlockElement): string => {
return node[CODE_BLOCK_CONFIG]?.language || DEFAULT_LANGUAGE;
};

export const parseCodeNodeRange = (
node: BlockElement,
path: Path,
language: string,
key: string
): Range[] => {
const texts: string[] = [];
const ranges: Range[] = [];
for (const child of node.children) {
if (!isText(child)) return ranges;
texts.push(child.text);
}
const str = texts.join("");
const codeRange = codeTokenize(str, language);
let index = 0;
let iterated = 0;
for (const range of codeRange) {
const start = range.start;
// NOTE: skip already iterated strings
while (index < texts.length && start >= iterated + texts[index].length) {
iterated = iterated + texts[index].length;
index++;
}
// NOTE: find the index of array and relative position
let offset = start - iterated;
let remaining = range.end - range.start;
while (index < texts.length && remaining > 0) {
const currentText = texts[index];
const currentPath = [...path, index];
const taken = Math.min(remaining, currentText.length - offset);
ranges.push({
anchor: { path: currentPath, offset },
focus: { path: currentPath, offset: offset + taken },
[key]: range.type,
});
remaining = remaining - taken;
if (remaining > 0) {
iterated = iterated + currentText.length;
// NOTE: next block will be indexed from 0
offset = 0;
index++;
}
}
}
return ranges;
};
16 changes: 12 additions & 4 deletions packages/plugin/src/react-live/components/viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,28 @@ import { useDebounceEffect } from "ahooks";
import type { EditorKit } from "doc-editor-core";
import { Void } from "doc-editor-core";
import type { BlockElement } from "doc-editor-delta";
import { isText } from "doc-editor-utils";
import type { FC } from "react";
import React, { useEffect, useRef, useState } from "react";
import React, { useEffect, useMemo, useRef, useState } from "react";
import ReactDOM from "react-dom";
import { compileWithSucrase, renderWithDependency, withSandbox } from "react-live-runtime";

import { collectText } from "../utils/parse";

export const ReactLiveView: FC<{
element: BlockElement;
editor: EditorKit;
}> = props => {
const ref = useRef<HTMLDivElement>(null);
const [loading, setLoading] = useState(true);
const code = collectText(props.editor.raw, props.element);

const code: string = useMemo(() => {
const element = props.element;
return element.children
.map(line => {
const children = line.children || [];
return children.map(it => (isText(it) ? it.text : ""));
})
.join("\n");
}, [props.element]);

useDebounceEffect(
() => {
Expand Down
30 changes: 14 additions & 16 deletions packages/plugin/src/react-live/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "./index.scss";

import type { BlockContext, CommandFn, LeafContext } from "doc-editor-core";
import type { EditorKit } from "doc-editor-core";
import type { EditorRaw } from "doc-editor-core";
import { BlockPlugin, LeafPlugin } from "doc-editor-core";
import type {
BaseRange,
Expand All @@ -11,11 +12,11 @@ import type {
RenderLeafProps,
} from "doc-editor-delta";
import { Transforms } from "doc-editor-delta";
import { getClosestBlockPath, getParentNode, isBlock } from "doc-editor-utils";
import { getClosestBlockPath, getParentNode, isBlock, isText } from "doc-editor-utils";

import { parseCodeNodeRange } from "../codeblock/utils/parser";
import { ReactLiveView } from "./components/viewer";
import { REACT_LIVE_KEY, REACT_LIVE_TYPE } from "./types";
import { codeTokenize, collectReactLiveText } from "./utils/parse";

class ReactLiveLeafPlugin extends LeafPlugin {
public readonly key: string = REACT_LIVE_TYPE;
Expand All @@ -34,9 +35,11 @@ class ReactLiveLeafPlugin extends LeafPlugin {

export class ReactLivePlugin extends BlockPlugin {
public key: string = REACT_LIVE_KEY;
private raw: EditorRaw;

constructor(private editor: EditorKit) {
super();
this.raw = editor.raw;
this.WITH_LEAF_PLUGINS = [new ReactLiveLeafPlugin()];
}

Expand Down Expand Up @@ -76,22 +79,17 @@ export class ReactLivePlugin extends BlockPlugin {

public onDecorate(entry: NodeEntry): BaseRange[] {
const [node, path] = entry;
const ranges: Range[] = [];
const parent = getParentNode(this.editor.raw, path);
if (parent && isBlock(this.editor.raw, node) && parent.node[REACT_LIVE_KEY]) {
const str = collectReactLiveText(this.editor.raw, node, path);
if (!str) return [];
const textPath = [...path, 0];
const codeRange = codeTokenize(str);
// TODO: 采取双迭代的方式 取较小值作为`range`
const ranges: Range[] = codeRange.map(item => ({
anchor: { path: textPath, offset: item.start },
focus: { path: textPath, offset: item.end },
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
[REACT_LIVE_TYPE]: item.type,
}));
if (!isBlock(this.raw, node) || !parent || !parent.node[REACT_LIVE_KEY]) {
return ranges;
}
return [];
const textNode = node.children[0];
if (!isText(textNode)) {
return ranges;
}
const language = "JavaScript";
ranges.push(...parseCodeNodeRange(node, path, language, REACT_LIVE_TYPE));
return ranges;
}
}
70 changes: 0 additions & 70 deletions packages/plugin/src/react-live/utils/parse.ts

This file was deleted.

10 changes: 10 additions & 0 deletions packages/plugin/src/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,13 @@ body {
--icon-plus-z-index: $icon-plus-z-index;
--box-shadow: $box-shadow;
}

.doc-line {
.language-css .token.string,
.style .token.string,
.token.entity,
.token.operator,
.token.url {
background-color: transparent;
}
}
4 changes: 4 additions & 0 deletions packages/utils/src/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export const isMatchedEvent = (event: React.KeyboardEvent<HTMLDivElement>, ...ar
return args.indexOf(key) > -1;
};

export const isInlineBlock = (editor: Editor, node: Node): node is BlockElement => {
return editor.isInline(node as BlockElement);
};

export const isTextBlock = (editor: Editor, node: Node): node is TextBlockElement => {
if (!isBlock(editor, node)) return false;
const firstNode = node.children[0];
Expand Down

0 comments on commit 981ee5c

Please sign in to comment.