├── .dockerignore
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .gitpod.yml
├── Dockerfile
├── LICENSE
├── Makefile
├── README.md
├── codemirror.next
├── README.md
├── codemirror.next.html
├── codemirror.next.js
├── package-lock.json
├── package.json
└── webpack.config.js
├── codemirror
├── README.md
├── codemirror.css
├── codemirror.html
├── codemirror.js
├── package-lock.json
├── package.json
└── webpack.config.js
├── demo-server
├── README.md
├── demo-server.js
├── package-lock.json
└── package.json
├── index.html
├── monaco-react
├── .gitignore
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.tsx
│ ├── global.css
│ └── main.tsx
└── vite.config.ts
├── monaco
├── README.md
├── monaco.html
├── monaco.js
├── package-lock.json
├── package.json
└── webpack.config.js
├── package-lock.json
├── package.json
├── prosemirror-versions
├── README.md
├── package-lock.json
├── package.json
├── prosemirror-versions.html
├── prosemirror-versions.js
├── prosemirror.css
├── schema.js
├── version.css
└── webpack.config.js
├── prosemirror
├── README.md
├── package-lock.json
├── package.json
├── prosemirror.css
├── prosemirror.html
├── prosemirror.js
├── schema.js
└── webpack.config.js
├── quill
├── README.md
├── package-lock.json
├── package.json
├── quill.css
├── quill.html
├── quill.js
└── webpack.config.js
└── react-prosemirror
├── README.md
├── index.html
├── index.jsx
├── package-lock.json
├── package.json
└── prosemirror.css
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/dist
3 | .vscode
4 | .git
5 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ main ]
9 | pull_request:
10 | branches: [ main ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [20.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v3
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | - run: npm ci
29 | - run: npm run build --if-present
30 | - run: npm test
31 |
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/node_modules
2 | **/dist
3 | .vscode
4 |
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | tasks:
2 | - init: npm i
3 | command: npm start
4 | ports:
5 | - port: 3000
6 | onOpen: open-preview
7 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile:1.4
2 |
3 | # Adjust NODE_VERSION as desired
4 | ARG NODE_VERSION=20.5.1
5 | FROM node:${NODE_VERSION}-slim as base
6 |
7 | LABEL fly_launch_runtime="Node.js"
8 |
9 | # Node.js app lives here
10 | WORKDIR /app
11 |
12 | # Set production environment
13 | ENV NODE_ENV="production"
14 |
15 |
16 | # Throw-away build stage to reduce size of final image
17 | FROM base as build
18 |
19 | # Install packages needed to build node modules
20 | RUN apt-get update -qq && \
21 | apt-get install -y build-essential pkg-config python-is-python3
22 |
23 | # Copy application code
24 | COPY . .
25 |
26 | RUN npm i -g webpack-cli
27 | RUN make static-content
28 |
29 | # Final stage for app image
30 | FROM base
31 |
32 | # Copy built application
33 | COPY --from=build /app /app
34 |
35 | # Start the server by default, this can be overwritten at runtime
36 | EXPOSE 3000
37 | CMD [ "npm", "run", "demo-server"]
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
47 | This is a demo of the Yjs ⇔ 48 | CodeMirror 6 binding: 49 | y-codemirror.next. 50 |
51 |52 | The content of this editor is shared with every client that visits this 53 | domain. 54 |
55 |47 | This is a demo of the Yjs ⇔ 48 | CodeMirror binding: 49 | y-codemirror. 50 |
51 |52 | The content of this editor is shared with every client that visits this 53 | domain. 54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /codemirror/codemirror.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | // @ts-ignore 4 | import CodeMirror from 'codemirror' 5 | import * as Y from 'yjs' 6 | import { WebsocketProvider } from 'y-websocket' 7 | import { CodemirrorBinding } from 'y-codemirror' 8 | import 'codemirror/mode/javascript/javascript.js' 9 | 10 | const roomname = `codemirror-demo-${new Date().toLocaleDateString('en-CA')}` 11 | 12 | window.addEventListener('load', () => { 13 | const ydoc = new Y.Doc() 14 | const provider = new WebsocketProvider( 15 | 'wss://demos.yjs.dev/ws', // use the public ws server 16 | // `ws${location.protocol.slice(4)}//${location.host}/ws`, // alternatively: use the local ws server (run `npm start` in root directory) 17 | roomname, 18 | ydoc 19 | ) 20 | const ytext = ydoc.getText('codemirror') 21 | const editorContainer = document.createElement('div') 22 | editorContainer.setAttribute('id', 'editor') 23 | document.body.insertBefore(editorContainer, null) 24 | 25 | const editor = CodeMirror(editorContainer, { 26 | mode: 'javascript', 27 | lineNumbers: true 28 | }) 29 | 30 | const binding = new CodemirrorBinding(ytext, editor, provider.awareness) 31 | 32 | const connectBtn = /** @type {HTMLElement} */ (document.getElementById('y-connect-btn')) 33 | connectBtn.addEventListener('click', () => { 34 | if (provider.shouldConnect) { 35 | provider.disconnect() 36 | connectBtn.textContent = 'Connect' 37 | } else { 38 | provider.connect() 39 | connectBtn.textContent = 'Disconnect' 40 | } 41 | }) 42 | 43 | // @ts-ignore 44 | window.example = { provider, ydoc, ytext, binding, Y } 45 | }) 46 | -------------------------------------------------------------------------------- /codemirror/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yjs-codemirror-demo", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "yjs-codemirror-demo", 9 | "version": "1.0.0", 10 | "license": "UNLICENSE", 11 | "dependencies": { 12 | "codemirror": "^5.64.0", 13 | "y-codemirror": "^2.1.1", 14 | "y-websocket": "^1.3.18", 15 | "yjs": "^13.5.22" 16 | } 17 | }, 18 | "node_modules/abstract-leveldown": { 19 | "version": "6.2.3", 20 | "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", 21 | "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", 22 | "optional": true, 23 | "dependencies": { 24 | "buffer": "^5.5.0", 25 | "immediate": "^3.2.3", 26 | "level-concat-iterator": "~2.0.0", 27 | "level-supports": "~1.0.0", 28 | "xtend": "~4.0.0" 29 | }, 30 | "engines": { 31 | "node": ">=6" 32 | } 33 | }, 34 | "node_modules/async-limiter": { 35 | "version": "1.0.1", 36 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 37 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", 38 | "optional": true 39 | }, 40 | "node_modules/base64-js": { 41 | "version": "1.5.1", 42 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 43 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 44 | "funding": [ 45 | { 46 | "type": "github", 47 | "url": "https://github.com/sponsors/feross" 48 | }, 49 | { 50 | "type": "patreon", 51 | "url": "https://www.patreon.com/feross" 52 | }, 53 | { 54 | "type": "consulting", 55 | "url": "https://feross.org/support" 56 | } 57 | ], 58 | "optional": true 59 | }, 60 | "node_modules/buffer": { 61 | "version": "5.7.1", 62 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 63 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 64 | "funding": [ 65 | { 66 | "type": "github", 67 | "url": "https://github.com/sponsors/feross" 68 | }, 69 | { 70 | "type": "patreon", 71 | "url": "https://www.patreon.com/feross" 72 | }, 73 | { 74 | "type": "consulting", 75 | "url": "https://feross.org/support" 76 | } 77 | ], 78 | "optional": true, 79 | "dependencies": { 80 | "base64-js": "^1.3.1", 81 | "ieee754": "^1.1.13" 82 | } 83 | }, 84 | "node_modules/codemirror": { 85 | "version": "5.65.16", 86 | "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.16.tgz", 87 | "integrity": "sha512-br21LjYmSlVL0vFCPWPfhzUCT34FM/pAdK7rRIZwa0rrtrIdotvP4Oh4GUHsu2E3IrQMCfRkL/fN3ytMNxVQvg==" 88 | }, 89 | "node_modules/deferred-leveldown": { 90 | "version": "5.3.0", 91 | "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", 92 | "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", 93 | "optional": true, 94 | "dependencies": { 95 | "abstract-leveldown": "~6.2.1", 96 | "inherits": "^2.0.3" 97 | }, 98 | "engines": { 99 | "node": ">=6" 100 | } 101 | }, 102 | "node_modules/encoding-down": { 103 | "version": "6.3.0", 104 | "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", 105 | "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", 106 | "optional": true, 107 | "dependencies": { 108 | "abstract-leveldown": "^6.2.1", 109 | "inherits": "^2.0.3", 110 | "level-codec": "^9.0.0", 111 | "level-errors": "^2.0.0" 112 | }, 113 | "engines": { 114 | "node": ">=6" 115 | } 116 | }, 117 | "node_modules/errno": { 118 | "version": "0.1.8", 119 | "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", 120 | "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", 121 | "optional": true, 122 | "dependencies": { 123 | "prr": "~1.0.1" 124 | }, 125 | "bin": { 126 | "errno": "cli.js" 127 | } 128 | }, 129 | "node_modules/ieee754": { 130 | "version": "1.2.1", 131 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 132 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 133 | "funding": [ 134 | { 135 | "type": "github", 136 | "url": "https://github.com/sponsors/feross" 137 | }, 138 | { 139 | "type": "patreon", 140 | "url": "https://www.patreon.com/feross" 141 | }, 142 | { 143 | "type": "consulting", 144 | "url": "https://feross.org/support" 145 | } 146 | ], 147 | "optional": true 148 | }, 149 | "node_modules/immediate": { 150 | "version": "3.3.0", 151 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", 152 | "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", 153 | "optional": true 154 | }, 155 | "node_modules/inherits": { 156 | "version": "2.0.4", 157 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 158 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 159 | "optional": true 160 | }, 161 | "node_modules/isomorphic.js": { 162 | "version": "0.2.5", 163 | "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", 164 | "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", 165 | "funding": { 166 | "type": "GitHub Sponsors ❤", 167 | "url": "https://github.com/sponsors/dmonad" 168 | } 169 | }, 170 | "node_modules/level": { 171 | "version": "6.0.1", 172 | "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", 173 | "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", 174 | "optional": true, 175 | "dependencies": { 176 | "level-js": "^5.0.0", 177 | "level-packager": "^5.1.0", 178 | "leveldown": "^5.4.0" 179 | }, 180 | "engines": { 181 | "node": ">=8.6.0" 182 | }, 183 | "funding": { 184 | "type": "opencollective", 185 | "url": "https://opencollective.com/level" 186 | } 187 | }, 188 | "node_modules/level-codec": { 189 | "version": "9.0.2", 190 | "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", 191 | "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", 192 | "optional": true, 193 | "dependencies": { 194 | "buffer": "^5.6.0" 195 | }, 196 | "engines": { 197 | "node": ">=6" 198 | } 199 | }, 200 | "node_modules/level-concat-iterator": { 201 | "version": "2.0.1", 202 | "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", 203 | "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", 204 | "optional": true, 205 | "engines": { 206 | "node": ">=6" 207 | } 208 | }, 209 | "node_modules/level-errors": { 210 | "version": "2.0.1", 211 | "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", 212 | "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", 213 | "optional": true, 214 | "dependencies": { 215 | "errno": "~0.1.1" 216 | }, 217 | "engines": { 218 | "node": ">=6" 219 | } 220 | }, 221 | "node_modules/level-iterator-stream": { 222 | "version": "4.0.2", 223 | "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", 224 | "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", 225 | "optional": true, 226 | "dependencies": { 227 | "inherits": "^2.0.4", 228 | "readable-stream": "^3.4.0", 229 | "xtend": "^4.0.2" 230 | }, 231 | "engines": { 232 | "node": ">=6" 233 | } 234 | }, 235 | "node_modules/level-js": { 236 | "version": "5.0.2", 237 | "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", 238 | "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", 239 | "optional": true, 240 | "dependencies": { 241 | "abstract-leveldown": "~6.2.3", 242 | "buffer": "^5.5.0", 243 | "inherits": "^2.0.3", 244 | "ltgt": "^2.1.2" 245 | } 246 | }, 247 | "node_modules/level-packager": { 248 | "version": "5.1.1", 249 | "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", 250 | "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", 251 | "optional": true, 252 | "dependencies": { 253 | "encoding-down": "^6.3.0", 254 | "levelup": "^4.3.2" 255 | }, 256 | "engines": { 257 | "node": ">=6" 258 | } 259 | }, 260 | "node_modules/level-supports": { 261 | "version": "1.0.1", 262 | "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", 263 | "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", 264 | "optional": true, 265 | "dependencies": { 266 | "xtend": "^4.0.2" 267 | }, 268 | "engines": { 269 | "node": ">=6" 270 | } 271 | }, 272 | "node_modules/leveldown": { 273 | "version": "5.6.0", 274 | "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", 275 | "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", 276 | "hasInstallScript": true, 277 | "optional": true, 278 | "dependencies": { 279 | "abstract-leveldown": "~6.2.1", 280 | "napi-macros": "~2.0.0", 281 | "node-gyp-build": "~4.1.0" 282 | }, 283 | "engines": { 284 | "node": ">=8.6.0" 285 | } 286 | }, 287 | "node_modules/levelup": { 288 | "version": "4.4.0", 289 | "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", 290 | "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", 291 | "optional": true, 292 | "dependencies": { 293 | "deferred-leveldown": "~5.3.0", 294 | "level-errors": "~2.0.0", 295 | "level-iterator-stream": "~4.0.0", 296 | "level-supports": "~1.0.0", 297 | "xtend": "~4.0.0" 298 | }, 299 | "engines": { 300 | "node": ">=6" 301 | } 302 | }, 303 | "node_modules/lib0": { 304 | "version": "0.2.88", 305 | "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.88.tgz", 306 | "integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==", 307 | "dependencies": { 308 | "isomorphic.js": "^0.2.4" 309 | }, 310 | "bin": { 311 | "0gentesthtml": "bin/gentesthtml.js", 312 | "0serve": "bin/0serve.js" 313 | }, 314 | "engines": { 315 | "node": ">=16" 316 | }, 317 | "funding": { 318 | "type": "GitHub Sponsors ❤", 319 | "url": "https://github.com/sponsors/dmonad" 320 | } 321 | }, 322 | "node_modules/lodash.debounce": { 323 | "version": "4.0.8", 324 | "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", 325 | "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" 326 | }, 327 | "node_modules/ltgt": { 328 | "version": "2.2.1", 329 | "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", 330 | "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", 331 | "optional": true 332 | }, 333 | "node_modules/napi-macros": { 334 | "version": "2.0.0", 335 | "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", 336 | "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", 337 | "optional": true 338 | }, 339 | "node_modules/node-gyp-build": { 340 | "version": "4.1.1", 341 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", 342 | "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", 343 | "optional": true, 344 | "bin": { 345 | "node-gyp-build": "bin.js", 346 | "node-gyp-build-optional": "optional.js", 347 | "node-gyp-build-test": "build-test.js" 348 | } 349 | }, 350 | "node_modules/prr": { 351 | "version": "1.0.1", 352 | "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", 353 | "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", 354 | "optional": true 355 | }, 356 | "node_modules/readable-stream": { 357 | "version": "3.6.2", 358 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 359 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 360 | "optional": true, 361 | "dependencies": { 362 | "inherits": "^2.0.3", 363 | "string_decoder": "^1.1.1", 364 | "util-deprecate": "^1.0.1" 365 | }, 366 | "engines": { 367 | "node": ">= 6" 368 | } 369 | }, 370 | "node_modules/safe-buffer": { 371 | "version": "5.2.1", 372 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 373 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 374 | "funding": [ 375 | { 376 | "type": "github", 377 | "url": "https://github.com/sponsors/feross" 378 | }, 379 | { 380 | "type": "patreon", 381 | "url": "https://www.patreon.com/feross" 382 | }, 383 | { 384 | "type": "consulting", 385 | "url": "https://feross.org/support" 386 | } 387 | ], 388 | "optional": true 389 | }, 390 | "node_modules/string_decoder": { 391 | "version": "1.3.0", 392 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 393 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 394 | "optional": true, 395 | "dependencies": { 396 | "safe-buffer": "~5.2.0" 397 | } 398 | }, 399 | "node_modules/util-deprecate": { 400 | "version": "1.0.2", 401 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 402 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 403 | "optional": true 404 | }, 405 | "node_modules/ws": { 406 | "version": "6.2.2", 407 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", 408 | "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", 409 | "optional": true, 410 | "dependencies": { 411 | "async-limiter": "~1.0.0" 412 | } 413 | }, 414 | "node_modules/xtend": { 415 | "version": "4.0.2", 416 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 417 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 418 | "optional": true, 419 | "engines": { 420 | "node": ">=0.4" 421 | } 422 | }, 423 | "node_modules/y-codemirror": { 424 | "version": "2.1.1", 425 | "resolved": "https://registry.npmjs.org/y-codemirror/-/y-codemirror-2.1.1.tgz", 426 | "integrity": "sha512-QXHaOkvEJs3pB82dkW1aGfWUd4S1RA1ORtXWtprHClbqBiCOY19VKiojScSTyl8rTaOZ/zblEq+SNH2sd3Umiw==", 427 | "dependencies": { 428 | "lib0": "^0.2.41" 429 | }, 430 | "funding": { 431 | "type": "GitHub Sponsors ❤", 432 | "url": "https://github.com/sponsors/dmonad" 433 | }, 434 | "peerDependencies": { 435 | "codemirror": "^5.52.2", 436 | "yjs": "^13.0.0" 437 | } 438 | }, 439 | "node_modules/y-leveldb": { 440 | "version": "0.1.2", 441 | "resolved": "https://registry.npmjs.org/y-leveldb/-/y-leveldb-0.1.2.tgz", 442 | "integrity": "sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg==", 443 | "optional": true, 444 | "dependencies": { 445 | "level": "^6.0.1", 446 | "lib0": "^0.2.31" 447 | }, 448 | "funding": { 449 | "type": "GitHub Sponsors ❤", 450 | "url": "https://github.com/sponsors/dmonad" 451 | }, 452 | "peerDependencies": { 453 | "yjs": "^13.0.0" 454 | } 455 | }, 456 | "node_modules/y-protocols": { 457 | "version": "1.0.6", 458 | "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz", 459 | "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", 460 | "dependencies": { 461 | "lib0": "^0.2.85" 462 | }, 463 | "engines": { 464 | "node": ">=16.0.0", 465 | "npm": ">=8.0.0" 466 | }, 467 | "funding": { 468 | "type": "GitHub Sponsors ❤", 469 | "url": "https://github.com/sponsors/dmonad" 470 | }, 471 | "peerDependencies": { 472 | "yjs": "^13.0.0" 473 | } 474 | }, 475 | "node_modules/y-websocket": { 476 | "version": "1.5.0", 477 | "resolved": "https://registry.npmjs.org/y-websocket/-/y-websocket-1.5.0.tgz", 478 | "integrity": "sha512-A8AO6XtnQlYwWFytWdkDCeXg4l8ghRTIw5h2YUgUYDmEC9ugWGIwYNW80yadhSFAF7CvuWTEkQNEpevnH6EiZw==", 479 | "dependencies": { 480 | "lib0": "^0.2.52", 481 | "lodash.debounce": "^4.0.8", 482 | "y-protocols": "^1.0.5" 483 | }, 484 | "bin": { 485 | "y-websocket": "bin/server.js", 486 | "y-websocket-server": "bin/server.js" 487 | }, 488 | "funding": { 489 | "type": "GitHub Sponsors ❤", 490 | "url": "https://github.com/sponsors/dmonad" 491 | }, 492 | "optionalDependencies": { 493 | "ws": "^6.2.1", 494 | "y-leveldb": "^0.1.0" 495 | }, 496 | "peerDependencies": { 497 | "yjs": "^13.5.6" 498 | } 499 | }, 500 | "node_modules/yjs": { 501 | "version": "13.6.10", 502 | "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.10.tgz", 503 | "integrity": "sha512-1JcyQek1vaMyrDm7Fqfa+pvHg/DURSbVo4VmeN7wjnTKB/lZrfIPhdCj7d8sboK6zLfRBJXegTjc9JlaDd8/Zw==", 504 | "dependencies": { 505 | "lib0": "^0.2.86" 506 | }, 507 | "engines": { 508 | "node": ">=16.0.0", 509 | "npm": ">=8.0.0" 510 | }, 511 | "funding": { 512 | "type": "GitHub Sponsors ❤", 513 | "url": "https://github.com/sponsors/dmonad" 514 | } 515 | } 516 | } 517 | } 518 | -------------------------------------------------------------------------------- /codemirror/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yjs-codemirror-demo", 3 | "version": "1.0.0", 4 | "description": "Yjs ❤ CodeMirror", 5 | "scripts": { 6 | "watch": "webpack -w --stats errors-only", 7 | "dist": "webpack --mode=production", 8 | "start": "webpack serve --open codemirror.html" 9 | }, 10 | "author": "Kevin JahnsThis is a demo of the Yjs ⇔ Monaco binding: y-monaco.
37 |The content of this editor is shared with every client that visits this domain.
38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /monaco/monaco.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import * as Y from 'yjs' 4 | import { WebsocketProvider } from 'y-websocket' 5 | import { MonacoBinding } from 'y-monaco' 6 | import * as monaco from 'monaco-editor' 7 | 8 | // // @ts-ignore 9 | // window.MonacoEnvironment = { 10 | // getWorkerUrl: function (moduleId, label) { 11 | // if (label === 'json') { 12 | // return '/monaco/dist/json.worker.bundle.js' 13 | // } 14 | // if (label === 'css') { 15 | // return '/monaco/dist/css.worker.bundle.js' 16 | // } 17 | // if (label === 'html') { 18 | // return '/monaco/dist/html.worker.bundle.js' 19 | // } 20 | // if (label === 'typescript' || label === 'javascript') { 21 | // return '/monaco/dist/ts.worker.bundle.js' 22 | // } 23 | // return '/monaco/dist/editor.worker.bundle.js' 24 | // } 25 | // } 26 | 27 | const roomname = `monaco-demo-${new Date().toLocaleDateString('en-CA')}` 28 | 29 | window.addEventListener('load', () => { 30 | const ydoc = new Y.Doc() 31 | const provider = new WebsocketProvider( 32 | 'wss://demos.yjs.dev/ws', // use the public ws server 33 | // `ws${location.protocol.slice(4)}//${location.host}/ws`, // alternatively: use the local ws server (run `npm start` in root directory) 34 | roomname, 35 | ydoc 36 | ) 37 | const ytext = ydoc.getText('monaco') 38 | 39 | const editor = monaco.editor.create(/** @type {HTMLElement} */ (document.getElementById('monaco-editor')), { 40 | value: '', 41 | language: 'javascript', 42 | theme: 'vs-dark' 43 | }) 44 | const monacoBinding = new MonacoBinding(ytext, /** @type {monaco.editor.ITextModel} */ (editor.getModel()), new Set([editor]), provider.awareness) 45 | 46 | const connectBtn = /** @type {HTMLElement} */ (document.getElementById('y-connect-btn')) 47 | connectBtn.addEventListener('click', () => { 48 | if (provider.shouldConnect) { 49 | provider.disconnect() 50 | connectBtn.textContent = 'Connect' 51 | } else { 52 | provider.connect() 53 | connectBtn.textContent = 'Disconnect' 54 | } 55 | }) 56 | 57 | // @ts-ignore 58 | window.example = { provider, ydoc, ytext, monacoBinding } 59 | }) 60 | -------------------------------------------------------------------------------- /monaco/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yjs-monaco-demo", 3 | "version": "1.0.0", 4 | "description": "Yjs ❤ Monaco", 5 | "main": "index.js", 6 | "scripts": { 7 | "ensureBundle": "[ -f ./dist/monaco.bundle.js ] || npm run dist", 8 | "watch": "webpack -w --stats errors-only", 9 | "dist": "webpack --mode=production", 10 | "start": "npm run ensureBundle && webpack serve --open monaco.html" 11 | }, 12 | "author": "Kevin JahnsThis is a demo of the Yjs ⇔ ProseMirror binding: y-prosemirror.
70 |The content of this editor is shared with every client that visits this domain.
71 | 72 | 73 | -------------------------------------------------------------------------------- /prosemirror-versions/prosemirror-versions.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import * as Y from 'yjs' 4 | import { WebsocketProvider } from 'y-websocket' 5 | import { ySyncPlugin, ySyncPluginKey, yCursorPlugin, yUndoPlugin, undo, redo } from 'y-prosemirror' 6 | import { EditorState } from 'prosemirror-state' 7 | import { EditorView } from 'prosemirror-view' 8 | import { schema } from './schema.js' 9 | import { exampleSetup } from 'prosemirror-example-setup' 10 | import { keymap } from 'prosemirror-keymap' 11 | import * as random from 'lib0/random.js' 12 | import { html, render } from 'lit-html' 13 | import * as dom from 'lib0/dom.js' 14 | import * as pair from 'lib0/pair.js' 15 | 16 | const roomname = `prosemirror-versions-demo-${new Date().toLocaleDateString('en-CA')}` 17 | 18 | /** 19 | * @typedef {Object} Version 20 | * @property {number} date 21 | * @property {Uint8Array} snapshot 22 | * @property {number} clientID 23 | */ 24 | 25 | /** 26 | * @param {Y.Doc} doc 27 | */ 28 | const addVersion = doc => { 29 | const versions = doc.getArray('versions') 30 | const prevVersion = versions.length === 0 ? null : versions.get(versions.length - 1) 31 | const prevSnapshot = prevVersion === null ? Y.emptySnapshot : Y.decodeSnapshot(prevVersion.snapshot) 32 | const snapshot = Y.snapshot(doc) 33 | if (prevVersion != null) { 34 | // account for the action of adding a version to ydoc 35 | prevSnapshot.sv.set(prevVersion.clientID, /** @type {number} */ (prevSnapshot.sv.get(prevVersion.clientID)) + 1) 36 | } 37 | if (!Y.equalSnapshots(prevSnapshot, snapshot)) { 38 | versions.push([{ 39 | date: new Date().getTime(), 40 | snapshot: Y.encodeSnapshot(snapshot), 41 | clientID: doc.clientID 42 | }]) 43 | } 44 | } 45 | 46 | const liveTracking = /** @type {HTMLInputElement} */ (dom.element('input', [ 47 | pair.create('type', 'checkbox'), 48 | pair.create('name', 'yjs-live-tracking'), 49 | pair.create('value', 'Live Tracking ') 50 | ])) 51 | 52 | const updateLiveTrackingState = editorstate => { 53 | setTimeout(() => { 54 | const syncState = ySyncPluginKey.getState(editorstate.state) 55 | liveTracking.checked = syncState.prevSnapshot != null && syncState.snapshot == null 56 | }, 500) 57 | } 58 | 59 | const renderVersion = (editorview, version, prevSnapshot) => { 60 | editorview.dispatch(editorview.state.tr.setMeta(ySyncPluginKey, { snapshot: Y.decodeSnapshot(version.snapshot), prevSnapshot: prevSnapshot == null ? Y.emptySnapshot : Y.decodeSnapshot(prevSnapshot) })) 61 | updateLiveTrackingState(editorview) 62 | } 63 | 64 | const unrenderVersion = editorview => { 65 | const binding = ySyncPluginKey.getState(editorview.state).binding 66 | if (binding != null) { 67 | binding.unrenderSnapshot() 68 | } 69 | updateLiveTrackingState(editorview) 70 | } 71 | 72 | /** 73 | * @param {EditorView} editorview 74 | * @param {Version} version 75 | * @param {Version|null} prevSnapshot 76 | */ 77 | const versionTemplate = (editorview, version, prevSnapshot) => html`` element. 44 | paragraph: { 45 | attrs: { ychange: { default: null } }, 46 | content: 'inline*', 47 | group: 'block', 48 | parseDOM: [{ tag: 'p' }], 49 | toDOM (node) { 50 | // only render changes if no child nodes 51 | const renderChanges = node.content.size === 0 52 | const attrs = renderChanges ? calcYchangeDomAttrs(node.attrs) : node.attrs 53 | const defChildren = [0] 54 | const children = renderChanges ? hoverWrapper(node.attrs.ychange, defChildren) : defChildren 55 | return ['p', attrs, ...children] 56 | } 57 | }, 58 | 59 | // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. 60 | blockquote: { 61 | attrs: { ychange: { default: null } }, 62 | content: 'block+', 63 | group: 'block', 64 | defining: true, 65 | parseDOM: [{ tag: 'blockquote' }], 66 | toDOM (node) { return ['blockquote', calcYchangeDomAttrs(node.attrs), ...hoverWrapper(node.attrs.ychange, [0])] } 67 | }, 68 | 69 | // :: NodeSpec A horizontal rule (`
`). 70 | horizontal_rule: { 71 | attrs: { ychange: { default: null } }, 72 | group: 'block', 73 | parseDOM: [{ tag: 'hr' }], 74 | toDOM (node) { 75 | return ['hr', calcYchangeDomAttrs(node.attrs), ...hoverWrapper(node.attrs.ychange, [])] 76 | } 77 | }, 78 | 79 | // :: NodeSpec A heading textblock, with a `level` attribute that 80 | // should hold the number 1 to 6. Parsed and serialized as `` to 81 | // `
` elements. 82 | heading: { 83 | attrs: { 84 | level: { default: 1 }, 85 | ychange: { default: null } 86 | }, 87 | content: 'inline*', 88 | group: 'block', 89 | defining: true, 90 | parseDOM: [{ tag: 'h1', attrs: { level: 1 } }, 91 | { tag: 'h2', attrs: { level: 2 } }, 92 | { tag: 'h3', attrs: { level: 3 } }, 93 | { tag: 'h4', attrs: { level: 4 } }, 94 | { tag: 'h5', attrs: { level: 5 } }, 95 | { tag: 'h6', attrs: { level: 6 } }], 96 | toDOM (node) { return ['h' + node.attrs.level, calcYchangeDomAttrs(node.attrs), ...hoverWrapper(node.attrs.ychange, [0])] } 97 | }, 98 | 99 | // :: NodeSpec A code listing. Disallows marks or non-text inline 100 | // nodes by default. Represented as a `
` element with a 101 | // `` element inside of it. 102 | code_block: { 103 | attrs: { ychange: { default: null } }, 104 | content: 'text*', 105 | marks: '', 106 | group: 'block', 107 | code: true, 108 | defining: true, 109 | parseDOM: [{ tag: 'pre', preserveWhitespace: 'full' }], 110 | toDOM (node) { return ['pre', calcYchangeDomAttrs(node.attrs), ...hoverWrapper(node.attrs.ychange, [['code', 0]])] } 111 | }, 112 | 113 | // :: NodeSpec The text node. 114 | text: { 115 | group: 'inline' 116 | }, 117 | 118 | // :: NodeSpec An inline image (`
`) node. Supports `src`, 119 | // `alt`, and `href` attributes. The latter two default to the empty 120 | // string. 121 | image: { 122 | inline: true, 123 | attrs: { 124 | ychange: { default: null }, 125 | src: {}, 126 | alt: { default: null }, 127 | title: { default: null } 128 | }, 129 | group: 'inline', 130 | draggable: true, 131 | parseDOM: [{ 132 | tag: 'img[src]', 133 | getAttrs (dom) { 134 | return { 135 | src: dom.getAttribute('src'), 136 | title: dom.getAttribute('title'), 137 | alt: dom.getAttribute('alt') 138 | } 139 | } 140 | }], 141 | toDOM (node) { 142 | const domAttrs = { 143 | src: node.attrs.src, 144 | title: node.attrs.title, 145 | alt: node.attrs.alt 146 | } 147 | return ['img', calcYchangeDomAttrs(node.attrs, domAttrs), ...hoverWrapper(node.attrs.ychange, [])] 148 | } 149 | }, 150 | 151 | // :: NodeSpec A hard line break, represented in the DOM as `
`. 152 | hard_break: { 153 | inline: true, 154 | group: 'inline', 155 | selectable: false, 156 | parseDOM: [{ tag: 'br' }], 157 | toDOM () { return brDOM } 158 | } 159 | } 160 | 161 | const emDOM = ['em', 0]; const strongDOM = ['strong', 0]; const codeDOM = ['code', 0] 162 | 163 | // :: Object [Specs](#model.MarkSpec) for the marks in the schema. 164 | export const marks = { 165 | // :: MarkSpec A link. Has `href` and `title` attributes. `title` 166 | // defaults to the empty string. Rendered and parsed as an `` 167 | // element. 168 | link: { 169 | attrs: { 170 | href: {}, 171 | title: { default: null } 172 | }, 173 | inclusive: false, 174 | parseDOM: [{ 175 | tag: 'a[href]', 176 | getAttrs (dom) { 177 | return { href: dom.getAttribute('href'), title: dom.getAttribute('title') } 178 | } 179 | }], 180 | toDOM (node) { return ['a', node.attrs, 0] } 181 | }, 182 | 183 | // :: MarkSpec An emphasis mark. Rendered as an `` element. 184 | // Has parse rules that also match `` and `font-style: italic`. 185 | em: { 186 | parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style=italic' }], 187 | toDOM () { return emDOM } 188 | }, 189 | 190 | // :: MarkSpec A strong mark. Rendered as ``, parse rules 191 | // also match `` and `font-weight: bold`. 192 | strong: { 193 | parseDOM: [{ tag: 'strong' }, 194 | // This works around a Google Docs misbehavior where 195 | // pasted content will be inexplicably wrapped in `` 196 | // tags with a font-weight normal. 197 | { tag: 'b', getAttrs: node => node.style.fontWeight !== 'normal' && null }, 198 | { style: 'font-weight', getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null }], 199 | toDOM () { return strongDOM } 200 | }, 201 | 202 | // :: MarkSpec Code font mark. Represented as a `` element. 203 | code: { 204 | parseDOM: [{ tag: 'code' }], 205 | toDOM () { return codeDOM } 206 | }, 207 | ychange: { 208 | attrs: { 209 | user: { default: null }, 210 | type: { default: null }, 211 | color: { default: null } 212 | }, 213 | inclusive: false, 214 | parseDOM: [{ tag: 'ychange' }], 215 | toDOM (node) { 216 | return ['ychange', { ychange_user: node.attrs.user, ychange_type: node.attrs.type, style: calcYChangeStyle(node.attrs), ychange_color: node.attrs.color.light }, ...hoverWrapper(node.attrs, [0])] 217 | } 218 | } 219 | } 220 | 221 | // :: Schema 222 | // This schema rougly corresponds to the document schema used by 223 | // [CommonMark](http://commonmark.org/), minus the list elements, 224 | // which are defined in the [`prosemirror-schema-list`](#schema-list) 225 | // module. 226 | // 227 | // To reuse elements from this schema, extend or read from its 228 | // `spec.nodes` and `spec.marks` [properties](#model.Schema.spec). 229 | export const schema = new Schema({ nodes, marks }) 230 | -------------------------------------------------------------------------------- /prosemirror-versions/version.css: -------------------------------------------------------------------------------- 1 | #y-version { 2 | position: relative; 3 | } 4 | 5 | .version-modal[hidden] { 6 | display: none; 7 | } 8 | 9 | .version-modal:not([hidden]) { 10 | position: absolute; 11 | top: 30px; 12 | right: 0; 13 | width: 300px; 14 | min-height: 300px; 15 | height: fit-content; 16 | z-index: 20; 17 | background-color: white; 18 | border: 1px solid #ccc; 19 | border-radius: 11px; 20 | border-top-right-radius: 0; 21 | box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); 22 | } 23 | 24 | .version-modal > button { 25 | position: absolute; 26 | right: 0; 27 | top: 0; 28 | } 29 | 30 | .version-list { 31 | line-height: 30px; 32 | padding: 0 8px; 33 | cursor: pointer; 34 | } 35 | 36 | .version-list:hover { 37 | background-color: #eee; 38 | } 39 | 40 | [ychange_type] { 41 | position: relative; 42 | } 43 | .ychange-hover { 44 | display: none; 45 | } 46 | *:hover > .ychange-hover { 47 | display: inline; 48 | position: absolute; 49 | top: -14px; 50 | left: 0; 51 | font-size: 12px; 52 | padding: 0 2px; 53 | border-radius: 3px 3px 0 0; 54 | color: #fdfdfe; 55 | user-select: none; 56 | word-break: normal; 57 | } 58 | 59 | ychange[ychange_type='removed'], p[ychange_type='removed'] { 60 | text-decoration: line-through; 61 | } 62 | /* 63 | p[ychange_type='removed'] > span > br { 64 | display: none; 65 | } 66 | */ 67 | *:not(ychange)[ychange_type='removed'] { 68 | background-color: #ff5a56; 69 | color: inherit !important; 70 | } 71 | img[ychange_type='removed'] { 72 | padding: 2px; 73 | } 74 | -------------------------------------------------------------------------------- /prosemirror-versions/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'development', 5 | devtool: 'source-map', 6 | entry: { 7 | 'prosemirror-versions': './prosemirror-versions.js' 8 | }, 9 | output: { 10 | globalObject: 'self', 11 | path: path.resolve(__dirname, './dist/'), 12 | filename: '[name].bundle.js', 13 | publicPath: '/dist/' 14 | }, 15 | devServer: { 16 | static: path.join(__dirname), 17 | compress: true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /prosemirror/README.md: -------------------------------------------------------------------------------- 1 | # ProseMirror Demo 2 | > [y-prosemirror](https://docs.yjs.dev/ecosystem/editor-bindings/prosemirror) / [y-websocket](https://docs.yjs.dev/ecosystem/connection-provider/y-websocket) / [Live Demo](https://demos.yjs.dev/prosemirror/prosemirror.html) 3 | 4 | This is a demo of a [ProseMirror](https://prosemirror.net/) editor that was made collaborative with Yjs & y-prosemirror. This demo uses the [prosemirror-example-setup](https://github.com/ProseMirror/prosemirror-example-setup) as a configuration. Learn more about how you can build your own custom editor with ProseMirror in [their documentation](https://github.com/yjs/y-prosemirror/). 5 | 6 | We use the [y-websocket](https://docs.yjs.dev/ecosystem/connection-provider/y-websocket) provider to share document updates through a server. There are many more providers available for Yjs - try switching to another provider. [See docs](https://docs.yjs.dev/ecosystem/connection-provider). 7 | 8 | Also you could try adding offline persistence to this demo. Wouldn't it be cool if document updates are persisted in the browser, so you can load your application load faster? Try it out: https://docs.yjs.dev/getting-started/allowing-offline-editing 9 | 10 | ## Quick Start 11 | 12 | ```sh 13 | npm i 14 | npm start 15 | # Project is running at http://localhost:8080/ 16 | ``` 17 | -------------------------------------------------------------------------------- /prosemirror/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yjs-prosemirror-demo", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "yjs-prosemirror-demo", 9 | "version": "1.0.0", 10 | "license": "UNLICENSE", 11 | "dependencies": { 12 | "prosemirror-example-setup": "^1.1.2", 13 | "prosemirror-model": "^1.13.1", 14 | "prosemirror-state": "^1.3.4", 15 | "prosemirror-view": "^1.18.7", 16 | "y-prosemirror": "^1.2.8", 17 | "y-websocket": "^1.3.15", 18 | "yjs": "^13.5.8" 19 | } 20 | }, 21 | "node_modules/abstract-leveldown": { 22 | "version": "6.2.3", 23 | "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.3.tgz", 24 | "integrity": "sha512-BsLm5vFMRUrrLeCcRc+G0t2qOaTzpoJQLOubq2XM72eNpjF5UdU5o/5NvlNhx95XHcAvcl8OMXr4mlg/fRgUXQ==", 25 | "optional": true, 26 | "dependencies": { 27 | "buffer": "^5.5.0", 28 | "immediate": "^3.2.3", 29 | "level-concat-iterator": "~2.0.0", 30 | "level-supports": "~1.0.0", 31 | "xtend": "~4.0.0" 32 | }, 33 | "engines": { 34 | "node": ">=6" 35 | } 36 | }, 37 | "node_modules/async-limiter": { 38 | "version": "1.0.1", 39 | "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", 40 | "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", 41 | "optional": true 42 | }, 43 | "node_modules/base64-js": { 44 | "version": "1.5.1", 45 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", 46 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", 47 | "funding": [ 48 | { 49 | "type": "github", 50 | "url": "https://github.com/sponsors/feross" 51 | }, 52 | { 53 | "type": "patreon", 54 | "url": "https://www.patreon.com/feross" 55 | }, 56 | { 57 | "type": "consulting", 58 | "url": "https://feross.org/support" 59 | } 60 | ], 61 | "optional": true 62 | }, 63 | "node_modules/buffer": { 64 | "version": "5.7.1", 65 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", 66 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", 67 | "funding": [ 68 | { 69 | "type": "github", 70 | "url": "https://github.com/sponsors/feross" 71 | }, 72 | { 73 | "type": "patreon", 74 | "url": "https://www.patreon.com/feross" 75 | }, 76 | { 77 | "type": "consulting", 78 | "url": "https://feross.org/support" 79 | } 80 | ], 81 | "optional": true, 82 | "dependencies": { 83 | "base64-js": "^1.3.1", 84 | "ieee754": "^1.1.13" 85 | } 86 | }, 87 | "node_modules/crelt": { 88 | "version": "1.0.6", 89 | "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", 90 | "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" 91 | }, 92 | "node_modules/deferred-leveldown": { 93 | "version": "5.3.0", 94 | "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", 95 | "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", 96 | "optional": true, 97 | "dependencies": { 98 | "abstract-leveldown": "~6.2.1", 99 | "inherits": "^2.0.3" 100 | }, 101 | "engines": { 102 | "node": ">=6" 103 | } 104 | }, 105 | "node_modules/encoding-down": { 106 | "version": "6.3.0", 107 | "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", 108 | "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", 109 | "optional": true, 110 | "dependencies": { 111 | "abstract-leveldown": "^6.2.1", 112 | "inherits": "^2.0.3", 113 | "level-codec": "^9.0.0", 114 | "level-errors": "^2.0.0" 115 | }, 116 | "engines": { 117 | "node": ">=6" 118 | } 119 | }, 120 | "node_modules/errno": { 121 | "version": "0.1.8", 122 | "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", 123 | "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", 124 | "optional": true, 125 | "dependencies": { 126 | "prr": "~1.0.1" 127 | }, 128 | "bin": { 129 | "errno": "cli.js" 130 | } 131 | }, 132 | "node_modules/ieee754": { 133 | "version": "1.2.1", 134 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", 135 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", 136 | "funding": [ 137 | { 138 | "type": "github", 139 | "url": "https://github.com/sponsors/feross" 140 | }, 141 | { 142 | "type": "patreon", 143 | "url": "https://www.patreon.com/feross" 144 | }, 145 | { 146 | "type": "consulting", 147 | "url": "https://feross.org/support" 148 | } 149 | ], 150 | "optional": true 151 | }, 152 | "node_modules/immediate": { 153 | "version": "3.3.0", 154 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz", 155 | "integrity": "sha512-HR7EVodfFUdQCTIeySw+WDRFJlPcLOJbXfwwZ7Oom6tjsvZ3bOkCDJHehQC3nxJrv7+f9XecwazynjU8e4Vw3Q==", 156 | "optional": true 157 | }, 158 | "node_modules/inherits": { 159 | "version": "2.0.4", 160 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 161 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 162 | "optional": true 163 | }, 164 | "node_modules/isomorphic.js": { 165 | "version": "0.2.5", 166 | "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", 167 | "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", 168 | "funding": { 169 | "type": "GitHub Sponsors ❤", 170 | "url": "https://github.com/sponsors/dmonad" 171 | } 172 | }, 173 | "node_modules/level": { 174 | "version": "6.0.1", 175 | "resolved": "https://registry.npmjs.org/level/-/level-6.0.1.tgz", 176 | "integrity": "sha512-psRSqJZCsC/irNhfHzrVZbmPYXDcEYhA5TVNwr+V92jF44rbf86hqGp8fiT702FyiArScYIlPSBTDUASCVNSpw==", 177 | "optional": true, 178 | "dependencies": { 179 | "level-js": "^5.0.0", 180 | "level-packager": "^5.1.0", 181 | "leveldown": "^5.4.0" 182 | }, 183 | "engines": { 184 | "node": ">=8.6.0" 185 | }, 186 | "funding": { 187 | "type": "opencollective", 188 | "url": "https://opencollective.com/level" 189 | } 190 | }, 191 | "node_modules/level-codec": { 192 | "version": "9.0.2", 193 | "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.2.tgz", 194 | "integrity": "sha512-UyIwNb1lJBChJnGfjmO0OR+ezh2iVu1Kas3nvBS/BzGnx79dv6g7unpKIDNPMhfdTEGoc7mC8uAu51XEtX+FHQ==", 195 | "optional": true, 196 | "dependencies": { 197 | "buffer": "^5.6.0" 198 | }, 199 | "engines": { 200 | "node": ">=6" 201 | } 202 | }, 203 | "node_modules/level-concat-iterator": { 204 | "version": "2.0.1", 205 | "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", 206 | "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", 207 | "optional": true, 208 | "engines": { 209 | "node": ">=6" 210 | } 211 | }, 212 | "node_modules/level-errors": { 213 | "version": "2.0.1", 214 | "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", 215 | "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", 216 | "optional": true, 217 | "dependencies": { 218 | "errno": "~0.1.1" 219 | }, 220 | "engines": { 221 | "node": ">=6" 222 | } 223 | }, 224 | "node_modules/level-iterator-stream": { 225 | "version": "4.0.2", 226 | "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", 227 | "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", 228 | "optional": true, 229 | "dependencies": { 230 | "inherits": "^2.0.4", 231 | "readable-stream": "^3.4.0", 232 | "xtend": "^4.0.2" 233 | }, 234 | "engines": { 235 | "node": ">=6" 236 | } 237 | }, 238 | "node_modules/level-js": { 239 | "version": "5.0.2", 240 | "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.2.tgz", 241 | "integrity": "sha512-SnBIDo2pdO5VXh02ZmtAyPP6/+6YTJg2ibLtl9C34pWvmtMEmRTWpra+qO/hifkUtBTOtfx6S9vLDjBsBK4gRg==", 242 | "optional": true, 243 | "dependencies": { 244 | "abstract-leveldown": "~6.2.3", 245 | "buffer": "^5.5.0", 246 | "inherits": "^2.0.3", 247 | "ltgt": "^2.1.2" 248 | } 249 | }, 250 | "node_modules/level-packager": { 251 | "version": "5.1.1", 252 | "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz", 253 | "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==", 254 | "optional": true, 255 | "dependencies": { 256 | "encoding-down": "^6.3.0", 257 | "levelup": "^4.3.2" 258 | }, 259 | "engines": { 260 | "node": ">=6" 261 | } 262 | }, 263 | "node_modules/level-supports": { 264 | "version": "1.0.1", 265 | "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", 266 | "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", 267 | "optional": true, 268 | "dependencies": { 269 | "xtend": "^4.0.2" 270 | }, 271 | "engines": { 272 | "node": ">=6" 273 | } 274 | }, 275 | "node_modules/leveldown": { 276 | "version": "5.6.0", 277 | "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.6.0.tgz", 278 | "integrity": "sha512-iB8O/7Db9lPaITU1aA2txU/cBEXAt4vWwKQRrrWuS6XDgbP4QZGj9BL2aNbwb002atoQ/lIotJkfyzz+ygQnUQ==", 279 | "hasInstallScript": true, 280 | "optional": true, 281 | "dependencies": { 282 | "abstract-leveldown": "~6.2.1", 283 | "napi-macros": "~2.0.0", 284 | "node-gyp-build": "~4.1.0" 285 | }, 286 | "engines": { 287 | "node": ">=8.6.0" 288 | } 289 | }, 290 | "node_modules/levelup": { 291 | "version": "4.4.0", 292 | "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.4.0.tgz", 293 | "integrity": "sha512-94++VFO3qN95cM/d6eBXvd894oJE0w3cInq9USsyQzzoJxmiYzPAocNcuGCPGGjoXqDVJcr3C1jzt1TSjyaiLQ==", 294 | "optional": true, 295 | "dependencies": { 296 | "deferred-leveldown": "~5.3.0", 297 | "level-errors": "~2.0.0", 298 | "level-iterator-stream": "~4.0.0", 299 | "level-supports": "~1.0.0", 300 | "xtend": "~4.0.0" 301 | }, 302 | "engines": { 303 | "node": ">=6" 304 | } 305 | }, 306 | "node_modules/lib0": { 307 | "version": "0.2.94", 308 | "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.94.tgz", 309 | "integrity": "sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==", 310 | "dependencies": { 311 | "isomorphic.js": "^0.2.4" 312 | }, 313 | "bin": { 314 | "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", 315 | "0gentesthtml": "bin/gentesthtml.js", 316 | "0serve": "bin/0serve.js" 317 | }, 318 | "engines": { 319 | "node": ">=16" 320 | }, 321 | "funding": { 322 | "type": "GitHub Sponsors ❤", 323 | "url": "https://github.com/sponsors/dmonad" 324 | } 325 | }, 326 | "node_modules/lodash.debounce": { 327 | "version": "4.0.8", 328 | "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", 329 | "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" 330 | }, 331 | "node_modules/ltgt": { 332 | "version": "2.2.1", 333 | "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz", 334 | "integrity": "sha512-AI2r85+4MquTw9ZYqabu4nMwy9Oftlfa/e/52t9IjtfG+mGBbTNdAoZ3RQKLHR6r0wQnwZnPIEh/Ya6XTWAKNA==", 335 | "optional": true 336 | }, 337 | "node_modules/napi-macros": { 338 | "version": "2.0.0", 339 | "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", 340 | "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==", 341 | "optional": true 342 | }, 343 | "node_modules/node-gyp-build": { 344 | "version": "4.1.1", 345 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", 346 | "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", 347 | "optional": true, 348 | "bin": { 349 | "node-gyp-build": "bin.js", 350 | "node-gyp-build-optional": "optional.js", 351 | "node-gyp-build-test": "build-test.js" 352 | } 353 | }, 354 | "node_modules/orderedmap": { 355 | "version": "2.1.1", 356 | "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", 357 | "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==" 358 | }, 359 | "node_modules/prosemirror-commands": { 360 | "version": "1.5.2", 361 | "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.5.2.tgz", 362 | "integrity": "sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==", 363 | "dependencies": { 364 | "prosemirror-model": "^1.0.0", 365 | "prosemirror-state": "^1.0.0", 366 | "prosemirror-transform": "^1.0.0" 367 | } 368 | }, 369 | "node_modules/prosemirror-dropcursor": { 370 | "version": "1.8.1", 371 | "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.1.tgz", 372 | "integrity": "sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==", 373 | "dependencies": { 374 | "prosemirror-state": "^1.0.0", 375 | "prosemirror-transform": "^1.1.0", 376 | "prosemirror-view": "^1.1.0" 377 | } 378 | }, 379 | "node_modules/prosemirror-example-setup": { 380 | "version": "1.2.2", 381 | "resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.2.2.tgz", 382 | "integrity": "sha512-pHJc656IgYm249RNp0eQaWNmnyWGk6OrzysWfYI4+NwE14HQ7YNYOlRBLErUS6uCAHIYJLNXf0/XCmf1OCtNbQ==", 383 | "dependencies": { 384 | "prosemirror-commands": "^1.0.0", 385 | "prosemirror-dropcursor": "^1.0.0", 386 | "prosemirror-gapcursor": "^1.0.0", 387 | "prosemirror-history": "^1.0.0", 388 | "prosemirror-inputrules": "^1.0.0", 389 | "prosemirror-keymap": "^1.0.0", 390 | "prosemirror-menu": "^1.0.0", 391 | "prosemirror-schema-list": "^1.0.0", 392 | "prosemirror-state": "^1.0.0" 393 | } 394 | }, 395 | "node_modules/prosemirror-gapcursor": { 396 | "version": "1.3.2", 397 | "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.3.2.tgz", 398 | "integrity": "sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==", 399 | "dependencies": { 400 | "prosemirror-keymap": "^1.0.0", 401 | "prosemirror-model": "^1.0.0", 402 | "prosemirror-state": "^1.0.0", 403 | "prosemirror-view": "^1.0.0" 404 | } 405 | }, 406 | "node_modules/prosemirror-history": { 407 | "version": "1.4.0", 408 | "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.4.0.tgz", 409 | "integrity": "sha512-UUiGzDVcqo1lovOPdi9YxxUps3oBFWAIYkXLu3Ot+JPv1qzVogRbcizxK3LhHmtaUxclohgiOVesRw5QSlMnbQ==", 410 | "dependencies": { 411 | "prosemirror-state": "^1.2.2", 412 | "prosemirror-transform": "^1.0.0", 413 | "prosemirror-view": "^1.31.0", 414 | "rope-sequence": "^1.3.0" 415 | } 416 | }, 417 | "node_modules/prosemirror-inputrules": { 418 | "version": "1.4.0", 419 | "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.4.0.tgz", 420 | "integrity": "sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==", 421 | "dependencies": { 422 | "prosemirror-state": "^1.0.0", 423 | "prosemirror-transform": "^1.0.0" 424 | } 425 | }, 426 | "node_modules/prosemirror-keymap": { 427 | "version": "1.2.2", 428 | "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.2.tgz", 429 | "integrity": "sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==", 430 | "dependencies": { 431 | "prosemirror-state": "^1.0.0", 432 | "w3c-keyname": "^2.2.0" 433 | } 434 | }, 435 | "node_modules/prosemirror-menu": { 436 | "version": "1.2.4", 437 | "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.4.tgz", 438 | "integrity": "sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==", 439 | "dependencies": { 440 | "crelt": "^1.0.0", 441 | "prosemirror-commands": "^1.0.0", 442 | "prosemirror-history": "^1.0.0", 443 | "prosemirror-state": "^1.0.0" 444 | } 445 | }, 446 | "node_modules/prosemirror-model": { 447 | "version": "1.21.1", 448 | "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.21.1.tgz", 449 | "integrity": "sha512-IVBAuMqOfltTr7yPypwpfdGT+6rGAteVOw2FO6GEvCGGa1ZwxLseqC1Eax/EChDvG/xGquB2d/hLdgh3THpsYg==", 450 | "dependencies": { 451 | "orderedmap": "^2.0.0" 452 | } 453 | }, 454 | "node_modules/prosemirror-schema-list": { 455 | "version": "1.4.0", 456 | "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.4.0.tgz", 457 | "integrity": "sha512-nZOIq/AkBSzCENxUyLm5ltWE53e2PLk65ghMN8qLQptOmDVixZlPqtMeQdiNw0odL9vNpalEjl3upgRkuJ/Jyw==", 458 | "dependencies": { 459 | "prosemirror-model": "^1.0.0", 460 | "prosemirror-state": "^1.0.0", 461 | "prosemirror-transform": "^1.7.3" 462 | } 463 | }, 464 | "node_modules/prosemirror-state": { 465 | "version": "1.4.3", 466 | "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.3.tgz", 467 | "integrity": "sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==", 468 | "dependencies": { 469 | "prosemirror-model": "^1.0.0", 470 | "prosemirror-transform": "^1.0.0", 471 | "prosemirror-view": "^1.27.0" 472 | } 473 | }, 474 | "node_modules/prosemirror-transform": { 475 | "version": "1.9.0", 476 | "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.9.0.tgz", 477 | "integrity": "sha512-5UXkr1LIRx3jmpXXNKDhv8OyAOeLTGuXNwdVfg8x27uASna/wQkr9p6fD3eupGOi4PLJfbezxTyi/7fSJypXHg==", 478 | "dependencies": { 479 | "prosemirror-model": "^1.21.0" 480 | } 481 | }, 482 | "node_modules/prosemirror-view": { 483 | "version": "1.33.8", 484 | "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.8.tgz", 485 | "integrity": "sha512-4PhMr/ufz2cdvFgpUAnZfs+0xij3RsFysreeG9V/utpwX7AJtYCDVyuRxzWoMJIEf4C7wVihuBNMPpFLPCiLQw==", 486 | "dependencies": { 487 | "prosemirror-model": "^1.20.0", 488 | "prosemirror-state": "^1.0.0", 489 | "prosemirror-transform": "^1.1.0" 490 | } 491 | }, 492 | "node_modules/prr": { 493 | "version": "1.0.1", 494 | "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", 495 | "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", 496 | "optional": true 497 | }, 498 | "node_modules/readable-stream": { 499 | "version": "3.6.2", 500 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 501 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 502 | "optional": true, 503 | "dependencies": { 504 | "inherits": "^2.0.3", 505 | "string_decoder": "^1.1.1", 506 | "util-deprecate": "^1.0.1" 507 | }, 508 | "engines": { 509 | "node": ">= 6" 510 | } 511 | }, 512 | "node_modules/rope-sequence": { 513 | "version": "1.3.4", 514 | "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", 515 | "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" 516 | }, 517 | "node_modules/safe-buffer": { 518 | "version": "5.2.1", 519 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 520 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 521 | "funding": [ 522 | { 523 | "type": "github", 524 | "url": "https://github.com/sponsors/feross" 525 | }, 526 | { 527 | "type": "patreon", 528 | "url": "https://www.patreon.com/feross" 529 | }, 530 | { 531 | "type": "consulting", 532 | "url": "https://feross.org/support" 533 | } 534 | ], 535 | "optional": true 536 | }, 537 | "node_modules/string_decoder": { 538 | "version": "1.3.0", 539 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 540 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 541 | "optional": true, 542 | "dependencies": { 543 | "safe-buffer": "~5.2.0" 544 | } 545 | }, 546 | "node_modules/util-deprecate": { 547 | "version": "1.0.2", 548 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 549 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", 550 | "optional": true 551 | }, 552 | "node_modules/w3c-keyname": { 553 | "version": "2.2.8", 554 | "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", 555 | "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" 556 | }, 557 | "node_modules/ws": { 558 | "version": "6.2.3", 559 | "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", 560 | "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", 561 | "optional": true, 562 | "dependencies": { 563 | "async-limiter": "~1.0.0" 564 | } 565 | }, 566 | "node_modules/xtend": { 567 | "version": "4.0.2", 568 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 569 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 570 | "optional": true, 571 | "engines": { 572 | "node": ">=0.4" 573 | } 574 | }, 575 | "node_modules/y-leveldb": { 576 | "version": "0.1.2", 577 | "resolved": "https://registry.npmjs.org/y-leveldb/-/y-leveldb-0.1.2.tgz", 578 | "integrity": "sha512-6ulEn5AXfXJYi89rXPEg2mMHAyyw8+ZfeMMdOtBbV8FJpQ1NOrcgi6DTAcXof0dap84NjHPT2+9d0rb6cFsjEg==", 579 | "optional": true, 580 | "dependencies": { 581 | "level": "^6.0.1", 582 | "lib0": "^0.2.31" 583 | }, 584 | "funding": { 585 | "type": "GitHub Sponsors ❤", 586 | "url": "https://github.com/sponsors/dmonad" 587 | }, 588 | "peerDependencies": { 589 | "yjs": "^13.0.0" 590 | } 591 | }, 592 | "node_modules/y-prosemirror": { 593 | "version": "1.2.9", 594 | "resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.2.9.tgz", 595 | "integrity": "sha512-fThGIVmSqrqnG/ckywEGlHM9ElfILC4TcMZd5zxWPe/i+UuP97TEr4swsopRKG3Y+KHBVt4Y/5NVBC3AAsUoUg==", 596 | "dependencies": { 597 | "lib0": "^0.2.42" 598 | }, 599 | "engines": { 600 | "node": ">=16.0.0", 601 | "npm": ">=8.0.0" 602 | }, 603 | "funding": { 604 | "type": "GitHub Sponsors ❤", 605 | "url": "https://github.com/sponsors/dmonad" 606 | }, 607 | "peerDependencies": { 608 | "prosemirror-model": "^1.7.1", 609 | "prosemirror-state": "^1.2.3", 610 | "prosemirror-view": "^1.9.10", 611 | "y-protocols": "^1.0.1", 612 | "yjs": "^13.5.38" 613 | } 614 | }, 615 | "node_modules/y-protocols": { 616 | "version": "1.0.6", 617 | "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.6.tgz", 618 | "integrity": "sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==", 619 | "dependencies": { 620 | "lib0": "^0.2.85" 621 | }, 622 | "engines": { 623 | "node": ">=16.0.0", 624 | "npm": ">=8.0.0" 625 | }, 626 | "funding": { 627 | "type": "GitHub Sponsors ❤", 628 | "url": "https://github.com/sponsors/dmonad" 629 | }, 630 | "peerDependencies": { 631 | "yjs": "^13.0.0" 632 | } 633 | }, 634 | "node_modules/y-websocket": { 635 | "version": "1.5.4", 636 | "resolved": "https://registry.npmjs.org/y-websocket/-/y-websocket-1.5.4.tgz", 637 | "integrity": "sha512-Y3021uy0anOIHqAPyAZbNDoR05JuMEGjRNI8c+K9MHzVS8dWoImdJUjccljAznc8H2L7WkIXhRHZ1igWNRSgPw==", 638 | "dependencies": { 639 | "lib0": "^0.2.52", 640 | "lodash.debounce": "^4.0.8", 641 | "y-protocols": "^1.0.5" 642 | }, 643 | "bin": { 644 | "y-websocket": "bin/server.js", 645 | "y-websocket-server": "bin/server.js" 646 | }, 647 | "engines": { 648 | "node": ">=16.0.0", 649 | "npm": ">=8.0.0" 650 | }, 651 | "funding": { 652 | "type": "GitHub Sponsors ❤", 653 | "url": "https://github.com/sponsors/dmonad" 654 | }, 655 | "optionalDependencies": { 656 | "ws": "^6.2.1", 657 | "y-leveldb": "^0.1.0" 658 | }, 659 | "peerDependencies": { 660 | "yjs": "^13.5.6" 661 | } 662 | }, 663 | "node_modules/yjs": { 664 | "version": "13.6.18", 665 | "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.18.tgz", 666 | "integrity": "sha512-GBTjO4QCmv2HFKFkYIJl7U77hIB1o22vSCSQD1Ge8ZxWbIbn8AltI4gyXbtL+g5/GJep67HCMq3Y5AmNwDSyEg==", 667 | "dependencies": { 668 | "lib0": "^0.2.86" 669 | }, 670 | "engines": { 671 | "node": ">=16.0.0", 672 | "npm": ">=8.0.0" 673 | }, 674 | "funding": { 675 | "type": "GitHub Sponsors ❤", 676 | "url": "https://github.com/sponsors/dmonad" 677 | } 678 | } 679 | } 680 | } 681 | -------------------------------------------------------------------------------- /prosemirror/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yjs-prosemirror-demo", 3 | "version": "1.0.0", 4 | "description": "Yjs ❤ ProseMirror", 5 | "scripts": { 6 | "watch": "webpack -w --stats errors-only", 7 | "dist": "webpack --mode=production", 8 | "start": "webpack server --open prosemirror.html" 9 | }, 10 | "author": "Kevin Jahns
", 11 | "license": "UNLICENSE", 12 | "dependencies": { 13 | "yjs": "^13.5.8", 14 | "y-prosemirror": "^1.2.8", 15 | "y-websocket": "^1.3.15", 16 | "prosemirror-example-setup": "^1.1.2", 17 | "prosemirror-model": "^1.13.1", 18 | "prosemirror-state": "^1.3.4", 19 | "prosemirror-view": "^1.18.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /prosemirror/prosemirror.css: -------------------------------------------------------------------------------- 1 | .ProseMirror { 2 | position: relative; 3 | } 4 | 5 | .ProseMirror { 6 | word-wrap: break-word; 7 | white-space: pre-wrap; 8 | -webkit-font-variant-ligatures: none; 9 | font-variant-ligatures: none; 10 | } 11 | 12 | .ProseMirror pre { 13 | white-space: pre-wrap; 14 | } 15 | 16 | .ProseMirror li { 17 | position: relative; 18 | } 19 | 20 | .ProseMirror-hideselection *::selection { background: transparent; } 21 | .ProseMirror-hideselection *::-moz-selection { background: transparent; } 22 | .ProseMirror-hideselection { caret-color: transparent; } 23 | 24 | .ProseMirror-selectednode { 25 | outline: 2px solid #8cf; 26 | } 27 | 28 | /* Make sure li selections wrap around markers */ 29 | 30 | li.ProseMirror-selectednode { 31 | outline: none; 32 | } 33 | 34 | li.ProseMirror-selectednode:after { 35 | content: ""; 36 | position: absolute; 37 | left: -32px; 38 | right: -2px; top: -2px; bottom: -2px; 39 | border: 2px solid #8cf; 40 | pointer-events: none; 41 | } 42 | .ProseMirror-textblock-dropdown { 43 | min-width: 3em; 44 | } 45 | 46 | .ProseMirror-menu { 47 | margin: 0 -4px; 48 | line-height: 1; 49 | } 50 | 51 | .ProseMirror-tooltip .ProseMirror-menu { 52 | width: -webkit-fit-content; 53 | width: fit-content; 54 | white-space: pre; 55 | } 56 | 57 | .ProseMirror-menuitem { 58 | margin-right: 3px; 59 | display: inline-block; 60 | } 61 | 62 | .ProseMirror-menuseparator { 63 | border-right: 1px solid #ddd; 64 | margin-right: 3px; 65 | } 66 | 67 | .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { 68 | font-size: 90%; 69 | white-space: nowrap; 70 | } 71 | 72 | .ProseMirror-menu-dropdown { 73 | vertical-align: 1px; 74 | cursor: pointer; 75 | position: relative; 76 | padding-right: 15px; 77 | } 78 | 79 | .ProseMirror-menu-dropdown-wrap { 80 | padding: 1px 0 1px 4px; 81 | display: inline-block; 82 | position: relative; 83 | } 84 | 85 | .ProseMirror-menu-dropdown:after { 86 | content: ""; 87 | border-left: 4px solid transparent; 88 | border-right: 4px solid transparent; 89 | border-top: 4px solid currentColor; 90 | opacity: .6; 91 | position: absolute; 92 | right: 4px; 93 | top: calc(50% - 2px); 94 | } 95 | 96 | .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { 97 | position: absolute; 98 | background: white; 99 | color: #666; 100 | border: 1px solid #aaa; 101 | padding: 2px; 102 | } 103 | 104 | .ProseMirror-menu-dropdown-menu { 105 | z-index: 15; 106 | min-width: 6em; 107 | } 108 | 109 | .ProseMirror-menu-dropdown-item { 110 | cursor: pointer; 111 | padding: 2px 8px 2px 4px; 112 | } 113 | 114 | .ProseMirror-menu-dropdown-item:hover { 115 | background: #f2f2f2; 116 | } 117 | 118 | .ProseMirror-menu-submenu-wrap { 119 | position: relative; 120 | margin-right: -4px; 121 | } 122 | 123 | .ProseMirror-menu-submenu-label:after { 124 | content: ""; 125 | border-top: 4px solid transparent; 126 | border-bottom: 4px solid transparent; 127 | border-left: 4px solid currentColor; 128 | opacity: .6; 129 | position: absolute; 130 | right: 4px; 131 | top: calc(50% - 4px); 132 | } 133 | 134 | .ProseMirror-menu-submenu { 135 | display: none; 136 | min-width: 4em; 137 | left: 100%; 138 | top: -3px; 139 | } 140 | 141 | .ProseMirror-menu-active { 142 | background: #eee; 143 | border-radius: 4px; 144 | } 145 | 146 | .ProseMirror-menu-active { 147 | background: #eee; 148 | border-radius: 4px; 149 | } 150 | 151 | .ProseMirror-menu-disabled { 152 | opacity: .3; 153 | } 154 | 155 | .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { 156 | display: block; 157 | } 158 | 159 | .ProseMirror-menubar { 160 | border-top-left-radius: inherit; 161 | border-top-right-radius: inherit; 162 | position: relative; 163 | min-height: 1em; 164 | color: #666; 165 | padding: 1px 6px; 166 | top: 0; left: 0; right: 0; 167 | border-bottom: 1px solid silver; 168 | background: white; 169 | z-index: 10; 170 | -moz-box-sizing: border-box; 171 | box-sizing: border-box; 172 | overflow: visible; 173 | } 174 | 175 | .ProseMirror-icon { 176 | display: inline-block; 177 | line-height: .8; 178 | vertical-align: -2px; /* Compensate for padding */ 179 | padding: 2px 8px; 180 | cursor: pointer; 181 | } 182 | 183 | .ProseMirror-menu-disabled.ProseMirror-icon { 184 | cursor: default; 185 | } 186 | 187 | .ProseMirror-icon svg { 188 | fill: currentColor; 189 | height: 1em; 190 | } 191 | 192 | .ProseMirror-icon span { 193 | vertical-align: text-top; 194 | } 195 | .ProseMirror-gapcursor { 196 | display: none; 197 | pointer-events: none; 198 | position: absolute; 199 | } 200 | 201 | .ProseMirror-gapcursor:after { 202 | content: ""; 203 | display: block; 204 | position: absolute; 205 | top: -2px; 206 | width: 20px; 207 | border-top: 1px solid black; 208 | animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite; 209 | } 210 | 211 | @keyframes ProseMirror-cursor-blink { 212 | to { 213 | visibility: hidden; 214 | } 215 | } 216 | 217 | .ProseMirror-focused .ProseMirror-gapcursor { 218 | display: block; 219 | } 220 | /* Add space around the hr to make clicking it easier */ 221 | 222 | .ProseMirror-example-setup-style hr { 223 | padding: 2px 10px; 224 | border: none; 225 | margin: 1em 0; 226 | } 227 | 228 | .ProseMirror-example-setup-style hr:after { 229 | content: ""; 230 | display: block; 231 | height: 1px; 232 | background-color: silver; 233 | line-height: 2px; 234 | } 235 | 236 | .ProseMirror ul, .ProseMirror ol { 237 | padding-left: 30px; 238 | } 239 | 240 | .ProseMirror blockquote { 241 | padding-left: 1em; 242 | border-left: 3px solid #eee; 243 | margin-left: 0; margin-right: 0; 244 | } 245 | 246 | .ProseMirror-example-setup-style img { 247 | cursor: default; 248 | } 249 | 250 | .ProseMirror-prompt { 251 | background: white; 252 | padding: 5px 10px 5px 15px; 253 | border: 1px solid silver; 254 | position: fixed; 255 | border-radius: 3px; 256 | z-index: 11; 257 | box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2); 258 | } 259 | 260 | .ProseMirror-prompt h5 { 261 | margin: 0; 262 | font-weight: normal; 263 | font-size: 100%; 264 | color: #444; 265 | } 266 | 267 | .ProseMirror-prompt input[type="text"], 268 | .ProseMirror-prompt textarea { 269 | background: #eee; 270 | border: none; 271 | outline: none; 272 | } 273 | 274 | .ProseMirror-prompt input[type="text"] { 275 | padding: 0 4px; 276 | } 277 | 278 | .ProseMirror-prompt-close { 279 | position: absolute; 280 | left: 2px; top: 1px; 281 | color: #666; 282 | border: none; background: transparent; padding: 0; 283 | } 284 | 285 | .ProseMirror-prompt-close:after { 286 | content: "✕"; 287 | font-size: 12px; 288 | } 289 | 290 | .ProseMirror-invalid { 291 | background: #ffc; 292 | border: 1px solid #cc7; 293 | border-radius: 4px; 294 | padding: 5px 10px; 295 | position: absolute; 296 | min-width: 10em; 297 | } 298 | 299 | .ProseMirror-prompt-buttons { 300 | margin-top: 5px; 301 | display: none; 302 | } 303 | #editor, .editor { 304 | background: white; 305 | color: black; 306 | background-clip: padding-box; 307 | border-radius: 4px; 308 | border: 2px solid rgba(0, 0, 0, 0.2); 309 | padding: 5px 0; 310 | margin-bottom: 23px; 311 | } 312 | 313 | .ProseMirror p:first-child, 314 | .ProseMirror h1:first-child, 315 | .ProseMirror h2:first-child, 316 | .ProseMirror h3:first-child, 317 | .ProseMirror h4:first-child, 318 | .ProseMirror h5:first-child, 319 | .ProseMirror h6:first-child { 320 | margin-top: 10px; 321 | } 322 | 323 | .ProseMirror { 324 | padding: 4px 8px 4px 14px; 325 | line-height: 1.2; 326 | outline: none; 327 | } 328 | 329 | .ProseMirror p { margin-bottom: 1em } 330 | 331 | -------------------------------------------------------------------------------- /prosemirror/prosemirror.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yjs Prosemirror Example 6 | 7 | 8 | 66 | 67 | 68 |69 | 70 | 71 |72 | 73 |This is a demo of the Yjs ⇔ ProseMirror binding: y-prosemirror.
74 |The content of this editor is shared with every client that visits this domain.
75 | 76 | 77 | -------------------------------------------------------------------------------- /prosemirror/prosemirror.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import * as Y from 'yjs' 4 | import { WebsocketProvider } from 'y-websocket' 5 | import { ySyncPlugin, yCursorPlugin, yUndoPlugin, undo, redo, initProseMirrorDoc } from 'y-prosemirror' 6 | import { EditorState } from 'prosemirror-state' 7 | import { EditorView } from 'prosemirror-view' 8 | import { schema } from './schema.js' 9 | import { exampleSetup } from 'prosemirror-example-setup' 10 | import { keymap } from 'prosemirror-keymap' 11 | 12 | const roomname = `prosemirror-demo-${new Date().toLocaleDateString('en-CA')}` 13 | 14 | window.addEventListener('load', () => { 15 | const ydoc = new Y.Doc() 16 | const provider = new WebsocketProvider( 17 | 'wss://demos.yjs.dev/ws', // use the public ws server 18 | // `ws${location.protocol.slice(4)}//${location.host}/ws`, // alternatively: use the local ws server (run `npm start` in root directory) 19 | roomname, 20 | ydoc 21 | ) 22 | const yXmlFragment = ydoc.getXmlFragment('prosemirror') 23 | 24 | const editor = document.createElement('div') 25 | editor.setAttribute('id', 'editor') 26 | const editorContainer = document.createElement('div') 27 | editorContainer.insertBefore(editor, null) 28 | const { doc, mapping } = initProseMirrorDoc(yXmlFragment, schema) 29 | const prosemirrorView = new EditorView(editor, { 30 | state: EditorState.create({ 31 | doc, 32 | schema, 33 | plugins: [ 34 | ySyncPlugin(yXmlFragment, { mapping }), 35 | yCursorPlugin(provider.awareness), 36 | yUndoPlugin(), 37 | keymap({ 38 | 'Mod-z': undo, 39 | 'Mod-y': redo, 40 | 'Mod-Shift-z': redo 41 | }) 42 | ].concat(exampleSetup({ schema })) 43 | }) 44 | }) 45 | document.body.insertBefore(editorContainer, null) 46 | 47 | const connectBtn = /** @type {HTMLElement} */ (document.getElementById('y-connect-btn')) 48 | connectBtn.addEventListener('click', () => { 49 | if (provider.shouldConnect) { 50 | provider.disconnect() 51 | connectBtn.textContent = 'Connect' 52 | } else { 53 | provider.connect() 54 | connectBtn.textContent = 'Disconnect' 55 | } 56 | }) 57 | 58 | // @ts-ignore 59 | window.example = { provider, ydoc, yXmlFragment, prosemirrorView } 60 | }) 61 | -------------------------------------------------------------------------------- /prosemirror/schema.js: -------------------------------------------------------------------------------- 1 | import { Schema } from 'prosemirror-model' 2 | 3 | const brDOM = ['br'] 4 | 5 | const calcYchangeDomAttrs = (attrs, domAttrs = {}) => { 6 | domAttrs = Object.assign({}, domAttrs) 7 | if (attrs.ychange !== null) { 8 | domAttrs.ychange_user = attrs.ychange.user 9 | domAttrs.ychange_state = attrs.ychange.state 10 | } 11 | return domAttrs 12 | } 13 | 14 | // :: Object 15 | // [Specs](#model.NodeSpec) for the nodes defined in this schema. 16 | export const nodes = { 17 | // :: NodeSpec The top level document node. 18 | doc: { 19 | content: 'block+' 20 | }, 21 | 22 | // :: NodeSpec A plain paragraph textblock. Represented in the DOM 23 | // as a `` element. 24 | paragraph: { 25 | attrs: { ychange: { default: null } }, 26 | content: 'inline*', 27 | group: 'block', 28 | parseDOM: [{ tag: 'p' }], 29 | toDOM (node) { return ['p', calcYchangeDomAttrs(node.attrs), 0] } 30 | }, 31 | 32 | // :: NodeSpec A blockquote (`
`) wrapping one or more blocks. 33 | blockquote: { 34 | attrs: { ychange: { default: null } }, 35 | content: 'block+', 36 | group: 'block', 37 | defining: true, 38 | parseDOM: [{ tag: 'blockquote' }], 39 | toDOM (node) { return ['blockquote', calcYchangeDomAttrs(node.attrs), 0] } 40 | }, 41 | 42 | // :: NodeSpec A horizontal rule (`
`). 43 | horizontal_rule: { 44 | attrs: { ychange: { default: null } }, 45 | group: 'block', 46 | parseDOM: [{ tag: 'hr' }], 47 | toDOM (node) { 48 | return ['hr', calcYchangeDomAttrs(node.attrs)] 49 | } 50 | }, 51 | 52 | // :: NodeSpec A heading textblock, with a `level` attribute that 53 | // should hold the number 1 to 6. Parsed and serialized as `` to 54 | // `
` elements. 55 | heading: { 56 | attrs: { 57 | level: { default: 1 }, 58 | ychange: { default: null } 59 | }, 60 | content: 'inline*', 61 | group: 'block', 62 | defining: true, 63 | parseDOM: [{ tag: 'h1', attrs: { level: 1 } }, 64 | { tag: 'h2', attrs: { level: 2 } }, 65 | { tag: 'h3', attrs: { level: 3 } }, 66 | { tag: 'h4', attrs: { level: 4 } }, 67 | { tag: 'h5', attrs: { level: 5 } }, 68 | { tag: 'h6', attrs: { level: 6 } }], 69 | toDOM (node) { return ['h' + node.attrs.level, calcYchangeDomAttrs(node.attrs), 0] } 70 | }, 71 | 72 | // :: NodeSpec A code listing. Disallows marks or non-text inline 73 | // nodes by default. Represented as a `
` element with a 74 | // `` element inside of it. 75 | code_block: { 76 | attrs: { ychange: { default: null } }, 77 | content: 'text*', 78 | marks: '', 79 | group: 'block', 80 | code: true, 81 | defining: true, 82 | parseDOM: [{ tag: 'pre', preserveWhitespace: 'full' }], 83 | toDOM (node) { return ['pre', calcYchangeDomAttrs(node.attrs), ['code', 0]] } 84 | }, 85 | 86 | // :: NodeSpec The text node. 87 | text: { 88 | group: 'inline' 89 | }, 90 | 91 | // :: NodeSpec An inline image (`
`) node. Supports `src`, 92 | // `alt`, and `href` attributes. The latter two default to the empty 93 | // string. 94 | image: { 95 | inline: true, 96 | attrs: { 97 | ychange: { default: null }, 98 | src: {}, 99 | alt: { default: null }, 100 | title: { default: null } 101 | }, 102 | group: 'inline', 103 | draggable: true, 104 | parseDOM: [{ 105 | tag: 'img[src]', 106 | getAttrs (dom) { 107 | return { 108 | src: dom.getAttribute('src'), 109 | title: dom.getAttribute('title'), 110 | alt: dom.getAttribute('alt') 111 | } 112 | } 113 | }], 114 | toDOM (node) { 115 | const domAttrs = { 116 | src: node.attrs.src, 117 | title: node.attrs.title, 118 | alt: node.attrs.alt 119 | } 120 | return ['img', calcYchangeDomAttrs(node.attrs, domAttrs)] 121 | } 122 | }, 123 | 124 | // :: NodeSpec A hard line break, represented in the DOM as `
`. 125 | hard_break: { 126 | inline: true, 127 | group: 'inline', 128 | selectable: false, 129 | parseDOM: [{ tag: 'br' }], 130 | toDOM () { return brDOM } 131 | } 132 | } 133 | 134 | const emDOM = ['em', 0]; const strongDOM = ['strong', 0]; const codeDOM = ['code', 0] 135 | 136 | // :: Object [Specs](#model.MarkSpec) for the marks in the schema. 137 | export const marks = { 138 | // :: MarkSpec A link. Has `href` and `title` attributes. `title` 139 | // defaults to the empty string. Rendered and parsed as an `` 140 | // element. 141 | link: { 142 | attrs: { 143 | href: {}, 144 | title: { default: null } 145 | }, 146 | inclusive: false, 147 | parseDOM: [{ 148 | tag: 'a[href]', 149 | getAttrs (dom) { 150 | return { href: dom.getAttribute('href'), title: dom.getAttribute('title') } 151 | } 152 | }], 153 | toDOM (node) { return ['a', node.attrs, 0] } 154 | }, 155 | 156 | // :: MarkSpec An emphasis mark. Rendered as an `` element. 157 | // Has parse rules that also match `` and `font-style: italic`. 158 | em: { 159 | parseDOM: [{ tag: 'i' }, { tag: 'em' }, { style: 'font-style=italic' }], 160 | toDOM () { return emDOM } 161 | }, 162 | 163 | // :: MarkSpec A strong mark. Rendered as ``, parse rules 164 | // also match `` and `font-weight: bold`. 165 | strong: { 166 | parseDOM: [{ tag: 'strong' }, 167 | // This works around a Google Docs misbehavior where 168 | // pasted content will be inexplicably wrapped in `` 169 | // tags with a font-weight normal. 170 | { tag: 'b', getAttrs: node => node.style.fontWeight !== 'normal' && null }, 171 | { style: 'font-weight', getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null }], 172 | toDOM () { return strongDOM } 173 | }, 174 | 175 | // :: MarkSpec Code font mark. Represented as a `` element. 176 | code: { 177 | parseDOM: [{ tag: 'code' }], 178 | toDOM () { return codeDOM } 179 | }, 180 | ychange: { 181 | attrs: { 182 | user: { default: null }, 183 | state: { default: null } 184 | }, 185 | inclusive: false, 186 | parseDOM: [{ tag: 'ychange' }], 187 | toDOM (node) { 188 | return ['ychange', { ychange_user: node.attrs.user, ychange_state: node.attrs.state }, 0] 189 | } 190 | } 191 | } 192 | 193 | // :: Schema 194 | // This schema rougly corresponds to the document schema used by 195 | // [CommonMark](http://commonmark.org/), minus the list elements, 196 | // which are defined in the [`prosemirror-schema-list`](#schema-list) 197 | // module. 198 | // 199 | // To reuse elements from this schema, extend or read from its 200 | // `spec.nodes` and `spec.marks` [properties](#model.Schema.spec). 201 | export const schema = new Schema({ nodes, marks }) 202 | -------------------------------------------------------------------------------- /prosemirror/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'development', 5 | devtool: 'source-map', 6 | entry: { 7 | prosemirror: './prosemirror.js' 8 | }, 9 | output: { 10 | globalObject: 'self', 11 | path: path.resolve(__dirname, './dist/'), 12 | filename: '[name].bundle.js', 13 | publicPath: '/dist/' 14 | }, 15 | devServer: { 16 | static: path.join(__dirname), 17 | compress: true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /quill/README.md: -------------------------------------------------------------------------------- 1 | # Quill Demo 2 | > [y-quill](https://docs.yjs.dev/ecosystem/editor-bindings/quill) / [y-websocket](https://docs.yjs.dev/ecosystem/connection-provider/y-websocket) / [Live Demo](https://demos.yjs.dev/quill/quill.html) 3 | 4 | This is a demo of a [Quill](https://quilljs.com/) editor that was made collaborative with Yjs & y-quill. Learn more about how you can customize Quill in [their documentation](https://quilljs.com/). 5 | 6 | We use the [y-websocket](https://docs.yjs.dev/ecosystem/connection-provider/y-websocket) provider to share document updates through a server. There are many more providers available for Yjs - try switching to another provider. [See docs](https://docs.yjs.dev/ecosystem/connection-provider). 7 | 8 | Also you could try adding offline persistence to this demo. Wouldn't it be cool if document updates are persisted in the browser, so you can load your application faster? Try it out: https://docs.yjs.dev/getting-started/allowing-offline-editing 9 | 10 | ## Quick Start 11 | 12 | ```sh 13 | npm i 14 | npm start 15 | # Project is running at http://localhost:8080/ 16 | ``` 17 | -------------------------------------------------------------------------------- /quill/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yjs-quill-demo", 3 | "version": "1.0.0", 4 | "description": "Yjs ❤ Quill", 5 | "author": "Kevin Jahns
", 6 | "license": "UNLICENSE", 7 | "scripts": { 8 | "watch": "webpack -w --stats errors-only", 9 | "dist": "webpack --mode=production", 10 | "start": "webpack serve --open ./quill.html" 11 | }, 12 | "dependencies": { 13 | "yjs": "^13.5.8", 14 | "y-quill": "^0.1.4", 15 | "y-websocket": "^1.4.1", 16 | "quill": "^1.3.7", 17 | "quill-cursors": "^2.2.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /quill/quill.css: -------------------------------------------------------------------------------- 1 | #editor { 2 | min-height: 500px; 3 | } -------------------------------------------------------------------------------- /quill/quill.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Yjs Quill Example 6 | 7 | 8 | 10 | 12 | 14 | 15 | 16 | 17 | 18 |This is a demo of the Yjs ⇔ Quill binding: y-quill.
19 |The content of this editor is shared with every client that visits this domain.
20 | 21 | 22 | -------------------------------------------------------------------------------- /quill/quill.js: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | 3 | import * as Y from 'yjs' 4 | import { WebsocketProvider } from 'y-websocket' 5 | import { QuillBinding } from 'y-quill' 6 | import Quill from 'quill' 7 | import QuillCursors from 'quill-cursors' 8 | 9 | Quill.register('modules/cursors', QuillCursors) 10 | const roomname = `codemirror-demo-${new Date().toLocaleDateString('en-CA')}` 11 | 12 | window.addEventListener('load', () => { 13 | const ydoc = new Y.Doc() 14 | const provider = new WebsocketProvider( 15 | 'wss://demos.yjs.dev/ws', // use the public ws server 16 | // `ws${location.protocol.slice(4)}//${location.host}/ws`, // alternatively: use the local ws server (run `npm start` in root directory) 17 | roomname, 18 | ydoc 19 | ) 20 | const ytext = ydoc.getText('quill') 21 | const editorContainer = document.createElement('div') 22 | editorContainer.setAttribute('id', 'editor') 23 | document.body.insertBefore(editorContainer, null) 24 | 25 | const editor = new Quill(editorContainer, { 26 | modules: { 27 | cursors: true, 28 | toolbar: [ 29 | [{ header: [1, 2, false] }], 30 | ['bold', 'italic', 'underline'], 31 | ['image', 'code-block'] 32 | ], 33 | history: { 34 | userOnly: true 35 | } 36 | }, 37 | placeholder: 'Start collaborating...', 38 | theme: 'snow' // or 'bubble' 39 | }) 40 | 41 | const binding = new QuillBinding(ytext, editor, provider.awareness) 42 | 43 | /* 44 | // Define user name and user name 45 | // Check the quill-cursors package on how to change the way cursors are rendered 46 | provider.awareness.setLocalStateField('user', { 47 | name: 'Typing Jimmy', 48 | color: 'blue' 49 | }) 50 | */ 51 | 52 | const connectBtn = document.getElementById('y-connect-btn') 53 | connectBtn.addEventListener('click', () => { 54 | if (provider.shouldConnect) { 55 | provider.disconnect() 56 | connectBtn.textContent = 'Connect' 57 | } else { 58 | provider.connect() 59 | connectBtn.textContent = 'Disconnect' 60 | } 61 | }) 62 | 63 | // @ts-ignore 64 | window.example = { provider, ydoc, ytext, binding, Y } 65 | }) 66 | -------------------------------------------------------------------------------- /quill/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | mode: 'development', 5 | devtool: 'source-map', 6 | entry: { 7 | quill: './quill.js' 8 | }, 9 | output: { 10 | globalObject: 'self', 11 | path: path.resolve(__dirname, './dist/'), 12 | filename: '[name].bundle.js', 13 | publicPath: '/dist/' 14 | }, 15 | devServer: { 16 | static: path.join(__dirname), 17 | compress: true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /react-prosemirror/README.md: -------------------------------------------------------------------------------- 1 | # react-prosemirror 2 | > Short example on how to set up `@handlewithcare/prosemirror` with y-prosemirror 3 | -------------------------------------------------------------------------------- /react-prosemirror/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |React-ProseMirror Demo 7 | 8 | 66 | 67 | 125 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /react-prosemirror/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import { EditorState } from 'prosemirror-state' 4 | import { ProseMirror, ProseMirrorDoc, reactKeys } from '@handlewithcare/react-prosemirror' 5 | 6 | import { exampleSetup } from 'prosemirror-example-setup' 7 | import { schema } from 'prosemirror-schema-basic' 8 | import { keymap } from 'prosemirror-keymap' 9 | import { baseKeymap } from 'prosemirror-commands' 10 | 11 | import * as Y from 'yjs' 12 | import { WebsocketProvider } from 'y-websocket' 13 | import { ySyncPlugin, yCursorPlugin, yUndoPlugin, undo, redo, initProseMirrorDoc } from 'y-prosemirror' 14 | 15 | const roomname = `prosemirror-react-demo-${new Date().toLocaleDateString('en-CA')}` 16 | 17 | const ydoc = new Y.Doc() 18 | const provider = new WebsocketProvider( 19 | 'wss://demos.yjs.dev/ws', // use the public ws server 20 | // `ws${location.protocol.slice(4)}//${location.host}/ws`, // alternatively: use the local ws server (run `npm start` in root directory) 21 | roomname, 22 | ydoc 23 | ) 24 | 25 | function App () { 26 | const yXmlFragment = ydoc.getXmlFragment('prosemirror') 27 | const { doc, mapping } = initProseMirrorDoc(yXmlFragment, schema) 28 | const defaultState = EditorState.create({ 29 | doc, 30 | schema, 31 | plugins: [ 32 | ySyncPlugin(yXmlFragment, { mapping }), 33 | yCursorPlugin(provider.awareness), 34 | yUndoPlugin(), 35 | keymap({ 36 | 'Mod-z': undo, 37 | 'Mod-y': redo, 38 | 'Mod-Shift-z': redo 39 | }), 40 | keymap(baseKeymap), 41 | reactKeys() 42 | ].concat(exampleSetup({ schema })) 43 | }) 44 | 45 | window.example = { ydoc, provider, yXmlFragment, pmDoc: doc } 46 | 47 | return ( 48 |49 | 51 | ) 52 | } 53 | 54 | const root = createRoot(document.getElementById('root')) 55 | root.render(50 | ) 56 | -------------------------------------------------------------------------------- /react-prosemirror/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-prosemirror", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "npx esbuild --bundle index.jsx --serve --outdir=dist --servedir=." 9 | }, 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": { 13 | "@types/react": "^19.0.10", 14 | "@types/react-dom": "^19.0.4", 15 | "esbuild": "^0.25.0", 16 | "react": "^19.0.0", 17 | "react-dom": "^19.0.0" 18 | }, 19 | "dependencies": { 20 | "@handlewithcare/react-prosemirror": "^2.2.4", 21 | "prosemirror-commands": "^1.7.0", 22 | "prosemirror-example-setup": "^1.2.3", 23 | "prosemirror-keymap": "^1.2.2", 24 | "prosemirror-schema-basic": "^1.2.3", 25 | "prosemirror-schema-list": "^1.5.0", 26 | "y-prosemirror": "^1.2.16", 27 | "y-websocket": "^2.1.0", 28 | "yjs": "^13.6.23" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /react-prosemirror/prosemirror.css: -------------------------------------------------------------------------------- 1 | .ProseMirror { 2 | position: relative; 3 | } 4 | 5 | .ProseMirror { 6 | word-wrap: break-word; 7 | white-space: pre-wrap; 8 | -webkit-font-variant-ligatures: none; 9 | font-variant-ligatures: none; 10 | } 11 | 12 | .ProseMirror pre { 13 | white-space: pre-wrap; 14 | } 15 | 16 | .ProseMirror li { 17 | position: relative; 18 | } 19 | 20 | .ProseMirror-hideselection *::selection { background: transparent; } 21 | .ProseMirror-hideselection *::-moz-selection { background: transparent; } 22 | .ProseMirror-hideselection { caret-color: transparent; } 23 | 24 | .ProseMirror-selectednode { 25 | outline: 2px solid #8cf; 26 | } 27 | 28 | /* Make sure li selections wrap around markers */ 29 | 30 | li.ProseMirror-selectednode { 31 | outline: none; 32 | } 33 | 34 | li.ProseMirror-selectednode:after { 35 | content: ""; 36 | position: absolute; 37 | left: -32px; 38 | right: -2px; top: -2px; bottom: -2px; 39 | border: 2px solid #8cf; 40 | pointer-events: none; 41 | } 42 | .ProseMirror-textblock-dropdown { 43 | min-width: 3em; 44 | } 45 | 46 | .ProseMirror-menu { 47 | margin: 0 -4px; 48 | line-height: 1; 49 | } 50 | 51 | .ProseMirror-tooltip .ProseMirror-menu { 52 | width: -webkit-fit-content; 53 | width: fit-content; 54 | white-space: pre; 55 | } 56 | 57 | .ProseMirror-menuitem { 58 | margin-right: 3px; 59 | display: inline-block; 60 | } 61 | 62 | .ProseMirror-menuseparator { 63 | border-right: 1px solid #ddd; 64 | margin-right: 3px; 65 | } 66 | 67 | .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu { 68 | font-size: 90%; 69 | white-space: nowrap; 70 | } 71 | 72 | .ProseMirror-menu-dropdown { 73 | vertical-align: 1px; 74 | cursor: pointer; 75 | position: relative; 76 | padding-right: 15px; 77 | } 78 | 79 | .ProseMirror-menu-dropdown-wrap { 80 | padding: 1px 0 1px 4px; 81 | display: inline-block; 82 | position: relative; 83 | } 84 | 85 | .ProseMirror-menu-dropdown:after { 86 | content: ""; 87 | border-left: 4px solid transparent; 88 | border-right: 4px solid transparent; 89 | border-top: 4px solid currentColor; 90 | opacity: .6; 91 | position: absolute; 92 | right: 4px; 93 | top: calc(50% - 2px); 94 | } 95 | 96 | .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu { 97 | position: absolute; 98 | background: white; 99 | color: #666; 100 | border: 1px solid #aaa; 101 | padding: 2px; 102 | } 103 | 104 | .ProseMirror-menu-dropdown-menu { 105 | z-index: 15; 106 | min-width: 6em; 107 | } 108 | 109 | .ProseMirror-menu-dropdown-item { 110 | cursor: pointer; 111 | padding: 2px 8px 2px 4px; 112 | } 113 | 114 | .ProseMirror-menu-dropdown-item:hover { 115 | background: #f2f2f2; 116 | } 117 | 118 | .ProseMirror-menu-submenu-wrap { 119 | position: relative; 120 | margin-right: -4px; 121 | } 122 | 123 | .ProseMirror-menu-submenu-label:after { 124 | content: ""; 125 | border-top: 4px solid transparent; 126 | border-bottom: 4px solid transparent; 127 | border-left: 4px solid currentColor; 128 | opacity: .6; 129 | position: absolute; 130 | right: 4px; 131 | top: calc(50% - 4px); 132 | } 133 | 134 | .ProseMirror-menu-submenu { 135 | display: none; 136 | min-width: 4em; 137 | left: 100%; 138 | top: -3px; 139 | } 140 | 141 | .ProseMirror-menu-active { 142 | background: #eee; 143 | border-radius: 4px; 144 | } 145 | 146 | .ProseMirror-menu-active { 147 | background: #eee; 148 | border-radius: 4px; 149 | } 150 | 151 | .ProseMirror-menu-disabled { 152 | opacity: .3; 153 | } 154 | 155 | .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu { 156 | display: block; 157 | } 158 | 159 | .ProseMirror-menubar { 160 | border-top-left-radius: inherit; 161 | border-top-right-radius: inherit; 162 | position: relative; 163 | min-height: 1em; 164 | color: #666; 165 | padding: 1px 6px; 166 | top: 0; left: 0; right: 0; 167 | border-bottom: 1px solid silver; 168 | background: white; 169 | z-index: 10; 170 | -moz-box-sizing: border-box; 171 | box-sizing: border-box; 172 | overflow: visible; 173 | } 174 | 175 | .ProseMirror-icon { 176 | display: inline-block; 177 | line-height: .8; 178 | vertical-align: -2px; /* Compensate for padding */ 179 | padding: 2px 8px; 180 | cursor: pointer; 181 | } 182 | 183 | .ProseMirror-menu-disabled.ProseMirror-icon { 184 | cursor: default; 185 | } 186 | 187 | .ProseMirror-icon svg { 188 | fill: currentColor; 189 | height: 1em; 190 | } 191 | 192 | .ProseMirror-icon span { 193 | vertical-align: text-top; 194 | } 195 | .ProseMirror-gapcursor { 196 | display: none; 197 | pointer-events: none; 198 | position: absolute; 199 | } 200 | 201 | .ProseMirror-gapcursor:after { 202 | content: ""; 203 | display: block; 204 | position: absolute; 205 | top: -2px; 206 | width: 20px; 207 | border-top: 1px solid black; 208 | animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite; 209 | } 210 | 211 | @keyframes ProseMirror-cursor-blink { 212 | to { 213 | visibility: hidden; 214 | } 215 | } 216 | 217 | .ProseMirror-focused .ProseMirror-gapcursor { 218 | display: block; 219 | } 220 | /* Add space around the hr to make clicking it easier */ 221 | 222 | .ProseMirror-example-setup-style hr { 223 | padding: 2px 10px; 224 | border: none; 225 | margin: 1em 0; 226 | } 227 | 228 | .ProseMirror-example-setup-style hr:after { 229 | content: ""; 230 | display: block; 231 | height: 1px; 232 | background-color: silver; 233 | line-height: 2px; 234 | } 235 | 236 | .ProseMirror ul, .ProseMirror ol { 237 | padding-left: 30px; 238 | } 239 | 240 | .ProseMirror blockquote { 241 | padding-left: 1em; 242 | border-left: 3px solid #eee; 243 | margin-left: 0; margin-right: 0; 244 | } 245 | 246 | .ProseMirror-example-setup-style img { 247 | cursor: default; 248 | } 249 | 250 | .ProseMirror-prompt { 251 | background: white; 252 | padding: 5px 10px 5px 15px; 253 | border: 1px solid silver; 254 | position: fixed; 255 | border-radius: 3px; 256 | z-index: 11; 257 | box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2); 258 | } 259 | 260 | .ProseMirror-prompt h5 { 261 | margin: 0; 262 | font-weight: normal; 263 | font-size: 100%; 264 | color: #444; 265 | } 266 | 267 | .ProseMirror-prompt input[type="text"], 268 | .ProseMirror-prompt textarea { 269 | background: #eee; 270 | border: none; 271 | outline: none; 272 | } 273 | 274 | .ProseMirror-prompt input[type="text"] { 275 | padding: 0 4px; 276 | } 277 | 278 | .ProseMirror-prompt-close { 279 | position: absolute; 280 | left: 2px; top: 1px; 281 | color: #666; 282 | border: none; background: transparent; padding: 0; 283 | } 284 | 285 | .ProseMirror-prompt-close:after { 286 | content: "✕"; 287 | font-size: 12px; 288 | } 289 | 290 | .ProseMirror-invalid { 291 | background: #ffc; 292 | border: 1px solid #cc7; 293 | border-radius: 4px; 294 | padding: 5px 10px; 295 | position: absolute; 296 | min-width: 10em; 297 | } 298 | 299 | .ProseMirror-prompt-buttons { 300 | margin-top: 5px; 301 | display: none; 302 | } 303 | #editor, .editor { 304 | background: white; 305 | color: black; 306 | background-clip: padding-box; 307 | border-radius: 4px; 308 | border: 2px solid rgba(0, 0, 0, 0.2); 309 | padding: 5px 0; 310 | margin-bottom: 23px; 311 | } 312 | 313 | .ProseMirror p:first-child, 314 | .ProseMirror h1:first-child, 315 | .ProseMirror h2:first-child, 316 | .ProseMirror h3:first-child, 317 | .ProseMirror h4:first-child, 318 | .ProseMirror h5:first-child, 319 | .ProseMirror h6:first-child { 320 | margin-top: 10px; 321 | } 322 | 323 | .ProseMirror { 324 | padding: 4px 8px 4px 14px; 325 | line-height: 1.2; 326 | outline: none; 327 | } 328 | 329 | .ProseMirror p { margin-bottom: 1em } 330 | 331 | --------------------------------------------------------------------------------