Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,778 changes: 1,261 additions & 517 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"workspaces": [
"packages/configs/eslint-config",
"packages/configs/prettier-config",
"packages/editor",
"packages/react"
],
"scripts": {
Expand Down
1 change: 1 addition & 0 deletions packages/configs/eslint-config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ module.exports = {
['^.+\\.types$'],
['^(@testing-library|@storybook|@date-io|date-fns)'],
['^clsx', '^.+\\.classes$'],
['^slate'],
['^@mui/base'],
['^@mui/material/styles', '^@mui/system', '^@mui/material', '^@mui/utils'],
['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
Expand Down
30 changes: 30 additions & 0 deletions packages/editor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@elonkit/editor",
"version": "0.0.0",
"description": "",
"main": "src/index.ts",
"sideEffects": false,
"scripts": {},
"repository": {
"type": "git",
"url": "git+https://github.com/Elonsoft/elonkit-react.git"
},
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/Elonsoft/elonkit-react/issues"
},
"homepage": "https://github.com/Elonsoft/elonkit-react#readme",
"dependencies": {
"slate": "^0.103.0",
"slate-history": "^0.100.0",
"slate-react": "^0.102.0"
},
"peerDependencies": {
"@elonkit/react": "^0.10.1",
"@emotion/react": "^11.4.1",
"@emotion/styled": "^11.3.0",
"@mui/material": "^5.15.16",
"react": "^17.0.0 || ^18.0.0"
}
}
18 changes: 18 additions & 0 deletions packages/editor/src/EntityState/EntityState.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { createContext, useContext } from 'react';

export interface EntityStateContextValue {
entities: Record<string, any>;
set: (id: string, value: any) => void;
}

export const EntityStateContext = createContext<EntityStateContextValue | null>(null);

export const useEntityStateContext = () => {
const value = useContext(EntityStateContext);

if (value === null) {
throw new Error('No provider for EntityStateContext.');
}

return value;
};
25 changes: 25 additions & 0 deletions packages/editor/src/EntityState/EntityStateProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useMemo, useState } from 'react';

import { EntityStateProviderProps } from './EntityStateProvider.types';

import { EntityStateContext } from './EntityState.context';

export const EntityStateProvider = ({ children }: EntityStateProviderProps) => {
const [entities, setEntities] = useState<Record<string, any>>({});

const value = useMemo(() => {
return {
entities,
setEntities,
set: (id: string, value: any) => {
setEntities((prev) => ({ ...prev, [id]: value }));

// setTimeout(() => {
// onEditorChange(latestEditor.current.children, true);
// });
}
};
}, [entities]);

return <EntityStateContext.Provider value={value}>{children}</EntityStateContext.Provider>;
};
5 changes: 5 additions & 0 deletions packages/editor/src/EntityState/EntityStateProvider.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ReactNode } from 'react';

export interface EntityStateProviderProps {
children?: ReactNode;
}
3 changes: 3 additions & 0 deletions packages/editor/src/EntityState/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './EntityState.context';
export * from './EntityStateProvider';
export * from './EntityStateProvider.types';
2 changes: 2 additions & 0 deletions packages/editor/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './EntityState';
export * from './plugins';
99 changes: 99 additions & 0 deletions packages/editor/src/plugins/base/base.editor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { Editor, Element, Node, NodeEntry, Path, Point, Range, Span, Transforms } from 'slate';

import * as Registry from './base.registry';

const schema = (editor: Editor) => {
return Registry.get(editor);
};

export const BaseEditor = {
// BaseEditor schema availability

isBaseEnabled: (editor: Editor) => {
return Registry.has(editor);
},
getBaseSchema: (editor: Editor) => {
return Registry.has(editor) ? Registry.get(editor) : undefined;
},

// Schema proxies

isDefaultTextNode: (editor: Editor, node: Node) => {
return schema(editor).isDefaultTextNode(node);
},
getDefaultTextNodeType: (editor: Editor) => {
return schema(editor).getDefaultTextNodeType();
},
createDefaultTextNode: (editor: Editor, props?: Partial<Element>) => {
return schema(editor).createDefaultTextNode(props);
},

// Checks & Getters

getCursorPosition: (editor: Editor, at: Range | Point | Span | Path | null): Point | null => {
if (!at) {
return null;
}

if (Range.isRange(at)) {
return Range.isCollapsed(at) ? at.focus : null;
}

if (Span.isSpan(at)) {
return Path.equals(at[0], at[1]) ? BaseEditor.getCursorPosition(editor, at[0]) : null;
}

if (Path.isPath(at)) {
return Editor.point(editor, at, { edge: 'start' });
}

return at;
},
getCursorPositionInNode: (editor: Editor, cursorLocation: Point, nodePath: Path) => {
const nodeStartPoint = Editor.start(editor, nodePath);
const nodeEndPoint = Editor.end(editor, nodePath);
const isStart = Point.equals(cursorLocation, nodeStartPoint);
const isEnd = Point.equals(cursorLocation, nodeEndPoint);
return { isEnd, isStart };
},
getNextSibling: (editor: Editor, path: Path): NodeEntry | null => {
let nextSiblingPath: Path;

try {
nextSiblingPath = Path.next(path);
} catch (error) {
// Unable to calculate `Path.next`, which means there is no next sibling.
return null;
}

if (Node.has(editor, nextSiblingPath)) {
return [Node.get(editor, nextSiblingPath), nextSiblingPath];
}

return null;
},
getPrevSibling: (editor: Editor, path: Path): NodeEntry | null => {
let prevSiblingPath: Path;

try {
prevSiblingPath = Path.previous(path);
} catch (error) {
// Unable to calculate `Path.prev`, which means there is no next sibling.
return null;
}

if (Node.has(editor, prevSiblingPath)) {
return [Node.get(editor, prevSiblingPath), prevSiblingPath];
}

return null;
},

// Transformations

addNodeForEmptyEditor: (editor: Editor) => {
if (Editor.last(editor, [])[1].length === 0) {
Transforms.insertNodes(editor, schema(editor).createDefaultTextNode());
}
}
};
37 changes: 37 additions & 0 deletions packages/editor/src/plugins/base/base.handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { KeyboardEvent, KeyboardEventHandler } from 'react';

