Skip to content

Commit

Permalink
feat: code-block-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
libra-co committed Oct 10, 2023
1 parent 015ce25 commit 87412e2
Show file tree
Hide file tree
Showing 12 changed files with 91 additions and 66 deletions.
1 change: 1 addition & 0 deletions src/constants/event-types.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export const INTERNAL_EVENTS = {
ON_ARTICLE_INFO_TOGGLE: 'on_article_info_toggle',
ON_MOUSE_ENTER_BLOCK: 'on_mouse_enter_block',
ON_CODE_LINE_FOCUS: 'on_code_line_focus',
};

export const EXTERNAL_EVENTS = {
Expand Down
2 changes: 1 addition & 1 deletion src/extension/constants/element-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ export const TABLE_CELL = 'table_cell';
export const IMAGE = 'image';
export const LINK = 'link';
export const FORMULA = 'formula';
export const COLUMN = 'column';
export const COLUMN = 'column';
31 changes: 0 additions & 31 deletions src/extension/plugins/code-block/func.md

This file was deleted.

47 changes: 30 additions & 17 deletions src/extension/plugins/code-block/helpers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Editor, Node, Transforms } from 'slate';
import { Editor, Element, Node, Point, Range, Transforms, isBlock } from 'slate';
import { CODE_BLOCK, CODE_LINE, PARAGRAPH } from '../../constants/element-types';
import { focusEditor, generateElementInCustom, getAboveBlockNode, getSelectedElems, getSelectedNodeEntryByType } from '../../core';
import { focusEditor, generateElementInCustom, getSelectedElems, getSelectedNodeEntryByType } from '../../core';
// eslint-disable-next-line no-unused-vars
import { EXPLAIN_TEXT, LANGUAGE_MAP } from './render-elem/constant';

Expand All @@ -13,7 +13,6 @@ 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;
};

Expand All @@ -23,7 +22,6 @@ export const getCodeBlockNodeEntry = (editor) => {
match: node => node.type === CODE_BLOCK,
mode: 'highest'
});
console.log('getSelectedElems(editor)',)
return codeBlock;
};

Expand All @@ -33,16 +31,22 @@ export const isInCodeBlock = (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 selectedElments = getSelectedElems(editor);
const isNotInCodeBlock = !selectedElments.find(element => ![CODE_BLOCK, CODE_LINE].includes(element.type));
console.log('isNotInCodeBlock', isNotInCodeBlock)
return isNotInCodeBlock;
}
};

export const transformToCodeBlock = (editor) => {
const selectedElments = getSelectedElems(editor);
const selectedCodeBlockNum = selectedElments.reduce(
(coudeBlockNum, node) => node.type === CODE_BLOCK
? ++coudeBlockNum
: coudeBlockNum
, 0);
if (selectedCodeBlockNum > 0) return;
const customSelection = editor.selection;
const { anchor: customAnchor, focus: customFocus } = customSelection;
const textList = [];
const nodeEntries = Editor.nodes(editor, {
match: node => editor.children.includes(node), // Match the highest level node that custom selected
Expand All @@ -58,9 +62,19 @@ export const transformToCodeBlock = (editor) => {
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);
Transforms.removeNodes(editor, {
at: editor.selection,
mode: 'highest',
match: node => Element.isElement(node) && isBlock(editor, node)
});

const selectedPath = Editor.path(editor, customSelection);
const isCollapsed = editor.selection && Range.isCollapsed(editor.selection);
const beginPath = Point.isBefore(customAnchor, customFocus) ? customAnchor.path : customFocus.path;
const focusPoint = Point.isAfter(customFocus, customAnchor) ? customFocus : customAnchor;
const insertPath = selectedPath && Object.keys(selectedPath).length ? [selectedPath[0]] : [beginPath[0]];
Transforms.insertNodes(editor, codeBlock, { at: insertPath });
focusEditor(editor, isCollapsed ? Editor.end(editor, insertPath) : focusPoint);
};

export const unwrapCodeBlock = (editor) => {
Expand All @@ -73,15 +87,14 @@ export const unwrapCodeBlock = (editor) => {
});
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);
Transforms.removeNodes(editor, { at: selectedCodeBlockPath, match: node => node.type === CODE_BLOCK, mode: 'highest' });
Transforms.insertNodes(editor, paragraphNodes, { at: selectedCodeBlockPath });
const focusPath = [selectedCodeBlockPath[0] + paragraphNodes.length - 1];
focusEditor(editor, Editor.end(editor, focusPath));
};

