@springmicro/rte 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +18 -0
- package/README.md +15 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +63921 -0
- package/dist/index.umd.cjs +469 -0
- package/dist/style.css +1 -0
- package/index.html +13 -0
- package/package.json +54 -0
- package/src/App.css +42 -0
- package/src/App.tsx +10 -0
- package/src/contexts/color-context.tsx +53 -0
- package/src/hooks/useSimpleFormik.tsx +74 -0
- package/src/index.css +68 -0
- package/src/index.tsx +3 -0
- package/src/main.tsx +10 -0
- package/src/slate/base-editor.stories.tsx +16 -0
- package/src/slate/base-editor.tsx +116 -0
- package/src/slate/blog-rte.stories.tsx +16 -0
- package/src/slate/blog-rte.tsx +126 -0
- package/src/slate/common/button.tsx +35 -0
- package/src/slate/common/element.tsx +13 -0
- package/src/slate/common/icon.jsx +97 -0
- package/src/slate/components/code-to-text/CodeToTextButton.jsx +19 -0
- package/src/slate/components/code-to-text/HtmlCode.jsx +64 -0
- package/src/slate/components/code-to-text/HtmlContextMenu.jsx +39 -0
- package/src/slate/components/code-to-text/index.jsx +111 -0
- package/src/slate/components/color-picker/color-cursor.stories.tsx +16 -0
- package/src/slate/components/color-picker/color-cursor.tsx +34 -0
- package/src/slate/components/color-picker/color-formats-view.stories.tsx +25 -0
- package/src/slate/components/color-picker/color-formats-view.tsx +115 -0
- package/src/slate/components/color-picker/color-gradient.stories.tsx +48 -0
- package/src/slate/components/color-picker/color-gradient.tsx +128 -0
- package/src/slate/components/color-picker/color-hue.stories.tsx +41 -0
- package/src/slate/components/color-picker/color-hue.tsx +110 -0
- package/src/slate/components/color-picker/color-picker.stories.tsx +25 -0
- package/src/slate/components/color-picker/color-picker.tsx +41 -0
- package/src/slate/components/color-picker/color-popover.stories.tsx +26 -0
- package/src/slate/components/color-picker/color-popover.tsx +58 -0
- package/src/slate/components/color-picker/color-swatch.stories.tsx +16 -0
- package/src/slate/components/color-picker/color-swatch.tsx +76 -0
- package/src/slate/components/color-picker/default-colors.ts +38 -0
- package/src/slate/components/color-picker/slate-color-button.tsx +128 -0
- package/src/slate/components/embed/Embed.jsx +96 -0
- package/src/slate/components/embed/Image.jsx +45 -0
- package/src/slate/components/embed/Video.jsx +65 -0
- package/src/slate/components/equation/Equation.jsx +19 -0
- package/src/slate/components/equation/EquationButton.jsx +68 -0
- package/src/slate/components/id/Id.jsx +57 -0
- package/src/slate/components/image/image.stories.tsx +17 -0
- package/src/slate/components/image/image.tsx +62 -0
- package/src/slate/components/image/insert-image-button.stories.tsx +83 -0
- package/src/slate/components/image/insert-image-button.tsx +132 -0
- package/src/slate/components/image/types.ts +9 -0
- package/src/slate/components/link/Link.jsx +56 -0
- package/src/slate/components/link/LinkButton.tsx +106 -0
- package/src/slate/components/table/Table.jsx +11 -0
- package/src/slate/components/table/TableSelector.jsx +97 -0
- package/src/slate/components/table-context-menu/TableContextMenu.tsx +106 -0
- package/src/slate/custom-types.d.ts +152 -0
- package/src/slate/editor.module.css +226 -0
- package/src/slate/paper-rte.stories.tsx +16 -0
- package/src/slate/paper-rte.tsx +47 -0
- package/src/slate/plugins/withEmbeds.js +33 -0
- package/src/slate/plugins/withEquation.js +8 -0
- package/src/slate/plugins/withImages.ts +69 -0
- package/src/slate/plugins/withLinks.js +9 -0
- package/src/slate/plugins/withTable.js +74 -0
- package/src/slate/serializers/generic.ts +44 -0
- package/src/slate/serializers/types.ts +20 -0
- package/src/slate/toolbar/index.tsx +186 -0
- package/src/slate/toolbar/paper-toolbar.tsx +494 -0
- package/src/slate/toolbar/shortcuts.tsx +77 -0
- package/src/slate/toolbar/toolbar-groups.ts +213 -0
- package/src/slate/types/index.ts +0 -0
- package/src/slate/utils/customHooks/useContextMenu.js +42 -0
- package/src/slate/utils/customHooks/useFormat.js +26 -0
- package/src/slate/utils/customHooks/usePopup.jsx +26 -0
- package/src/slate/utils/customHooks/useResize.js +27 -0
- package/src/slate/utils/embed.js +18 -0
- package/src/slate/utils/equation.js +22 -0
- package/src/slate/utils/index.jsx +267 -0
- package/src/slate/utils/link.js +44 -0
- package/src/slate/utils/p.js +4 -0
- package/src/slate/utils/table.js +131 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +32 -0
- package/tsconfig.node.json +10 -0
- package/vite.config.ts +41 -0
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
._popupWrapper_g4nbi_3{display:inline;position:relative}._popup_g4nbi_3{position:absolute;left:0;background-color:#fff;padding:6px 10px;border:1px solid #e6e8f0;height:fit-content;z-index:1000}._editorWrapper_g4nbi_29{border-radius:8px;min-height:100px;min-width:100%;width:fit-content;height:fit-content;margin-top:6px;border:1px solid #e6e8f0;padding:0 10px}._editorWrapper_g4nbi_29>div:focus-visible{border:none;outline:none}._slate_g4nbi_59 blockquote{border-left:2px solid #ddd;margin-left:0;margin-right:0;padding-left:10px;color:#aaa;font-style:italic}._slate_g4nbi_59 table,._slate_g4nbi_59 th,._slate_g4nbi_59 td{border:1px solid black}._slate_g4nbi_59 table{border-collapse:collapse}._slate_g4nbi_59 button{background-color:#fff;border:none;opacity:.5}._slate_g4nbi_59 ._btnActive_g4nbi_101{opacity:1}._slate_g4nbi_59 table{width:100%}._slate_g4nbi_59 td{height:50px;padding:0 5px}._slate_g4nbi_59 ul,._slate_g4nbi_59 ol{padding-left:1rem}._slate_g4nbi_59 button{cursor:pointer}._slate_g4nbi_59 code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}._slate_g4nbi_59 [data-slate-node=element]:not(li){margin:10px 0}._slate_g4nbi_59 ._code-wrapper_g4nbi_157{position:fixed;top:0;left:0;width:100vw;height:100vh;background:#000000e6;z-index:2;display:flex;justify-content:center;align-items:center}._slate_g4nbi_59 ._codeToTextWrapper_g4nbi_181{width:80%;height:80%;grid-template-columns:45% 10% 45%}._slate_g4nbi_59 ._codeToText_g4nbi_181{width:100%;height:90%;display:grid;grid-template-columns:45% 10% 45%}._slate_g4nbi_59 ._codeToText_g4nbi_181 textarea,._textOutput_g4nbi_205{border-radius:15px;padding:10px}._slate_g4nbi_59 ._codeToText_g4nbi_181 textarea{resize:none}._slate_g4nbi_59 ._codeToText_g4nbi_181 textarea:focus{outline:none}._slate_g4nbi_59 ._textOutput_g4nbi_205{background:#fff;overflow:scroll}._slate_g4nbi_59 ._codeToTextWrapper_g4nbi_181 button{margin:3% 1%;padding:10px 37px;cursor:pointer;border-radius:5px;opacity:1;font-weight:bolder}._slate_g4nbi_59 ._done_g4nbi_251{background:#3fc79a;color:#fff}._slate_g4nbi_59 ._clear_g4nbi_259{background:#fff;color:#a9a4a4;border:1px solid #e6e8f0}._slate_g4nbi_59 ._embed_g4nbi_273{width:fit-content;position:relative;margin-right:20px}._slate_g4nbi_59 ._embed_g4nbi_273 img,._slate_g4nbi_59 ._embed_g4nbi_273 iframe{width:100%;height:100%}._slate_g4nbi_59 ._embed_g4nbi_273 button{position:absolute;bottom:-6px;right:0}._slate_g4nbi_59 ._equation-inline_g4nbi_307{display:inline;position:relative}._slate_g4nbi_59 ._table-option_g4nbi_319{display:flex;margin:5px 2px;gap:5px}._slate_g4nbi_59 ._table-option_g4nbi_319{white-space:nowrap}._slate_g4nbi_59 ._table-input_g4nbi_335{display:grid;grid-template-columns:auto auto auto auto auto auto;gap:3px}._slate_g4nbi_59 ._table-unit_g4nbi_345{width:15px;height:15px;border:1px solid #e6e8f0}._slate_g4nbi_59 ._contextMenu_g4nbi_359{width:fit-content;height:fit-content;position:fixed;background:#fff;border:1px solid #e6e8f0;border-radius:10px;padding:.5%;display:flex;gap:15px;flex-direction:column;cursor:pointer}._slate_g4nbi_59 ._menuOption_g4nbi_385{display:flex;gap:15px}._slate_g4nbi_59 ._toolbar_g4nbi_397{border-radius:10px;background:#fff;box-shadow:-8px 8px 13px #ededed,8px -8px 13px #fff;margin:5% 0;display:flex;flex-direction:row;flex-wrap:wrap;align-items:center;padding:15px 10px;row-gap:15px;position:-webkit-sticky;position:sticky;top:0;z-index:2}._slate_g4nbi_59 ._toolbar-grp_g4nbi_429>*{margin-right:10px;cursor:pointer}._slate_g4nbi_59 ._toolbar-grp_g4nbi_429{margin:0 10px}._slate_g4nbi_59 select{height:30px;border:none;width:6.9rem}
|
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>Vite + React + TS</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@springmicro/rte",
|
|
3
|
+
"version": "0.1.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": false,
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public",
|
|
8
|
+
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
},
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"types": "dist/index.d.ts",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"dev": "vite",
|
|
14
|
+
"build": "rm -rf dist && vite build",
|
|
15
|
+
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
|
16
|
+
"preview": "vite preview"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"@mui/material": "^5.0.0",
|
|
20
|
+
"@springmicro/utils": "^0.1.0",
|
|
21
|
+
"react": "^18.0.0",
|
|
22
|
+
"react-dom": "^18.0.0",
|
|
23
|
+
"react-icons": "^5.0.0"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@springmicro/utils": "^0.1.3",
|
|
27
|
+
"@uiw/react-textarea-code-editor": "^3.0.2",
|
|
28
|
+
"domhandler": "^5.0.3",
|
|
29
|
+
"escape-html": "^1.0.3",
|
|
30
|
+
"html-to-rtf": "^2.1.0",
|
|
31
|
+
"interweave": "^13.1.0",
|
|
32
|
+
"is-url": "^1.2.4",
|
|
33
|
+
"react-katex": "^3.0.1",
|
|
34
|
+
"slate": "^0.103.0",
|
|
35
|
+
"slate-history": "^0.100.0",
|
|
36
|
+
"slate-react": "^0.104.0",
|
|
37
|
+
"slate-serializers": "^2.1.6"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^20.12.8",
|
|
41
|
+
"@types/react": "^18.2.66",
|
|
42
|
+
"@types/react-dom": "^18.2.22",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
|
44
|
+
"@typescript-eslint/parser": "^7.2.0",
|
|
45
|
+
"@vitejs/plugin-react": "^4.2.1",
|
|
46
|
+
"eslint": "^8.57.0",
|
|
47
|
+
"eslint-plugin-react-hooks": "^4.6.0",
|
|
48
|
+
"eslint-plugin-react-refresh": "^0.4.6",
|
|
49
|
+
"typescript": "^5.2.2",
|
|
50
|
+
"vite": "^5.2.0",
|
|
51
|
+
"vite-plugin-dts": "^3.9.0"
|
|
52
|
+
},
|
|
53
|
+
"gitHead": "d09e3906681dadcf2680775e01a1aabc35b82812"
|
|
54
|
+
}
|
package/src/App.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#root {
|
|
2
|
+
max-width: 1280px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo {
|
|
9
|
+
height: 6em;
|
|
10
|
+
padding: 1.5em;
|
|
11
|
+
will-change: filter;
|
|
12
|
+
transition: filter 300ms;
|
|
13
|
+
}
|
|
14
|
+
.logo:hover {
|
|
15
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
16
|
+
}
|
|
17
|
+
.logo.react:hover {
|
|
18
|
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes logo-spin {
|
|
22
|
+
from {
|
|
23
|
+
transform: rotate(0deg);
|
|
24
|
+
}
|
|
25
|
+
to {
|
|
26
|
+
transform: rotate(360deg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
31
|
+
a:nth-of-type(2) .logo {
|
|
32
|
+
animation: logo-spin infinite 20s linear;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.card {
|
|
37
|
+
padding: 2em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.read-the-docs {
|
|
41
|
+
color: #888;
|
|
42
|
+
}
|
package/src/App.tsx
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
// FontMetadata,
|
|
5
|
+
// PagesType,
|
|
6
|
+
// LayoutsType,
|
|
7
|
+
ColorFormats,
|
|
8
|
+
} from "@springmicro/utils/types";
|
|
9
|
+
import {
|
|
10
|
+
colorStringToColorFormats,
|
|
11
|
+
// defaultSelectedColors,
|
|
12
|
+
// defaultSelectedFonts,
|
|
13
|
+
} from "@springmicro/utils/editor";
|
|
14
|
+
|
|
15
|
+
export const ColorContext = React.createContext({
|
|
16
|
+
colorFormats: colorStringToColorFormats("#ffffff") as ColorFormats,
|
|
17
|
+
setColorFormats: ((colorFormats: ColorFormats) => {}) as React.Dispatch<
|
|
18
|
+
React.SetStateAction<ColorFormats>
|
|
19
|
+
>,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export function ColorProvider({
|
|
23
|
+
initialColor,
|
|
24
|
+
colorChangeCallback,
|
|
25
|
+
children,
|
|
26
|
+
}: React.PropsWithChildren<{
|
|
27
|
+
initialColor: string;
|
|
28
|
+
colorChangeCallback?: (color: string) => void;
|
|
29
|
+
}>) {
|
|
30
|
+
const [colorFormats, setColorFormats] = React.useState(
|
|
31
|
+
colorStringToColorFormats(initialColor)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
setColorFormats(colorStringToColorFormats(initialColor));
|
|
36
|
+
}, [initialColor]);
|
|
37
|
+
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
// the callback updates the selectedColors for the site
|
|
40
|
+
if (colorChangeCallback && initialColor !== "#" + colorFormats.hex) {
|
|
41
|
+
colorChangeCallback("#" + colorFormats.hex);
|
|
42
|
+
}
|
|
43
|
+
}, [colorFormats]);
|
|
44
|
+
|
|
45
|
+
const value = {
|
|
46
|
+
colorFormats,
|
|
47
|
+
setColorFormats,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<ColorContext.Provider value={value}>{children}</ColorContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// drop-in replacement for formik and Yup
|
|
2
|
+
// in the minimum way we use it in color-formats-view.tsx
|
|
3
|
+
import { useState, useCallback, ChangeEvent, FocusEvent } from "react";
|
|
4
|
+
|
|
5
|
+
interface FormikValues {
|
|
6
|
+
[key: string]: any;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface FormikValidators {
|
|
10
|
+
[key: string]: {
|
|
11
|
+
fn: (value: any) => boolean;
|
|
12
|
+
msg: string;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface UseSimpleFormik<T> {
|
|
17
|
+
errors: Record<string, undefined | string>;
|
|
18
|
+
values: T;
|
|
19
|
+
setValues: React.Dispatch<React.SetStateAction<T>>;
|
|
20
|
+
handleChange: (
|
|
21
|
+
event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
22
|
+
) => void;
|
|
23
|
+
handleBlur: (
|
|
24
|
+
event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>
|
|
25
|
+
) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const useSimpleFormik = <T extends FormikValues>(
|
|
29
|
+
initialValues: T,
|
|
30
|
+
validators: FormikValidators
|
|
31
|
+
): UseSimpleFormik<T> => {
|
|
32
|
+
const [values, setValues] = useState<T>(initialValues);
|
|
33
|
+
const [errors, setErrors] = useState(
|
|
34
|
+
Object.fromEntries(
|
|
35
|
+
Object.entries(initialValues).map(([k, _]) => [k, undefined])
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const handleChange = useCallback(
|
|
40
|
+
(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
41
|
+
const { name, value } = event.target;
|
|
42
|
+
if (validators[name]?.fn(value) === false) {
|
|
43
|
+
setErrors((prevValues) => ({
|
|
44
|
+
...prevValues,
|
|
45
|
+
[name]: validators[name].msg,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
setValues((prevValues) => ({
|
|
49
|
+
...prevValues,
|
|
50
|
+
[name]: value,
|
|
51
|
+
}));
|
|
52
|
+
},
|
|
53
|
+
[]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const handleBlur = useCallback(
|
|
57
|
+
(event: FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
|
58
|
+
const { name } = event.target;
|
|
59
|
+
// Here you can handle the blur event, e.g., setting touched fields if needed
|
|
60
|
+
console.log(`${name} field blurred`);
|
|
61
|
+
},
|
|
62
|
+
[]
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
errors,
|
|
67
|
+
values,
|
|
68
|
+
setValues,
|
|
69
|
+
handleChange,
|
|
70
|
+
handleBlur,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
export default useSimpleFormik;
|
package/src/index.css
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
font-weight: 400;
|
|
5
|
+
|
|
6
|
+
color-scheme: light dark;
|
|
7
|
+
color: rgba(255, 255, 255, 0.87);
|
|
8
|
+
background-color: #242424;
|
|
9
|
+
|
|
10
|
+
font-synthesis: none;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
a {
|
|
17
|
+
font-weight: 500;
|
|
18
|
+
color: #646cff;
|
|
19
|
+
text-decoration: inherit;
|
|
20
|
+
}
|
|
21
|
+
a:hover {
|
|
22
|
+
color: #535bf2;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
display: flex;
|
|
28
|
+
place-items: center;
|
|
29
|
+
min-width: 320px;
|
|
30
|
+
min-height: 100vh;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
h1 {
|
|
34
|
+
font-size: 3.2em;
|
|
35
|
+
line-height: 1.1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
button {
|
|
39
|
+
border-radius: 8px;
|
|
40
|
+
border: 1px solid transparent;
|
|
41
|
+
padding: 0.6em 1.2em;
|
|
42
|
+
font-size: 1em;
|
|
43
|
+
font-weight: 500;
|
|
44
|
+
font-family: inherit;
|
|
45
|
+
background-color: #1a1a1a;
|
|
46
|
+
cursor: pointer;
|
|
47
|
+
transition: border-color 0.25s;
|
|
48
|
+
}
|
|
49
|
+
button:hover {
|
|
50
|
+
border-color: #646cff;
|
|
51
|
+
}
|
|
52
|
+
button:focus,
|
|
53
|
+
button:focus-visible {
|
|
54
|
+
outline: 4px auto -webkit-focus-ring-color;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
@media (prefers-color-scheme: light) {
|
|
58
|
+
:root {
|
|
59
|
+
color: #213547;
|
|
60
|
+
background-color: #ffffff;
|
|
61
|
+
}
|
|
62
|
+
a:hover {
|
|
63
|
+
color: #747bff;
|
|
64
|
+
}
|
|
65
|
+
button {
|
|
66
|
+
background-color: #f9f9f9;
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.tsx
ADDED
package/src/main.tsx
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SlateEditor, SlateEditorProps } from "./base-editor";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { StoryFn, Meta } from "@storybook/react";
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
/* 👇 The title prop is optional.
|
|
7
|
+
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
|
8
|
+
* to learn how to generate automatic titles
|
|
9
|
+
*/
|
|
10
|
+
title: "Slate/Slate Editor Base",
|
|
11
|
+
component: SlateEditor,
|
|
12
|
+
} as Meta<typeof SlateEditor>;
|
|
13
|
+
|
|
14
|
+
export const Primary = {
|
|
15
|
+
args: { readOnly: false } as SlateEditorProps,
|
|
16
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Descendant, createEditor } from "slate";
|
|
3
|
+
import { withHistory } from "slate-history";
|
|
4
|
+
import {
|
|
5
|
+
Slate,
|
|
6
|
+
Editable,
|
|
7
|
+
withReact,
|
|
8
|
+
RenderLeafProps,
|
|
9
|
+
RenderElementProps,
|
|
10
|
+
} from "slate-react";
|
|
11
|
+
import Toolbar from "./toolbar";
|
|
12
|
+
import { getMarked, getBlock } from "./utils";
|
|
13
|
+
import withLinks from "./plugins/withLinks.js";
|
|
14
|
+
import withTables from "./plugins/withTable.js";
|
|
15
|
+
import withEmbeds from "./plugins/withEmbeds.js";
|
|
16
|
+
import withEquation from "./plugins/withEquation.js";
|
|
17
|
+
import styles from "./editor.module.css";
|
|
18
|
+
import CodeToText from "./components/code-to-text";
|
|
19
|
+
import { issuesToolbarGroups } from "./toolbar/toolbar-groups";
|
|
20
|
+
import { Element } from "./common/element";
|
|
21
|
+
import { ScopedCssBaseline } from "@mui/material";
|
|
22
|
+
// import { serialize } from "./utils/serializer"
|
|
23
|
+
|
|
24
|
+
export type SlateEditorProps = {
|
|
25
|
+
value?: Descendant[];
|
|
26
|
+
setValue?: React.Dispatch<React.SetStateAction<Descendant[]>>;
|
|
27
|
+
readOnly: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => {
|
|
31
|
+
children = getMarked(leaf, children);
|
|
32
|
+
return <span {...attributes}>{children}</span>;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export function SlateEditor(props: SlateEditorProps) {
|
|
36
|
+
const { readOnly } = props;
|
|
37
|
+
const initialValue = props.value || [
|
|
38
|
+
{
|
|
39
|
+
type: "p",
|
|
40
|
+
children: [{ text: "" }],
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const [value, setValue] = React.useState(initialValue);
|
|
45
|
+
const [htmlAction, setHtmlAction] = React.useState({
|
|
46
|
+
showInput: false,
|
|
47
|
+
html: "",
|
|
48
|
+
action: "",
|
|
49
|
+
location: "",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const editor = React.useMemo(
|
|
53
|
+
() =>
|
|
54
|
+
withEquation(
|
|
55
|
+
withHistory(
|
|
56
|
+
withEmbeds(withTables(withLinks(withReact(createEditor()))))
|
|
57
|
+
)
|
|
58
|
+
),
|
|
59
|
+
[]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const renderElement = React.useCallback(
|
|
63
|
+
(props: RenderElementProps) => <Element {...props} />,
|
|
64
|
+
[]
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const renderLeaf = React.useCallback((props: RenderLeafProps) => {
|
|
68
|
+
return <Leaf {...props} />;
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const handleEditorChange = (newValue: Descendant[]) => {
|
|
72
|
+
if (props.setValue) {
|
|
73
|
+
props.setValue(newValue);
|
|
74
|
+
} else {
|
|
75
|
+
setValue(newValue);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleCodeToText = (partialState: any) => {
|
|
80
|
+
setHtmlAction((prev) => ({
|
|
81
|
+
...prev,
|
|
82
|
+
...partialState,
|
|
83
|
+
}));
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className={styles.slate}>
|
|
88
|
+
<ScopedCssBaseline>
|
|
89
|
+
<Slate
|
|
90
|
+
editor={editor}
|
|
91
|
+
initialValue={props.value !== undefined ? props.value : value}
|
|
92
|
+
onChange={handleEditorChange}
|
|
93
|
+
>
|
|
94
|
+
{!readOnly && (
|
|
95
|
+
<Toolbar
|
|
96
|
+
handleCodeToText={handleCodeToText}
|
|
97
|
+
toolbarGroups={issuesToolbarGroups}
|
|
98
|
+
/>
|
|
99
|
+
)}
|
|
100
|
+
<div className={readOnly ? "" : styles.editorWrapper}>
|
|
101
|
+
<Editable
|
|
102
|
+
placeholder="Write something"
|
|
103
|
+
renderElement={renderElement}
|
|
104
|
+
renderLeaf={renderLeaf}
|
|
105
|
+
readOnly={props.readOnly}
|
|
106
|
+
spellCheck={true}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
{htmlAction.showInput && (
|
|
110
|
+
<CodeToText {...htmlAction} handleCodeToText={handleCodeToText} />
|
|
111
|
+
)}
|
|
112
|
+
</Slate>
|
|
113
|
+
</ScopedCssBaseline>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BlogEditor, BlogEditorProps } from "./blog-rte"
|
|
2
|
+
import React from "react"
|
|
3
|
+
import { StoryFn, Meta } from "@storybook/react"
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
/* 👇 The title prop is optional.
|
|
7
|
+
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
|
|
8
|
+
* to learn how to generate automatic titles
|
|
9
|
+
*/
|
|
10
|
+
title: "Slate/Blog Editor",
|
|
11
|
+
component: BlogEditor,
|
|
12
|
+
} as Meta<typeof BlogEditor>
|
|
13
|
+
|
|
14
|
+
export const Primary = {
|
|
15
|
+
args: { readOnly: false } as BlogEditorProps,
|
|
16
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Box, ScopedCssBaseline } from "@mui/material";
|
|
3
|
+
import { BaseEditor, Descendant, createEditor } from "slate";
|
|
4
|
+
import { withHistory } from "slate-history";
|
|
5
|
+
import {
|
|
6
|
+
Slate,
|
|
7
|
+
Editable,
|
|
8
|
+
withReact,
|
|
9
|
+
RenderLeafProps,
|
|
10
|
+
RenderElementProps,
|
|
11
|
+
ReactEditor,
|
|
12
|
+
} from "slate-react";
|
|
13
|
+
import Toolbar from "./toolbar";
|
|
14
|
+
import { getMarked, getBlock } from "./utils";
|
|
15
|
+
import withLinks from "./plugins/withLinks.js";
|
|
16
|
+
import withTables from "./plugins/withTable.js";
|
|
17
|
+
import withEmbeds from "./plugins/withEmbeds.js";
|
|
18
|
+
import withEquation from "./plugins/withEquation.js";
|
|
19
|
+
import withImages from "./plugins/withImages";
|
|
20
|
+
import styles from "./editor.module.css";
|
|
21
|
+
import CodeToText from "./components/code-to-text";
|
|
22
|
+
import { blogToolbarGroups } from "./toolbar/toolbar-groups";
|
|
23
|
+
import { Element } from "./common/element";
|
|
24
|
+
|
|
25
|
+
export type BlogEditorProps = {
|
|
26
|
+
value?: Descendant[];
|
|
27
|
+
setValue?: React.Dispatch<React.SetStateAction<Descendant[]>>;
|
|
28
|
+
readOnly: boolean;
|
|
29
|
+
editor?: BaseEditor & ReactEditor;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const Leaf = ({ attributes, children, leaf }: RenderLeafProps) => {
|
|
33
|
+
children = getMarked(leaf, children);
|
|
34
|
+
return <span {...attributes}>{children}</span>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function BlogEditor(props: BlogEditorProps) {
|
|
38
|
+
const { readOnly } = props;
|
|
39
|
+
const initialValue = props.value || [
|
|
40
|
+
{
|
|
41
|
+
type: "p",
|
|
42
|
+
children: [{ text: "" }],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const [value, setValue] = React.useState(initialValue);
|
|
47
|
+
const [htmlAction, setHtmlAction] = React.useState({
|
|
48
|
+
showInput: false,
|
|
49
|
+
html: "",
|
|
50
|
+
action: "",
|
|
51
|
+
location: "",
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const editor =
|
|
55
|
+
props.editor ||
|
|
56
|
+
React.useMemo(
|
|
57
|
+
() =>
|
|
58
|
+
withImages(
|
|
59
|
+
withEquation(
|
|
60
|
+
withHistory(
|
|
61
|
+
withEmbeds(withTables(withLinks(withReact(createEditor()))))
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
),
|
|
65
|
+
[]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const renderElement = React.useCallback(
|
|
69
|
+
(props: RenderElementProps) => <Element {...props} />,
|
|
70
|
+
[]
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
const renderLeaf = React.useCallback((props: RenderLeafProps) => {
|
|
74
|
+
return <Leaf {...props} />;
|
|
75
|
+
}, []);
|
|
76
|
+
|
|
77
|
+
const handleEditorChange = (newValue: Descendant[]) => {
|
|
78
|
+
// console.log(newValue)
|
|
79
|
+
if (props.setValue) {
|
|
80
|
+
props.setValue(newValue);
|
|
81
|
+
} else {
|
|
82
|
+
setValue(newValue);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const handleCodeToText = (partialState: any) => {
|
|
87
|
+
setHtmlAction((prev) => ({
|
|
88
|
+
...prev,
|
|
89
|
+
...partialState,
|
|
90
|
+
}));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div className={styles.slate}>
|
|
95
|
+
<ScopedCssBaseline>
|
|
96
|
+
<Slate
|
|
97
|
+
editor={editor}
|
|
98
|
+
initialValue={props.value ? props.value : value}
|
|
99
|
+
onChange={handleEditorChange}
|
|
100
|
+
>
|
|
101
|
+
{!readOnly && (
|
|
102
|
+
<Toolbar
|
|
103
|
+
handleCodeToText={handleCodeToText}
|
|
104
|
+
toolbarGroups={blogToolbarGroups}
|
|
105
|
+
/>
|
|
106
|
+
)}
|
|
107
|
+
<Box
|
|
108
|
+
className={readOnly ? "" : styles.editorWrapper}
|
|
109
|
+
sx={{ minHeight: 400 }}
|
|
110
|
+
>
|
|
111
|
+
<Editable
|
|
112
|
+
placeholder="Write something"
|
|
113
|
+
renderElement={renderElement}
|
|
114
|
+
renderLeaf={renderLeaf}
|
|
115
|
+
readOnly={props.readOnly}
|
|
116
|
+
spellCheck={true}
|
|
117
|
+
/>
|
|
118
|
+
</Box>
|
|
119
|
+
{htmlAction.showInput && (
|
|
120
|
+
<CodeToText {...htmlAction} handleCodeToText={handleCodeToText} />
|
|
121
|
+
)}
|
|
122
|
+
</Slate>
|
|
123
|
+
</ScopedCssBaseline>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Box, useTheme } from "@mui/material";
|
|
3
|
+
import { snakeToTitle } from "@springmicro/utils/string";
|
|
4
|
+
import { getShortcutKey } from "../toolbar/shortcuts";
|
|
5
|
+
|
|
6
|
+
const Button = (props: React.PropsWithChildren<any>) => {
|
|
7
|
+
const theme = useTheme();
|
|
8
|
+
const { children, format, active, ...rest } = props;
|
|
9
|
+
const [key, combo] = getShortcutKey(format, true);
|
|
10
|
+
const title =
|
|
11
|
+
key && combo
|
|
12
|
+
? `${snakeToTitle(format)} (${combo}+${key.toUpperCase()})`
|
|
13
|
+
: snakeToTitle(format);
|
|
14
|
+
return (
|
|
15
|
+
<Box
|
|
16
|
+
component="button"
|
|
17
|
+
title={title}
|
|
18
|
+
{...rest}
|
|
19
|
+
sx={{
|
|
20
|
+
width: "30px",
|
|
21
|
+
height: "20px",
|
|
22
|
+
margin: "0 2px",
|
|
23
|
+
paddingBottom: "24px",
|
|
24
|
+
borderBottom: active
|
|
25
|
+
? `2px solid ${theme.palette.primary.main} !important`
|
|
26
|
+
: "",
|
|
27
|
+
...rest.sx,
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</Box>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export default Button;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import Image from "../components/image/image";
|
|
2
|
+
import { getBlock } from "../utils";
|
|
3
|
+
|
|
4
|
+
export const Element = (props: any) => {
|
|
5
|
+
const { attributes, children, element } = props;
|
|
6
|
+
|
|
7
|
+
switch (element.type) {
|
|
8
|
+
case "image":
|
|
9
|
+
return <Image {...props} />;
|
|
10
|
+
default:
|
|
11
|
+
return getBlock(props);
|
|
12
|
+
}
|
|
13
|
+
};
|