import { Editor, Element, Node, Transforms } from 'slate';

import { BaseEditor } from './base.editor';

export const onBaseKeyDown = (editor: Editor, func: KeyboardEventHandler<HTMLElement>) => {
return (event: KeyboardEvent<HTMLElement>) => {
if (event.key === 'Enter') {
if (event.shiftKey) {
event.preventDefault();
editor.insertText('\n');
return;
}

if (editor.selection) {
const descendant = Node.descendant(editor, editor.selection.anchor.path.slice(0, -1));

if (Element.isElement(descendant)) {
event.preventDefault();
const leaf = Node.descendant(editor, editor.selection.anchor.path);

if (leaf.text.length === editor.selection.anchor.offset) {
Transforms.insertNodes(editor, BaseEditor.createDefaultTextNode(editor));
} else {
Transforms.splitNodes(editor);
Transforms.setNodes(editor, { type: BaseEditor.getDefaultTextNodeType(editor) });
}

return;
}
}
}

func(event);
};
};
29 changes: 29 additions & 0 deletions packages/editor/src/plugins/base/base.registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { BaseSchema } from './base.types';

import { Editor } from 'slate';

const EDITOR_BASE_SCHEMA = new WeakMap<Editor, BaseSchema>();

export function register(editor: Editor, schema: BaseSchema): void {
EDITOR_BASE_SCHEMA.set(editor, schema);
}

export function unregister(editor: Editor): void {
EDITOR_BASE_SCHEMA.delete(editor);
}

export function has(editor: Editor) {
return EDITOR_BASE_SCHEMA.has(editor);
}

export function get(editor: Editor): BaseSchema {
const schema = EDITOR_BASE_SCHEMA.get(editor);

if (!schema) {
throw new Error(
'This editor instance does not have a BaseSchema associated. Make sure you initialize it with withBase() before using BaseEditor functionality.'
);
}

return schema;
}
11 changes: 11 additions & 0 deletions packages/editor/src/plugins/base/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { BaseSchema } from './base.types';

import { Editor } from 'slate';

import * as Registry from './base.registry';

export const withBase = (schema: BaseSchema) => (editor: Editor) => {
Registry.register(editor, schema);

return editor;
};
7 changes: 7 additions & 0 deletions packages/editor/src/plugins/base/base.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { Element, Node } from 'slate';

export interface BaseSchema {
isDefaultTextNode(node: Node): boolean;
getDefaultTextNodeType(): Element['type'];
createDefaultTextNode(props?: Partial<Element>): Element;
}
4 changes: 4 additions & 0 deletions packages/editor/src/plugins/base/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './base';
export * from './base.editor';
export * from './base.handlers';
export * from './base.types';
26 changes: 26 additions & 0 deletions packages/editor/src/plugins/ids/ids.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Editor, Element } from 'slate';

import { getNodeId } from './utils';

export const withNodeId = (editor: Editor) => {
const { apply } = editor;

editor.apply = (operation) => {
if (operation.type === 'insert_node') {
if (Element.isElement(operation.node) && !Editor.isInline(editor, operation.node)) {
(operation.node as any).id = getNodeId();
}

return apply(operation);
}

if (operation.type === 'split_node' && (operation.properties as any)?.id) {
(operation.properties as any).id = getNodeId();
return apply(operation);
}

return apply(operation);
};

return editor;
};
2 changes: 2 additions & 0 deletions packages/editor/src/plugins/ids/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ids';
export * from './utils';
3 changes: 3 additions & 0 deletions packages/editor/src/plugins/ids/utils/get-node-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let id = 1;

export const getNodeId = () => (id++).toString();
1 change: 1 addition & 0 deletions packages/editor/src/plugins/ids/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './get-node-id';
4 changes: 4 additions & 0 deletions packages/editor/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './base';
export * from './ids';
export * from './links';
export * from './lists';
4 changes: 4 additions & 0 deletions packages/editor/src/plugins/links/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './links';
export * from './links.editor';
export * from './links.types';
export * from './utils';
Loading