diff --git a/.yarn/cache/@lexical-clipboard-npm-0.6.0-6da46a39a4-da1d43a546.zip b/.yarn/cache/@lexical-clipboard-npm-0.6.0-6da46a39a4-da1d43a546.zip new file mode 100644 index 000000000..6321e57a7 Binary files /dev/null and b/.yarn/cache/@lexical-clipboard-npm-0.6.0-6da46a39a4-da1d43a546.zip differ diff --git a/.yarn/cache/@lexical-code-npm-0.6.0-144c86663d-abb3914c79.zip b/.yarn/cache/@lexical-code-npm-0.6.0-144c86663d-abb3914c79.zip new file mode 100644 index 000000000..e592463b8 Binary files /dev/null and b/.yarn/cache/@lexical-code-npm-0.6.0-144c86663d-abb3914c79.zip differ diff --git a/.yarn/cache/@lexical-dragon-npm-0.6.0-456b5ab50c-6195d39122.zip b/.yarn/cache/@lexical-dragon-npm-0.6.0-456b5ab50c-6195d39122.zip new file mode 100644 index 000000000..50b5cf772 Binary files /dev/null and b/.yarn/cache/@lexical-dragon-npm-0.6.0-456b5ab50c-6195d39122.zip differ diff --git a/.yarn/cache/@lexical-hashtag-npm-0.6.0-f18660a3c0-f6cc2ecc9e.zip b/.yarn/cache/@lexical-hashtag-npm-0.6.0-f18660a3c0-f6cc2ecc9e.zip new file mode 100644 index 000000000..a108cf18c Binary files /dev/null and b/.yarn/cache/@lexical-hashtag-npm-0.6.0-f18660a3c0-f6cc2ecc9e.zip differ diff --git a/.yarn/cache/@lexical-history-npm-0.6.0-ca3291465c-148865aaf7.zip b/.yarn/cache/@lexical-history-npm-0.6.0-ca3291465c-148865aaf7.zip new file mode 100644 index 000000000..40a78a40b Binary files /dev/null and b/.yarn/cache/@lexical-history-npm-0.6.0-ca3291465c-148865aaf7.zip differ diff --git a/.yarn/cache/@lexical-html-npm-0.6.0-f5648cd99d-7f15e585f1.zip b/.yarn/cache/@lexical-html-npm-0.6.0-f5648cd99d-7f15e585f1.zip new file mode 100644 index 000000000..ff3b8ef85 Binary files /dev/null and b/.yarn/cache/@lexical-html-npm-0.6.0-f5648cd99d-7f15e585f1.zip differ diff --git a/.yarn/cache/@lexical-link-npm-0.6.0-63edfb55fb-01864a352e.zip b/.yarn/cache/@lexical-link-npm-0.6.0-63edfb55fb-01864a352e.zip new file mode 100644 index 000000000..806f15013 Binary files /dev/null and b/.yarn/cache/@lexical-link-npm-0.6.0-63edfb55fb-01864a352e.zip differ diff --git a/.yarn/cache/@lexical-list-npm-0.6.0-914f30740e-1e6d1e8cec.zip b/.yarn/cache/@lexical-list-npm-0.6.0-914f30740e-1e6d1e8cec.zip new file mode 100644 index 000000000..424a5fa8e Binary files /dev/null and b/.yarn/cache/@lexical-list-npm-0.6.0-914f30740e-1e6d1e8cec.zip differ diff --git a/.yarn/cache/@lexical-mark-npm-0.6.0-2acbe5982b-c2043e638e.zip b/.yarn/cache/@lexical-mark-npm-0.6.0-2acbe5982b-c2043e638e.zip new file mode 100644 index 000000000..3c144291c Binary files /dev/null and b/.yarn/cache/@lexical-mark-npm-0.6.0-2acbe5982b-c2043e638e.zip differ diff --git a/.yarn/cache/@lexical-markdown-npm-0.6.0-b77cf75bd0-14e9b2235d.zip b/.yarn/cache/@lexical-markdown-npm-0.6.0-b77cf75bd0-14e9b2235d.zip new file mode 100644 index 000000000..21cc88463 Binary files /dev/null and b/.yarn/cache/@lexical-markdown-npm-0.6.0-b77cf75bd0-14e9b2235d.zip differ diff --git a/.yarn/cache/@lexical-offset-npm-0.6.0-a98e894ee5-59d4a99c94.zip b/.yarn/cache/@lexical-offset-npm-0.6.0-a98e894ee5-59d4a99c94.zip new file mode 100644 index 000000000..60bd3bb5a Binary files /dev/null and b/.yarn/cache/@lexical-offset-npm-0.6.0-a98e894ee5-59d4a99c94.zip differ diff --git a/.yarn/cache/@lexical-overflow-npm-0.6.0-c5e703c2e6-099a526868.zip b/.yarn/cache/@lexical-overflow-npm-0.6.0-c5e703c2e6-099a526868.zip new file mode 100644 index 000000000..702e5bb19 Binary files /dev/null and b/.yarn/cache/@lexical-overflow-npm-0.6.0-c5e703c2e6-099a526868.zip differ diff --git a/.yarn/cache/@lexical-plain-text-npm-0.6.0-e7aee2116a-b71d621469.zip b/.yarn/cache/@lexical-plain-text-npm-0.6.0-e7aee2116a-b71d621469.zip new file mode 100644 index 000000000..e4d851476 Binary files /dev/null and b/.yarn/cache/@lexical-plain-text-npm-0.6.0-e7aee2116a-b71d621469.zip differ diff --git a/.yarn/cache/@lexical-react-npm-0.6.0-2011702f32-5c32864746.zip b/.yarn/cache/@lexical-react-npm-0.6.0-2011702f32-5c32864746.zip new file mode 100644 index 000000000..5ff499335 Binary files /dev/null and b/.yarn/cache/@lexical-react-npm-0.6.0-2011702f32-5c32864746.zip differ diff --git a/.yarn/cache/@lexical-rich-text-npm-0.6.0-25a3f7ffb5-08845ed453.zip b/.yarn/cache/@lexical-rich-text-npm-0.6.0-25a3f7ffb5-08845ed453.zip new file mode 100644 index 000000000..2c0e197eb Binary files /dev/null and b/.yarn/cache/@lexical-rich-text-npm-0.6.0-25a3f7ffb5-08845ed453.zip differ diff --git a/.yarn/cache/@lexical-selection-npm-0.6.0-a9df097d51-457a36be17.zip b/.yarn/cache/@lexical-selection-npm-0.6.0-a9df097d51-457a36be17.zip new file mode 100644 index 000000000..6e5f0d7d1 Binary files /dev/null and b/.yarn/cache/@lexical-selection-npm-0.6.0-a9df097d51-457a36be17.zip differ diff --git a/.yarn/cache/@lexical-table-npm-0.6.0-cefb9bdcda-8f52250920.zip b/.yarn/cache/@lexical-table-npm-0.6.0-cefb9bdcda-8f52250920.zip new file mode 100644 index 000000000..217a610e2 Binary files /dev/null and b/.yarn/cache/@lexical-table-npm-0.6.0-cefb9bdcda-8f52250920.zip differ diff --git a/.yarn/cache/@lexical-text-npm-0.6.0-8054e7783b-cfd1b387d4.zip b/.yarn/cache/@lexical-text-npm-0.6.0-8054e7783b-cfd1b387d4.zip new file mode 100644 index 000000000..cb2e70907 Binary files /dev/null and b/.yarn/cache/@lexical-text-npm-0.6.0-8054e7783b-cfd1b387d4.zip differ diff --git a/.yarn/cache/@lexical-utils-npm-0.6.0-3139006946-2b3440c1fc.zip b/.yarn/cache/@lexical-utils-npm-0.6.0-3139006946-2b3440c1fc.zip new file mode 100644 index 000000000..aabadc92b Binary files /dev/null and b/.yarn/cache/@lexical-utils-npm-0.6.0-3139006946-2b3440c1fc.zip differ diff --git a/.yarn/cache/@lexical-yjs-npm-0.6.0-ba6a32820a-e53c07b2bd.zip b/.yarn/cache/@lexical-yjs-npm-0.6.0-ba6a32820a-e53c07b2bd.zip new file mode 100644 index 000000000..6594f7564 Binary files /dev/null and b/.yarn/cache/@lexical-yjs-npm-0.6.0-ba6a32820a-e53c07b2bd.zip differ diff --git a/.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.0-18cb5f8eb9-e58258f525.zip b/.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.0-18cb5f8eb9-e58258f525.zip new file mode 100644 index 000000000..23214414f Binary files /dev/null and b/.yarn/cache/@standardnotes-sncrypto-common-npm-1.13.0-18cb5f8eb9-e58258f525.zip differ diff --git a/.yarn/cache/lexical-npm-0.6.0-eddd533da5-cac65609c2.zip b/.yarn/cache/lexical-npm-0.6.0-eddd533da5-cac65609c2.zip new file mode 100644 index 000000000..9c0a48029 Binary files /dev/null and b/.yarn/cache/lexical-npm-0.6.0-eddd533da5-cac65609c2.zip differ diff --git a/.yarn/cache/react-error-boundary-npm-3.1.4-2310dba89e-f36270a5d7.zip b/.yarn/cache/react-error-boundary-npm-3.1.4-2310dba89e-f36270a5d7.zip new file mode 100644 index 000000000..3ef1c956d Binary files /dev/null and b/.yarn/cache/react-error-boundary-npm-3.1.4-2310dba89e-f36270a5d7.zip differ diff --git a/packages/blocks-editor/.eslintignore b/packages/blocks-editor/.eslintignore new file mode 100644 index 000000000..904b3531d --- /dev/null +++ b/packages/blocks-editor/.eslintignore @@ -0,0 +1,12 @@ +# NOTE: In general this should be kept in sync with .eslintignore + +**/dist/** +**/config/** +**/build/** +**/npm/** +**/*.js.flow +**/*.d.ts +**/playwright*/** +**/vite.config.js +**/vite.prod.config.js +**/node_modules diff --git a/packages/blocks-editor/.eslintrc.js b/packages/blocks-editor/.eslintrc.js new file mode 100644 index 000000000..592685e7e --- /dev/null +++ b/packages/blocks-editor/.eslintrc.js @@ -0,0 +1,254 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +'use strict'; + +const restrictedGlobals = require('confusing-browser-globals'); + +const OFF = 0; +const ERROR = 2; + +module.exports = { + root: true, + // Prettier must be last so it can override other configs (https://github.com/prettier/eslint-config-prettier#installation) + extends: [ + 'fbjs', + 'plugin:react-hooks/recommended', + 'plugin:lexical/all', + 'prettier', + ], + + globals: { + JSX: true, + __DEV__: true, + }, + + overrides: [ + { + // We apply these settings to the source files that get compiled. + // They can use all features including JSX (but shouldn't use `var`). + files: [ + 'packages/*/src/**/*.js', + 'packages/*/__tests__/**/*.?(m)js', + 'packages/*/src/**/*.jsx', + ], + parser: 'babel-eslint', + parserOptions: { + allowImportExportEverywhere: true, + sourceType: 'module', + }, + rules: { + 'no-var': ERROR, + 'prefer-const': ERROR, + strict: OFF, + }, + }, + { + // node scripts should be console logging so don't lint against that + files: ['scripts/**/*.js'], + rules: { + 'no-console': OFF, + }, + }, + { + env: { + browser: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + ], + files: ['**/*.ts', '**/*.tsx'], + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + }, + plugins: ['react', '@typescript-eslint', 'header'], + rules: { + '@typescript-eslint/ban-ts-comment': OFF, + '@typescript-eslint/no-this-alias': OFF, + '@typescript-eslint/no-unused-vars': [ERROR, {args: 'none'}], + 'header/header': [2, 'scripts/www/headerTemplate.js'], + }, + }, + { + // don't lint headers in entrypoint files so we can add TypeDoc module comments + files: ['packages/**/src/index.ts'], + rules: { + 'header/header': OFF, + }, + }, + { + files: [ + 'packages/**/src/__tests__/**', + 'packages/lexical-playground/**', + 'packages/lexical-devtools/**', + ], + rules: { + 'lexical/no-optional-chaining': OFF, + }, + }, + ], + + parser: 'babel-eslint', + + parserOptions: { + ecmaFeatures: { + experimentalObjectRestSpread: true, + }, + ecmaVersion: 8, + sourceType: 'script', + }, + + plugins: [ + 'sort-keys-fix', + 'simple-import-sort', + 'header', + + // import helps to configure simple-import-sort + 'import', + 'jest', + 'no-function-declare-after-return', + 'react', + 'no-only-tests', + 'lexical', + ], + + // Stop ESLint from looking for a configuration file in parent folders + root: true, + // We're stricter than the default config, mostly. We'll override a few rules + // and then enable some React specific ones. + rules: { + 'accessor-pairs': OFF, + + 'brace-style': [ERROR, '1tbs'], + 'consistent-return': OFF, + 'dot-location': [ERROR, 'property'], + // We use console['error']() as a signal to not transform it: + 'dot-notation': [ERROR, {allowPattern: '^(error|warn)$'}], + + 'eol-last': ERROR, + eqeqeq: [ERROR, 'allow-null'], + // Prettier forces semicolons in a few places + 'flowtype/object-type-delimiter': OFF, + + 'flowtype/sort-keys': ERROR, + + 'header/header': [2, 'scripts/www/headerTemplate.js'], + + // (This helps configure simple-import-sort) Make sure all imports are at the top of the file + 'import/first': ERROR, + + // (This helps configure simple-import-sort) Make sure there's a newline after the imports + 'import/newline-after-import': ERROR, + + // (This helps configure simple-import-sort) Merge imports of the same file + 'import/no-duplicates': ERROR, + + indent: OFF, + + 'jsx-quotes': [ERROR, 'prefer-double'], + + 'keyword-spacing': [ERROR, {after: true, before: true}], + + // Enforced by Prettier + // TODO: Prettier doesn't handle long strings or long comments. Not a big + // deal. But I turned it off because loading the plugin causes some obscure + // syntax error and it didn't seem worth investigating. + 'max-len': OFF, + + 'no-bitwise': OFF, + + 'no-console': ERROR, + + 'no-debugger': ERROR, + + // Prevent function declarations after return statements + 'no-function-declare-after-return/no-function-declare-after-return': ERROR, + + 'no-inner-declarations': [ERROR, 'functions'], + + 'no-multi-spaces': ERROR, + + 'no-only-tests/no-only-tests': ERROR, + + 'no-restricted-globals': [ERROR].concat(restrictedGlobals), + + 'no-restricted-syntax': [ERROR, 'WithStatement'], + + 'no-shadow': ERROR, + + 'no-unused-expressions': ERROR, + + 'no-unused-vars': [ERROR, {args: 'none'}], + + 'no-use-before-define': OFF, + + // Flow fails with with non-string literal keys + 'no-useless-computed-key': OFF, + + 'no-useless-concat': OFF, + + // We apply these settings to files that should run on Node. + // They can't use JSX or ES6 modules, and must be in strict mode. + // They can, however, use other ES6 features. + // (Note these rules are overridden later for source files.) + 'no-var': ERROR, + + quotes: [ERROR, 'single', {allowTemplateLiterals: true, avoidEscape: true}], + + // React & JSX + // Our transforms set this automatically + 'react/jsx-boolean-value': [ERROR, 'always'], + + 'react/jsx-no-undef': ERROR, + + // We don't care to do this + 'react/jsx-sort-prop-types': OFF, + + 'react/jsx-tag-spacing': ERROR, + + 'react/jsx-uses-react': ERROR, + + // We don't care to do this + 'react/jsx-wrap-multilines': [ + ERROR, + {assignment: false, declaration: false}, + ], + + 'react/no-is-mounted': OFF, + + // This isn't useful in our test code + 'react/react-in-jsx-scope': ERROR, + + 'react/self-closing-comp': ERROR, + + // This sorts re-exports (`export * from 'foo';`), but not other types of exports. + 'simple-import-sort/exports': ERROR, + + 'simple-import-sort/imports': [ + ERROR, + { + // The default grouping, but with type imports first as a separate group. + // See: https://github.com/lydell/eslint-plugin-simple-import-sort/blob/d9a116f71302c5dcfc1581fc7ded8d77392f1924/examples/.eslintrc.js#L122-L133 + groups: [['^.*\\u0000$'], ['^\\u0000'], ['^@?\\w'], ['^'], ['^\\.']], + }, + ], + + 'sort-keys-fix/sort-keys-fix': ERROR, + + 'space-before-blocks': ERROR, + + 'space-before-function-paren': OFF, + + strict: ERROR, + + 'valid-typeof': [ERROR, {requireStringLiterals: true}], + }, +}; diff --git a/packages/blocks-editor/.gitignore b/packages/blocks-editor/.gitignore new file mode 100644 index 000000000..53c37a166 --- /dev/null +++ b/packages/blocks-editor/.gitignore @@ -0,0 +1 @@ +dist \ No newline at end of file diff --git a/packages/blocks-editor/.prettierignore b/packages/blocks-editor/.prettierignore new file mode 100644 index 000000000..dac34b8b6 --- /dev/null +++ b/packages/blocks-editor/.prettierignore @@ -0,0 +1,15 @@ +# NOTE: In general this should be kept in sync with .eslintignore + +packages/**/dist/*.js +packages/**/build/*.js +packages/**/npm/**/* +packages/**/config/*.js +packages/playwright +packages/playwright-core +packages/**/vite.config.js +packages/**/vite.prod.config.js +**/*.md +**/node_modules +flow-typed +.github/CODEOWNERS +.prettierignore diff --git a/packages/blocks-editor/.prettierrc.js b/packages/blocks-editor/.prettierrc.js new file mode 100644 index 000000000..892289423 --- /dev/null +++ b/packages/blocks-editor/.prettierrc.js @@ -0,0 +1,11 @@ +'use strict'; + +module.exports = { + bracketSpacing: false, + singleQuote: true, + bracketSameLine: true, + printWidth: 80, + trailingComma: 'all', + htmlWhitespaceSensitivity: 'ignore', + attributeGroups: ['$DEFAULT', '^data-'], +}; diff --git a/packages/blocks-editor/CHANGELOG.md b/packages/blocks-editor/CHANGELOG.md new file mode 100644 index 000000000..8bd063113 --- /dev/null +++ b/packages/blocks-editor/CHANGELOG.md @@ -0,0 +1,94 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [1.3.6](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.5...@standardnotes/toast@1.3.6) (2022-11-04) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.3.5](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.4...@standardnotes/toast@1.3.5) (2022-08-11) + +### Bug Fixes + +* optimize toasts for mobile ([#1392](https://github.com/standardnotes/app/issues/1392)) ([40d9392](https://github.com/standardnotes/app/commit/40d9392599e871225abcabcddd51de6cc99a0fe9)) + +## [1.3.4](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.3...@standardnotes/toast@1.3.4) (2022-07-14) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.3.3](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.2...@standardnotes/toast@1.3.3) (2022-07-13) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.3.2](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.1...@standardnotes/toast@1.3.2) (2022-07-06) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.3.1](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.3.0...@standardnotes/toast@1.3.1) (2022-06-28) + +**Note:** Version bump only for package @standardnotes/toast + +# [1.3.0](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.4...@standardnotes/toast@1.3.0) (2022-06-27) + +### Features + +* **web:** tailwind css ([#1147](https://github.com/standardnotes/app/issues/1147)) ([b80038f](https://github.com/standardnotes/app/commit/b80038f607d7411912fa99366abf559a44874ef3)) + +## [1.2.4](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.4-alpha.0...@standardnotes/toast@1.2.4) (2022-06-18) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.2.4-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.3...@standardnotes/toast@1.2.4-alpha.0) (2022-06-18) + +**Note:** Version bump only for package @standardnotes/toast + +## 1.2.3 (2022-06-16) + +**Note:** Version bump only for package @standardnotes/toast + +## 1.2.2 (2022-06-16) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.2.1](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.1-alpha.4...@standardnotes/toast@1.2.1) (2022-06-16) + +**Note:** Version bump only for package @standardnotes/toast + +## 1.2.1-alpha.4 (2022-06-16) + +**Note:** Version bump only for package @standardnotes/toast + +## 1.2.1-alpha.3 (2022-06-16) + +**Note:** Version bump only for package @standardnotes/toast + +## 1.2.1-alpha.2 (2022-06-15) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.2.1-alpha.1](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.1-alpha.0...@standardnotes/toast@1.2.1-alpha.1) (2022-06-14) + +**Note:** Version bump only for package @standardnotes/toast + +## [1.2.1-alpha.0](https://github.com/standardnotes/app/compare/@standardnotes/toast@1.2.0...@standardnotes/toast@1.2.1-alpha.0) (2022-06-14) + +**Note:** Version bump only for package @standardnotes/toast + +# 1.2.0 (2022-06-10) + +### Features + +* mobile app package ([#1075](https://github.com/standardnotes/app/issues/1075)) ([8248a38](https://github.com/standardnotes/app/commit/8248a38280cb7c92da2b2e9c7db298f34ae8ffdf)) +* styles package ([#1074](https://github.com/standardnotes/app/issues/1074)) ([3100327](https://github.com/standardnotes/app/commit/31003276b73d3e89824bc002fe616fa055e918c4)) +* toast package ([#1073](https://github.com/standardnotes/app/issues/1073)) ([6d0b6e9](https://github.com/standardnotes/app/commit/6d0b6e9018b2a612b8df4827336883fe04033128)) +* **wip:** components monorepo ([#1082](https://github.com/standardnotes/app/issues/1082)) ([e3d6001](https://github.com/standardnotes/app/commit/e3d6001a178e11e619ca724b2b155b7c0405c023)) + +# 1.1.0 (2022-06-10) + +### Features + +* mobile app package ([#1075](https://github.com/standardnotes/app/issues/1075)) ([8248a38](https://github.com/standardnotes/app/commit/8248a38280cb7c92da2b2e9c7db298f34ae8ffdf)) +* styles package ([#1074](https://github.com/standardnotes/app/issues/1074)) ([3100327](https://github.com/standardnotes/app/commit/31003276b73d3e89824bc002fe616fa055e918c4)) +* toast package ([#1073](https://github.com/standardnotes/app/issues/1073)) ([6d0b6e9](https://github.com/standardnotes/app/commit/6d0b6e9018b2a612b8df4827336883fe04033128)) +* **wip:** components monorepo ([8c5e11c](https://github.com/standardnotes/app/commit/8c5e11c22b717ada7a6a9b3115fc4c9b757ec71c)) diff --git a/packages/blocks-editor/README.md b/packages/blocks-editor/README.md new file mode 100644 index 000000000..4015c1ce7 --- /dev/null +++ b/packages/blocks-editor/README.md @@ -0,0 +1 @@ +Based on https://github.com/facebook/lexical/tree/main/packages/lexical-playground \ No newline at end of file diff --git a/packages/blocks-editor/blocks.webpack.config.js b/packages/blocks-editor/blocks.webpack.config.js new file mode 100644 index 000000000..f96895882 --- /dev/null +++ b/packages/blocks-editor/blocks.webpack.config.js @@ -0,0 +1,32 @@ +const path = require('path') +module.exports = () => { + return { + entry: './src/index.ts', + output: { + filename: './dist.js', + }, + mode: 'production', + resolve: { + extensions: ['.ts', '.tsx', '.js'], + }, + externals: { + "@standardnotes/icons": path.resolve(__dirname, "./node_modules/@standardnotes/icons") + }, + module: { + rules: [ + { + test: /\.(js|tsx?)$/, + use: [ + 'babel-loader', + { + loader: 'ts-loader', + options: { + transpileOnly: true, + }, + }, + ], + }, + ], + }, + } +} diff --git a/packages/blocks-editor/package.json b/packages/blocks-editor/package.json new file mode 100644 index 000000000..6840c3554 --- /dev/null +++ b/packages/blocks-editor/package.json @@ -0,0 +1,23 @@ +{ + "name": "@standardnotes/blocks-editor", + "version": "1.3.6", + "private": true, + "main": "./src/index.ts", + "scripts": { + "start": "tsc -p tsconfig.json --watch" + }, + "dependencies": { + "@lexical/react": "^0.6.0", + "@standardnotes/icons": "workspace:*", + "lexical": "^0.6.0", + "react": "link:../web/node_modules/react", + "react-dom": "link:../web/node_modules/react-dom" + }, + "devDependencies": { + "@types/react": "link:../web/node_modules/@types/react", + "@types/react-dom": "link:../web/node_modules/@types/react-dom", + "eslint": "*", + "prettier": "*", + "typescript": "*" + } +} diff --git a/packages/blocks-editor/src/Editor/BlocksEditor.tsx b/packages/blocks-editor/src/Editor/BlocksEditor.tsx new file mode 100644 index 000000000..edbf9bd26 --- /dev/null +++ b/packages/blocks-editor/src/Editor/BlocksEditor.tsx @@ -0,0 +1,115 @@ +import {FunctionComponent, useCallback, useState} from 'react'; +import {LexicalComposer} from '@lexical/react/LexicalComposer'; +import {RichTextPlugin} from '@lexical/react/LexicalRichTextPlugin'; +import {ContentEditable} from '@lexical/react/LexicalContentEditable'; +import {OnChangePlugin} from '@lexical/react/LexicalOnChangePlugin'; +import {AutoFocusPlugin} from '@lexical/react/LexicalAutoFocusPlugin'; +import {CheckListPlugin} from '@lexical/react/LexicalCheckListPlugin'; +import {ClearEditorPlugin} from '@lexical/react/LexicalClearEditorPlugin'; +import {MarkdownShortcutPlugin} from '@lexical/react/LexicalMarkdownShortcutPlugin'; +import {TablePlugin} from '@lexical/react/LexicalTablePlugin'; +import { + CHECK_LIST, + ELEMENT_TRANSFORMERS, + TEXT_FORMAT_TRANSFORMERS, + TEXT_MATCH_TRANSFORMERS, +} from '@lexical/markdown'; +import LexicalErrorBoundary from '@lexical/react/LexicalErrorBoundary'; +import {HashtagPlugin} from '@lexical/react/LexicalHashtagPlugin'; +import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; +import {LinkPlugin} from '@lexical/react/LexicalLinkPlugin'; +import {ListPlugin} from '@lexical/react/LexicalListPlugin'; +import {EditorState, LexicalEditor} from 'lexical'; + +import ComponentPickerMenuPlugin from '../Lexical/Plugins/ComponentPickerPlugin'; +import BlocksEditorTheme from '../Lexical/Theme/Theme'; +import HorizontalRulePlugin from '../Lexical/Plugins/HorizontalRulePlugin'; +import TwitterPlugin from '../Lexical/Plugins/TwitterPlugin'; +import YouTubePlugin from '../Lexical/Plugins/YouTubePlugin'; +import AutoEmbedPlugin from '../Lexical/Plugins/AutoEmbedPlugin'; +import CollapsiblePlugin from '../Lexical/Plugins/CollapsiblePlugin'; +import {BlockEditorNodes} from '../Lexical/Nodes/AllNodes'; +// import DraggableBlockPlugin from '../Lexical/Plugins/DraggableBlockPlugin'; + +type BlocksEditorProps = { + initialValue: string; + onChange: (value: string) => void; + className?: string; +}; + +export const BlocksEditor: FunctionComponent = ({ + initialValue, + onChange, + className, +}) => { + const handleChange = useCallback( + (editorState: EditorState, _editor: LexicalEditor) => { + const stringifiedEditorState = JSON.stringify(editorState.toJSON()); + onChange(stringifiedEditorState); + }, + [onChange], + ); + + const [floatingAnchorElem, setFloatingAnchorElem] = + useState(null); + + const onRef = (_floatingAnchorElem: HTMLDivElement) => { + if (_floatingAnchorElem !== null) { + setFloatingAnchorElem(_floatingAnchorElem); + } + }; + + return ( + console.error(error), + editorState: + initialValue && initialValue.length > 0 ? initialValue : undefined, + nodes: BlockEditorNodes, + }}> + <> + +
+ +
+ + } + placeholder="" + ErrorBoundary={LexicalErrorBoundary} + /> + + + + + + + + + + + + + + + + + {floatingAnchorElem && ( + <>{/* */} + )} + +
+ ); +}; diff --git a/packages/blocks-editor/src/Lexical/Hooks/useModal.tsx b/packages/blocks-editor/src/Lexical/Hooks/useModal.tsx new file mode 100644 index 000000000..3b7dfba8d --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Hooks/useModal.tsx @@ -0,0 +1,59 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {useCallback, useMemo, useState} from 'react'; + +import Modal from '../UI/Modal'; + +export default function useModal(): [ + JSX.Element | null, + (title: string, showModal: (onClose: () => void) => JSX.Element) => void, +] { + const [modalContent, setModalContent] = useState(null); + + const onClose = useCallback(() => { + setModalContent(null); + }, []); + + const modal = useMemo(() => { + if (modalContent === null) { + return null; + } + const {title, content, closeOnClickOutside} = modalContent; + return ( + + {content} + + ); + }, [modalContent, onClose]); + + const showModal = useCallback( + ( + title: string, + // eslint-disable-next-line no-shadow + getContent: (onClose: () => void) => JSX.Element, + closeOnClickOutside = false, + ) => { + setModalContent({ + closeOnClickOutside, + content: getContent(onClose), + title, + }); + }, + [onClose], + ); + + return [modal, showModal]; +} diff --git a/packages/blocks-editor/src/Lexical/Icons/LICENSE.md b/packages/blocks-editor/src/Lexical/Icons/LICENSE.md new file mode 100644 index 000000000..ce74f6abe --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/LICENSE.md @@ -0,0 +1,5 @@ +Bootstrap Icons +https://icons.getbootstrap.com + +Licensed under MIT license +https://github.com/twbs/icons/blob/main/LICENSE.md diff --git a/packages/blocks-editor/src/Lexical/Icons/arrow-clockwise.svg b/packages/blocks-editor/src/Lexical/Icons/arrow-clockwise.svg new file mode 100644 index 000000000..80b3ad066 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/arrow-clockwise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/arrow-counterclockwise.svg b/packages/blocks-editor/src/Lexical/Icons/arrow-counterclockwise.svg new file mode 100644 index 000000000..46d3581d8 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/arrow-counterclockwise.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/bg-color.svg b/packages/blocks-editor/src/Lexical/Icons/bg-color.svg new file mode 100644 index 000000000..ae08b2c1d --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/bg-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/camera.svg b/packages/blocks-editor/src/Lexical/Icons/camera.svg new file mode 100755 index 000000000..968ebf4eb --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/camera.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/card-checklist.svg b/packages/blocks-editor/src/Lexical/Icons/card-checklist.svg new file mode 100644 index 000000000..f81734be4 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/card-checklist.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/caret-right-fill.svg b/packages/blocks-editor/src/Lexical/Icons/caret-right-fill.svg new file mode 100644 index 000000000..04c258e6d --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/caret-right-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/chat-left-text.svg b/packages/blocks-editor/src/Lexical/Icons/chat-left-text.svg new file mode 100644 index 000000000..7c7acc239 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/chat-left-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/chat-right-dots.svg b/packages/blocks-editor/src/Lexical/Icons/chat-right-dots.svg new file mode 100644 index 000000000..110925a12 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/chat-right-dots.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/chat-right-text.svg b/packages/blocks-editor/src/Lexical/Icons/chat-right-text.svg new file mode 100644 index 000000000..08daa52bc --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/chat-right-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/chat-right.svg b/packages/blocks-editor/src/Lexical/Icons/chat-right.svg new file mode 100644 index 000000000..d9c2b110e --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/chat-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/chat-square-quote.svg b/packages/blocks-editor/src/Lexical/Icons/chat-square-quote.svg new file mode 100755 index 000000000..5501848a5 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/chat-square-quote.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/chevron-down.svg b/packages/blocks-editor/src/Lexical/Icons/chevron-down.svg new file mode 100644 index 000000000..ef1a6ba3b --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/chevron-down.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/clipboard.svg b/packages/blocks-editor/src/Lexical/Icons/clipboard.svg new file mode 100755 index 000000000..f09e1a1c9 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/clipboard.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/close.svg b/packages/blocks-editor/src/Lexical/Icons/close.svg new file mode 100644 index 000000000..4f5bb3938 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/code.svg b/packages/blocks-editor/src/Lexical/Icons/code.svg new file mode 100755 index 000000000..c9070bf06 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/comments.svg b/packages/blocks-editor/src/Lexical/Icons/comments.svg new file mode 100644 index 000000000..6a23ac546 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/comments.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/copy.svg b/packages/blocks-editor/src/Lexical/Icons/copy.svg new file mode 100644 index 000000000..e757cdfe5 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/diagram-2.svg b/packages/blocks-editor/src/Lexical/Icons/diagram-2.svg new file mode 100644 index 000000000..7b7b696d0 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/diagram-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/download.svg b/packages/blocks-editor/src/Lexical/Icons/download.svg new file mode 100755 index 000000000..cd27d96c1 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/draggable-block-menu.svg b/packages/blocks-editor/src/Lexical/Icons/draggable-block-menu.svg new file mode 100644 index 000000000..7086d2990 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/draggable-block-menu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/dropdown-more.svg b/packages/blocks-editor/src/Lexical/Icons/dropdown-more.svg new file mode 100644 index 000000000..399ea8de5 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/dropdown-more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/figma.svg b/packages/blocks-editor/src/Lexical/Icons/figma.svg new file mode 100644 index 000000000..fa319e12b --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/figma.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/file-image.svg b/packages/blocks-editor/src/Lexical/Icons/file-image.svg new file mode 100644 index 000000000..73a9ff15f --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/file-image.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/filetype-gif.svg b/packages/blocks-editor/src/Lexical/Icons/filetype-gif.svg new file mode 100644 index 000000000..12acb80f3 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/filetype-gif.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/font-color.svg b/packages/blocks-editor/src/Lexical/Icons/font-color.svg new file mode 100644 index 000000000..1ac53f7ac --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/font-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/font-family.svg b/packages/blocks-editor/src/Lexical/Icons/font-family.svg new file mode 100644 index 000000000..a13f5ad1e --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/font-family.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/gear.svg b/packages/blocks-editor/src/Lexical/Icons/gear.svg new file mode 100755 index 000000000..ee6efa044 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/gear.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/horizontal-rule.svg b/packages/blocks-editor/src/Lexical/Icons/horizontal-rule.svg new file mode 100644 index 000000000..cb84970fb --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/horizontal-rule.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/indent.svg b/packages/blocks-editor/src/Lexical/Icons/indent.svg new file mode 100644 index 000000000..c9c5df7bf --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/indent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/journal-code.svg b/packages/blocks-editor/src/Lexical/Icons/journal-code.svg new file mode 100755 index 000000000..9db6666a7 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/journal-code.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/journal-text.svg b/packages/blocks-editor/src/Lexical/Icons/journal-text.svg new file mode 100755 index 000000000..9defed2c3 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/journal-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/justify.svg b/packages/blocks-editor/src/Lexical/Icons/justify.svg new file mode 100644 index 000000000..6c5f8d0f7 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/justify.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/link.svg b/packages/blocks-editor/src/Lexical/Icons/link.svg new file mode 100755 index 000000000..bc38ff5d4 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/link.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/list-ol.svg b/packages/blocks-editor/src/Lexical/Icons/list-ol.svg new file mode 100755 index 000000000..ad288e8ea --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/list-ol.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/list-ul.svg b/packages/blocks-editor/src/Lexical/Icons/list-ul.svg new file mode 100755 index 000000000..6d7aae75d --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/list-ul.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/lock-fill.svg b/packages/blocks-editor/src/Lexical/Icons/lock-fill.svg new file mode 100644 index 000000000..466ca138f --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/lock-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/lock.svg b/packages/blocks-editor/src/Lexical/Icons/lock.svg new file mode 100644 index 000000000..3e19e71b5 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/lock.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/markdown.svg b/packages/blocks-editor/src/Lexical/Icons/markdown.svg new file mode 100644 index 000000000..310bff6d5 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/markdown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/mic.svg b/packages/blocks-editor/src/Lexical/Icons/mic.svg new file mode 100644 index 000000000..afdb58da9 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/mic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/outdent.svg b/packages/blocks-editor/src/Lexical/Icons/outdent.svg new file mode 100644 index 000000000..a98e0e192 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/outdent.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/paint-bucket.svg b/packages/blocks-editor/src/Lexical/Icons/paint-bucket.svg new file mode 100644 index 000000000..baa02d3b3 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/paint-bucket.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/palette.svg b/packages/blocks-editor/src/Lexical/Icons/palette.svg new file mode 100644 index 000000000..338222ec6 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/palette.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/pencil-fill.svg b/packages/blocks-editor/src/Lexical/Icons/pencil-fill.svg new file mode 100755 index 000000000..eb01fb2a4 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/pencil-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/plug-fill.svg b/packages/blocks-editor/src/Lexical/Icons/plug-fill.svg new file mode 100644 index 000000000..3863ef840 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/plug-fill.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/plug.svg b/packages/blocks-editor/src/Lexical/Icons/plug.svg new file mode 100644 index 000000000..de8d4c80b --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/plug.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/plus-slash-minus.svg b/packages/blocks-editor/src/Lexical/Icons/plus-slash-minus.svg new file mode 100644 index 000000000..40ff781e5 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/plus-slash-minus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/plus.svg b/packages/blocks-editor/src/Lexical/Icons/plus.svg new file mode 100644 index 000000000..1a26928a1 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/plus.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/prettier-error.svg b/packages/blocks-editor/src/Lexical/Icons/prettier-error.svg new file mode 100644 index 000000000..8fc8450d0 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/prettier-error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/prettier.svg b/packages/blocks-editor/src/Lexical/Icons/prettier.svg new file mode 100644 index 000000000..b25a626c7 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/prettier.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/send.svg b/packages/blocks-editor/src/Lexical/Icons/send.svg new file mode 100644 index 000000000..04e9f2983 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/send.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/square-check.svg b/packages/blocks-editor/src/Lexical/Icons/square-check.svg new file mode 100644 index 000000000..352ba6158 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/square-check.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/sticky.svg b/packages/blocks-editor/src/Lexical/Icons/sticky.svg new file mode 100644 index 000000000..2b14115cd --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/sticky.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/success.svg b/packages/blocks-editor/src/Lexical/Icons/success.svg new file mode 100644 index 000000000..8e11879e0 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/success.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/table.svg b/packages/blocks-editor/src/Lexical/Icons/table.svg new file mode 100644 index 000000000..e514555c7 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/table.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/text-center.svg b/packages/blocks-editor/src/Lexical/Icons/text-center.svg new file mode 100644 index 000000000..97ced49e6 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/text-center.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/text-left.svg b/packages/blocks-editor/src/Lexical/Icons/text-left.svg new file mode 100644 index 000000000..5fe4cc445 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/text-left.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/text-paragraph.svg b/packages/blocks-editor/src/Lexical/Icons/text-paragraph.svg new file mode 100755 index 000000000..1b943ab44 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/text-paragraph.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/text-right.svg b/packages/blocks-editor/src/Lexical/Icons/text-right.svg new file mode 100644 index 000000000..de984517f --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/text-right.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/trash.svg b/packages/blocks-editor/src/Lexical/Icons/trash.svg new file mode 100644 index 000000000..75680bb7a --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/trash3.svg b/packages/blocks-editor/src/Lexical/Icons/trash3.svg new file mode 100644 index 000000000..5c38b387e --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/trash3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/tweet.svg b/packages/blocks-editor/src/Lexical/Icons/tweet.svg new file mode 100644 index 000000000..3304020e6 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/tweet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-bold.svg b/packages/blocks-editor/src/Lexical/Icons/type-bold.svg new file mode 100755 index 000000000..ec0dc2ec0 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-bold.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-h1.svg b/packages/blocks-editor/src/Lexical/Icons/type-h1.svg new file mode 100755 index 000000000..379da930d --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-h1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-h2.svg b/packages/blocks-editor/src/Lexical/Icons/type-h2.svg new file mode 100755 index 000000000..e724a0be3 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-h2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-h3.svg b/packages/blocks-editor/src/Lexical/Icons/type-h3.svg new file mode 100755 index 000000000..02d4a06c5 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-h3.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-h4.svg b/packages/blocks-editor/src/Lexical/Icons/type-h4.svg new file mode 100755 index 000000000..eb950c9ed --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-h4.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-h5.svg b/packages/blocks-editor/src/Lexical/Icons/type-h5.svg new file mode 100755 index 000000000..5d565639c --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-h5.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-h6.svg b/packages/blocks-editor/src/Lexical/Icons/type-h6.svg new file mode 100755 index 000000000..8274acacd --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-h6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-italic.svg b/packages/blocks-editor/src/Lexical/Icons/type-italic.svg new file mode 100755 index 000000000..ac139f3cc --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-italic.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-strikethrough.svg b/packages/blocks-editor/src/Lexical/Icons/type-strikethrough.svg new file mode 100755 index 000000000..a0d7e17e2 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-strikethrough.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-subscript.svg b/packages/blocks-editor/src/Lexical/Icons/type-subscript.svg new file mode 100644 index 000000000..f6ebe4b6f --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-subscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-superscript.svg b/packages/blocks-editor/src/Lexical/Icons/type-superscript.svg new file mode 100644 index 000000000..bed98f9d8 --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-superscript.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/type-underline.svg b/packages/blocks-editor/src/Lexical/Icons/type-underline.svg new file mode 100755 index 000000000..d5c7046ee --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/type-underline.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/upload.svg b/packages/blocks-editor/src/Lexical/Icons/upload.svg new file mode 100644 index 000000000..81328ddbc --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/upload.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/user.svg b/packages/blocks-editor/src/Lexical/Icons/user.svg new file mode 100644 index 000000000..823b72d1e --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Icons/youtube.svg b/packages/blocks-editor/src/Lexical/Icons/youtube.svg new file mode 100644 index 000000000..e7fb9faab --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Icons/youtube.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/blocks-editor/src/Lexical/Nodes/AllNodes.ts b/packages/blocks-editor/src/Lexical/Nodes/AllNodes.ts new file mode 100644 index 000000000..93c0a043e --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Nodes/AllNodes.ts @@ -0,0 +1,37 @@ +import {CodeHighlightNode, CodeNode} from '@lexical/code'; +import {HashtagNode} from '@lexical/hashtag'; +import {AutoLinkNode, LinkNode} from '@lexical/link'; +import {ListItemNode, ListNode} from '@lexical/list'; +import {MarkNode} from '@lexical/mark'; +import {OverflowNode} from '@lexical/overflow'; +import {HorizontalRuleNode} from '@lexical/react/LexicalHorizontalRuleNode'; +import {HeadingNode, QuoteNode} from '@lexical/rich-text'; +import {TableCellNode, TableNode, TableRowNode} from '@lexical/table'; +import {TweetNode} from './TweetNode'; +import {YouTubeNode} from './YouTubeNode'; +import {CollapsibleContainerNode} from '../Plugins/CollapsiblePlugin/CollapsibleContainerNode'; +import {CollapsibleContentNode} from '../Plugins/CollapsiblePlugin/CollapsibleContentNode'; +import {CollapsibleTitleNode} from '../Plugins/CollapsiblePlugin/CollapsibleTitleNode'; + +export const BlockEditorNodes = [ + AutoLinkNode, + CodeHighlightNode, + CodeNode, + CollapsibleContainerNode, + CollapsibleContentNode, + CollapsibleTitleNode, + HashtagNode, + HeadingNode, + HorizontalRuleNode, + LinkNode, + ListItemNode, + ListNode, + MarkNode, + OverflowNode, + QuoteNode, + TableCellNode, + TableNode, + TableRowNode, + TweetNode, + YouTubeNode, +]; diff --git a/packages/blocks-editor/src/Lexical/Nodes/TableComponent.tsx b/packages/blocks-editor/src/Lexical/Nodes/TableComponent.tsx new file mode 100644 index 000000000..995cebead --- /dev/null +++ b/packages/blocks-editor/src/Lexical/Nodes/TableComponent.tsx @@ -0,0 +1,1766 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import { + $generateJSONFromSelectedNodes, + $generateNodesFromSerializedNodes, + $insertGeneratedNodes, +} from '@lexical/clipboard'; +import {$generateHtmlFromNodes, $generateNodesFromDOM} from '@lexical/html'; +import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; +import {LexicalNestedComposer} from '@lexical/react/LexicalNestedComposer'; +import {useLexicalNodeSelection} from '@lexical/react/useLexicalNodeSelection'; +import {mergeRegister} from '@lexical/utils'; +import { + RangeSelection, + TextFormatType, + $addUpdateTag, + $createParagraphNode, + $createRangeSelection, + $getNodeByKey, + $getRoot, + $getSelection, + $isNodeSelection, + $isRangeSelection, + CLICK_COMMAND, + COMMAND_PRIORITY_LOW, + COPY_COMMAND, + createEditor, + CUT_COMMAND, + EditorThemeClasses, + FORMAT_TEXT_COMMAND, + KEY_ARROW_DOWN_COMMAND, + KEY_ARROW_LEFT_COMMAND, + KEY_ARROW_RIGHT_COMMAND, + KEY_ARROW_UP_COMMAND, + KEY_BACKSPACE_COMMAND, + KEY_DELETE_COMMAND, + KEY_ENTER_COMMAND, + KEY_ESCAPE_COMMAND, + KEY_TAB_COMMAND, + LexicalEditor, + NodeKey, + PASTE_COMMAND, +} from 'lexical'; +import { + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from 'react'; +import {createPortal} from 'react-dom'; +import {IS_APPLE} from '../Shared/environment'; + +import {CellContext} from '../Plugins/TablePlugin'; +import { + $isTableNode, + Cell, + cellHTMLCache, + cellTextContentCache, + createRow, + createUID, + exportTableCellsToHTML, + extractRowsFromHTML, + Rows, + TableNode, +} from './TableNode'; + +type SortOptions = {type: 'ascending' | 'descending'; x: number}; + +const NO_CELLS: [] = []; + +function $createSelectAll(): RangeSelection { + const sel = $createRangeSelection(); + sel.focus.set('root', $getRoot().getChildrenSize(), 'element'); + return sel; +} + +function createEmptyParagraphHTML(theme: EditorThemeClasses): string { + return `


`; +} + +function focusCell(tableElem: HTMLElement, id: string): void { + const cellElem = tableElem.querySelector(`[data-id=${id}]`) as HTMLElement; + if (cellElem == null) { + return; + } + cellElem.focus(); +} + +function isStartingResize(target: HTMLElement): boolean { + return target.nodeType === 1 && target.hasAttribute('data-table-resize'); +} + +function generateHTMLFromJSON( + editorStateJSON: string, + cellEditor: LexicalEditor, +): string { + const editorState = cellEditor.parseEditorState(editorStateJSON); + let html = cellHTMLCache.get(editorStateJSON); + if (html === undefined) { + html = editorState.read(() => $generateHtmlFromNodes(cellEditor, null)); + const textContent = editorState.read(() => $getRoot().getTextContent()); + cellHTMLCache.set(editorStateJSON, html); + cellTextContentCache.set(editorStateJSON, textContent); + } + return html; +} + +function getCurrentDocument(editor: LexicalEditor): Document { + const rootElement = editor.getRootElement(); + return rootElement !== null ? rootElement.ownerDocument : document; +} + +function isCopy( + keyCode: number, + shiftKey: boolean, + metaKey: boolean, + ctrlKey: boolean, +): boolean { + if (shiftKey) { + return false; + } + if (keyCode === 67) { + return IS_APPLE ? metaKey : ctrlKey; + } + + return false; +} + +function isCut( + keyCode: number, + shiftKey: boolean, + metaKey: boolean, + ctrlKey: boolean, +): boolean { + if (shiftKey) { + return false; + } + if (keyCode === 88) { + return IS_APPLE ? metaKey : ctrlKey; + } + + return false; +} + +function isPaste( + keyCode: number, + shiftKey: boolean, + metaKey: boolean, + ctrlKey: boolean, +): boolean { + if (shiftKey) { + return false; + } + if (keyCode === 86) { + return IS_APPLE ? metaKey : ctrlKey; + } + + return false; +} + +function getCellID(domElement: HTMLElement): null | string { + let node: null | HTMLElement = domElement; + while (node !== null) { + const possibleID = node.getAttribute('data-id'); + if (possibleID != null) { + return possibleID; + } + node = node.parentElement; + } + return null; +} + +function getTableCellWidth(domElement: HTMLElement): number { + let node: null | HTMLElement = domElement; + while (node !== null) { + if (node.nodeName === 'TH' || node.nodeName === 'TD') { + return node.getBoundingClientRect().width; + } + node = node.parentElement; + } + return 0; +} + +function $updateCells( + rows: Rows, + ids: Array, + cellCoordMap: Map, + cellEditor: null | LexicalEditor, + updateTableNode: (fn2: (tableNode: TableNode) => void) => void, + fn: () => void, +): void { + for (const id of ids) { + const cell = getCell(rows, id, cellCoordMap); + if (cell !== null && cellEditor !== null) { + const editorState = cellEditor.parseEditorState(cell.json); + cellEditor._headless = true; + cellEditor.setEditorState(editorState); + cellEditor.update(fn, {discrete: true}); + cellEditor._headless = false; + const newJSON = JSON.stringify(cellEditor.getEditorState()); + updateTableNode((tableNode) => { + const [x, y] = cellCoordMap.get(id) as [number, number]; + $addUpdateTag('history-push'); + tableNode.updateCellJSON(x, y, newJSON); + }); + } + } +} + +function isTargetOnPossibleUIControl(target: HTMLElement): boolean { + let node: HTMLElement | null = target; + while (node !== null) { + const nodeName = node.nodeName; + if ( + nodeName === 'BUTTON' || + nodeName === 'INPUT' || + nodeName === 'TEXTAREA' + ) { + return true; + } + node = node.parentElement; + } + return false; +} + +function getSelectedRect( + startID: string, + endID: string, + cellCoordMap: Map, +): null | {startX: number; endX: number; startY: number; endY: number} { + const startCoords = cellCoordMap.get(startID); + const endCoords = cellCoordMap.get(endID); + if (startCoords === undefined || endCoords === undefined) { + return null; + } + const startX = Math.min(startCoords[0], endCoords[0]); + const endX = Math.max(startCoords[0], endCoords[0]); + const startY = Math.min(startCoords[1], endCoords[1]); + const endY = Math.max(startCoords[1], endCoords[1]); + + return { + endX, + endY, + startX, + startY, + }; +} + +function getSelectedIDs( + rows: Rows, + startID: string, + endID: string, + cellCoordMap: Map, +): Array { + const rect = getSelectedRect(startID, endID, cellCoordMap); + if (rect === null) { + return []; + } + const {startX, endY, endX, startY} = rect; + const ids = []; + + for (let x = startX; x <= endX; x++) { + for (let y = startY; y <= endY; y++) { + ids.push(rows[y].cells[x].id); + } + } + return ids; +} + +function extractCellsFromRows( + rows: Rows, + rect: {startX: number; endX: number; startY: number; endY: number}, +): Rows { + const {startX, endY, endX, startY} = rect; + const newRows: Rows = []; + + for (let y = startY; y <= endY; y++) { + const row = rows[y]; + const newRow = createRow(); + for (let x = startX; x <= endX; x++) { + const cellClone = {...row.cells[x]}; + cellClone.id = createUID(); + newRow.cells.push(cellClone); + } + newRows.push(newRow); + } + return newRows; +} + +function TableCellEditor({cellEditor}: {cellEditor: LexicalEditor}) { + const {cellEditorConfig, cellEditorPlugins} = useContext(CellContext); + + if (cellEditorPlugins === null || cellEditorConfig === null) { + return null; + } + + return ( + + {cellEditorPlugins} + + ); +} + +function getCell( + rows: Rows, + cellID: string, + cellCoordMap: Map, +): null | Cell { + const coords = cellCoordMap.get(cellID); + if (coords === undefined) { + return null; + } + const [x, y] = coords; + const row = rows[y]; + return row.cells[x]; +} + +function TableActionMenu({ + cell, + rows, + cellCoordMap, + menuElem, + updateCellsByID, + onClose, + updateTableNode, + setSortingOptions, + sortingOptions, +}: { + cell: Cell; + menuElem: HTMLElement; + updateCellsByID: (ids: Array, fn: () => void) => void; + onClose: () => void; + updateTableNode: (fn2: (tableNode: TableNode) => void) => void; + cellCoordMap: Map; + rows: Rows; + setSortingOptions: (options: null | SortOptions) => void; + sortingOptions: null | SortOptions; +}) { + const dropDownRef = useRef(null); + + useEffect(() => { + const dropdownElem = dropDownRef.current; + if (dropdownElem !== null) { + const rect = menuElem.getBoundingClientRect(); + dropdownElem.style.top = `${rect.y}px`; + dropdownElem.style.left = `${rect.x}px`; + } + }, [menuElem]); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + const dropdownElem = dropDownRef.current; + if ( + dropdownElem !== null && + !dropdownElem.contains(event.target as Node) + ) { + event.stopPropagation(); + } + }; + + window.addEventListener('click', handleClickOutside); + return () => window.removeEventListener('click', handleClickOutside); + }, [onClose]); + const coords = cellCoordMap.get(cell.id); + + if (coords === undefined) { + return null; + } + const [x, y] = coords; + + return ( + // eslint-disable-next-line jsx-a11y/no-static-element-interactions +
{ + e.stopPropagation(); + }} + onPointerDown={(e) => { + e.stopPropagation(); + }} + onPointerUp={(e) => { + e.stopPropagation(); + }} + onClick={(e) => { + e.stopPropagation(); + }}> + + +
+ {cell.type === 'header' && y === 0 && ( + <> + {sortingOptions !== null && sortingOptions.x === x && ( + + )} + {(sortingOptions === null || + sortingOptions.x !== x || + sortingOptions.type === 'descending') && ( + + )} + {(sortingOptions === null || + sortingOptions.x !== x || + sortingOptions.type === 'ascending') && ( + + )} +
+ + )} + + +
+ + +
+ {rows[0].cells.length !== 1 && ( + + )} + {rows.length !== 1 && ( + + )} + +
+ ); +} + +function TableCell({ + cell, + cellCoordMap, + cellEditor, + isEditing, + isSelected, + isPrimarySelected, + theme, + updateCellsByID, + updateTableNode, + rows, + setSortingOptions, + sortingOptions, +}: { + cell: Cell; + isEditing: boolean; + isSelected: boolean; + isPrimarySelected: boolean; + theme: EditorThemeClasses; + cellEditor: LexicalEditor; + updateCellsByID: (ids: Array, fn: () => void) => void; + updateTableNode: (fn2: (tableNode: TableNode) => void) => void; + cellCoordMap: Map; + rows: Rows; + setSortingOptions: (options: null | SortOptions) => void; + sortingOptions: null | SortOptions; +}) { + const [showMenu, setShowMenu] = useState(false); + const menuRootRef = useRef(null); + const isHeader = cell.type !== 'normal'; + const editorStateJSON = cell.json; + const CellComponent = isHeader ? 'th' : 'td'; + const cellWidth = cell.width; + const menuElem = menuRootRef.current; + const coords = cellCoordMap.get(cell.id); + const isSorted = + sortingOptions !== null && + coords !== undefined && + coords[0] === sortingOptions.x && + coords[1] === 0; + + useEffect(() => { + if (isEditing || !isPrimarySelected) { + setShowMenu(false); + } + }, [isEditing, isPrimarySelected]); + + return ( + + {isPrimarySelected && ( +
+ )} + {isPrimarySelected && isEditing ? ( + + ) : ( + <> +
+
+ + )} + {isPrimarySelected && !isEditing && ( +
+ +
+ )} + {showMenu && + menuElem !== null && + createPortal( + setShowMenu(false)} + updateTableNode={updateTableNode} + cellCoordMap={cellCoordMap} + rows={rows} + setSortingOptions={setSortingOptions} + sortingOptions={sortingOptions} + />, + document.body, + )} + {isSorted &&
} + + ); +} + +export default function TableComponent({ + nodeKey, + rows: rawRows, + theme, +}: { + nodeKey: NodeKey; + rows: Rows; + theme: EditorThemeClasses; +}) { + const [isSelected, setSelected, clearSelection] = + useLexicalNodeSelection(nodeKey); + const resizeMeasureRef = useRef<{size: number; point: number}>({ + point: 0, + size: 0, + }); + const [sortingOptions, setSortingOptions] = useState( + null, + ); + const addRowsRef = useRef(null); + const lastCellIDRef = useRef(null); + const tableResizerRulerRef = useRef(null); + const {cellEditorConfig} = useContext(CellContext); + const [isEditing, setIsEditing] = useState(false); + const [showAddColumns, setShowAddColumns] = useState(false); + const [showAddRows, setShowAddRows] = useState(false); + const [editor] = useLexicalComposerContext(); + const mouseDownRef = useRef(false); + const [resizingID, setResizingID] = useState(null); + const tableRef = useRef(null); + const cellCoordMap = useMemo(() => { + const map = new Map(); + + for (let y = 0; y < rawRows.length; y++) { + const row = rawRows[y]; + const cells = row.cells; + for (let x = 0; x < cells.length; x++) { + const cell = cells[x]; + map.set(cell.id, [x, y]); + } + } + return map; + }, [rawRows]); + const rows = useMemo(() => { + if (sortingOptions === null) { + return rawRows; + } + const _rows = rawRows.slice(1); + _rows.sort((a, b) => { + const aCells = a.cells; + const bCells = b.cells; + const x = sortingOptions.x; + const aContent = cellTextContentCache.get(aCells[x].json) || ''; + const bContent = cellTextContentCache.get(bCells[x].json) || ''; + if (aContent === '' || bContent === '') { + return 1; + } + if (sortingOptions.type === 'ascending') { + return aContent.localeCompare(bContent); + } + return bContent.localeCompare(aContent); + }); + _rows.unshift(rawRows[0]); + return _rows; + }, [rawRows, sortingOptions]); + const [primarySelectedCellID, setPrimarySelectedCellID] = useState< + null | string + >(null); + const cellEditor = useMemo(() => { + if (cellEditorConfig === null) { + return null; + } + const _cellEditor = createEditor({ + namespace: cellEditorConfig.namespace, + nodes: cellEditorConfig.nodes, + onError: (error) => cellEditorConfig.onError(error, _cellEditor), + theme: cellEditorConfig.theme, + }); + return _cellEditor; + }, [cellEditorConfig]); + const [selectedCellIDs, setSelectedCellIDs] = useState>([]); + const selectedCellSet = useMemo>( + () => new Set(selectedCellIDs), + [selectedCellIDs], + ); + + useEffect(() => { + const tableElem = tableRef.current; + if ( + isSelected && + document.activeElement === document.body && + tableElem !== null + ) { + tableElem.focus(); + } + }, [isSelected]); + + const updateTableNode = useCallback( + (fn: (tableNode: TableNode) => void) => { + editor.update(() => { + const tableNode = $getNodeByKey(nodeKey); + if ($isTableNode(tableNode)) { + fn(tableNode); + } + }); + }, + [editor, nodeKey], + ); + + const addColumns = () => { + updateTableNode((tableNode) => { + $addUpdateTag('history-push'); + tableNode.addColumns(1); + }); + }; + + const addRows = () => { + updateTableNode((tableNode) => { + $addUpdateTag('history-push'); + tableNode.addRows(1); + }); + }; + + const modifySelectedCells = useCallback( + (x: number, y: number, extend: boolean) => { + const id = rows[y].cells[x].id; + lastCellIDRef.current = id; + if (extend) { + const selectedIDs = getSelectedIDs( + rows, + primarySelectedCellID as string, + id, + cellCoordMap, + ); + setSelectedCellIDs(selectedIDs); + } else { + setPrimarySelectedCellID(id); + setSelectedCellIDs(NO_CELLS); + focusCell(tableRef.current as HTMLElement, id); + } + }, + [cellCoordMap, primarySelectedCellID, rows], + ); + + const saveEditorToJSON = useCallback(() => { + if (cellEditor !== null && primarySelectedCellID !== null) { + const json = JSON.stringify(cellEditor.getEditorState()); + updateTableNode((tableNode) => { + const coords = cellCoordMap.get(primarySelectedCellID); + if (coords === undefined) { + return; + } + $addUpdateTag('history-push'); + const [x, y] = coords; + tableNode.updateCellJSON(x, y, json); + }); + } + }, [cellCoordMap, cellEditor, primarySelectedCellID, updateTableNode]); + + const selectTable = useCallback(() => { + setTimeout(() => { + const parentRootElement = editor.getRootElement(); + if (parentRootElement !== null) { + parentRootElement.focus({preventScroll: true}); + window.getSelection()?.removeAllRanges(); + } + }, 20); + }, [editor]); + + useEffect(() => { + const tableElem = tableRef.current; + if (tableElem === null) { + return; + } + const doc = getCurrentDocument(editor); + + const isAtEdgeOfTable = (event: PointerEvent) => { + const x = event.clientX - tableRect.x; + const y = event.clientY - tableRect.y; + return x < 5 || y < 5; + }; + + const handlePointerDown = (event: PointerEvent) => { + const possibleID = getCellID(event.target as HTMLElement); + if ( + possibleID !== null && + editor.isEditable() && + tableElem.contains(event.target as HTMLElement) + ) { + if (isAtEdgeOfTable(event)) { + setSelected(true); + setPrimarySelectedCellID(null); + selectTable(); + return; + } + setSelected(false); + if (isStartingResize(event.target as HTMLElement)) { + setResizingID(possibleID); + tableElem.style.userSelect = 'none'; + resizeMeasureRef.current = { + point: event.clientX, + size: getTableCellWidth(event.target as HTMLElement), + }; + return; + } + mouseDownRef.current = true; + if (primarySelectedCellID !== possibleID) { + if (isEditing) { + saveEditorToJSON(); + } + setPrimarySelectedCellID(possibleID); + setIsEditing(false); + lastCellIDRef.current = possibleID; + } else { + lastCellIDRef.current = null; + } + setSelectedCellIDs(NO_CELLS); + } else if ( + primarySelectedCellID !== null && + !isTargetOnPossibleUIControl(event.target as HTMLElement) + ) { + setSelected(false); + mouseDownRef.current = false; + if (isEditing) { + saveEditorToJSON(); + } + setPrimarySelectedCellID(null); + setSelectedCellIDs(NO_CELLS); + setIsEditing(false); + lastCellIDRef.current = null; + } + }; + + const tableRect = tableElem.getBoundingClientRect(); + + const handlePointerMove = (event: PointerEvent) => { + if (resizingID !== null) { + const tableResizerRulerElem = tableResizerRulerRef.current; + if (tableResizerRulerElem !== null) { + const {size, point} = resizeMeasureRef.current; + const diff = event.clientX - point; + const newWidth = size + diff; + let x = event.clientX - tableRect.x; + if (x < 10) { + x = 10; + } else if (x > tableRect.width - 10) { + x = tableRect.width - 10; + } else if (newWidth < 20) { + x = point - size + 20 - tableRect.x; + } + tableResizerRulerElem.style.left = `${x}px`; + } + return; + } + if (!isEditing) { + const {clientX, clientY} = event; + const {width, x, y, height} = tableRect; + const isOnRightEdge = + clientX > x + width * 0.9 && + clientX < x + width + 40 && + !mouseDownRef.current; + setShowAddColumns(isOnRightEdge); + const isOnBottomEdge = + event.target === addRowsRef.current || + (clientY > y + height * 0.85 && + clientY < y + height + 5 && + !mouseDownRef.current); + setShowAddRows(isOnBottomEdge); + } + if ( + isEditing || + !mouseDownRef.current || + primarySelectedCellID === null + ) { + return; + } + const possibleID = getCellID(event.target as HTMLElement); + if (possibleID !== null && possibleID !== lastCellIDRef.current) { + if (selectedCellIDs.length === 0) { + tableElem.style.userSelect = 'none'; + } + const selectedIDs = getSelectedIDs( + rows, + primarySelectedCellID, + possibleID, + cellCoordMap, + ); + if (selectedIDs.length === 1) { + setSelectedCellIDs(NO_CELLS); + } else { + setSelectedCellIDs(selectedIDs); + } + lastCellIDRef.current = possibleID; + } + }; + + const handlePointerUp = (event: PointerEvent) => { + if (resizingID !== null) { + const {size, point} = resizeMeasureRef.current; + const diff = event.clientX - point; + let newWidth = size + diff; + if (newWidth < 10) { + newWidth = 10; + } + updateTableNode((tableNode) => { + const [x] = cellCoordMap.get(resizingID) as [number, number]; + $addUpdateTag('history-push'); + tableNode.updateColumnWidth(x, newWidth); + }); + setResizingID(null); + } + if ( + tableElem !== null && + selectedCellIDs.length > 1 && + mouseDownRef.current + ) { + tableElem.style.userSelect = 'text'; + window.getSelection()?.removeAllRanges(); + } + mouseDownRef.current = false; + }; + + doc.addEventListener('pointerdown', handlePointerDown); + doc.addEventListener('pointermove', handlePointerMove); + doc.addEventListener('pointerup', handlePointerUp); + + return () => { + doc.removeEventListener('pointerdown', handlePointerDown); + doc.removeEventListener('pointermove', handlePointerMove); + doc.removeEventListener('pointerup', handlePointerUp); + }; + }, [ + cellEditor, + editor, + isEditing, + rows, + saveEditorToJSON, + primarySelectedCellID, + selectedCellSet, + selectedCellIDs, + cellCoordMap, + resizingID, + updateTableNode, + setSelected, + selectTable, + ]); + + useEffect(() => { + if (!isEditing && primarySelectedCellID !== null) { + const doc = getCurrentDocument(editor); + + const loadContentIntoCell = (cell: Cell | null) => { + if (cell !== null && cellEditor !== null) { + const editorStateJSON = cell.json; + const editorState = cellEditor.parseEditorState(editorStateJSON); + cellEditor.setEditorState(editorState); + } + }; + + const handleDblClick = (event: MouseEvent) => { + const possibleID = getCellID(event.target as HTMLElement); + if (possibleID === primarySelectedCellID && editor.isEditable()) { + const cell = getCell(rows, possibleID, cellCoordMap); + loadContentIntoCell(cell); + setIsEditing(true); + setSelectedCellIDs(NO_CELLS); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { + // Ignore arrow keys, escape or tab + const keyCode = event.keyCode; + if ( + keyCode === 16 || + keyCode === 27 || + keyCode === 9 || + keyCode === 37 || + keyCode === 38 || + keyCode === 39 || + keyCode === 40 || + keyCode === 8 || + keyCode === 46 || + !editor.isEditable() + ) { + return; + } + if (keyCode === 13) { + event.preventDefault(); + } + if ( + !isEditing && + primarySelectedCellID !== null && + editor.getEditorState().read(() => $getSelection() === null) && + (event.target as HTMLElement).contentEditable !== 'true' + ) { + if (isCopy(keyCode, event.shiftKey, event.metaKey, event.ctrlKey)) { + editor.dispatchCommand(COPY_COMMAND, event); + return; + } + if (isCut(keyCode, event.shiftKey, event.metaKey, event.ctrlKey)) { + editor.dispatchCommand(CUT_COMMAND, event); + return; + } + if (isPaste(keyCode, event.shiftKey, event.metaKey, event.ctrlKey)) { + editor.dispatchCommand(PASTE_COMMAND, event); + return; + } + } + if (event.metaKey || event.ctrlKey || event.altKey) { + return; + } + const cell = getCell(rows, primarySelectedCellID, cellCoordMap); + loadContentIntoCell(cell); + setIsEditing(true); + setSelectedCellIDs(NO_CELLS); + }; + + doc.addEventListener('dblclick', handleDblClick); + doc.addEventListener('keydown', handleKeyDown); + + return () => { + doc.removeEventListener('dblclick', handleDblClick); + doc.removeEventListener('keydown', handleKeyDown); + }; + } + }, [ + cellEditor, + editor, + isEditing, + rows, + primarySelectedCellID, + cellCoordMap, + ]); + + const updateCellsByID = useCallback( + (ids: Array, fn: () => void) => { + $updateCells(rows, ids, cellCoordMap, cellEditor, updateTableNode, fn); + }, + [cellCoordMap, cellEditor, rows, updateTableNode], + ); + + const clearCellsCommand = useCallback((): boolean => { + if (primarySelectedCellID !== null && !isEditing) { + updateCellsByID([primarySelectedCellID, ...selectedCellIDs], () => { + const root = $getRoot(); + root.clear(); + root.append($createParagraphNode()); + }); + return true; + } else if (isSelected) { + updateTableNode((tableNode) => { + $addUpdateTag('history-push'); + tableNode.selectNext(); + tableNode.remove(); + }); + } + return false; + }, [ + isEditing, + isSelected, + primarySelectedCellID, + selectedCellIDs, + updateCellsByID, + updateTableNode, + ]); + + useEffect(() => { + const tableElem = tableRef.current; + if (tableElem === null) { + return; + } + + const copyDataToClipboard = ( + event: ClipboardEvent, + htmlString: string, + lexicalString: string, + plainTextString: string, + ) => { + const clipboardData = + event instanceof KeyboardEvent ? null : event.clipboardData; + event.preventDefault(); + + if (clipboardData != null) { + clipboardData.setData('text/html', htmlString); + clipboardData.setData('text/plain', plainTextString); + clipboardData.setData('application/x-lexical-editor', lexicalString); + } else { + const clipboard = navigator.clipboard; + if (clipboard != null) { + // Most browsers only support a single item in the clipboard at one time. + // So we optimize by only putting in HTML. + const data = [ + new ClipboardItem({ + 'text/html': new Blob([htmlString as BlobPart], { + type: 'text/html', + }), + }), + ]; + clipboard.write(data); + } + } + }; + + const getTypeFromObject = async ( + clipboardData: DataTransfer | ClipboardItem, + type: string, + ): Promise => { + try { + return clipboardData instanceof DataTransfer + ? clipboardData.getData(type) + : clipboardData instanceof ClipboardItem + ? await (await clipboardData.getType(type)).text() + : ''; + } catch { + return ''; + } + }; + + const pasteContent = async (event: ClipboardEvent) => { + let clipboardData: null | DataTransfer | ClipboardItem = + (event instanceof InputEvent ? null : event.clipboardData) || null; + + if (primarySelectedCellID !== null && cellEditor !== null) { + event.preventDefault(); + + if (clipboardData === null) { + try { + const items = await navigator.clipboard.read(); + clipboardData = items[0]; + } catch { + // NO-OP + } + } + const lexicalString = + clipboardData !== null + ? await getTypeFromObject( + clipboardData, + 'application/x-lexical-editor', + ) + : ''; + + if (lexicalString) { + try { + const payload = JSON.parse(lexicalString); + if ( + payload.namespace === editor._config.namespace && + Array.isArray(payload.nodes) + ) { + $updateCells( + rows, + [primarySelectedCellID], + cellCoordMap, + cellEditor, + updateTableNode, + () => { + const root = $getRoot(); + root.clear(); + root.append($createParagraphNode()); + root.selectEnd(); + const nodes = $generateNodesFromSerializedNodes( + payload.nodes, + ); + const sel = $getSelection(); + if ($isRangeSelection(sel)) { + $insertGeneratedNodes(cellEditor, nodes, sel); + } + }, + ); + return; + } + // eslint-disable-next-line no-empty + } catch {} + } + const htmlString = + clipboardData !== null + ? await getTypeFromObject(clipboardData, 'text/html') + : ''; + + if (htmlString) { + try { + const parser = new DOMParser(); + const dom = parser.parseFromString(htmlString, 'text/html'); + const possibleTableElement = dom.querySelector('table'); + + if (possibleTableElement != null) { + const pasteRows = extractRowsFromHTML(possibleTableElement); + updateTableNode((tableNode) => { + const [x, y] = cellCoordMap.get(primarySelectedCellID) as [ + number, + number, + ]; + $addUpdateTag('history-push'); + tableNode.mergeRows(x, y, pasteRows); + }); + return; + } + $updateCells( + rows, + [primarySelectedCellID], + cellCoordMap, + cellEditor, + updateTableNode, + () => { + const root = $getRoot(); + root.clear(); + root.append($createParagraphNode()); + root.selectEnd(); + const nodes = $generateNodesFromDOM(editor, dom); + const sel = $getSelection(); + if ($isRangeSelection(sel)) { + $insertGeneratedNodes(cellEditor, nodes, sel); + } + }, + ); + return; + // eslint-disable-next-line no-empty + } catch {} + } + + // Multi-line plain text in rich text mode pasted as separate paragraphs + // instead of single paragraph with linebreaks. + const text = + clipboardData !== null + ? await getTypeFromObject(clipboardData, 'text/plain') + : ''; + + if (text != null) { + $updateCells( + rows, + [primarySelectedCellID], + cellCoordMap, + cellEditor, + updateTableNode, + () => { + const root = $getRoot(); + root.clear(); + root.selectEnd(); + const sel = $getSelection(); + if (sel !== null) { + sel.insertRawText(text); + } + }, + ); + } + } + }; + + const copyPrimaryCell = (event: ClipboardEvent) => { + if (primarySelectedCellID !== null && cellEditor !== null) { + const cell = getCell(rows, primarySelectedCellID, cellCoordMap) as Cell; + const json = cell.json; + const htmlString = cellHTMLCache.get(json) || null; + if (htmlString === null) { + return; + } + const editorState = cellEditor.parseEditorState(json); + const plainTextString = editorState.read(() => + $getRoot().getTextContent(), + ); + const lexicalString = editorState.read(() => { + return JSON.stringify( + $generateJSONFromSelectedNodes(cellEditor, null), + ); + }); + + copyDataToClipboard(event, htmlString, lexicalString, plainTextString); + } + }; + + const copyCellRange = (event: ClipboardEvent) => { + const lastCellID = lastCellIDRef.current; + if ( + primarySelectedCellID !== null && + cellEditor !== null && + lastCellID !== null + ) { + const rect = getSelectedRect( + primarySelectedCellID, + lastCellID, + cellCoordMap, + ); + if (rect === null) { + return; + } + const dom = exportTableCellsToHTML(rows, rect); + const htmlString = dom.outerHTML; + const plainTextString = dom.outerText; + const tableNodeJSON = editor.getEditorState().read(() => { + const tableNode = $getNodeByKey(nodeKey) as TableNode; + return tableNode.exportJSON(); + }); + tableNodeJSON.rows = extractCellsFromRows(rows, rect); + const lexicalJSON = { + namespace: cellEditor._config.namespace, + nodes: [tableNodeJSON], + }; + const lexicalString = JSON.stringify(lexicalJSON); + copyDataToClipboard(event, htmlString, lexicalString, plainTextString); + } + }; + + const handlePaste = ( + event: ClipboardEvent, + activeEditor: LexicalEditor, + ) => { + const selection = $getSelection(); + if ( + primarySelectedCellID !== null && + !isEditing && + selection === null && + activeEditor === editor + ) { + pasteContent(event); + mouseDownRef.current = false; + setSelectedCellIDs(NO_CELLS); + return true; + } + return false; + }; + + const handleCopy = (event: ClipboardEvent, activeEditor: LexicalEditor) => { + const selection = $getSelection(); + if ( + primarySelectedCellID !== null && + !isEditing && + selection === null && + activeEditor === editor + ) { + if (selectedCellIDs.length === 0) { + copyPrimaryCell(event); + } else { + copyCellRange(event); + } + return true; + } + return false; + }; + + return mergeRegister( + editor.registerCommand( + CLICK_COMMAND, + (payload) => { + const selection = $getSelection(); + if ($isNodeSelection(selection)) { + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + PASTE_COMMAND, + handlePaste, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + COPY_COMMAND, + handleCopy, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + CUT_COMMAND, + (event: ClipboardEvent, activeEditor) => { + if (handleCopy(event, activeEditor)) { + clearCellsCommand(); + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_BACKSPACE_COMMAND, + clearCellsCommand, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_DELETE_COMMAND, + clearCellsCommand, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + FORMAT_TEXT_COMMAND, + (payload) => { + if (primarySelectedCellID !== null && !isEditing) { + $updateCells( + rows, + [primarySelectedCellID, ...selectedCellIDs], + cellCoordMap, + cellEditor, + updateTableNode, + () => { + const sel = $createSelectAll(); + sel.formatText(payload); + }, + ); + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_ENTER_COMMAND, + (event, targetEditor) => { + const selection = $getSelection(); + if ( + primarySelectedCellID === null && + !isEditing && + $isNodeSelection(selection) && + selection.has(nodeKey) && + selection.getNodes().length === 1 && + targetEditor === editor + ) { + const firstCellID = rows[0].cells[0].id; + setPrimarySelectedCellID(firstCellID); + focusCell(tableElem, firstCellID); + event.preventDefault(); + event.stopPropagation(); + clearSelection(); + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_TAB_COMMAND, + (event) => { + const selection = $getSelection(); + if ( + !isEditing && + selection === null && + primarySelectedCellID !== null + ) { + const isBackward = event.shiftKey; + const [x, y] = cellCoordMap.get(primarySelectedCellID) as [ + number, + number, + ]; + event.preventDefault(); + let nextX = null; + let nextY = null; + if (x === 0 && isBackward) { + if (y !== 0) { + nextY = y - 1; + nextX = rows[nextY].cells.length - 1; + } + } else if (x === rows[y].cells.length - 1 && !isBackward) { + if (y !== rows.length - 1) { + nextY = y + 1; + nextX = 0; + } + } else if (!isBackward) { + nextX = x + 1; + nextY = y; + } else { + nextX = x - 1; + nextY = y; + } + if (nextX !== null && nextY !== null) { + modifySelectedCells(nextX, nextY, false); + return true; + } + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_ARROW_UP_COMMAND, + (event, targetEditor) => { + const selection = $getSelection(); + if (!isEditing && selection === null) { + const extend = event.shiftKey; + const cellID = extend + ? lastCellIDRef.current || primarySelectedCellID + : primarySelectedCellID; + if (cellID !== null) { + const [x, y] = cellCoordMap.get(cellID) as [number, number]; + if (y !== 0) { + modifySelectedCells(x, y - 1, extend); + return true; + } + } + } + if (!$isRangeSelection(selection) || targetEditor !== cellEditor) { + return false; + } + if ( + selection.isCollapsed() && + selection.anchor + .getNode() + .getTopLevelElementOrThrow() + .getPreviousSibling() === null + ) { + event.preventDefault(); + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_ARROW_DOWN_COMMAND, + (event, targetEditor) => { + const selection = $getSelection(); + if (!isEditing && selection === null) { + const extend = event.shiftKey; + const cellID = extend + ? lastCellIDRef.current || primarySelectedCellID + : primarySelectedCellID; + if (cellID !== null) { + const [x, y] = cellCoordMap.get(cellID) as [number, number]; + if (y !== rows.length - 1) { + modifySelectedCells(x, y + 1, extend); + return true; + } + } + } + if (!$isRangeSelection(selection) || targetEditor !== cellEditor) { + return false; + } + if ( + selection.isCollapsed() && + selection.anchor + .getNode() + .getTopLevelElementOrThrow() + .getNextSibling() === null + ) { + event.preventDefault(); + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_ARROW_LEFT_COMMAND, + (event, targetEditor) => { + const selection = $getSelection(); + if (!isEditing && selection === null) { + const extend = event.shiftKey; + const cellID = extend + ? lastCellIDRef.current || primarySelectedCellID + : primarySelectedCellID; + if (cellID !== null) { + const [x, y] = cellCoordMap.get(cellID) as [number, number]; + if (x !== 0) { + modifySelectedCells(x - 1, y, extend); + return true; + } + } + } + if (!$isRangeSelection(selection) || targetEditor !== cellEditor) { + return false; + } + if (selection.isCollapsed() && selection.anchor.offset === 0) { + event.preventDefault(); + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_ARROW_RIGHT_COMMAND, + (event, targetEditor) => { + const selection = $getSelection(); + if (!isEditing && selection === null) { + const extend = event.shiftKey; + const cellID = extend + ? lastCellIDRef.current || primarySelectedCellID + : primarySelectedCellID; + if (cellID !== null) { + const [x, y] = cellCoordMap.get(cellID) as [number, number]; + if (x !== rows[y].cells.length - 1) { + modifySelectedCells(x + 1, y, extend); + return true; + } + } + } + if (!$isRangeSelection(selection) || targetEditor !== cellEditor) { + return false; + } + if (selection.isCollapsed()) { + const anchor = selection.anchor; + if ( + (anchor.type === 'text' && + anchor.offset === anchor.getNode().getTextContentSize()) || + (anchor.type === 'element' && + anchor.offset === anchor.getNode().getChildrenSize()) + ) { + event.preventDefault(); + return true; + } + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + editor.registerCommand( + KEY_ESCAPE_COMMAND, + (event, targetEditor) => { + const selection = $getSelection(); + if (!isEditing && selection === null && targetEditor === editor) { + setSelected(true); + setPrimarySelectedCellID(null); + selectTable(); + return true; + } + if (!$isRangeSelection(selection)) { + return false; + } + if (isEditing) { + saveEditorToJSON(); + setIsEditing(false); + if (primarySelectedCellID !== null) { + setTimeout(() => { + focusCell(tableElem, primarySelectedCellID); + }, 20); + } + return true; + } + return false; + }, + COMMAND_PRIORITY_LOW, + ), + ); + }, [ + cellCoordMap, + cellEditor, + clearCellsCommand, + clearSelection, + editor, + isEditing, + modifySelectedCells, + nodeKey, + primarySelectedCellID, + rows, + saveEditorToJSON, + selectTable, + selectedCellIDs, + setSelected, + updateTableNode, + ]); + + if (cellEditor === null) { + return; + } + + return ( +
+ + + {rows.map((row) => ( + + {row.cells.map((cell) => { + const {id} = cell; + return ( + + ); + })} + + ))} + +
+ {showAddColumns && ( +