UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

429 lines (414 loc) 20.4 kB
'use client'; function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var _excluded = ["autoFocus", "disabled", "form", "classNames", "styles", "ignoreTabKey", "insertSpaces", "maxLength", "minLength", "onBlur", "onClick", "onFocus", "onKeyDown", "onKeyUp", "onValueChange", "placeholder", "readOnly", "required", "style", "className", "tabSize", "textareaId", "value", "language", "fontSize", "variant"]; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : String(i); } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _slicedToArray(arr, i) { return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); } function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } } function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react'; import { Flexbox } from 'react-layout-kit'; import { useStyles } from "./style"; import { isMacLike } from "../utils/platform"; import SyntaxHighlighter from "../Highlighter/SyntaxHighlighter"; import { jsx as _jsx } from "react/jsx-runtime"; import { jsxs as _jsxs } from "react/jsx-runtime"; var KEYCODE_Y = 89; var KEYCODE_Z = 90; var KEYCODE_M = 77; var KEYCODE_PARENS = 57; var KEYCODE_BRACKETS = 219; var KEYCODE_QUOTE = 222; var KEYCODE_BACK_QUOTE = 192; var HISTORY_LIMIT = 100; var HISTORY_TIME_GAP = 3000; var isWindows = typeof window !== 'undefined' && 'navigator' in window && /win/i.test(navigator.platform); var getLines = function getLines(text, position) { return text.slice(0, Math.max(0, position)).split('\n'); }; var Editor = /*#__PURE__*/forwardRef(function (_ref, ref) { var autoFocus = _ref.autoFocus, disabled = _ref.disabled, form = _ref.form, _ref$classNames = _ref.classNames, classNames = _ref$classNames === void 0 ? {} : _ref$classNames, _ref$styles = _ref.styles, s = _ref$styles === void 0 ? {} : _ref$styles, _ref$ignoreTabKey = _ref.ignoreTabKey, ignoreTabKey = _ref$ignoreTabKey === void 0 ? false : _ref$ignoreTabKey, _ref$insertSpaces = _ref.insertSpaces, insertSpaces = _ref$insertSpaces === void 0 ? true : _ref$insertSpaces, maxLength = _ref.maxLength, minLength = _ref.minLength, onBlur = _ref.onBlur, onClick = _ref.onClick, onFocus = _ref.onFocus, onKeyDown = _ref.onKeyDown, onKeyUp = _ref.onKeyUp, onValueChange = _ref.onValueChange, placeholder = _ref.placeholder, readOnly = _ref.readOnly, required = _ref.required, style = _ref.style, className = _ref.className, _ref$tabSize = _ref.tabSize, tabSize = _ref$tabSize === void 0 ? 2 : _ref$tabSize, textareaId = _ref.textareaId, value = _ref.value, language = _ref.language, _ref$fontSize = _ref.fontSize, fontSize = _ref$fontSize === void 0 ? 12 : _ref$fontSize, _ref$variant = _ref.variant, variant = _ref$variant === void 0 ? 'ghost' : _ref$variant, rest = _objectWithoutProperties(_ref, _excluded); var _useStyles = useStyles({ fontSize: fontSize, variant: variant }), styles = _useStyles.styles, cx = _useStyles.cx; var historyRef = useRef({ offset: -1, stack: [] }); var inputRef = useRef(null); var _useState = useState(true), _useState2 = _slicedToArray(_useState, 2), capture = _useState2[0], setCapture = _useState2[1]; var recordChange = useCallback(function (record) { var overwrite = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; var _historyRef$current = historyRef.current, stack = _historyRef$current.stack, offset = _historyRef$current.offset; if (stack.length > 0 && offset > -1) { // When something updates, drop the redo operations historyRef.current.stack = stack.slice(0, offset + 1); // Limit the number of operations to 100 var count = historyRef.current.stack.length; if (count > HISTORY_LIMIT) { var extras = count - HISTORY_LIMIT; historyRef.current.stack = stack.slice(extras, count); historyRef.current.offset = Math.max(historyRef.current.offset - extras, 0); } } var timestamp = Date.now(); if (overwrite) { var last = historyRef.current.stack[historyRef.current.offset]; if (last && timestamp - last.timestamp < HISTORY_TIME_GAP) { var _getLines$pop, _getLines$pop2, _current$; // A previous entry exists and was in short interval // Match the last word in the line var re = /[^\da-z]([\da-z]+)$/i; // Get the previous line var previous = (_getLines$pop = getLines(last.value, last.selectionStart).pop()) === null || _getLines$pop === void 0 ? void 0 : _getLines$pop.match(re); // Get the current line var current = (_getLines$pop2 = getLines(record.value, record.selectionStart).pop()) === null || _getLines$pop2 === void 0 ? void 0 : _getLines$pop2.match(re); if (previous !== null && previous !== void 0 && previous[1] && current !== null && current !== void 0 && (_current$ = current[1]) !== null && _current$ !== void 0 && _current$.startsWith(previous[1])) { // The last word of the previous line and current line match // Overwrite previous entry so that undo will remove whole word historyRef.current.stack[historyRef.current.offset] = _objectSpread(_objectSpread({}, record), {}, { timestamp: timestamp }); return; } } } // Add the new operation to the stack historyRef.current.stack.push(_objectSpread(_objectSpread({}, record), {}, { timestamp: timestamp })); historyRef.current.offset++; }, []); var recordCurrentState = useCallback(function () { var input = inputRef.current; if (!input) return; // Save current state of the input var value = input.value, selectionStart = input.selectionStart, selectionEnd = input.selectionEnd; recordChange({ selectionEnd: selectionEnd, selectionStart: selectionStart, value: value }); }, [recordChange]); var updateInput = function updateInput(record) { var input = inputRef.current; if (!input) return; // Update values and selection state input.value = record.value; input.selectionStart = record.selectionStart; input.selectionEnd = record.selectionEnd; onValueChange === null || onValueChange === void 0 || onValueChange(record.value); }; var applyEdits = function applyEdits(record) { // Save last selection state var input = inputRef.current; var last = historyRef.current.stack[historyRef.current.offset]; if (last && input) { historyRef.current.stack[historyRef.current.offset] = _objectSpread(_objectSpread({}, last), {}, { selectionEnd: input.selectionEnd, selectionStart: input.selectionStart }); } // Save the changes recordChange(record); updateInput(record); }; var undoEdit = function undoEdit() { var _historyRef$current2 = historyRef.current, stack = _historyRef$current2.stack, offset = _historyRef$current2.offset; // Get the previous edit var record = stack[offset - 1]; if (record) { // Apply the changes and update the offset updateInput(record); historyRef.current.offset = Math.max(offset - 1, 0); } }; var redoEdit = function redoEdit() { var _historyRef$current3 = historyRef.current, stack = _historyRef$current3.stack, offset = _historyRef$current3.offset; // Get the next edit var record = stack[offset + 1]; if (record) { // Apply the changes and update the offset updateInput(record); historyRef.current.offset = Math.min(offset + 1, stack.length - 1); } }; var handleKeyDown = function handleKeyDown(e) { if (onKeyDown) { onKeyDown(e); if (e.defaultPrevented) { return; } } if (e.key === 'Escape') { e.currentTarget.blur(); } var _e$currentTarget = e.currentTarget, value = _e$currentTarget.value, selectionStart = _e$currentTarget.selectionStart, selectionEnd = _e$currentTarget.selectionEnd; var tabCharacter = (insertSpaces ? ' ' : '\t').repeat(tabSize); if (e.key === 'Tab' && !ignoreTabKey && capture) { // Prevent focus change e.preventDefault(); if (e.shiftKey) { // Unindent selected lines var linesBeforeCaret = getLines(value, selectionStart); var startLine = linesBeforeCaret.length - 1; var endLine = getLines(value, selectionEnd).length - 1; var nextValue = value.split('\n').map(function (line, i) { if (i >= startLine && i <= endLine && line.startsWith(tabCharacter)) { return line.slice(tabCharacter.length); } return line; }).join('\n'); if (value !== nextValue) { var startLineText = linesBeforeCaret[startLine]; applyEdits({ // Move the end cursor by total number of characters removed selectionEnd: selectionEnd - (value.length - nextValue.length), // Move the start cursor if first line in selection was modified // It was modified only if it started with a tab selectionStart: startLineText !== null && startLineText !== void 0 && startLineText.startsWith(tabCharacter) ? selectionStart - tabCharacter.length : selectionStart, value: nextValue }); } } else if (selectionStart === selectionEnd) { var updatedSelection = selectionStart + tabCharacter.length; applyEdits({ selectionEnd: updatedSelection, // Update caret position selectionStart: updatedSelection, // Insert tab character at caret value: value.slice(0, Math.max(0, selectionStart)) + tabCharacter + value.slice(Math.max(0, selectionEnd)) }); } else { // Indent selected lines var _linesBeforeCaret = getLines(value, selectionStart); var _startLine = _linesBeforeCaret.length - 1; var _endLine = getLines(value, selectionEnd).length - 1; var _startLineText = _linesBeforeCaret[_startLine]; applyEdits({ // Move the end cursor by total number of characters added selectionEnd: selectionEnd + tabCharacter.length * (_endLine - _startLine + 1), // Move the start cursor by number of characters added in first line of selection // Don't move it if it there was no text before cursor selectionStart: _startLineText && /\S/.test(_startLineText) ? selectionStart + tabCharacter.length : selectionStart, value: value.split('\n').map(function (line, i) { if (i >= _startLine && i <= _endLine) { return tabCharacter + line; } return line; }).join('\n') }); } } else if (e.key === 'Backspace') { var hasSelection = selectionStart !== selectionEnd; var textBeforeCaret = value.slice(0, Math.max(0, selectionStart)); if (textBeforeCaret.endsWith(tabCharacter) && !hasSelection) { // Prevent default delete behaviour e.preventDefault(); var _updatedSelection = selectionStart - tabCharacter.length; applyEdits({ selectionEnd: _updatedSelection, // Update caret position selectionStart: _updatedSelection, // Remove tab character at caret value: value.slice(0, Math.max(0, selectionStart - tabCharacter.length)) + value.slice(Math.max(0, selectionEnd)) }); } } else if (e.key === 'Enter') { // Ignore selections if (selectionStart === selectionEnd) { // Get the current line var line = getLines(value, selectionStart).pop(); var matches = line === null || line === void 0 ? void 0 : line.match(/^\s+/); if (matches !== null && matches !== void 0 && matches[0]) { e.preventDefault(); // Preserve indentation on inserting a new line var indent = '\n' + matches[0]; var _updatedSelection2 = selectionStart + indent.length; applyEdits({ selectionEnd: _updatedSelection2, // Update caret position selectionStart: _updatedSelection2, // Insert indentation character at caret value: value.slice(0, Math.max(0, selectionStart)) + indent + value.slice(Math.max(0, selectionEnd)) }); } } } else if (e.keyCode === KEYCODE_PARENS || e.keyCode === KEYCODE_BRACKETS || e.keyCode === KEYCODE_QUOTE || e.keyCode === KEYCODE_BACK_QUOTE) { var chars; if (e.keyCode === KEYCODE_PARENS && e.shiftKey) { chars = ['(', ')']; } else if (e.keyCode === KEYCODE_BRACKETS) { chars = e.shiftKey ? ['{', '}'] : ['[', ']']; } else if (e.keyCode === KEYCODE_QUOTE) { chars = e.shiftKey ? ['"', '"'] : ["'", "'"]; } else if (e.keyCode === KEYCODE_BACK_QUOTE && !e.shiftKey) { chars = ['`', '`']; } // If text is selected, wrap them in the characters if (selectionStart !== selectionEnd && chars) { e.preventDefault(); applyEdits({ selectionEnd: selectionEnd + 2, // Update caret position selectionStart: selectionStart, value: value.slice(0, Math.max(0, selectionStart)) + chars[0] + // eslint-disable-next-line unicorn/prefer-string-slice value.substring(selectionStart, selectionEnd) + chars[1] + value.slice(Math.max(0, selectionEnd)) }); } } else if ((isMacLike ? // Trigger undo with ⌘+Z on Mac e.metaKey && e.keyCode === KEYCODE_Z : // Trigger undo with Ctrl+Z on other platforms e.ctrlKey && e.keyCode === KEYCODE_Z) && !e.shiftKey && !e.altKey) { e.preventDefault(); undoEdit(); } else if ((isMacLike ? // Trigger redo with ⌘+Shift+Z on Mac e.metaKey && e.keyCode === KEYCODE_Z && e.shiftKey : isWindows ? // Trigger redo with Ctrl+Y on Windows e.ctrlKey && e.keyCode === KEYCODE_Y : // Trigger redo with Ctrl+Shift+Z on other platforms e.ctrlKey && e.keyCode === KEYCODE_Z && e.shiftKey) && !e.altKey) { e.preventDefault(); redoEdit(); } else if (e.keyCode === KEYCODE_M && e.ctrlKey && (isMacLike ? e.shiftKey : true)) { e.preventDefault(); // Toggle capturing tab key so users can focus away setCapture(function (prev) { return !prev; }); } }; var handleChange = function handleChange(e) { var _e$currentTarget2 = e.currentTarget, value = _e$currentTarget2.value, selectionStart = _e$currentTarget2.selectionStart, selectionEnd = _e$currentTarget2.selectionEnd; recordChange({ selectionEnd: selectionEnd, selectionStart: selectionStart, value: value }, true); onValueChange(value); }; useEffect(function () { recordCurrentState(); }, [recordCurrentState]); // @ts-ignore useImperativeHandle(ref, function () { return { get session() { return { history: historyRef.current }; }, set session(session) { historyRef.current = session.history; } }; }, []); return /*#__PURE__*/_jsx(Flexbox, _objectSpread(_objectSpread({ className: cx(styles.container, className), style: style }, rest), {}, { children: /*#__PURE__*/_jsxs("div", { className: styles.editor, children: [/*#__PURE__*/_jsx(SyntaxHighlighter, { className: cx(styles.highlight, classNames === null || classNames === void 0 ? void 0 : classNames.highlight), language: language, style: s.highlight, children: value }), /*#__PURE__*/_jsx("textarea", { autoCapitalize: "off", autoComplete: "off", autoCorrect: "off", autoFocus: autoFocus, className: cx(styles.textarea, classNames === null || classNames === void 0 ? void 0 : classNames.textarea), "data-gramm": false, disabled: disabled, form: form, id: textareaId, maxLength: maxLength, minLength: minLength, onBlur: onBlur, onChange: handleChange, onClick: onClick, onFocus: onFocus, onKeyDown: handleKeyDown, onKeyUp: onKeyUp, placeholder: placeholder, readOnly: readOnly // @ts-ignore , ref: function ref(c) { return inputRef.current = c; }, required: required, spellCheck: false, style: _objectSpread({}, s === null || s === void 0 ? void 0 : s.textarea), value: value })] }) })); }); export default Editor;