@deep-foundation/deepcase
Version:
[](https://gitpod.io/#https://github.com/deep-foundation/deepcase) [](https://discord.gg/deep-
473 lines • 22.2 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { Box, Heading, useColorMode } from '@chakra-ui/react';
import { useDeep } from '@deep-foundation/deeplinks/imports/client';
import { useDebounceCallback } from '@react-hook/debounce';
import { motion, useAnimation } from 'framer-motion';
import isHotkey from 'is-hotkey';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { CiPenpot, CiTextAlignCenter, CiTextAlignJustify, CiTextAlignLeft, CiTextAlignRight, } from 'react-icons/ci';
import { FiBold, FiCode, FiItalic, FiUnderline, } from 'react-icons/fi';
import { TbList, TbListNumbers, TbNumber1, TbNumber2, TbQuote, TbBrandVscode, } from 'react-icons/tb';
import { Editor, Element as SlateElement, Transforms, createEditor, Point, Range } from 'slate';
import { Editable, Slate, useFocused, useSlate, withReact } from 'slate-react';
import { htmlToSlate, slateToHtml } from 'slate-serializers';
import { ClientHandlerSlateProxy } from './client-handler-slate-proxy';
import dynamic from 'next/dynamic';
const MonacoEditor = dynamic(() => import('@monaco-editor/react').then(m => m.default), { ssr: false });
const HOTKEYS = {
'mod+b': 'bold',
'mod+i': 'italic',
'mod+u': 'underline',
'mod+`': 'code',
};
const SHORTCUTS = {
'*': 'list-item',
'-': 'list-item',
'+': 'list-item',
'>': 'block-quote',
'#': 'heading-one',
'##': 'heading-two',
'###': 'heading-three',
'####': 'heading-four',
'#####': 'heading-five',
'######': 'heading-six',
'```': 'backtick-code-block',
};
;
;
const LIST_TYPES = ['numbered-list', 'bulleted-list'];
const TEXT_ALIGN_TYPES = ['left', 'center', 'right', 'justify'];
const withShortcuts = editor => {
const { deleteBackward, insertText } = editor;
editor.insertText = text => {
const { selection } = editor;
if (text.endsWith(' ') && selection && Range.isCollapsed(selection)) {
const { anchor } = selection;
const block = Editor.above(editor, {
match: n => SlateElement.isElement(n) && Editor.isBlock(editor, n),
});
const path = block ? block[1] : [];
const start = Editor.start(editor, path);
const range = { anchor, focus: start };
const beforeText = Editor.string(editor, range) + text.slice(0, -1);
const type = SHORTCUTS[beforeText];
if (type) {
Transforms.select(editor, range);
if (!Range.isCollapsed(range)) {
Transforms.delete(editor);
}
const newProperties = {
type,
};
Transforms.setNodes(editor, newProperties, {
match: n => SlateElement.isElement(n) && Editor.isBlock(editor, n),
});
if (type === 'list-item') {
const list = {
type: 'bulleted-list',
children: [],
};
Transforms.wrapNodes(editor, list, {
match: n => !Editor.isEditor(n) &&
SlateElement.isElement(n) &&
n.type === 'list-item',
});
}
else if (type === 'backtick-code-block') {
return;
}
}
insertText(text);
}
editor.deleteBackward = (...args) => {
const { selection } = editor;
if (selection && Range.isCollapsed(selection)) {
const match = Editor.above(editor, {
match: n => SlateElement.isElement(n) && Editor.isBlock(editor, n),
});
if (match) {
const [block, path] = match;
const start = Editor.start(editor, path);
if (!Editor.isEditor(block) &&
SlateElement.isElement(block) &&
block.type !== 'paragraph' &&
Point.equals(selection.anchor, start)) {
const newProperties = {
type: 'paragraph',
};
Transforms.setNodes(editor, newProperties);
if (block.type === 'list-item') {
Transforms.unwrapNodes(editor, {
match: n => !Editor.isEditor(n) &&
SlateElement.isElement(n) &&
n.type === 'bulleted-list',
split: true,
});
}
return;
}
}
deleteBackward(...args);
}
};
return editor;
};
};
const topmenuVariants = {
initial: {
scale: 1,
opacity: 1,
},
hide: {
scale: 0,
opacity: 0,
position: 'absolute',
top: 0,
},
show: {
opacity: 1,
scale: 1,
}
};
const boxVariants = {
initial: {
height: '100%',
},
hide: {
height: '50%',
transition: { duration: 0.3, type: 'spring' }
},
show: {
height: '100%',
transition: { duration: 0.3, type: 'spring' }
}
};
const Leaf = ({ attributes, children, leaf }) => {
const { colorMode } = useColorMode();
const editorRef = useRef(null);
const boxRef = useRef(null);
const handleEditorDidMount = (editor, monaco) => {
editorRef.current = editor;
const container = boxRef.current;
const updateHeight = () => {
container.style.height = `${editor.getContentHeight()}px`;
editor.layout();
};
editor.onDidContentSizeChange(updateHeight);
};
if (leaf.bold) {
children = _jsx("strong", { children: children });
}
if (leaf.code) {
children = (_jsx("div", { children: _jsx(Box, { minHeight: '5rem', width: '100%', resize: 'vertical', overflow: 'auto', ref: boxRef, children: _jsx(MonacoEditor, { options: {
minimap: {
enabled: false
},
lineNumbers: 'off',
wordWrap: 'on',
scrollBeyondLastLine: false,
wrappingStrategy: 'advanced',
}, height: "100%", width: "100%", theme: colorMode === 'light' ? 'light' : "vs-dark", defaultLanguage: "json", defaultValue: leaf.text, onMount: handleEditorDidMount }) }) }));
}
if (leaf.italic) {
children = _jsx("em", { children: children });
}
if (leaf.underline) {
children = _jsx("u", { children: children });
}
return _jsx("span", Object.assign({}, attributes, { children: children }));
};
const Element = ({ attributes, children, element, state }) => {
const { colorMode } = useColorMode();
const style = { textAlign: element.align };
switch (element.type) {
case 'block-quote':
return (_jsx("blockquote", Object.assign({ style: {
fontSize: '1.4em',
width: '100%',
margin: '0 0',
fontFamily: 'Open Sans',
fontStyle: 'italic',
color: '#555555',
padding: '1.2em 1.2rem 1.2em 1.7rem',
borderLeft: `3px solid #${state}`,
lineHeight: '1.1',
position: 'relative',
background: '#f3f3f3',
style
} }, attributes, { children: children })));
case 'bulleted-list':
return (_jsx("ul", Object.assign({ style: style }, attributes, { children: children })));
case 'client-handler':
return (_jsx(ClientHandlerSlateProxy, { children: children }));
case 'heading-one':
return (_jsx(Heading, Object.assign({ as: 'h1', size: 'xl', noOfLines: 1, sx: style }, attributes, { children: children })));
case 'heading-two':
return (_jsx(Heading, Object.assign({ as: 'h2', size: 'lg', noOfLines: 1, sx: style }, attributes, { children: children })));
case 'list-item':
return (_jsx("li", Object.assign({ style: style }, attributes, { children: children })));
case 'numbered-list':
return (_jsx("ol", Object.assign({ style: style }, attributes, { children: children })));
default:
return (_jsx("p", Object.assign({ style: style }, attributes, { children: children })));
}
};
const FocusCatcher = ({ onFocusChanged }) => {
const focused = useFocused();
useEffect(() => {
onFocusChanged && onFocusChanged(focused);
}, [focused]);
return null;
};
var level = (el) => {
if (el && check(el))
mutate(el);
else {
if (typeof (el) === 'object') {
if (Array.isArray(el)) {
for (let i = 0; i < el.length; i++) {
level(el[i]);
}
}
else {
level(el === null || el === void 0 ? void 0 : el.children);
}
}
}
};
var mutate = (el) => {
el.type = 'client-handler';
};
var check = (el) => {
var _a, _b, _c;
return (!el.type &&
((_c = (_b = (_a = el === null || el === void 0 ? void 0 : el.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.text) === null || _c === void 0 ? void 0 : _c.slice(0, 2)) === '##');
};
export const DeepWysiwyg = React.memo(function DeepWysiwyg(_a) {
var { fillSize, disabled = false, topmenu = true, borderRadiusEditor = 0.5, borderWidthEditor = 'thin', paddingEditor = 1, handleKeyPress, onChange, value, width = '28.29rem', initialValue = [
{
type: 'paragraph',
children: [{ text: '' }],
},
], onFocusChanged, autoFocus = false, borderColorEditor, backgroundColorEditor, EditableProps = {}, SlateProps = {} } = _a, props = __rest(_a, ["fillSize", "disabled", "topmenu", "borderRadiusEditor", "borderWidthEditor", "paddingEditor", "handleKeyPress", "onChange", "value", "width", "initialValue", "onFocusChanged", "autoFocus", "borderColorEditor", "backgroundColorEditor", "EditableProps", "SlateProps"]);
const _value = useMemo(() => {
if (typeof (value) === 'string' && !!value) {
const sl = htmlToSlate(value);
level(sl);
return sl;
}
else
return initialValue;
}, [value, initialValue]);
const random = Math.floor(Math.random() * 16777215).toString(16);
const [color, setColor] = useState(random);
const renderLeaf = useCallback(props => _jsx(Leaf, Object.assign({}, props)), []);
const renderElement = useCallback(props => _jsx(Element, Object.assign({}, props, { state: color })), []);
const [editor] = useState(() => withReact(createEditor()));
const { colorMode } = useColorMode();
const control = useAnimation();
const boxControl = useAnimation();
useEffect(() => {
if (!topmenu) {
control.start('hide');
boxControl.start('hide');
}
else {
control.start('show');
boxControl.start('show');
}
}, [topmenu, control, boxControl]);
return (_jsx(Box, Object.assign({ sx: {
w: fillSize ? '100%' : width,
'& > * > *:nth-of-type(2)': {}
} }, props, { children: _jsxs(Slate, Object.assign({ editor: editor, value: _value, onChange: (value) => {
const serializedToHtml = slateToHtml(value);
onChange && onChange({
value: serializedToHtml,
slateValue: value,
});
} }, SlateProps, { children: [_jsx(FocusCatcher, { onFocusChanged: onFocusChanged }), !!topmenu && _jsx(_Fragment, { children: _jsxs(Box, { as: motion.div, animate: control, variants: topmenuVariants, initial: 'initial', display: 'flex', overflowX: 'scroll', gap: '0.5rem', sx: {
backgroundColor: 'handlersInput',
borderLeftWidth: 'thin',
borderTopWidth: 'thin',
borderRightWidth: 'thin',
borderLeftColor: 'borderColor',
borderTopColor: 'borderColor',
borderRightColor: 'borderColor',
borderRadius: '0.5rem',
padding: '0.5rem',
'& > *:hover': {
transform: 'scale(1.15)'
}
}, children: [_jsx(MarkButton, { colorMode: colorMode, icon: _jsx(FiBold, { style: { padding: '0.2rem' } }), format: 'bold' }), _jsx(MarkButton, { colorMode: colorMode, icon: _jsx(FiItalic, { style: { padding: '0.2rem' } }), format: "italic" }), _jsx(MarkButton, { colorMode: colorMode, icon: _jsx(FiUnderline, { style: { padding: '0.2rem' } }), format: "underline" }), _jsx(MarkButton, { colorMode: colorMode, icon: _jsx(FiCode, { style: { padding: '0.2rem' } }), format: "code" }), _jsx(BlockButton, { colorMode: colorMode, format: "heading-one", icon: _jsx(TbNumber1, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "heading-two", icon: _jsx(TbNumber2, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "block-quote", icon: _jsx(TbQuote, { style: { padding: '0.2rem' } }), setColor: () => setColor(random) }), _jsx(BlockButton, { colorMode: colorMode, format: "numbered-list", icon: _jsx(TbListNumbers, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "bulleted-list", icon: _jsx(TbList, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "left", icon: _jsx(CiTextAlignLeft, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "center", icon: _jsx(CiTextAlignCenter, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "right", icon: _jsx(CiTextAlignRight, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "justify", icon: _jsx(CiTextAlignJustify, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "client-handler", icon: _jsx(CiPenpot, { style: { padding: '0.2rem' } }) }), _jsx(BlockButton, { colorMode: colorMode, format: "code-editor", icon: _jsx(TbBrandVscode, { style: { padding: '0.2rem' } }) })] }) }), _jsx(Editable, Object.assign({ style: {
borderWidth: borderWidthEditor,
borderColor: borderColorEditor,
borderRadius: `${borderRadiusEditor}rem`,
padding: `${paddingEditor}rem`,
backgroundColor: backgroundColorEditor,
}, spellCheck: true, readOnly: !!disabled, autoFocus: autoFocus, renderElement: renderElement, renderLeaf: renderLeaf, onKeyDown: event => {
for (const hotkey in HOTKEYS) {
if (isHotkey(hotkey, event)) {
event.preventDefault();
const mark = HOTKEYS[hotkey];
toggleMark(editor, mark);
}
}
handleKeyPress && handleKeyPress(event);
} }, EditableProps))] })) })));
});
const toggleMark = (editor, format) => {
const isActive = isMarkActive(editor, format);
if (isActive) {
Editor.removeMark(editor, format);
}
else {
Editor.addMark(editor, format, true);
}
};
const isMarkActive = (editor, format) => {
const marks = Editor.marks(editor);
return marks ? marks[format] === true : false;
};
const toggleBlock = (editor, format) => {
const isActive = isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type');
const isList = LIST_TYPES.includes(format);
Transforms.unwrapNodes(editor, {
match: n => !Editor.isEditor(n) &&
SlateElement.isElement(n) &&
LIST_TYPES.includes(n.type) &&
!TEXT_ALIGN_TYPES.includes(format),
split: true,
});
let newProperties;
if (TEXT_ALIGN_TYPES.includes(format)) {
newProperties = {
align: isActive ? undefined : format,
};
}
else {
newProperties = {
type: isActive ? 'paragraph' : isList ? 'list-item' : format,
};
}
Transforms.setNodes(editor, newProperties);
if (!isActive && isList) {
const block = { type: format, children: [] };
Transforms.wrapNodes(editor, block);
}
};
const isBlockActive = (editor, format, blockType = 'type') => {
const { selection } = editor;
if (!selection)
return false;
const [match] = Array.from(Editor.nodes(editor, {
at: Editor.unhangRange(editor, selection),
match: n => !Editor.isEditor(n) &&
SlateElement.isElement(n) &&
n[blockType] === format,
}));
return !!match;
};
export const BlockButton = React.memo(({ format, icon, colorMode, setColor }) => {
const editor = useSlate();
return (_jsx(Button, { colorMode: colorMode, active: isBlockActive(editor, format, TEXT_ALIGN_TYPES.includes(format) ? 'align' : 'type'), onMouseDown: event => {
event.preventDefault();
toggleBlock(editor, format);
if (format === 'block-quote') {
setColor();
}
console.log('format', format);
}, children: _jsx(Icon, { children: icon }) }));
});
export const MarkButton = ({ format, icon, colorMode }) => {
const editor = useSlate();
return (_jsx(Button, { colorMode: colorMode, active: isMarkActive(editor, format), onMouseDown: event => {
event.preventDefault();
toggleMark(editor, format);
}, children: _jsx(Icon, { children: icon }) }));
};
const Button = React.forwardRef((_a) => {
var { colorMode, active, reversed, ref } = _a, props = __rest(_a, ["colorMode", "active", "reversed", "ref"]);
return (_jsx("span", Object.assign({}, props, { ref: ref, style: {
cursor: 'pointer',
border: 'thin solid #c5c5c5',
borderRadius: '0.2rem',
backgroundColor: (colorMode === 'dark' && active)
? '#A8E0FF'
: (colorMode === 'light' && active)
? 'white'
: '#E8F1FC',
color: reversed
? active
? 'white'
: '#aaa'
: active
? '#344055'
: 'gray'
} })));
});
const Icon = React.forwardRef((_a) => {
var { className, ref } = _a, props = __rest(_a, ["className", "ref"]);
return (_jsx("span", Object.assign({}, props, { ref: ref, style: {
fontSize: 24,
verticalAlign: 'text-bottom',
} })));
});
export function useStringSaver(link) {
var _a, _b;
const deep = useDeep();
const [value, setValue] = useState(((_a = link === null || link === void 0 ? void 0 : link.value) === null || _a === void 0 ? void 0 : _a.value) || '');
const focusedRef = useRef(false);
const save = (value) => __awaiter(this, void 0, void 0, function* () {
try {
if (!link.value)
deep.insert({
link_id: link.id, value,
}, { table: 'strings' });
deep.update({ link_id: link.id }, { value }, { table: 'strings' });
}
catch (error) { }
});
const saveDebounced = useDebounceCallback((value) => __awaiter(this, void 0, void 0, function* () {
yield save(value);
}), 500);
useEffect(() => {
if (focusedRef.current)
saveDebounced(value);
}, [value]);
useEffect(() => {
var _a, _b;
console.log('on link value change', { focused: focusedRef.current, value: (_a = link === null || link === void 0 ? void 0 : link.value) === null || _a === void 0 ? void 0 : _a.value });
if (!focusedRef.current)
setValue((_b = link === null || link === void 0 ? void 0 : link.value) === null || _b === void 0 ? void 0 : _b.value);
}, [(_b = link === null || link === void 0 ? void 0 : link.value) === null || _b === void 0 ? void 0 : _b.value]);
const onFocusChanged = useCallback((isFocused) => {
focusedRef.current = isFocused;
}, []);
return {
value,
setValue,
onFocusChanged,
};
}
//# sourceMappingURL=deep-wysiwyg.js.map