Skip to content

Commit

Permalink
feat: 40%
Browse files Browse the repository at this point in the history
  • Loading branch information
libra-co committed Oct 9, 2023
1 parent 4ed38b8 commit 015ce25
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 52 deletions.
19 changes: 19 additions & 0 deletions src/extension/core/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import slugid from 'slugid';
import { useTranslation } from 'react-i18next';
import { Node } from 'slate';

export const match = (node, path, predicate) => {
if (!predicate) return true;
Expand All @@ -26,6 +27,24 @@ export const generateEmptyElement = (type) => {
return { id: slugid.nice(), type, children: [generateDefaultText()] };
};

export const generateTextInCustom = (text = '') => {
return { id: slugid.nice(), text: text };
};

/**
* @param {String} type
* @param {Node[] | object | String} [children = LeafNode[]] If provide a string,that will be generate a text node as children automatically
* @param {object} [props = {}]
* @returns {Node}
*/
export const generateElementInCustom = (type, children = generateDefaultText(), props = {}) => {
if (typeof children === 'string') {
children = generateTextInCustom(children);
}
const nodeChildren = Array.isArray(children) ? children : [children];
return { id: slugid.nice(), type, ...props, children: nodeChildren };
};

export const isEmptyParagraph = (node) => {
if (node.type !== 'paragraph') return false;
if (node.children.length !== 1) return false;
Expand Down
4 changes: 4 additions & 0 deletions src/extension/plugins/code-block/func.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
* 问题与实现
* 在插入代码块时会检测下方是否有空行,如果没有则自动插入一行`Paragraph`,此操作为避免无法结束代码块,或无法在代码块后插入新的`Paragraph`

* 与seafile-editor的区别
* code-line不会存在于code-block中
* quote-block不会存在于code-block中

Todo:

* [ ] 加粗,斜体,代码块选中后光标没有自动归位
Expand Down
91 changes: 71 additions & 20 deletions src/extension/plugins/code-block/helpers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Editor, Location, Node, Path, Point, Range, Transforms, next } from 'slate';
import { CODE_BLOCK, PARAGRAPH } from '../../constants/element-types';
import { focusEditor, generateEmptyElement, getNextNode, getSelectedElems, getSelectedNodeByType, getSelectedNodeEntryByType, isEndPoint } from '../../core';
import { LANGUAGE_MAP } from './render-elem/constant';
import { Editor, Node, Transforms } from 'slate';
import { CODE_BLOCK, CODE_LINE, PARAGRAPH } from '../../constants/element-types';
import { focusEditor, generateElementInCustom, getAboveBlockNode, getSelectedElems, getSelectedNodeEntryByType } from '../../core';
// eslint-disable-next-line no-unused-vars
import { EXPLAIN_TEXT, LANGUAGE_MAP } from './render-elem/constant';

export const isMenuDisabled = (editor, readonly) => {
if (readonly) return true;
Expand All @@ -12,32 +13,82 @@ export const isMenuDisabled = (editor, readonly) => {
if (isSelectedVoid) return true;
// Disable the menu when selection is not in the paragraph or code block
const isEnable = selectedElments.some(node => [CODE_BLOCK, PARAGRAPH].includes(node.type));
console.log('isEnable', isEnable)
return !isEnable;
};

export const isCodeBlockNode = (editor) => {
if (!editor.selection) return false;
const [node] = Editor.nodes(editor, {
match: node => node.type === CODE_BLOCK
export const getCodeBlockNodeEntry = (editor) => {
if (!editor.selection) return;
const [codeBlock] = Editor.nodes(editor, {
match: node => node.type === CODE_BLOCK,
mode: 'highest'
});
return !!node;
console.log('getSelectedElems(editor)',)
return codeBlock;
};

export const isInCodeBlock = (editor) => {
if (!editor.selection) return false;
const [codeBlock] = Editor.nodes(editor, {
match: node => node.type === CODE_BLOCK,
mode: 'highest'
});
console.log('codeBlock', codeBlock)
if (!codeBlock) return false;
const selectedElments = getSelectedElems(editor)
console.log('selectedElments', selectedElments)
const isNotInCodeBlock = !selectedElments.find(element => ![CODE_BLOCK, CODE_LINE].includes(element.type));
console.log('isNotInCodeBlock', isNotInCodeBlock)
return isNotInCodeBlock;
}

export const transformToCodeBlock = (editor) => {
const selectedNode = getSelectedNodeEntryByType(editor, PARAGRAPH);
const endPointOfSelectParagraph = Editor.end(editor, selectedNode[1]);
Transforms.select(editor, endPointOfSelectParagraph);
Transforms.setNodes(editor, { type: CODE_BLOCK });
const nextNode = getNextNode(editor);
// If the next node is not empty, insert a new paragraph
const isInsertParagraph = !(nextNode && nextNode[0].children.length === 1 && nextNode[0].children[0].text === '');
if (isInsertParagraph) {
Transforms.insertNodes(editor, generateEmptyElement(PARAGRAPH));
const textList = [];
const nodeEntries = Editor.nodes(editor, {
match: node => editor.children.includes(node), // Match the highest level node that custom selected
universal: true,
});
for (let nodeEntry of nodeEntries) {
const [node] = nodeEntry;
if (node) {
textList.push(Node.string(node));
}
}
focusEditor(editor, endPointOfSelectParagraph);
// Generate code block
const codeBlockChildren = textList.map(text => generateElementInCustom(CODE_LINE, text));
const codeBlock = generateElementInCustom(CODE_BLOCK, codeBlockChildren, { lang: EXPLAIN_TEXT });

Transforms.removeNodes(editor, { at: editor.selection });
Transforms.insertNodes(editor, codeBlock, { mode: 'highest' });
focusEditor(editor);
};

export const unwrapCodeBlock = (editor) => {
Transforms.setNodes(editor, { type: PARAGRAPH });
const selectedCodeBlock = getSelectedNodeEntryByType(editor, CODE_BLOCK);
if (!selectedCodeBlock) return;
const selectedCodeBlockPath = selectedCodeBlock[1];
const codeLineEntries = Editor.nodes(editor, {
at: selectedCodeBlockPath,
match: node => node.type === CODE_LINE,
});
const paragraphNodes = [];
for (const codeLineEntry of codeLineEntries) {
console.log('codeLineEntry', codeLineEntry)
const [codeLineNode] = codeLineEntry;
const paragraph = generateElementInCustom(PARAGRAPH, Node.string(codeLineNode));
paragraphNodes.push(paragraph);
}
console.log('paragraphNodes', paragraphNodes)
Transforms.removeNodes(editor, { at: selectedCodeBlockPath, match: node => node.type === CODE_LINE, mode: 'lowest' });
Transforms.insertNodes(editor, paragraphNodes, { at: Editor.end(editor, editor.selection) });
focusEditor(editor);
};

/**
* @param {Object} editor
* @param {keyof LANGUAGE_MAP} [language = EXPLAIN_TEXT] by default is 'none'
*/
export const setCodeBlockLanguage = (editor, language) => {
const selectedNode = getSelectedNodeEntryByType(editor, CODE_BLOCK);
Transforms.setNodes(editor, { lang: language }, { at: selectedNode[1] });
};
4 changes: 2 additions & 2 deletions src/extension/plugins/code-block/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { CODE_BLOCK } from '../../constants/element-types';
import CodeBlockMenu from './menu';
import withCodeBlock from './plugin';
import renderCodeBlock from './render-elem';
import renderCodeBlock, { renderCodeLine } from './render-elem';

const CodeBlockPlugin = {
type: CODE_BLOCK,
nodeType: 'element',
editorMenus: [CodeBlockMenu],
editorPlugin: withCodeBlock,
renderElements: [renderCodeBlock],
renderElements: [renderCodeBlock,renderCodeLine],
};

export default CodeBlockPlugin;
8 changes: 5 additions & 3 deletions src/extension/plugins/code-block/menu/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import React, { useCallback } from 'react';
import React, { useCallback, useMemo } from 'react';
import { MenuItem } from '../../../commons';
import { CODE_BLOCK } from '../../../constants/element-types';
import { isCodeBlockNode, isMenuDisabled, transformToCodeBlock, unwrapCodeBlock } from '../helpers';
import { getCodeBlockNodeEntry, isInCodeBlock, isMenuDisabled, transformToCodeBlock, unwrapCodeBlock } from '../helpers';
import { MENUS_CONFIG_MAP } from '../../../constants';

const menuConfig = MENUS_CONFIG_MAP[CODE_BLOCK];

const CodeBlockMenu = ({ isRichEditor, className, readonly, editor }) => {
const isActive = isCodeBlockNode(editor);
const isActive = useMemo(() => isInCodeBlock(editor), [editor.selection]);
isInCodeBlock(editor)
const onMousedown = useCallback((e) => {
e.stopPropagation();
e.preventDefault();
isActive ? unwrapCodeBlock(editor) : transformToCodeBlock(editor);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isActive]);
Expand Down
Loading

0 comments on commit 015ce25

Please sign in to comment.