├── .babelrc.json ├── .eslintignore ├── .eslintrc.json ├── .github └── workflows │ ├── codecov.yaml │ └── release-please.yaml ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── config ├── webpack.apple.js ├── webpack.common.js ├── webpack.dev.js ├── webpack.iframe.js └── webpack.prd.js ├── jest.config.json ├── package-lock.json ├── package.json ├── src ├── adapter │ ├── dataManipulator │ │ ├── nbCRDT │ │ │ ├── crdt.ts │ │ │ ├── history │ │ │ │ ├── delta.ts │ │ │ │ ├── history.ts │ │ │ │ ├── index.ts │ │ │ │ ├── operation.ts │ │ │ │ └── remote.ts │ │ │ ├── index.ts │ │ │ ├── operator │ │ │ │ ├── base.ts │ │ │ │ ├── command │ │ │ │ │ ├── clipboard.ts │ │ │ │ │ ├── format.ts │ │ │ │ │ ├── indentation.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── insBlock.ts │ │ │ │ │ ├── insParagraphAndSelect.ts │ │ │ │ │ ├── lowLevel │ │ │ │ │ │ ├── block │ │ │ │ │ │ │ ├── del.ts │ │ │ │ │ │ │ ├── indent.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── ins.ts │ │ │ │ │ │ │ ├── merge.ts │ │ │ │ │ │ │ ├── mov.ts │ │ │ │ │ │ │ └── set.ts │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── text │ │ │ │ │ │ │ ├── del.ts │ │ │ │ │ │ │ ├── fmt.ts │ │ │ │ │ │ │ ├── indent.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── ins.ts │ │ │ │ │ │ │ └── mod.ts │ │ │ │ │ └── texting │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ ├── deleteText.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── prop.ts │ │ │ │ ├── ctrb │ │ │ │ │ ├── index.ts │ │ │ │ │ └── op.ts │ │ │ │ └── index.ts │ │ │ ├── parser │ │ │ │ ├── clipboard.ts │ │ │ │ ├── data.ts │ │ │ │ ├── html │ │ │ │ │ ├── decoder.ts │ │ │ │ │ ├── encoder.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── markdown │ │ │ │ │ ├── index.ts │ │ │ │ │ └── text.ts │ │ │ │ └── text.ts │ │ │ └── synchronizer │ │ │ │ ├── ctrbs.ts │ │ │ │ └── index.ts │ │ └── nbCRDTWithNBDB │ │ │ ├── index.ts │ │ │ ├── keymap │ │ │ ├── board.ts │ │ │ ├── common.ts │ │ │ ├── index.ts │ │ │ └── spreadsheet.ts │ │ │ ├── nbdb │ │ │ ├── evaluator │ │ │ │ ├── aggregation.ts │ │ │ │ ├── formula.test.ts │ │ │ │ ├── formula.ts │ │ │ │ └── record.ts │ │ │ ├── field.ts │ │ │ ├── index.ts │ │ │ ├── label.ts │ │ │ ├── labeledRecordIDSet.ts │ │ │ └── value.ts │ │ │ ├── operator │ │ │ ├── clipboard │ │ │ │ ├── index.ts │ │ │ │ └── remapper.ts │ │ │ └── index.ts │ │ │ ├── parser │ │ │ ├── clipboard.ts │ │ │ ├── data.ts │ │ │ ├── html │ │ │ │ ├── decoder.ts │ │ │ │ ├── encoder.ts │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ └── markdown.ts │ │ │ ├── presenter │ │ │ ├── NBDBSymbol.tsx │ │ │ ├── blockTypedContent │ │ │ │ ├── board │ │ │ │ │ ├── container │ │ │ │ │ │ ├── editingValue.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── common │ │ │ │ │ ├── Adder.tsx │ │ │ │ │ ├── controller │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── title.tsx │ │ │ │ │ ├── editing.tsx │ │ │ │ │ ├── label.tsx │ │ │ │ │ └── value │ │ │ │ │ │ ├── bool.tsx │ │ │ │ │ │ ├── date.tsx │ │ │ │ │ │ ├── formula.tsx │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── labels.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── spreadsheet │ │ │ │ │ ├── container │ │ │ │ │ ├── cell │ │ │ │ │ │ ├── index.tsx │ │ │ │ │ │ └── value.tsx │ │ │ │ │ ├── footer.tsx │ │ │ │ │ ├── header.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── records.tsx │ │ │ │ │ └── index.tsx │ │ │ └── popup │ │ │ │ ├── Field.tsx │ │ │ │ ├── FieldLabel.tsx │ │ │ │ ├── common │ │ │ │ ├── AdderBTN.tsx │ │ │ │ ├── EditableLabel.tsx │ │ │ │ ├── Fields.tsx │ │ │ │ ├── Label.tsx │ │ │ │ ├── LabelEditor.tsx │ │ │ │ ├── Labels.tsx │ │ │ │ ├── LabelsEditor.tsx │ │ │ │ ├── Mover.tsx │ │ │ │ └── formula │ │ │ │ │ ├── editor.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── input.tsx │ │ │ │ │ ├── model │ │ │ │ │ ├── decoder.ts │ │ │ │ │ ├── helper │ │ │ │ │ │ ├── function.ts │ │ │ │ │ │ ├── operator.ts │ │ │ │ │ │ └── types.ts │ │ │ │ │ └── interpreter │ │ │ │ │ │ ├── elements │ │ │ │ │ │ ├── basic.ts │ │ │ │ │ │ ├── complex.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── selector │ │ │ │ │ ├── helpers.ts │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── pickers │ │ │ │ ├── Aggregation.tsx │ │ │ │ ├── Date.tsx │ │ │ │ ├── Labels.tsx │ │ │ │ └── Template.tsx │ │ │ │ └── template │ │ │ │ ├── EditFields.tsx │ │ │ │ ├── EditLabels.tsx │ │ │ │ ├── Filter.tsx │ │ │ │ ├── LabeledField.tsx │ │ │ │ ├── Settings.tsx │ │ │ │ └── Sort.tsx │ │ │ ├── range.ts │ │ │ └── state │ │ │ ├── drag │ │ │ ├── board │ │ │ │ ├── col.ts │ │ │ │ └── record.ts │ │ │ ├── index.ts │ │ │ ├── popup │ │ │ │ ├── fields.tsx │ │ │ │ └── labels.tsx │ │ │ └── spreadsheet │ │ │ │ ├── col.ts │ │ │ │ └── record.ts │ │ │ ├── index.ts │ │ │ └── template │ │ │ ├── board.ts │ │ │ ├── common │ │ │ ├── index.ts │ │ │ ├── state.ts │ │ │ └── template.ts │ │ │ ├── index.ts │ │ │ └── spreadsheet.ts │ └── device │ │ ├── apple.ts │ │ ├── common.ts │ │ ├── debug.ts │ │ └── iframe.ts ├── demo │ ├── basics.ts │ └── mermaid.json ├── domain │ ├── entity │ │ ├── block │ │ │ ├── common.ts │ │ │ ├── index.ts │ │ │ └── props │ │ │ │ ├── color.ts │ │ │ │ ├── common.ts │ │ │ │ ├── database │ │ │ │ ├── functions.ts │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── text.ts │ │ ├── common.ts │ │ ├── global │ │ │ ├── common.d.ts │ │ │ ├── svg.d.ts │ │ │ └── window.ts │ │ ├── index.ts │ │ ├── range.ts │ │ ├── selection.ts │ │ └── theme.ts │ ├── index.ts │ └── usecase │ │ ├── dataManipulator │ │ ├── index.ts │ │ └── operator.ts │ │ ├── dom │ │ ├── common.ts │ │ ├── index.ts │ │ └── selector.ts │ │ ├── editor │ │ ├── index.ts │ │ ├── present.ts │ │ └── selector.ts │ │ ├── emitter │ │ ├── index.ts │ │ └── uploader.ts │ │ ├── index.ts │ │ ├── props │ │ ├── codeLangs.test.ts │ │ └── codeLangs.ts │ │ ├── sanitizer │ │ ├── index.ts │ │ └── text.ts │ │ ├── state │ │ ├── changed.ts │ │ ├── common │ │ │ ├── index.ts │ │ │ └── recoilExternalPortal.tsx │ │ ├── drag.ts │ │ ├── edited.ts │ │ ├── index.ts │ │ ├── mouse.ts │ │ ├── popup.ts │ │ ├── selection.ts │ │ ├── theme.ts │ │ └── working.ts │ │ └── uiHandler │ │ ├── clipboard.ts │ │ ├── common.ts │ │ ├── drag │ │ ├── block.ts │ │ ├── common.ts │ │ └── index.ts │ │ ├── external │ │ ├── index.ts │ │ └── searcher.ts │ │ ├── index.ts │ │ ├── keymap │ │ ├── common.ts │ │ ├── formatText.ts │ │ ├── history.ts │ │ ├── indentation.ts │ │ ├── index.ts │ │ ├── prettyArrow.ts │ │ ├── preventInvalidDeletion.ts │ │ ├── selectAll.ts │ │ ├── space │ │ │ ├── index.ts │ │ │ ├── markdownBlock.ts │ │ │ └── markdownInline.ts │ │ └── voidBlock.ts │ │ ├── mouse.ts │ │ ├── texting │ │ ├── index.ts │ │ └── onDOMBeforeInput.ts │ │ └── touch.ts ├── index.html ├── main.apple.ts ├── main.component.tsx ├── main.debug.tsx ├── main.iframe.ts ├── presenter │ ├── blocks │ │ ├── index.tsx │ │ ├── note.tsx │ │ ├── parts │ │ │ └── handle.tsx │ │ └── typedContent │ │ │ ├── basicText.tsx │ │ │ ├── cl.tsx │ │ │ ├── code │ │ │ ├── decorator.ts │ │ │ ├── index.tsx │ │ │ └── prismjs.ts │ │ │ ├── common │ │ │ ├── EditableText.tsx │ │ │ └── textContent.tsx │ │ │ ├── hr.tsx │ │ │ ├── img │ │ │ ├── caption.tsx │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── link.tsx │ │ │ ├── mermaid │ │ │ ├── index.tsx │ │ │ └── mermaid.ts │ │ │ ├── ol.tsx │ │ │ └── ul.tsx │ ├── common │ │ ├── InputEl.tsx │ │ ├── SelectEl.tsx │ │ ├── ViewModel.tsx │ │ └── icon │ │ │ ├── board.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── chevronRight.tsx │ │ │ ├── cross.tsx │ │ │ ├── diameter.tsx │ │ │ ├── more.tsx │ │ │ ├── move.tsx │ │ │ ├── plus.tsx │ │ │ ├── table.tsx │ │ │ └── trash.tsx │ ├── external │ │ ├── style.scss │ │ └── toolbar.tsx │ ├── index.tsx │ └── layer │ │ ├── dragging.tsx │ │ ├── editor.tsx │ │ ├── index.ts │ │ ├── popup │ │ ├── Popup.tsx │ │ ├── block │ │ │ ├── CodeLanguagePicker.tsx │ │ │ ├── LinkURLSetter.tsx │ │ │ └── Mermaid.tsx │ │ ├── blockHandle.tsx │ │ ├── common │ │ │ └── Layout.tsx │ │ └── index.tsx │ │ └── selection.tsx ├── resource │ └── style │ │ ├── all.scss │ │ ├── common │ │ ├── color.scss │ │ ├── mixin.scss │ │ ├── theme.scss │ │ └── var.scss │ │ ├── global.scss │ │ ├── nb-block │ │ ├── all.scss │ │ ├── blockquote.scss │ │ ├── checkbox.scss │ │ ├── codeblock.scss │ │ ├── common │ │ │ ├── all.scss │ │ │ ├── destination.scss │ │ │ ├── handle.scss │ │ │ └── label.scss │ │ ├── database │ │ │ ├── all.scss │ │ │ ├── board.scss │ │ │ ├── common.scss │ │ │ └── spreadsheet.scss │ │ ├── header.scss │ │ ├── hr.scss │ │ ├── img.scss │ │ ├── link.scss │ │ ├── list.scss │ │ ├── mermaid.scss │ │ ├── prismjs │ │ │ ├── prism-okaidia.scss │ │ │ └── prism.scss │ │ └── text.scss │ │ ├── nb-editor.scss │ │ ├── nb-ui-layer │ │ ├── all.scss │ │ ├── ghost.scss │ │ └── popup │ │ │ ├── all.scss │ │ │ ├── code-language.scss │ │ │ ├── layer.scss │ │ │ ├── mermaid.scss │ │ │ ├── nbdb-field.scss │ │ │ ├── nbdb-formula.scss │ │ │ ├── nbdb-labels.scss │ │ │ ├── nbdb-list.scss │ │ │ └── nbdb.scss │ │ ├── nb-ui │ │ ├── all.scss │ │ ├── buttons.scss │ │ ├── collapsible.scss │ │ ├── common.scss │ │ ├── formula-symbols.scss │ │ ├── is-hoverable.scss │ │ └── list.scss │ │ └── selection.scss ├── standalone.ts ├── test │ └── di.ts └── utils │ ├── environment.ts │ ├── hotkeys.ts │ ├── react │ └── index.ts │ ├── saveFile.ts │ ├── smooth-scroll.js │ └── string.ts └── tsconfig.json /.babelrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", {"targets": {"node": "current"}}], 4 | "@babel/preset-typescript", 5 | "@babel/preset-react" 6 | ] 7 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | coverage/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "plugins": [ 4 | "@typescript-eslint", 5 | "unused-imports" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/recommended" 10 | ], 11 | "rules": { 12 | "@typescript-eslint/no-empty-function": "off", 13 | "@typescript-eslint/no-explicit-any": "off", 14 | "@typescript-eslint/no-non-null-assertion": "off", 15 | "@typescript-eslint/no-unused-vars": [ 16 | "warn", 17 | { 18 | "argsIgnorePattern": "^_", 19 | "varsIgnorePattern": "^_", 20 | "caughtErrorsIgnorePattern": "^_" 21 | } 22 | ], 23 | "eqeqeq": "off", 24 | "import/no-anonymous-default-export": "off", 25 | "indent": ["error", 2], 26 | "unused-imports/no-unused-imports": "error", 27 | "quotes": [ 28 | "error", 29 | "double" 30 | ], 31 | "react/display-name": "off", 32 | "react/no-unescaped-entities": "off", 33 | "react-hooks/exhaustive-deps": "off", 34 | "react-hooks/rules-of-hooks": "off", 35 | "semi": [ 36 | "error", 37 | "never" 38 | ], 39 | "space-in-parens": [ 40 | "error", 41 | "never" 42 | ], 43 | "object-curly-spacing": [ 44 | "error", 45 | "never" 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yaml: -------------------------------------------------------------------------------- 1 | name: codecov 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18 22 | 23 | - name: Cache 24 | id: npm-cache 25 | uses: actions/cache@v4 26 | with: 27 | path: ./node_modules 28 | key: ${{ runner.os }}-npm-cache-${{ hashFiles('package-lock.json') }} 29 | 30 | - name: Install 31 | if: steps.npm-cache.outputs.cache-hit != 'true' 32 | run: npm ci 33 | 34 | - name: Lint 35 | run: npm run lint 36 | 37 | - name: Test 38 | run: npm test -- --coverage 39 | 40 | - name: Codecov 41 | uses: codecov/codecov-action@v4 42 | with: 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | slug: notebox/nb-editor 45 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: release-please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: google-github-actions/release-please-action@v3 17 | with: 18 | release-type: node 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # production 8 | /dist 9 | 10 | # editor 11 | .idea 12 | .vscode 13 | *.code-workspace 14 | *.suo 15 | *.ntvs* 16 | *.njsproj 17 | *.sln 18 | *.svd 19 | *.userprefs 20 | *.csproj 21 | *.pidb 22 | *.user 23 | *.unityproj 24 | *.booproj 25 | ExportedObj/ 26 | *.xcuserstate 27 | 28 | # misc 29 | .DS_Store 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | .eslintcache 35 | 36 | # log 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | 41 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "bracketSpacing": false, 3 | "trailingComma": "es5", 4 | "arrowParens": "avoid" 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nb-editor 2 | 3 | [![codecov](https://codecov.io/gh/notebox/nb-editor/graph/badge.svg?token=65QQDKPEPV)](https://codecov.io/gh/notebox/nb-editor) 4 | [![sponsor](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/notebox) 5 | 6 | A block based editor which is powered by [nb-crdt](https://github.com/notebox/nb-crdt) that enables collaborative editing across multiple participants allowing for flexible data sharing across spreadsheets, boards, rich text, and more. 7 | 8 | Web-based multi-platform support (primarily focusing on macOS and iOS). Support for Composition input, such as Korean and Japanese, suggestion input, and iPad Pencil scribble. 9 | 10 | ## Development 11 | 12 | ### Prerequisites 13 | ``` 14 | npm install 15 | ``` 16 | 17 | ### Run 18 | ```bash 19 | npm start 20 | ``` 21 | 22 | ## Usage 23 | ### for iframe 24 | ``` 25 | ... 26 | // set "./static/app.css" to "node_modules/@notebox/nb-editor/iframe/app.css" 27 |