/**
Expand Down
6 changes: 3 additions & 3 deletions src/extension/plugins/code-block/menu/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import React, { useCallback, useMemo } from 'react';
import { MenuItem } from '../../../commons';
import { CODE_BLOCK } from '../../../constants/element-types';
import { getCodeBlockNodeEntry, isInCodeBlock, isMenuDisabled, transformToCodeBlock, unwrapCodeBlock } from '../helpers';
import { 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 }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const isActive = useMemo(() => isInCodeBlock(editor), [editor.selection]);
isInCodeBlock(editor)
isInCodeBlock(editor);
const onMousedown = useCallback((e) => {
e.stopPropagation();
e.preventDefault();
isActive ? unwrapCodeBlock(editor) : transformToCodeBlock(editor);
// eslint-disable-next-line react-hooks/exhaustive-deps
Expand Down
5 changes: 2 additions & 3 deletions src/extension/plugins/code-block/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { getNodeType, isLastNode, getSelectedNodeByType, generateEmptyElement, g
import { getCodeBlockNodeEntry } from './helpers';
import { CODE_BLOCK, CODE_LINE, PARAGRAPH } from '../../constants/element-types';

const withCodeBlock = (editor: Editor) => {
const withCodeBlock = (editor) => {
const { normalizeNode, insertFragment, insertText, insertBreak, insertData, insertNode, onHotKeyDown } = editor;
const newEditor = editor;

Expand Down Expand Up @@ -124,15 +124,14 @@ const withCodeBlock = (editor: Editor) => {
}

if (isHotkey('tab', event)) {
event.stopPropagation();
event.preventDefault();
const nodeEntries = Editor.nodes(newEditor, {
mode: 'lowest',
match: node => node.type === CODE_LINE,
});
const nodeEntryList = Array.from(nodeEntries);
for (const nodeEntry of nodeEntryList) {
const [node, path] = nodeEntry;
const [, path] = nodeEntry;
// Insert 4 spaces for easier remove space
Transforms.insertText(newEditor, ' '.repeat(4), { at: { path: [...path, 0], offset: 0 } });
}
Expand Down
52 changes: 45 additions & 7 deletions src/extension/plugins/code-block/render-elem/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
import React from 'react';
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useCallback, useEffect, useRef, useState } from 'react';
import LanguageSelector from './languageSelector';
import { useSlate } from 'slate-react';
import { isInCodeBlock } from '../helpers';

const renderCodeBlock = ({ attributes, children, element }) => {
const editor = useSlate();
const [isShowLanguageSelector, setIsShowLanguageSelector] = useState(true);
const codeBlockRef = useRef(null);

useEffect(() => {
if (isShowLanguageSelector && !isInCodeBlock(editor)) onHideLanguageSelector();
if (!isShowLanguageSelector && isInCodeBlock(editor)) registerEventHandler();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editor.selection]);

const onHideLanguageSelector = useCallback((e) => {
if (codeBlockRef?.current?.contains(e?.target)) return;
setIsShowLanguageSelector(false);
unregisterEventHandler();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setIsShowLanguageSelector]);
const registerEventHandler = useCallback(() => {
setIsShowLanguageSelector(true);
document.addEventListener('click', onHideLanguageSelector, true);
}, [onHideLanguageSelector]);

const unregisterEventHandler = useCallback(() => {
document.removeEventListener('click', onHideLanguageSelector);
}, [onHideLanguageSelector]);

return (
<div className='sf-code-block-container'>
<pre {...attributes}>
<code>{children}</code>
<div
ref={codeBlockRef}
className='sf-code-block-container'
>
<pre
onFocus={() => { console.log(123123123); setIsShowLanguageSelector(true); }}
{...attributes}>
<code
onFocus={() => { console.log(123123123); setIsShowLanguageSelector(true); }}

>{children}</code>
</pre>
<LanguageSelector lang={element.lang} />
{isShowLanguageSelector && <LanguageSelector lang={element.lang} />}
</div>
);
};
Expand All @@ -16,9 +52,11 @@ export default renderCodeBlock;

export const renderCodeLine = (props, editor) => {
const { element, attributes, children } = props;

const handleOnFocus = () => {
console.log('onFocus');
};
return (
<div data-id={element.id} {...attributes} className={'sf-code-line'}>
<div onFocus={handleOnFocus} data-id={element.id} {...attributes} className={'sf-code-line'}>
{children}
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/extension/plugins/code-block/render-elem/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
position: absolute;
top: 0;
right: 0;
z-index: 1;
/* z-index: 1; */
padding: 2px 5px;
width: max-content;
display: flex;
Expand Down
2 changes: 0 additions & 2 deletions src/extension/plugins/header/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ export const isMenuDisabled = (editor, readonly = false) => {
const parentNode = getParentNode(node, node.id);
type = getNodeType(parentNode);
}
if (type === ELementTypes.CODE_LINE) return true;
if (type === ELementTypes.CODE_BLOCK) return true;
if (type === ELementTypes.PARAGRAPH) return true;
if (type.startsWith(ELementTypes.HEADER)) return true;
return false;
Expand Down
1 change: 0 additions & 1 deletion src/extension/plugins/header/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,6 @@ const withHeader = (editor) => {

event.preventDefault();
if (isMenuDisabled(newEditor)) return true;
console.log('999', 999)
const currentHeaderType = getHeaderType(editor);
if (currentHeaderType === headerType) {
setHeaderType(newEditor, ELementTypes.PARAGRAPH);
Expand Down
2 changes: 2 additions & 0 deletions src/extension/plugins/image/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { Transforms } from 'slate';
import { getHeaderType } from '../header/helper';
import { IMAGE } from '../../constants/element-types';
import { generateEmptyElement } from '../../core';
import { isInCodeBlock } from '../code-block/helpers';

export const isMenuDisabled = (editor, readonly) => {
if (readonly) return true;
const isHeader = getHeaderType(editor);
if (isHeader) return true;
if (isInCodeBlock(editor)) return true;
return false;
};

Expand Down
6 changes: 6 additions & 0 deletions src/extension/plugins/text-style/helpers.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Editor } from 'slate';
import { isInCodeBlock } from '../code-block/helpers';
import { focusEditor } from '../../core';

export const isMenuDisabled = (editor, isReadonly) => {
if (isReadonly) return true;
const { selection } = editor;
if (!selection) return true;
if (isInCodeBlock(editor)) return true;
return false;
};

export const isMarkActive = (editor, mark) => {
const marks = Editor.marks(editor);


// If curMarks exists, you need to set this parameter manually. curMarks prevails
if (marks && Object.keys(marks).length > 0) {
return !!marks[mark];
Expand All @@ -24,10 +28,12 @@ export const isMarkActive = (editor, mark) => {

export const addMark = (editor, type) => {
Editor.addMark(editor, type, true);
focusEditor(editor);
};

export const removeMark = (editor, type) => {
Editor.removeMark(editor, type);
focusEditor(editor);
};

export const toggleTextStyle = (editor, type) => {
Expand Down

0 comments on commit 87412e2

Please sign in to comment.