Compare commits
10 Commits
sync-msg-d
...
rich-text
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55d1e7b03a | ||
|
|
0f37a2ba69 | ||
|
|
00019c7578 | ||
|
|
cdb0ac2b9f | ||
|
|
a1d84c433c | ||
|
|
40eb81135c | ||
|
|
65fad3d84c | ||
|
|
00cad168c9 | ||
|
|
ce5475f127 | ||
|
|
3095caaa6d |
13
examples/richtext/.eslintrc.cjs
Normal file
13
examples/richtext/.eslintrc.cjs
Normal file
@@ -0,0 +1,13 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2020: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react-refresh'],
|
||||
rules: {},
|
||||
}
|
||||
24
examples/richtext/.gitignore
vendored
Normal file
24
examples/richtext/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
528
examples/richtext/CHANGELOG.md
Normal file
528
examples/richtext/CHANGELOG.md
Normal file
@@ -0,0 +1,528 @@
|
||||
# jazz-example-chat
|
||||
|
||||
## 0.0.57
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.10
|
||||
- jazz-react@0.7.10
|
||||
- jazz-tools@0.7.10
|
||||
|
||||
## 0.0.56
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.9
|
||||
- jazz-react@0.7.9
|
||||
- jazz-tools@0.7.9
|
||||
|
||||
## 0.0.55
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.8
|
||||
- jazz-react@0.7.8
|
||||
|
||||
## 0.0.54
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [9fdc91c]
|
||||
- jazz-react@0.7.7
|
||||
|
||||
## 0.0.53
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.6
|
||||
- jazz-react@0.7.6
|
||||
|
||||
## 0.0.52
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.5
|
||||
|
||||
## 0.0.51
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.4
|
||||
|
||||
## 0.0.50
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.3
|
||||
- jazz-react@0.7.3
|
||||
|
||||
## 0.0.49
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.2
|
||||
|
||||
## 0.0.48
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.1
|
||||
- jazz-react@0.7.1
|
||||
|
||||
## 0.0.47
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [96c494f]
|
||||
- Updated dependencies [59c18c3]
|
||||
- Updated dependencies [19f52b7]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [d8fe2b1]
|
||||
- Updated dependencies [19004b4]
|
||||
- Updated dependencies [a78f168]
|
||||
- Updated dependencies [1200aae]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [52675c9]
|
||||
- Updated dependencies [129e2c1]
|
||||
- Updated dependencies [6d49e9b]
|
||||
- Updated dependencies [1cfa279]
|
||||
- Updated dependencies [704af7d]
|
||||
- Updated dependencies [e97f730]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [460478f]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [e299c3e]
|
||||
- Updated dependencies [ed5643a]
|
||||
- Updated dependencies [bde684f]
|
||||
- Updated dependencies [bf0f8ec]
|
||||
- Updated dependencies [c4151fc]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [01ac646]
|
||||
- Updated dependencies [a5e68a4]
|
||||
- Updated dependencies [8636319]
|
||||
- Updated dependencies [952982e]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [5fa277c]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [21771c4]
|
||||
- Updated dependencies [77c2b56]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [d2e03ff]
|
||||
- Updated dependencies [354bdcd]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [60d5ca2]
|
||||
- Updated dependencies [69ac514]
|
||||
- Updated dependencies [f8a5c46]
|
||||
- Updated dependencies [f0f6f1b]
|
||||
- Updated dependencies [e5eed5b]
|
||||
- Updated dependencies [1a44f87]
|
||||
- Updated dependencies [627d895]
|
||||
- Updated dependencies [1200aae]
|
||||
- Updated dependencies [63374cc]
|
||||
- Updated dependencies [ece35b3]
|
||||
- Updated dependencies [38d4410]
|
||||
- Updated dependencies [85d2b62]
|
||||
- Updated dependencies [fd86c11]
|
||||
- Updated dependencies [52675c9]
|
||||
- jazz-tools@0.7.0
|
||||
- cojson@0.7.0
|
||||
- jazz-react@0.7.0
|
||||
- hash-slash@0.2.0
|
||||
|
||||
## 0.0.47-alpha.42
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.42
|
||||
- cojson@0.7.0-alpha.42
|
||||
- jazz-react@0.7.0-alpha.42
|
||||
|
||||
## 0.0.47-alpha.41
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-tools@0.7.0-alpha.41
|
||||
- jazz-react@0.7.0-alpha.41
|
||||
|
||||
## 0.0.47-alpha.40
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.40
|
||||
|
||||
## 0.0.47-alpha.39
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- cojson@0.7.0-alpha.39
|
||||
- jazz-react@0.7.0-alpha.39
|
||||
- jazz-tools@0.7.0-alpha.39
|
||||
|
||||
## 0.0.47-alpha.38
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.38
|
||||
- jazz-react@0.7.0-alpha.38
|
||||
- cojson@0.7.0-alpha.38
|
||||
|
||||
## 0.0.47-alpha.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.37
|
||||
- cojson@0.7.0-alpha.37
|
||||
- jazz-tools@0.7.0-alpha.37
|
||||
|
||||
## 0.0.47-alpha.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [1a35307]
|
||||
- Updated dependencies [6b0418f]
|
||||
- Updated dependencies [1a35307]
|
||||
- cojson@0.7.0-alpha.36
|
||||
- jazz-tools@0.7.0-alpha.36
|
||||
- jazz-react@0.7.0-alpha.36
|
||||
|
||||
## 0.0.47-alpha.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- cojson@0.7.0-alpha.35
|
||||
- jazz-tools@0.7.0-alpha.35
|
||||
- jazz-react@0.7.0-alpha.35
|
||||
|
||||
## 0.0.47-alpha.34
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.34
|
||||
- jazz-react@0.7.0-alpha.34
|
||||
|
||||
## 0.0.47-alpha.33
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.33
|
||||
|
||||
## 0.0.47-alpha.32
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.3
|
||||
- jazz-tools@0.7.0-alpha.32
|
||||
- jazz-react@0.7.0-alpha.32
|
||||
|
||||
## 0.0.47-alpha.31
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.31
|
||||
- jazz-react@0.7.0-alpha.31
|
||||
|
||||
## 0.0.47-alpha.30
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.30
|
||||
- jazz-react@0.7.0-alpha.30
|
||||
|
||||
## 0.0.47-alpha.29
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.29
|
||||
- cojson@0.7.0-alpha.29
|
||||
- jazz-react@0.7.0-alpha.29
|
||||
|
||||
## 0.0.47-alpha.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.28
|
||||
- cojson@0.7.0-alpha.28
|
||||
- jazz-react@0.7.0-alpha.28
|
||||
|
||||
## 0.0.47-alpha.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.27
|
||||
- cojson@0.7.0-alpha.27
|
||||
- jazz-react@0.7.0-alpha.27
|
||||
|
||||
## 0.0.47-alpha.26
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.26
|
||||
- jazz-react@0.7.0-alpha.26
|
||||
|
||||
## 0.0.47-alpha.25
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.25
|
||||
- jazz-react@0.7.0-alpha.25
|
||||
|
||||
## 0.0.47-alpha.24
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.24
|
||||
- cojson@0.7.0-alpha.24
|
||||
- jazz-react@0.7.0-alpha.24
|
||||
|
||||
## 0.0.47-alpha.23
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.23
|
||||
- jazz-react@0.7.0-alpha.23
|
||||
|
||||
## 0.0.47-alpha.22
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.22
|
||||
- jazz-react@0.7.0-alpha.22
|
||||
|
||||
## 0.0.47-alpha.21
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.21
|
||||
- jazz-tools@0.7.0-alpha.21
|
||||
|
||||
## 0.0.47-alpha.20
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.20
|
||||
- jazz-tools@0.7.0-alpha.20
|
||||
|
||||
## 0.0.47-alpha.19
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.19
|
||||
- jazz-react@0.7.0-alpha.19
|
||||
|
||||
## 0.0.47-alpha.18
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- jazz-react@0.7.0-alpha.18
|
||||
|
||||
## 0.0.47-alpha.17
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.17
|
||||
- jazz-react@0.7.0-alpha.17
|
||||
|
||||
## 0.0.47-alpha.16
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.16
|
||||
- jazz-react@0.7.0-alpha.16
|
||||
|
||||
## 0.0.47-alpha.15
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.15
|
||||
- jazz-react@0.7.0-alpha.15
|
||||
|
||||
## 0.0.47-alpha.14
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.14
|
||||
- jazz-react@0.7.0-alpha.14
|
||||
|
||||
## 0.0.47-alpha.13
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.13
|
||||
- jazz-react@0.7.0-alpha.13
|
||||
|
||||
## 0.0.47-alpha.12
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.12
|
||||
- jazz-tools@0.7.0-alpha.12
|
||||
|
||||
## 0.0.47-alpha.11
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.11
|
||||
- jazz-tools@0.7.0-alpha.11
|
||||
- cojson@0.7.0-alpha.11
|
||||
|
||||
## 0.0.47-alpha.10
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.10
|
||||
- jazz-tools@0.7.0-alpha.10
|
||||
- cojson@0.7.0-alpha.10
|
||||
|
||||
## 0.0.47-alpha.9
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.9
|
||||
- jazz-tools@0.7.0-alpha.9
|
||||
|
||||
## 0.0.47-alpha.8
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.8
|
||||
- jazz-tools@0.7.0-alpha.8
|
||||
|
||||
## 0.0.47-alpha.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.7
|
||||
- jazz-tools@0.7.0-alpha.7
|
||||
- cojson@0.7.0-alpha.7
|
||||
|
||||
## 0.0.47-alpha.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.6
|
||||
- jazz-tools@0.7.0-alpha.6
|
||||
|
||||
## 0.0.47-alpha.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.7.0-alpha.5
|
||||
- jazz-tools@0.7.0-alpha.5
|
||||
- cojson@0.7.0-alpha.5
|
||||
|
||||
## 0.0.47-alpha.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.4
|
||||
- jazz-react@0.7.0-alpha.4
|
||||
|
||||
## 0.0.47-alpha.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-tools@0.7.0-alpha.3
|
||||
- jazz-react@0.7.0-alpha.3
|
||||
|
||||
## 0.0.47-alpha.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.2
|
||||
- jazz-react@0.7.0-alpha.2
|
||||
- jazz-tools@0.7.0-alpha.2
|
||||
|
||||
## 0.0.47-alpha.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.1
|
||||
- jazz-react@0.7.0-alpha.1
|
||||
- jazz-tools@0.7.0-alpha.1
|
||||
- cojson@0.7.0-alpha.1
|
||||
|
||||
## 0.0.47-alpha.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- hash-slash@0.2.0-alpha.0
|
||||
- jazz-react@0.7.0-alpha.0
|
||||
- jazz-tools@0.7.0-alpha.0
|
||||
- cojson@0.7.0-alpha.0
|
||||
|
||||
## 0.0.46
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies
|
||||
- jazz-react@0.5.0
|
||||
- jazz-react-auth-local@0.4.16
|
||||
4
examples/richtext/Dockerfile
Normal file
4
examples/richtext/Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
||||
FROM caddy:2.7.3-alpine
|
||||
LABEL org.opencontainers.image.source="https://github.com/gardencmp/jazz"
|
||||
|
||||
COPY ./dist /usr/share/caddy/
|
||||
42
examples/richtext/README.md
Normal file
42
examples/richtext/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Jazz Chat Example
|
||||
|
||||
Live version: https://example-chat.jazz.tools
|
||||
|
||||
## Installing & running the example locally
|
||||
|
||||
(this requires `pnpm` to be installed, see [https://pnpm.io/installation](https://pnpm.io/installation))
|
||||
|
||||
Start by checking out `jazz`
|
||||
```bash
|
||||
git clone https://github.com/gardencmp/jazz.git
|
||||
cd jazz/examples/chat
|
||||
pnpm pack --pack-destination /tmp
|
||||
mkdir -p ~/jazz-examples/chat # or any other directory
|
||||
tar -xf /tmp/jazz-example-chat-* --strip-components 1 -C ~/jazz-examples/chat
|
||||
cd ~/jazz-examples/chat
|
||||
```
|
||||
|
||||
This ensures that you have the example app without git history and independent of the Jazz multi-package monorepo.
|
||||
|
||||
Install dependencies:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
Start the dev server:
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Questions / problems / feedback
|
||||
|
||||
If you have feedback, let us know on [Discord](https://discord.gg/utDMjHYg42) or open an issue or PR to fix something that seems wrong.
|
||||
|
||||
|
||||
## Configuration: sync server
|
||||
|
||||
By default, the example app uses [Jazz Global Mesh](https://jazz.tools/mesh) (`wss://sync.jazz.tools`) - so cross-device use, invites and collaboration should just work.
|
||||
|
||||
You can also run a local sync server by running `npx cojson-simple-sync` and adding the query param `?sync=ws://localhost:4200` to the URL of the example app (for example: `http://localhost:5173/?sync=ws://localhost:4200`), or by setting the `sync` parameter of the `<Jazz.Provider>` provider component in [./src/2_main.tsx](./src/2_main.tsx).
|
||||
14
examples/richtext/index.html
Normal file
14
examples/richtext/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/png" href="/jazz-logo.png" />
|
||||
<link rel="stylesheet" href="/src/index.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Jazz Chat Example</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/app.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
56
examples/richtext/job-template.nomad
Normal file
56
examples/richtext/job-template.nomad
Normal file
@@ -0,0 +1,56 @@
|
||||
job "chat$BRANCH_SUFFIX" {
|
||||
region = "global"
|
||||
datacenters = ["*"]
|
||||
|
||||
group "static" {
|
||||
count = 4
|
||||
|
||||
network {
|
||||
port "http" {
|
||||
to = 80
|
||||
}
|
||||
}
|
||||
|
||||
constraint {
|
||||
attribute = "${node.class}"
|
||||
operator = "="
|
||||
value = "mesh"
|
||||
}
|
||||
|
||||
spread {
|
||||
attribute = "${node.datacenter}"
|
||||
weight = 100
|
||||
}
|
||||
|
||||
constraint {
|
||||
distinct_hosts = true
|
||||
}
|
||||
|
||||
task "server" {
|
||||
driver = "docker"
|
||||
|
||||
config {
|
||||
image = "$DOCKER_TAG"
|
||||
ports = ["http"]
|
||||
|
||||
auth = {
|
||||
username = "$DOCKER_USER"
|
||||
password = "$DOCKER_PASSWORD"
|
||||
}
|
||||
}
|
||||
|
||||
service {
|
||||
tags = ["public"]
|
||||
name = "chat$BRANCH_SUFFIX"
|
||||
port = "http"
|
||||
provider = "consul"
|
||||
}
|
||||
|
||||
resources {
|
||||
cpu = 50 # MHz
|
||||
memory = 50 # MB
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# deploy bump 4
|
||||
61
examples/richtext/package.json
Normal file
61
examples/richtext/package.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"name": "jazz-example-richtext",
|
||||
"private": true,
|
||||
"version": "0.0.57",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"format": "echo 'chat example is codegolfed'",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{ts,tsx}": "eslint --fix",
|
||||
"*.{js,jsx,mdx,json}": "prettier --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-toast": "^1.1.4",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
"cojson": "workspace:*",
|
||||
"hash-slash": "workspace:*",
|
||||
"jazz-react": "workspace:*",
|
||||
"jazz-tools": "workspace:*",
|
||||
"lucide-react": "^0.274.0",
|
||||
"prosemirror-example-setup": "^1.2.2",
|
||||
"prosemirror-menu": "^1.2.4",
|
||||
"prosemirror-model": "^1.21.1",
|
||||
"prosemirror-schema-basic": "^1.2.2",
|
||||
"prosemirror-state": "^1.4.3",
|
||||
"prosemirror-transform": "^1.9.0",
|
||||
"prosemirror-view": "^1.33.7",
|
||||
"qrcode": "^1.5.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router": "^6.16.0",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-use": "^17.4.0",
|
||||
"tailwind-merge": "^1.14.0",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"uniqolor": "^1.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.15",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||
"@typescript-eslint/parser": "^6.0.0",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.3",
|
||||
"postcss": "^8.4.27",
|
||||
"tailwindcss": "^3.3.3",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^5.0.10"
|
||||
}
|
||||
}
|
||||
6
examples/richtext/postcss.config.js
Normal file
6
examples/richtext/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
BIN
examples/richtext/public/jazz-logo.png
Normal file
BIN
examples/richtext/public/jazz-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.3 KiB |
313
examples/richtext/src/app.tsx
Normal file
313
examples/richtext/src/app.tsx
Normal file
@@ -0,0 +1,313 @@
|
||||
import { Group, ID, CoRichText, Marks, TreeNode, TreeLeaf } from "jazz-tools";
|
||||
import { createJazzReactContext, DemoAuth } from "jazz-react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { useIframeHashRouter } from "hash-slash";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export class Document extends CoRichText {}
|
||||
|
||||
const Jazz = createJazzReactContext({
|
||||
auth: DemoAuth({ appName: "Jazz Richtext Doc" }),
|
||||
peer: `wss://mesh.jazz.tools/?key=you@example.com`,
|
||||
});
|
||||
export const { useAccount, useCoState } = Jazz;
|
||||
|
||||
function App() {
|
||||
const { me, logOut } = useAccount();
|
||||
|
||||
const createDocument = () => {
|
||||
const group = Group.create({ owner: me });
|
||||
group.addMember("everyone", "writer");
|
||||
const Doc = Document.createFromPlainTextAndMark("", Marks.Paragraph, {tag: "paragraph"}, { owner: me });
|
||||
setTimeout(() => {
|
||||
location.hash = "/doc/" + Doc.id;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-screen h-screen p-2 dark:bg-black dark:text-white">
|
||||
<div className="rounded mb-5 px-2 py-1 text-sm self-end">
|
||||
{me.profile?.name} · <button onClick={logOut}>Log Out</button>
|
||||
</div>
|
||||
{useIframeHashRouter().route({
|
||||
"/": () => createDocument() as never,
|
||||
"/doc/:id": (id) => (
|
||||
<DocumentComponent docID={id as ID<Document>} />
|
||||
),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<Jazz.Provider>
|
||||
<App />
|
||||
</Jazz.Provider>
|
||||
);
|
||||
|
||||
import {
|
||||
EditorState,
|
||||
Transaction as ProsemirrorTransaction,
|
||||
TextSelection,
|
||||
} from "prosemirror-state";
|
||||
import {
|
||||
Node as ProsemirrorNode,
|
||||
Mark as ProsemirrorMark,
|
||||
} from "prosemirror-model";
|
||||
import { ReplaceStep, AddMarkStep } from "prosemirror-transform";
|
||||
import { EditorView } from "prosemirror-view";
|
||||
import { schema } from "prosemirror-schema-basic";
|
||||
import { exampleSetup } from "prosemirror-example-setup";
|
||||
import "prosemirror-example-setup/style/style.css";
|
||||
import "prosemirror-menu/style/menu.css";
|
||||
import "prosemirror-view/style/prosemirror.css";
|
||||
|
||||
function DocumentComponent({ docID }: { docID: ID<Document> }) {
|
||||
const { me } = useAccount();
|
||||
const [mount, setMount] = useState<HTMLElement | null>(null);
|
||||
|
||||
console.log("rerendering");
|
||||
|
||||
useEffect(() => {
|
||||
if (!mount) return;
|
||||
|
||||
console.log("Creating EditorView");
|
||||
|
||||
const setupPlugins = exampleSetup({ schema, history: false });
|
||||
console.log("setupPlugins", setupPlugins, schema);
|
||||
|
||||
const editorView = new EditorView(mount, {
|
||||
state: EditorState.create({
|
||||
doc: schema.node("doc", undefined, [
|
||||
schema.node("paragraph", undefined, undefined),
|
||||
]),
|
||||
schema: schema,
|
||||
plugins: setupPlugins,
|
||||
}),
|
||||
dispatchTransaction(tr) {
|
||||
const expectedNewState = editorView.state.apply(tr);
|
||||
|
||||
if (lastDoc) {
|
||||
applyTxToPlainText(lastDoc, tr);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"Setting view state to normal new state",
|
||||
expectedNewState
|
||||
);
|
||||
|
||||
editorView.updateState(expectedNewState);
|
||||
},
|
||||
});
|
||||
|
||||
let lastDoc: Document | undefined;
|
||||
|
||||
const unsub = Document.subscribe(
|
||||
docID,
|
||||
me,
|
||||
{ text: true, marks: [[]] },
|
||||
(doc) => {
|
||||
lastDoc = doc;
|
||||
|
||||
console.log("Applying doc update");
|
||||
console.log(
|
||||
"marks",
|
||||
doc.toString(),
|
||||
doc.resolveAndDiffuseAndFocusMarks()
|
||||
);
|
||||
console.log("tree", doc.toTree(["strong", "em"]));
|
||||
|
||||
console.log(richTextToProsemirrorDoc(doc));
|
||||
|
||||
const focusedBefore = editorView.hasFocus();
|
||||
|
||||
editorView.updateState(
|
||||
EditorState.create({
|
||||
doc: richTextToProsemirrorDoc(doc),
|
||||
plugins: editorView.state.plugins,
|
||||
selection: editorView.state.selection,
|
||||
schema: editorView.state.schema,
|
||||
storedMarks: editorView.state.storedMarks,
|
||||
})
|
||||
);
|
||||
|
||||
if (focusedBefore) {
|
||||
editorView.focus();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
console.log("Destroying");
|
||||
editorView.destroy();
|
||||
unsub();
|
||||
};
|
||||
}, [mount, docID, !!me]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Document</h1>
|
||||
<div ref={setMount} className="border min-w-96 p-5 min-h-96" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function richTextToProsemirrorDoc(
|
||||
text: CoRichText
|
||||
): ProsemirrorNode | undefined {
|
||||
const asString = text.toString();
|
||||
return schema.node("doc", undefined, [
|
||||
schema.node(
|
||||
"paragraph",
|
||||
{ start: 0, end: asString.length },
|
||||
asString.length === 0
|
||||
? undefined
|
||||
: text.toTree(["strong", "em"]).children.map((child) => {
|
||||
if (
|
||||
child.type === "leaf" ||
|
||||
child.tag === "strong" ||
|
||||
child.tag === "em"
|
||||
) {
|
||||
return collectInlineMarks(asString, child, []);
|
||||
} else {
|
||||
throw new Error("Unsupported tag " + child.tag);
|
||||
}
|
||||
})
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
function collectInlineMarks(
|
||||
fullString: string,
|
||||
node: TreeNode | TreeLeaf,
|
||||
currentMarks: ProsemirrorMark[]
|
||||
) {
|
||||
if (node.type === "leaf") {
|
||||
return schema.text(
|
||||
fullString.slice(node.start, node.end),
|
||||
currentMarks
|
||||
);
|
||||
} else {
|
||||
if (node.tag === "strong") {
|
||||
return collectInlineMarks(
|
||||
fullString,
|
||||
node.children[0],
|
||||
currentMarks.concat(schema.mark("strong"))
|
||||
);
|
||||
} else if (node.tag === "em") {
|
||||
return collectInlineMarks(
|
||||
fullString,
|
||||
node.children[0],
|
||||
currentMarks.concat(schema.mark("em"))
|
||||
);
|
||||
} else {
|
||||
throw new Error("Unsupported tag " + node.tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyTxToPlainText(text: CoRichText, tr: ProsemirrorTransaction) {
|
||||
console.log("transaction", tr);
|
||||
for (const step of tr.steps) {
|
||||
if (step instanceof ReplaceStep) {
|
||||
const resolvedStart = tr.before.resolve(step.from);
|
||||
const resolvedEnd = tr.before.resolve(step.to);
|
||||
|
||||
const selectionToStart = TextSelection.between(
|
||||
tr.before.resolve(0),
|
||||
resolvedStart
|
||||
);
|
||||
const start = selectionToStart
|
||||
.content()
|
||||
.content.textBetween(
|
||||
0,
|
||||
selectionToStart.content().content.size
|
||||
).length;
|
||||
|
||||
const selectionToEnd = TextSelection.between(
|
||||
tr.before.resolve(0),
|
||||
resolvedEnd
|
||||
);
|
||||
const end = selectionToEnd
|
||||
.content()
|
||||
.content.textBetween(
|
||||
0,
|
||||
selectionToEnd.content().content.size
|
||||
).length;
|
||||
|
||||
console.log(
|
||||
"step",
|
||||
step,
|
||||
resolvedStart,
|
||||
resolvedEnd,
|
||||
selectionToStart,
|
||||
start,
|
||||
end
|
||||
);
|
||||
|
||||
if (start === end) {
|
||||
if (step.slice.content.firstChild?.text) {
|
||||
text.insertAfter(start, step.slice.content.firstChild.text);
|
||||
} else {
|
||||
// this is a split operation
|
||||
const splitNodeType =
|
||||
step.slice.content.firstChild?.type.name;
|
||||
if (splitNodeType === "paragraph") {
|
||||
const matchingMarks =
|
||||
text.marks?.filter(
|
||||
(m): m is Exclude<typeof m, null> =>
|
||||
!!m &&
|
||||
m.tag === "paragraph" &&
|
||||
(m.startAfter && text.idxAfter(m.startAfter) || 0) <
|
||||
start &&
|
||||
(m.endBefore && text.idxBefore(m.endBefore) || Infinity) >
|
||||
start
|
||||
) || [];
|
||||
|
||||
console.log("split before", start, matchingMarks);
|
||||
|
||||
let lastSeenEnd = start;
|
||||
for (const matchingMark of matchingMarks) {
|
||||
const originalEnd = text.idxAfter(
|
||||
matchingMark.endAfter
|
||||
)!; // TODO: non-tight case
|
||||
if (originalEnd > lastSeenEnd) {
|
||||
lastSeenEnd = originalEnd;
|
||||
}
|
||||
matchingMark.endBefore = text.posBefore(start + 1)!;
|
||||
matchingMark.endAfter = text.posAfter(start)!;
|
||||
}
|
||||
|
||||
console.log("split after", matchingMarks, lastSeenEnd);
|
||||
|
||||
text.insertMark(start, lastSeenEnd, Marks.Paragraph, {
|
||||
tag: "paragraph",
|
||||
});
|
||||
} else {
|
||||
console.warn(
|
||||
"Unknown node type to split",
|
||||
splitNodeType
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
text.deleteRange({ from: start, to: end });
|
||||
}
|
||||
} else if (step instanceof AddMarkStep) {
|
||||
console.log("step", step);
|
||||
if (step.mark.type.name === "strong") {
|
||||
text.insertMark(step.from, step.to - 1, Marks.Strong, {
|
||||
tag: "strong",
|
||||
});
|
||||
} else if (step.mark.type.name === "em") {
|
||||
text.insertMark(step.from, step.to - 1, Marks.Em, {
|
||||
tag: "em",
|
||||
});
|
||||
} else {
|
||||
console.warn("Unsupported mark type", step.mark);
|
||||
}
|
||||
} else {
|
||||
console.warn("Unsupported step type", step);
|
||||
}
|
||||
}
|
||||
}
|
||||
78
examples/richtext/src/index.css
Normal file
78
examples/richtext/src/index.css
Normal file
@@ -0,0 +1,78 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 20 14.3% 4.1%;
|
||||
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 20 14.3% 4.1%;
|
||||
|
||||
--primary: 24 9.8% 10%;
|
||||
--primary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--secondary: 60 4.8% 95.9%;
|
||||
--secondary-foreground: 24 9.8% 10%;
|
||||
|
||||
--muted: 60 4.8% 95.9%;
|
||||
--muted-foreground: 25 5.3% 44.7%;
|
||||
|
||||
--accent: 60 4.8% 95.9%;
|
||||
--accent-foreground: 24 9.8% 10%;
|
||||
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 20 5.9% 90%;
|
||||
--input: 20 5.9% 90%;
|
||||
--ring: 20 14.3% 4.1%;
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 20 14.3% 4.1%;
|
||||
--foreground: 60 9.1% 97.8%;
|
||||
|
||||
--card: 20 14.3% 4.1%;
|
||||
--card-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--popover: 20 14.3% 4.1%;
|
||||
--popover-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--primary: 60 9.1% 97.8%;
|
||||
--primary-foreground: 24 9.8% 10%;
|
||||
|
||||
--secondary: 12 6.5% 15.1%;
|
||||
--secondary-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--muted: 12 6.5% 15.1%;
|
||||
--muted-foreground: 24 5.4% 63.9%;
|
||||
|
||||
--accent: 12 6.5% 15.1%;
|
||||
--accent-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 60 9.1% 97.8%;
|
||||
|
||||
--border: 12 6.5% 15.1%;
|
||||
--input: 12 6.5% 15.1%;
|
||||
--ring: 24 5.7% 82.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
1
examples/richtext/src/vite-env.d.ts
vendored
Normal file
1
examples/richtext/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
75
examples/richtext/tailwind.config.js
Normal file
75
examples/richtext/tailwind.config.js
Normal file
@@ -0,0 +1,75 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
'./pages/**/*.{ts,tsx}',
|
||||
'./components/**/*.{ts,tsx}',
|
||||
'./app/**/*.{ts,tsx}',
|
||||
'./src/**/*.{ts,tsx}',
|
||||
],
|
||||
theme: {
|
||||
container: {
|
||||
center: true,
|
||||
padding: "2rem",
|
||||
screens: {
|
||||
"2xl": "1400px",
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
border: "hsl(var(--border))",
|
||||
input: "hsl(var(--input))",
|
||||
ring: "hsl(var(--ring))",
|
||||
background: "hsl(var(--background))",
|
||||
foreground: "hsl(var(--foreground))",
|
||||
primary: {
|
||||
DEFAULT: "hsl(var(--primary))",
|
||||
foreground: "hsl(var(--primary-foreground))",
|
||||
},
|
||||
secondary: {
|
||||
DEFAULT: "hsl(var(--secondary))",
|
||||
foreground: "hsl(var(--secondary-foreground))",
|
||||
},
|
||||
destructive: {
|
||||
DEFAULT: "hsl(var(--destructive))",
|
||||
foreground: "hsl(var(--destructive-foreground))",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "hsl(var(--muted))",
|
||||
foreground: "hsl(var(--muted-foreground))",
|
||||
},
|
||||
accent: {
|
||||
DEFAULT: "hsl(var(--accent))",
|
||||
foreground: "hsl(var(--accent-foreground))",
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: "hsl(var(--popover))",
|
||||
foreground: "hsl(var(--popover-foreground))",
|
||||
},
|
||||
card: {
|
||||
DEFAULT: "hsl(var(--card))",
|
||||
foreground: "hsl(var(--card-foreground))",
|
||||
},
|
||||
},
|
||||
borderRadius: {
|
||||
lg: "var(--radius)",
|
||||
md: "calc(var(--radius) - 2px)",
|
||||
sm: "calc(var(--radius) - 4px)",
|
||||
},
|
||||
keyframes: {
|
||||
"accordion-down": {
|
||||
from: { height: 0 },
|
||||
to: { height: "var(--radix-accordion-content-height)" },
|
||||
},
|
||||
"accordion-up": {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: 0 },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
"accordion-up": "accordion-up 0.2s ease-out",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("tailwindcss-animate")],
|
||||
}
|
||||
29
examples/richtext/tsconfig.json
Normal file
29
examples/richtext/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
10
examples/richtext/tsconfig.node.json
Normal file
10
examples/richtext/tsconfig.node.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
16
examples/richtext/vite.config.ts
Normal file
16
examples/richtext/vite.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react-swc'
|
||||
import path from "path";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
build: {
|
||||
minify: false
|
||||
}
|
||||
})
|
||||
@@ -6,6 +6,7 @@ import { RawCoList } from "./coValues/coList.js";
|
||||
import { CoValueCore } from "./coValueCore.js";
|
||||
import { RawGroup } from "./coValues/group.js";
|
||||
import { RawAccount, Profile } from "./index.js";
|
||||
import { RawCoPlainText } from "./coValues/coPlainText.js";
|
||||
|
||||
export type CoID<T extends RawCoValue> = RawCoID & {
|
||||
readonly __type: T;
|
||||
@@ -66,3 +67,11 @@ export function expectStream(content: RawCoValue): RawCoStream {
|
||||
|
||||
return content as RawCoStream;
|
||||
}
|
||||
|
||||
export function expectPlainText(content: RawCoValue): RawCoPlainText {
|
||||
if (content.type !== "coplaintext") {
|
||||
throw new Error("Expected plaintext");
|
||||
}
|
||||
|
||||
return content as RawCoPlainText;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ import { AgentID, SessionID, TransactionID } from "../ids.js";
|
||||
import { AccountID } from "./account.js";
|
||||
import { RawGroup } from "./group.js";
|
||||
|
||||
type OpID = TransactionID & { changeIdx: number };
|
||||
export type OpID = TransactionID & { changeIdx: number };
|
||||
|
||||
type InsertionOpPayload<T extends JsonValue> =
|
||||
export type InsertionOpPayload<T extends JsonValue> =
|
||||
| {
|
||||
op: "pre";
|
||||
value: T;
|
||||
@@ -21,7 +21,7 @@ type InsertionOpPayload<T extends JsonValue> =
|
||||
after: OpID | "start";
|
||||
};
|
||||
|
||||
type DeletionOpPayload = {
|
||||
export type DeletionOpPayload = {
|
||||
op: "del";
|
||||
insertion: OpID;
|
||||
};
|
||||
@@ -49,7 +49,7 @@ export class RawCoListView<
|
||||
/** @category 6. Meta */
|
||||
id: CoID<this>;
|
||||
/** @category 6. Meta */
|
||||
type = "colist" as const;
|
||||
type: "colist" | "coplaintext" = "colist" as const;
|
||||
/** @category 6. Meta */
|
||||
core: CoValueCore;
|
||||
/** @internal */
|
||||
@@ -446,13 +446,7 @@ export class RawCoList<
|
||||
privacy,
|
||||
);
|
||||
|
||||
const listAfter = new RawCoList(this.core) as this;
|
||||
|
||||
this.afterStart = listAfter.afterStart;
|
||||
this.beforeEnd = listAfter.beforeEnd;
|
||||
this.insertions = listAfter.insertions;
|
||||
this.deletionsByInsertion = listAfter.deletionsByInsertion;
|
||||
this._cachedEntries = undefined;
|
||||
this.rebuildFromCore();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -499,13 +493,7 @@ export class RawCoList<
|
||||
privacy,
|
||||
);
|
||||
|
||||
const listAfter = new RawCoList(this.core) as this;
|
||||
|
||||
this.afterStart = listAfter.afterStart;
|
||||
this.beforeEnd = listAfter.beforeEnd;
|
||||
this.insertions = listAfter.insertions;
|
||||
this.deletionsByInsertion = listAfter.deletionsByInsertion;
|
||||
this._cachedEntries = undefined;
|
||||
this.rebuildFromCore();
|
||||
}
|
||||
|
||||
/** Deletes the item at index `at`.
|
||||
@@ -532,13 +520,7 @@ export class RawCoList<
|
||||
privacy,
|
||||
);
|
||||
|
||||
const listAfter = new RawCoList(this.core) as this;
|
||||
|
||||
this.afterStart = listAfter.afterStart;
|
||||
this.beforeEnd = listAfter.beforeEnd;
|
||||
this.insertions = listAfter.insertions;
|
||||
this.deletionsByInsertion = listAfter.deletionsByInsertion;
|
||||
this._cachedEntries = undefined;
|
||||
this.rebuildFromCore();
|
||||
}
|
||||
|
||||
replace(
|
||||
@@ -566,6 +548,11 @@ export class RawCoList<
|
||||
],
|
||||
privacy,
|
||||
);
|
||||
this.rebuildFromCore();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
rebuildFromCore() {
|
||||
const listAfter = new RawCoList(this.core) as this;
|
||||
|
||||
this.afterStart = listAfter.afterStart;
|
||||
|
||||
129
packages/cojson/src/coValues/coPlainText.ts
Normal file
129
packages/cojson/src/coValues/coPlainText.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { CoValueCore } from "../coValueCore.js";
|
||||
import { JsonObject } from "../jsonValue.js";
|
||||
import { DeletionOpPayload, InsertionOpPayload, OpID, RawCoList } from "./coList.js";
|
||||
|
||||
export type StringifiedOpID = string & { __stringifiedOpID: true };
|
||||
|
||||
export function stringifyOpID(opID: OpID): StringifiedOpID {
|
||||
return `${opID.sessionID}:${opID.txIndex}:${opID.changeIdx}` as StringifiedOpID;
|
||||
}
|
||||
|
||||
type PlaintextIdxMapping = {
|
||||
opIDbeforeIdx: OpID[];
|
||||
opIDafterIdx: OpID[];
|
||||
idxAfterOpID: { [opID: StringifiedOpID]: number };
|
||||
idxBeforeOpID: { [opID: StringifiedOpID]: number };
|
||||
};
|
||||
|
||||
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
|
||||
|
||||
export class RawCoPlainText<
|
||||
Meta extends JsonObject | null = JsonObject | null,
|
||||
> extends RawCoList<string, Meta> {
|
||||
/** @category 6. Meta */
|
||||
type = "coplaintext" as const;
|
||||
|
||||
_cachedMapping: WeakMap<
|
||||
NonNullable<typeof this._cachedEntries>,
|
||||
PlaintextIdxMapping
|
||||
>;
|
||||
|
||||
constructor(core: CoValueCore) {
|
||||
super(core);
|
||||
this._cachedMapping = new WeakMap();
|
||||
}
|
||||
|
||||
get mapping() {
|
||||
const entries = this.entries();
|
||||
let mapping = this._cachedMapping.get(entries);
|
||||
if (mapping) {
|
||||
return mapping;
|
||||
}
|
||||
|
||||
mapping = {
|
||||
opIDbeforeIdx: [],
|
||||
opIDafterIdx: [],
|
||||
idxAfterOpID: {},
|
||||
idxBeforeOpID: {},
|
||||
};
|
||||
|
||||
let idxBefore = 0;
|
||||
|
||||
for (const entry of entries) {
|
||||
const idxAfter = idxBefore + entry.value.length;
|
||||
|
||||
mapping.opIDafterIdx[idxBefore] = entry.opID;
|
||||
mapping.opIDbeforeIdx[idxAfter] = entry.opID;
|
||||
mapping.idxAfterOpID[stringifyOpID(entry.opID)] = idxAfter;
|
||||
mapping.idxBeforeOpID[stringifyOpID(entry.opID)] = idxBefore;
|
||||
|
||||
idxBefore = idxAfter;
|
||||
}
|
||||
|
||||
this._cachedMapping.set(entries, mapping);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.entries()
|
||||
.map((entry) => entry.value)
|
||||
.join("");
|
||||
}
|
||||
|
||||
insertAfter(
|
||||
idx: number,
|
||||
text: string,
|
||||
privacy: "private" | "trusting" = "private"
|
||||
) {
|
||||
const ops: InsertionOpPayload<string>[] = [];
|
||||
let prevOpId: OpID | "start" | undefined = this.mapping.opIDbeforeIdx[idx];
|
||||
if (!prevOpId) {
|
||||
if (idx === 0) {
|
||||
prevOpId = "start"
|
||||
} else {
|
||||
throw new Error("Invalid idx");
|
||||
}
|
||||
}
|
||||
const nextTxId = this.core.nextTransactionID();
|
||||
let changeIdx = 0;
|
||||
for (const grapheme of segmenter.segment(text)) {
|
||||
ops.push({
|
||||
op: "app",
|
||||
value: grapheme.segment,
|
||||
after: prevOpId,
|
||||
});
|
||||
prevOpId = {
|
||||
sessionID: nextTxId.sessionID,
|
||||
txIndex: nextTxId.txIndex,
|
||||
changeIdx,
|
||||
};
|
||||
changeIdx++;
|
||||
}
|
||||
this.core.makeTransaction(ops, privacy);
|
||||
|
||||
this.rebuildFromCore();
|
||||
}
|
||||
|
||||
deleteRange({from, to}: {from: number, to: number}, privacy: "private" | "trusting" = "private") {
|
||||
const ops: DeletionOpPayload[] = [];
|
||||
for (let idx = from; idx < to;) {
|
||||
const insertion = this.mapping.opIDafterIdx[idx];
|
||||
if (!insertion) {
|
||||
throw new Error("Invalid idx to delete " + (idx));
|
||||
}
|
||||
ops.push({
|
||||
op: "del",
|
||||
insertion,
|
||||
});
|
||||
console.log("deleting idx", idx)
|
||||
let nextIdx = idx + 1;
|
||||
while (!this.mapping.opIDbeforeIdx[nextIdx] && nextIdx < to) {
|
||||
nextIdx++;
|
||||
}
|
||||
idx = nextIdx;
|
||||
}
|
||||
this.core.makeTransaction(ops, privacy);
|
||||
|
||||
this.rebuildFromCore();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { AgentID, isAgentID } from "../ids.js";
|
||||
import { RawAccount, AccountID, ControlledAccountOrAgent } from "./account.js";
|
||||
import { Role } from "../permissions.js";
|
||||
import { base58 } from "@scure/base";
|
||||
import { RawCoPlainText } from "./coPlainText.js";
|
||||
|
||||
export const EVERYONE = "everyone" as const;
|
||||
export type Everyone = "everyone";
|
||||
@@ -307,6 +308,36 @@ export class RawGroup<
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new `CoList` within this group, with the specified specialized
|
||||
* `CoList` type `L` and optional static metadata.
|
||||
*
|
||||
* @category 3. Value creation
|
||||
*/
|
||||
createPlainText<T extends RawCoPlainText>(
|
||||
init?: string,
|
||||
meta?: T["headerMeta"],
|
||||
initPrivacy: "trusting" | "private" = "private"
|
||||
): T {
|
||||
const text = this.core.node
|
||||
.createCoValue({
|
||||
type: "coplaintext",
|
||||
ruleset: {
|
||||
type: "ownedByGroup",
|
||||
group: this.id,
|
||||
},
|
||||
meta: meta || null,
|
||||
...this.core.crypto.createdNowUnique(),
|
||||
})
|
||||
.getCurrentContent() as T;
|
||||
|
||||
if (init) {
|
||||
text.insertAfter(0, init, initPrivacy);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
/** @category 3. Value creation */
|
||||
createStream<C extends RawCoStream>(meta?: C["headerMeta"]): C {
|
||||
return this.core.node
|
||||
|
||||
@@ -5,6 +5,7 @@ import { RawCoMap } from "./coValues/coMap.js";
|
||||
import { RawCoList } from "./coValues/coList.js";
|
||||
import { RawCoStream } from "./coValues/coStream.js";
|
||||
import { RawBinaryCoStream } from "./coValues/coStream.js";
|
||||
import { RawCoPlainText } from "./coValues/coPlainText.js";
|
||||
|
||||
export function coreToCoValue(
|
||||
core: CoValueCore,
|
||||
@@ -38,6 +39,8 @@ export function coreToCoValue(
|
||||
} else {
|
||||
return new RawCoStream(core);
|
||||
}
|
||||
} else if (core.header.type === "coplaintext") {
|
||||
return new RawCoPlainText(core);
|
||||
} else {
|
||||
throw new Error(`Unknown coValue type ${core.header.type}`);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { LocalNode } from "./localNode.js";
|
||||
import type { RawCoValue } from "./coValue.js";
|
||||
import { RawCoMap } from "./coValues/coMap.js";
|
||||
import { RawCoList } from "./coValues/coList.js";
|
||||
import { RawCoPlainText, stringifyOpID } from "./coValues/coPlainText.js";
|
||||
import { RawCoStream, RawBinaryCoStream } from "./coValues/coStream.js";
|
||||
import {
|
||||
secretSeedLength,
|
||||
@@ -94,6 +95,7 @@ export {
|
||||
Everyone,
|
||||
RawCoMap,
|
||||
RawCoList,
|
||||
RawCoPlainText,
|
||||
RawCoStream,
|
||||
RawBinaryCoStream,
|
||||
RawCoValue,
|
||||
@@ -122,6 +124,7 @@ export {
|
||||
PureJSCrypto,
|
||||
SyncMessage,
|
||||
isRawCoID,
|
||||
stringifyOpID,
|
||||
LSMStorage,
|
||||
DisconnectedError,
|
||||
PingTimeoutError,
|
||||
@@ -153,4 +156,5 @@ export namespace CojsonInternalTypes {
|
||||
export type SealerSecret = import("./crypto/crypto.js").SealerSecret;
|
||||
export type SignerSecret = import("./crypto/crypto.js").SignerSecret;
|
||||
export type JsonObject = import("./jsonValue.js").JsonObject;
|
||||
export type OpID = import("./coValues/coList.js").OpID;
|
||||
}
|
||||
|
||||
73
packages/cojson/src/tests/coPlainText.test.ts
Normal file
73
packages/cojson/src/tests/coPlainText.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { expect, test } from "vitest";
|
||||
import { expectPlainText } from "../coValue.js";
|
||||
import { WasmCrypto } from "../index.js";
|
||||
import { LocalNode } from "../localNode.js";
|
||||
import { randomAnonymousAccountAndSessionID } from "./testUtils.js";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
|
||||
test("Empty CoPlainText works", () => {
|
||||
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
||||
|
||||
const coValue = node.createCoValue({
|
||||
type: "coplaintext",
|
||||
ruleset: { type: "unsafeAllowAll" },
|
||||
meta: null,
|
||||
...Crypto.createdNowUnique(),
|
||||
});
|
||||
|
||||
const content = expectPlainText(coValue.getCurrentContent());
|
||||
|
||||
expect(content.type).toEqual("coplaintext");
|
||||
expect(content.toString()).toEqual("");
|
||||
});
|
||||
|
||||
test("Can insert into empty CoPlainText", () => {
|
||||
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
||||
|
||||
const coValue = node.createCoValue({
|
||||
type: "coplaintext",
|
||||
ruleset: { type: "unsafeAllowAll" },
|
||||
meta: null,
|
||||
...Crypto.createdNowUnique(),
|
||||
});
|
||||
|
||||
const content = expectPlainText(coValue.getCurrentContent());
|
||||
|
||||
expect(content.type).toEqual("coplaintext");
|
||||
|
||||
content.insertAfter(0, "a", "trusting");
|
||||
expect(content.toString()).toEqual("a");
|
||||
});
|
||||
|
||||
test("Can insert and delete in CoPlainText", () => {
|
||||
const node = new LocalNode(...randomAnonymousAccountAndSessionID(), Crypto);
|
||||
|
||||
const coValue = node.createCoValue({
|
||||
type: "coplaintext",
|
||||
ruleset: { type: "unsafeAllowAll" },
|
||||
meta: null,
|
||||
...Crypto.createdNowUnique(),
|
||||
});
|
||||
|
||||
const content = expectPlainText(coValue.getCurrentContent());
|
||||
|
||||
expect(content.type).toEqual("coplaintext");
|
||||
|
||||
content.insertAfter(0, "hello", "trusting");
|
||||
expect(content.toString()).toEqual("hello");
|
||||
|
||||
content.insertAfter(5, " world", "trusting");
|
||||
expect(content.toString()).toEqual("hello world");
|
||||
|
||||
console.log("first delete")
|
||||
content.deleteRange({from: 3, to: 8}, "trusting");
|
||||
expect(content.toString()).toEqual("helrld");
|
||||
|
||||
content.insertAfter(2, "😍", "trusting");
|
||||
expect(content.toString()).toEqual("he😍lrld")
|
||||
|
||||
console.log("second delete")
|
||||
content.deleteRange({from: 2, to: 4}, "trusting");
|
||||
expect(content.toString()).toEqual("helrld");
|
||||
})
|
||||
211
packages/jazz-tools/src/coValues/coPlainText.ts
Normal file
211
packages/jazz-tools/src/coValues/coPlainText.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
import type { CojsonInternalTypes, RawCoPlainText } from "cojson";
|
||||
import { RawAccount, stringifyOpID } from "cojson";
|
||||
import type {
|
||||
AccountCtx,
|
||||
CoValue,
|
||||
CoValueClass,
|
||||
ID,
|
||||
UnavailableError,
|
||||
} from "../internal.js";
|
||||
import { Account, Group, inspect, loadCoValue, loadCoValueEf, subscribeToCoValue, subscribeToCoValueEf, subscribeToExistingCoValue } from "../internal.js";
|
||||
import type { Effect, Stream } from "effect";
|
||||
|
||||
export type TextPos = CojsonInternalTypes.OpID;
|
||||
|
||||
export class CoPlainText
|
||||
extends String
|
||||
implements CoValue
|
||||
{
|
||||
declare id: ID<this>;
|
||||
declare _type: "CoPlainText";
|
||||
declare _raw: RawCoPlainText;
|
||||
|
||||
get _owner(): Account | Group {
|
||||
return this._raw.group instanceof RawAccount
|
||||
? Account.fromRaw(this._raw.group)
|
||||
: Group.fromRaw(this._raw.group);
|
||||
}
|
||||
|
||||
get _loadedAs() {
|
||||
return Account.fromNode(this._raw.core.node);
|
||||
}
|
||||
|
||||
constructor(options: { fromRaw: RawCoPlainText } | { text: string, owner: Account | Group }) {
|
||||
super();
|
||||
|
||||
let raw;
|
||||
|
||||
if ("fromRaw" in options) {
|
||||
raw = options.fromRaw;
|
||||
} else {
|
||||
raw = options.owner._raw.createPlainText(options.text);
|
||||
}
|
||||
|
||||
Object.defineProperties(this, {
|
||||
id: { value: raw.id, enumerable: false },
|
||||
_type: { value: "CoPlainText", enumerable: false },
|
||||
_raw: { value: raw, enumerable: false },
|
||||
});
|
||||
}
|
||||
|
||||
static create<T extends CoPlainText>(this: CoValueClass<T>, text: string, options: { owner: Account | Group }) {
|
||||
return new this({ text, owner: options.owner });
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this._raw.toString();
|
||||
}
|
||||
|
||||
valueOf() {
|
||||
return this._raw.toString();
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return this._raw.toString();
|
||||
}
|
||||
|
||||
[inspect]() {
|
||||
return this.toJSON();
|
||||
}
|
||||
|
||||
insertAfter(idx: number, text: string) {
|
||||
this._raw.insertAfter(idx, text);
|
||||
}
|
||||
|
||||
deleteRange(range: {from: number, to: number}) {
|
||||
this._raw.deleteRange(range);
|
||||
}
|
||||
|
||||
posBefore(idx: number): TextPos | undefined {
|
||||
return this._raw.mapping.opIDbeforeIdx[idx];
|
||||
}
|
||||
|
||||
posAfter(idx: number): TextPos | undefined {
|
||||
return this._raw.mapping.opIDafterIdx[idx];
|
||||
}
|
||||
|
||||
idxBefore(pos: TextPos): number | undefined {
|
||||
return this._raw.mapping.idxBeforeOpID[stringifyOpID(pos)];
|
||||
}
|
||||
|
||||
idxAfter(pos: TextPos): number | undefined {
|
||||
return this._raw.mapping.idxAfterOpID[stringifyOpID(pos)];
|
||||
}
|
||||
|
||||
static fromRaw<V extends CoPlainText>(
|
||||
this: CoValueClass<V> & typeof CoPlainText,
|
||||
raw: RawCoPlainText
|
||||
) {
|
||||
return new this({ fromRaw: raw });
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a `CoPlainText` with a given ID, as a given account.
|
||||
*
|
||||
* `depth` specifies which (if any) fields that reference other CoValues to load as well before resolving.
|
||||
* The `DeeplyLoaded` return type guarantees that corresponding referenced CoValues are loaded to the specified depth.
|
||||
*
|
||||
* You can pass `[]` or `{}` for shallowly loading only this CoPlainText, or `{ fieldA: depthA, fieldB: depthB }` for recursively loading referenced CoValues.
|
||||
*
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const person = await Person.load(
|
||||
* "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
|
||||
* me,
|
||||
* { pet: {} }
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @category Subscription & Loading
|
||||
*/
|
||||
static load<T extends CoPlainText>(
|
||||
this: CoValueClass<T>,
|
||||
id: ID<T>,
|
||||
as: Account,
|
||||
): Promise<T | undefined> {
|
||||
return loadCoValue(this, id, as, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectful version of `CoMap.load()`.
|
||||
*
|
||||
* Needs to be run inside an `AccountCtx` context.
|
||||
*
|
||||
* @category Subscription & Loading
|
||||
*/
|
||||
static loadEf<T extends CoPlainText>(
|
||||
this: CoValueClass<T>,
|
||||
id: ID<T>,
|
||||
): Effect.Effect<T, UnavailableError, AccountCtx> {
|
||||
return loadCoValueEf(this, id, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and subscribe to a `CoMap` with a given ID, as a given account.
|
||||
*
|
||||
* Automatically also subscribes to updates to all referenced/nested CoValues as soon as they are accessed in the listener.
|
||||
*
|
||||
* `depth` specifies which (if any) fields that reference other CoValues to load as well before calling `listener` for the first time.
|
||||
* The `DeeplyLoaded` return type guarantees that corresponding referenced CoValues are loaded to the specified depth.
|
||||
*
|
||||
* You can pass `[]` or `{}` for shallowly loading only this CoMap, or `{ fieldA: depthA, fieldB: depthB }` for recursively loading referenced CoValues.
|
||||
*
|
||||
* Check out the `load` methods on `CoMap`/`CoList`/`CoStream`/`Group`/`Account` to see which depth structures are valid to nest.
|
||||
*
|
||||
* Returns an unsubscribe function that you should call when you no longer need updates.
|
||||
*
|
||||
* Also see the `useCoState` hook to reactively subscribe to a CoValue in a React component.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const unsub = Person.subscribe(
|
||||
* "co_zdsMhHtfG6VNKt7RqPUPvUtN2Ax",
|
||||
* me,
|
||||
* { pet: {} },
|
||||
* (person) => console.log(person)
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @category Subscription & Loading
|
||||
*/
|
||||
static subscribe<T extends CoPlainText>(
|
||||
this: CoValueClass<T>,
|
||||
id: ID<T>,
|
||||
as: Account,
|
||||
listener: (value: T) => void,
|
||||
): () => void {
|
||||
return subscribeToCoValue(this, id, as, [], listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Effectful version of `CoMap.subscribe()` that returns a stream of updates.
|
||||
*
|
||||
* Needs to be run inside an `AccountCtx` context.
|
||||
*
|
||||
* @category Subscription & Loading
|
||||
*/
|
||||
static subscribeEf<T extends CoPlainText>(
|
||||
this: CoValueClass<T>,
|
||||
id: ID<T>,
|
||||
): Stream.Stream<T, UnavailableError, AccountCtx> {
|
||||
return subscribeToCoValueEf(this, id, []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an already loaded `CoMap`, subscribe to updates to the `CoMap` and ensure that the specified fields are loaded to the specified depth.
|
||||
*
|
||||
* Works like `CoMap.subscribe()`, but you don't need to pass the ID or the account to load as again.
|
||||
*
|
||||
* Returns an unsubscribe function that you should call when you no longer need updates.
|
||||
*
|
||||
* @category Subscription & Loading
|
||||
**/
|
||||
subscribe<T extends CoPlainText>(
|
||||
this: T,
|
||||
listener: (value: T) => void,
|
||||
): () => void {
|
||||
return subscribeToExistingCoValue(this, [], listener);
|
||||
}
|
||||
}
|
||||
396
packages/jazz-tools/src/coValues/coRichText.ts
Normal file
396
packages/jazz-tools/src/coValues/coRichText.ts
Normal file
@@ -0,0 +1,396 @@
|
||||
import type { Account, CoMapInit, Group, TextPos } from "../internal.js";
|
||||
import { CoList, CoMap, CoPlainText, co } from "../internal.js";
|
||||
|
||||
export class Mark extends CoMap {
|
||||
startAfter = co.json<TextPos | null>();
|
||||
startBefore = co.json<TextPos>();
|
||||
endAfter = co.json<TextPos>();
|
||||
endBefore = co.json<TextPos | null>();
|
||||
tag = co.string;
|
||||
}
|
||||
|
||||
export type ResolvedMark<R extends Mark = Mark> = {
|
||||
startAfter: number;
|
||||
startBefore: number;
|
||||
endAfter: number;
|
||||
endBefore: number;
|
||||
sourceMark: R;
|
||||
};
|
||||
|
||||
export type ResolvedAndDiffusedMark<R extends Mark = Mark> = {
|
||||
start: number;
|
||||
end: number;
|
||||
side: "uncertainStart" | "certainMiddle" | "uncertainEnd";
|
||||
sourceMark: R;
|
||||
};
|
||||
|
||||
export type FocusBias = "far" | "close" | "closestWhitespace";
|
||||
|
||||
export type ResolvedAndFocusedMark<R extends Mark = Mark> = {
|
||||
start: number;
|
||||
end: number;
|
||||
sourceMark: R;
|
||||
};
|
||||
|
||||
export class CoRichText extends CoMap {
|
||||
text = co.ref(CoPlainText);
|
||||
marks = co.ref(CoList.Of(co.ref(Mark)));
|
||||
|
||||
static createFromPlainText(
|
||||
text: string,
|
||||
options: { owner: Account | Group },
|
||||
) {
|
||||
return this.create(
|
||||
{
|
||||
text: CoPlainText.create(text, { owner: options.owner }),
|
||||
marks: CoList.Of(co.ref(Mark)).create([], {
|
||||
owner: options.owner,
|
||||
}),
|
||||
},
|
||||
{ owner: options.owner },
|
||||
);
|
||||
}
|
||||
|
||||
static createFromPlainTextAndMark<MarkClass extends {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
new (...args: any[]): Mark;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
create(init: any, options: { owner: Account | Group }): Mark;
|
||||
}>(
|
||||
text: string,
|
||||
WrapIn: MarkClass,
|
||||
extraArgs: Omit<
|
||||
CoMapInit<InstanceType<MarkClass>>,
|
||||
"startAfter" | "startBefore" | "endAfter" | "endBefore"
|
||||
>,
|
||||
options: { owner: Account | Group },
|
||||
) {
|
||||
const richtext = this.createFromPlainText(text, options);
|
||||
|
||||
richtext.insertMark(0, text.length, WrapIn, extraArgs);
|
||||
|
||||
return richtext;
|
||||
}
|
||||
|
||||
insertAfter(idx: number, text: string) {
|
||||
if (!this.text)
|
||||
throw new Error(
|
||||
"Cannot insert into a CoRichText without loaded text",
|
||||
);
|
||||
this.text.insertAfter(idx, text);
|
||||
}
|
||||
|
||||
deleteRange(range: { from: number; to: number }) {
|
||||
if (!this.text)
|
||||
throw new Error(
|
||||
"Cannot delete from a CoRichText without loaded text",
|
||||
);
|
||||
this.text.deleteRange(range);
|
||||
}
|
||||
|
||||
posBefore(idx: number): TextPos | undefined {
|
||||
if (!this.text)
|
||||
throw new Error(
|
||||
"Cannot get posBefore in a CoRichText without loaded text",
|
||||
);
|
||||
return this.text.posBefore(idx);
|
||||
}
|
||||
|
||||
posAfter(idx: number): TextPos | undefined {
|
||||
if (!this.text)
|
||||
throw new Error(
|
||||
"Cannot get posAfter in a CoRichText without loaded text",
|
||||
);
|
||||
return this.text.posAfter(idx);
|
||||
}
|
||||
|
||||
idxBefore(pos: TextPos): number | undefined {
|
||||
if (!this.text)
|
||||
throw new Error(
|
||||
"Cannot get idxBefore in a CoRichText without loaded text",
|
||||
);
|
||||
return this.text.idxBefore(pos);
|
||||
}
|
||||
|
||||
idxAfter(pos: TextPos): number | undefined {
|
||||
if (!this.text)
|
||||
throw new Error(
|
||||
"Cannot get idxAfter in a CoRichText without loaded text",
|
||||
);
|
||||
return this.text.idxAfter(pos);
|
||||
}
|
||||
|
||||
insertMark<
|
||||
MarkClass extends {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
new (...args: any[]): Mark;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
create(init: any, options: { owner: Account | Group }): Mark;
|
||||
},
|
||||
>(
|
||||
start: number,
|
||||
end: number,
|
||||
RangeClass: MarkClass,
|
||||
extraArgs: Omit<
|
||||
CoMapInit<InstanceType<MarkClass>>,
|
||||
"startAfter" | "startBefore" | "endAfter" | "endBefore"
|
||||
>,
|
||||
options?: { markOwner?: Account | Group },
|
||||
) {
|
||||
if (!this.marks) {
|
||||
throw new Error("Cannot insert a range without loaded ranges");
|
||||
}
|
||||
const range = RangeClass.create(
|
||||
{
|
||||
...extraArgs,
|
||||
startAfter: this.posBefore(start),
|
||||
startBefore: this.posAfter(start),
|
||||
endAfter: this.posBefore(end),
|
||||
endBefore: this.posAfter(end),
|
||||
},
|
||||
{ owner: options?.markOwner || this._owner },
|
||||
);
|
||||
this.marks.push(range);
|
||||
}
|
||||
|
||||
resolveMarks(): ResolvedMark[] {
|
||||
if (!this.text || !this.marks) {
|
||||
throw new Error(
|
||||
"Cannot resolve ranges without loaded text and ranges",
|
||||
);
|
||||
}
|
||||
const ranges = this.marks.flatMap((mark) => {
|
||||
if (!mark) return [];
|
||||
const startBefore = this.idxAfter(mark.startBefore);
|
||||
const endAfter = this.idxAfter(mark.endAfter);
|
||||
if (
|
||||
startBefore === undefined ||
|
||||
endAfter === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
const startAfter = mark.startAfter ? this.idxAfter(mark.startAfter) : startBefore - 1;
|
||||
const endBefore = mark.endBefore ? this.idxAfter(mark.endBefore) : endAfter + 1;
|
||||
if (
|
||||
startAfter === undefined ||
|
||||
endBefore === undefined
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
{
|
||||
sourceMark: mark,
|
||||
startAfter,
|
||||
startBefore,
|
||||
endAfter,
|
||||
endBefore,
|
||||
tag: mark.tag,
|
||||
from: mark,
|
||||
},
|
||||
];
|
||||
});
|
||||
return ranges;
|
||||
}
|
||||
|
||||
resolveAndDiffuseMarks(): ResolvedAndDiffusedMark[] {
|
||||
return this.resolveMarks().flatMap((range) => [
|
||||
...(range.startAfter < range.startBefore - 1
|
||||
? [
|
||||
{
|
||||
start: range.startAfter,
|
||||
end: range.startBefore - 1,
|
||||
side: "uncertainStart" as const,
|
||||
sourceMark: range.sourceMark,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
start: range.startBefore - 1,
|
||||
end: range.endAfter + 1,
|
||||
side: "certainMiddle" as const,
|
||||
sourceMark: range.sourceMark,
|
||||
},
|
||||
...(range.endAfter + 1 < range.endBefore
|
||||
? [
|
||||
{
|
||||
start: range.endAfter + 1,
|
||||
end: range.endBefore,
|
||||
side: "uncertainEnd" as const,
|
||||
sourceMark: range.sourceMark,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
}
|
||||
|
||||
resolveAndDiffuseAndFocusMarks(): ResolvedAndFocusedMark[] {
|
||||
// for now we only keep the certainMiddle ranges
|
||||
return this.resolveAndDiffuseMarks().filter(
|
||||
(range) => range.side === "certainMiddle",
|
||||
);
|
||||
}
|
||||
|
||||
toTree(tagPrecedence: string[]): TreeNode {
|
||||
const ranges = this.resolveAndDiffuseAndFocusMarks();
|
||||
|
||||
// convert a bunch of (potentially overlapping) ranges into a tree
|
||||
// - make sure we include all text in leaves, even if it's not covered by a range
|
||||
// - we split overlapping ranges in a way where the higher precedence (tag earlier in tagPrecedence)
|
||||
// stays intact and the lower precende tag is split into two ranges, one inside and one outside the higher precedence range
|
||||
|
||||
const text = this.text?.toString() || "";
|
||||
|
||||
let currentNodes: (TreeLeaf | TreeNode)[] = [
|
||||
{
|
||||
type: "leaf",
|
||||
start: 0,
|
||||
end: text.length,
|
||||
},
|
||||
];
|
||||
|
||||
const rangesSortedLowToHighPrecedence = ranges.sort((a, b) => {
|
||||
const aPrecedence = tagPrecedence.indexOf(a.sourceMark.tag);
|
||||
const bPrecedence = tagPrecedence.indexOf(b.sourceMark.tag);
|
||||
return bPrecedence - aPrecedence;
|
||||
});
|
||||
|
||||
// for each range, split the current nodes where necessary (no matter if leaf or already a node), wrapping the resulting "inside" parts in a node with the range's tag
|
||||
for (const range of rangesSortedLowToHighPrecedence) {
|
||||
// console.log("currentNodes", currentNodes);
|
||||
const newNodes = currentNodes.flatMap((node) => {
|
||||
const [before, inOrAfter] = splitNode(node, range.start);
|
||||
const [inside, after] = inOrAfter
|
||||
? splitNode(inOrAfter, range.end)
|
||||
: [undefined, undefined];
|
||||
|
||||
// console.log("split", range.start, range.end, {
|
||||
// before,
|
||||
// inside,
|
||||
// after,
|
||||
// });
|
||||
|
||||
// TODO: also split children
|
||||
|
||||
return [
|
||||
...(before ? [before] : []),
|
||||
...(inside
|
||||
? [
|
||||
{
|
||||
type: "node" as const,
|
||||
tag: range.sourceMark.tag,
|
||||
start: inside.start,
|
||||
end: inside.end,
|
||||
children: [inside],
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(after ? [after] : []),
|
||||
];
|
||||
});
|
||||
|
||||
currentNodes = newNodes;
|
||||
}
|
||||
|
||||
return {
|
||||
type: "node",
|
||||
tag: "root",
|
||||
start: 0,
|
||||
end: text.length,
|
||||
children: currentNodes,
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
if (!this.text) return "";
|
||||
return this.text.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export type TreeLeaf = {
|
||||
type: "leaf";
|
||||
start: number;
|
||||
end: number;
|
||||
};
|
||||
|
||||
export type TreeNode = {
|
||||
type: "node";
|
||||
tag: string;
|
||||
start: number;
|
||||
end: number;
|
||||
range?: ResolvedAndFocusedMark;
|
||||
children: (TreeNode | TreeLeaf)[];
|
||||
};
|
||||
|
||||
function splitNode(
|
||||
node: TreeNode | TreeLeaf,
|
||||
at: number,
|
||||
): [TreeNode | TreeLeaf | undefined, TreeNode | TreeLeaf | undefined] {
|
||||
if (node.type === "leaf") {
|
||||
return [
|
||||
at > node.start
|
||||
? {
|
||||
type: "leaf",
|
||||
start: node.start,
|
||||
end: Math.min(at, node.end),
|
||||
}
|
||||
: undefined,
|
||||
at < node.end
|
||||
? {
|
||||
type: "leaf",
|
||||
start: Math.max(at, node.start),
|
||||
end: node.end,
|
||||
}
|
||||
: undefined,
|
||||
];
|
||||
} else {
|
||||
const children = node.children;
|
||||
return [
|
||||
at > node.start
|
||||
? {
|
||||
type: "node",
|
||||
tag: node.tag,
|
||||
start: node.start,
|
||||
end: Math.min(at, node.end),
|
||||
children: children
|
||||
.map((child) => splitNode(child, at)[0])
|
||||
.filter(
|
||||
(c): c is Exclude<typeof c, undefined> => !!c,
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
at < node.end
|
||||
? {
|
||||
type: "node",
|
||||
tag: node.tag,
|
||||
start: Math.max(at, node.start),
|
||||
end: node.end,
|
||||
children: children
|
||||
.map((child) => splitNode(child, at)[1])
|
||||
.filter(
|
||||
(c): c is Exclude<typeof c, undefined> => !!c,
|
||||
),
|
||||
}
|
||||
: undefined,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export const Marks = {
|
||||
Heading: class Heading extends Mark {
|
||||
tag = co.literal("heading");
|
||||
level = co.number;
|
||||
},
|
||||
Paragraph: class Paragraph extends Mark {
|
||||
tag = co.literal("paragraph");
|
||||
},
|
||||
Link: class Link extends Mark {
|
||||
tag = co.literal("link");
|
||||
url = co.string;
|
||||
},
|
||||
Strong: class Strong extends Mark {
|
||||
tag = co.literal("strong");
|
||||
},
|
||||
Em: class Italic extends Mark {
|
||||
tag = co.literal("em");
|
||||
},
|
||||
};
|
||||
@@ -81,7 +81,10 @@ export function fulfillsDepth(depth: any, value: CoValue): boolean {
|
||||
).optional,
|
||||
);
|
||||
}
|
||||
} else if (value._type === "BinaryCoStream") {
|
||||
} else if (
|
||||
value._type === "BinaryCoStream" ||
|
||||
value._type === "CoPlainText"
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
console.error(value);
|
||||
@@ -226,4 +229,10 @@ export type DeeplyLoaded<
|
||||
},
|
||||
]
|
||||
? V
|
||||
: never;
|
||||
: [V] extends [
|
||||
{
|
||||
_type: "CoPlainText";
|
||||
},
|
||||
]
|
||||
? V
|
||||
: never;
|
||||
|
||||
@@ -19,6 +19,8 @@ export { Encoders, co } from "./internal.js";
|
||||
|
||||
export { CoMap, type CoMapInit } from "./internal.js";
|
||||
export { CoList } from "./internal.js";
|
||||
export { CoPlainText, TextPos } from "./internal.js";
|
||||
export { CoRichText, Mark, Marks, TreeNode, TreeLeaf } from "./internal.js";
|
||||
export { CoStream, BinaryCoStream } from "./internal.js";
|
||||
export { Group, Profile } from "./internal.js";
|
||||
export { Account, isControlledAccount } from "./internal.js";
|
||||
|
||||
@@ -5,6 +5,8 @@ export * from "./coValues/interfaces.js";
|
||||
export * from "./coValues/coMap.js";
|
||||
export * from "./coValues/account.js";
|
||||
export * from "./coValues/coList.js";
|
||||
export * from "./coValues/coPlainText.js";
|
||||
export * from "./coValues/coRichText.js";
|
||||
export * from "./coValues/coStream.js";
|
||||
export * from "./coValues/group.js";
|
||||
|
||||
|
||||
33
packages/jazz-tools/src/tests/coPlainText.test.ts
Normal file
33
packages/jazz-tools/src/tests/coPlainText.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { expect, describe, test } from "vitest";
|
||||
import { Account, CoPlainText, WasmCrypto } from "../index.js";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
|
||||
describe("Simple CoPlainText operations", async () => {
|
||||
const me = await Account.create({
|
||||
creationProps: { name: "Hermes Puggington" },
|
||||
crypto: Crypto
|
||||
});
|
||||
|
||||
const text = CoPlainText.create("hello world", { owner: me });
|
||||
|
||||
test("Construction", () => {
|
||||
expect(text + "").toEqual("hello world");
|
||||
});
|
||||
|
||||
describe("Mutation", () => {
|
||||
test("insertion", () => {
|
||||
const text = CoPlainText.create("hello world", { owner: me });
|
||||
|
||||
text.insertAfter(5, " cruel");
|
||||
expect(text + "").toEqual("hello cruel world");
|
||||
});
|
||||
|
||||
test("deletion", () => {
|
||||
const text = CoPlainText.create("hello world", { owner: me });
|
||||
|
||||
text.deleteRange({from: 3, to: 8});
|
||||
expect(text + "").toEqual("helrld");
|
||||
});
|
||||
});
|
||||
});
|
||||
59
packages/jazz-tools/src/tests/coRichText.test.ts
Normal file
59
packages/jazz-tools/src/tests/coRichText.test.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { expect, describe, test } from "vitest";
|
||||
import { Account, CoRichText, Marks, WasmCrypto } from "../index.js";
|
||||
|
||||
const Crypto = await WasmCrypto.create();
|
||||
|
||||
describe("Simple CoRichText operations", async () => {
|
||||
const me = await Account.create({
|
||||
creationProps: { name: "Hermes Puggington" },
|
||||
crypto: Crypto
|
||||
});
|
||||
|
||||
const text = CoRichText.createFromPlainText("hello world", { owner: me });
|
||||
|
||||
test("Construction", () => {
|
||||
expect(text + "").toEqual("hello world");
|
||||
});
|
||||
|
||||
describe("Mutation", () => {
|
||||
test("insertion", () => {
|
||||
const text = CoRichText.createFromPlainText("hello world", {
|
||||
owner: me,
|
||||
});
|
||||
|
||||
text.insertAfter(5, " cruel");
|
||||
expect(text + "").toEqual("hello cruel world");
|
||||
});
|
||||
|
||||
test("deletion", () => {
|
||||
const text = CoRichText.createFromPlainText("hello world", {
|
||||
owner: me,
|
||||
});
|
||||
|
||||
text.deleteRange({from: 3, to: 8});
|
||||
expect(text + "").toEqual("helrld");
|
||||
});
|
||||
|
||||
test("inserting ranges", () => {
|
||||
const text = CoRichText.createFromPlainText("hello world", {
|
||||
owner: me,
|
||||
});
|
||||
|
||||
text.insertMark(6, 9, Marks.Strong, { tag: "strong" });
|
||||
|
||||
console.log(text.text?._raw.entries());
|
||||
console.log(text.text?._raw.mapping);
|
||||
|
||||
expect(text.resolveMarks()).toEqual([
|
||||
{
|
||||
startAfter: 6,
|
||||
startBefore: 7,
|
||||
endAfter: 9,
|
||||
endBefore: 10,
|
||||
tag: "bold",
|
||||
from: text.marks![0],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
271
pnpm-lock.yaml
generated
271
pnpm-lock.yaml
generated
@@ -330,6 +330,130 @@ importers:
|
||||
specifier: ^4.4.5
|
||||
version: 4.5.1(@types/node@20.10.5)
|
||||
|
||||
examples/richtext:
|
||||
dependencies:
|
||||
'@radix-ui/react-checkbox':
|
||||
specifier: ^1.0.4
|
||||
version: 1.0.4(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@radix-ui/react-slot':
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(@types/react@18.2.45)(react@18.2.0)
|
||||
'@radix-ui/react-toast':
|
||||
specifier: ^1.1.4
|
||||
version: 1.1.5(@types/react-dom@18.2.18)(@types/react@18.2.45)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
'@types/qrcode':
|
||||
specifier: ^1.5.1
|
||||
version: 1.5.5
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.0
|
||||
version: 0.7.0
|
||||
clsx:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0
|
||||
cojson:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/cojson
|
||||
hash-slash:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/hash-slash
|
||||
jazz-react:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/jazz-react
|
||||
jazz-tools:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/jazz-tools
|
||||
lucide-react:
|
||||
specifier: ^0.274.0
|
||||
version: 0.274.0(react@18.2.0)
|
||||
prosemirror-example-setup:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
prosemirror-menu:
|
||||
specifier: ^1.2.4
|
||||
version: 1.2.4
|
||||
prosemirror-model:
|
||||
specifier: ^1.21.1
|
||||
version: 1.21.1
|
||||
prosemirror-schema-basic:
|
||||
specifier: ^1.2.2
|
||||
version: 1.2.2
|
||||
prosemirror-state:
|
||||
specifier: ^1.4.3
|
||||
version: 1.4.3
|
||||
prosemirror-transform:
|
||||
specifier: ^1.9.0
|
||||
version: 1.9.0
|
||||
prosemirror-view:
|
||||
specifier: ^1.33.7
|
||||
version: 1.33.7
|
||||
qrcode:
|
||||
specifier: ^1.5.3
|
||||
version: 1.5.3
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
react-dom:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0(react@18.2.0)
|
||||
react-router:
|
||||
specifier: ^6.16.0
|
||||
version: 6.21.0(react@18.2.0)
|
||||
react-router-dom:
|
||||
specifier: ^6.16.0
|
||||
version: 6.21.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
react-use:
|
||||
specifier: ^17.4.0
|
||||
version: 17.4.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||
tailwind-merge:
|
||||
specifier: ^1.14.0
|
||||
version: 1.14.0
|
||||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@3.4.0(ts-node@10.9.2(@swc/core@1.3.101)(@types/node@20.10.5)(typescript@5.3.3)))
|
||||
uniqolor:
|
||||
specifier: ^1.1.0
|
||||
version: 1.1.1
|
||||
devDependencies:
|
||||
'@types/react':
|
||||
specifier: ^18.2.15
|
||||
version: 18.2.45
|
||||
'@types/react-dom':
|
||||
specifier: ^18.2.7
|
||||
version: 18.2.18
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^6.0.0
|
||||
version: 6.15.0(@typescript-eslint/parser@6.15.0(eslint@8.56.0)(typescript@5.3.3))(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@typescript-eslint/parser':
|
||||
specifier: ^6.0.0
|
||||
version: 6.15.0(eslint@8.56.0)(typescript@5.3.3)
|
||||
'@vitejs/plugin-react-swc':
|
||||
specifier: ^3.3.2
|
||||
version: 3.5.0(vite@5.0.10(@types/node@20.10.5))
|
||||
autoprefixer:
|
||||
specifier: ^10.4.14
|
||||
version: 10.4.16(postcss@8.4.32)
|
||||
eslint:
|
||||
specifier: ^8.45.0
|
||||
version: 8.56.0
|
||||
eslint-plugin-react-hooks:
|
||||
specifier: ^4.6.0
|
||||
version: 4.6.0(eslint@8.56.0)
|
||||
eslint-plugin-react-refresh:
|
||||
specifier: ^0.4.3
|
||||
version: 0.4.5(eslint@8.56.0)
|
||||
postcss:
|
||||
specifier: ^8.4.27
|
||||
version: 8.4.32
|
||||
tailwindcss:
|
||||
specifier: ^3.3.3
|
||||
version: 3.4.0(ts-node@10.9.2(@swc/core@1.3.101)(@types/node@20.10.5)(typescript@5.3.3))
|
||||
typescript:
|
||||
specifier: ^5.0.2
|
||||
version: 5.3.3
|
||||
vite:
|
||||
specifier: ^5.0.10
|
||||
version: 5.0.10(@types/node@20.10.5)
|
||||
|
||||
examples/todo:
|
||||
dependencies:
|
||||
'@radix-ui/react-checkbox':
|
||||
@@ -2251,6 +2375,9 @@ packages:
|
||||
create-require@1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
|
||||
|
||||
@@ -3482,6 +3609,9 @@ packages:
|
||||
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
orderedmap@2.1.1:
|
||||
resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
|
||||
|
||||
os-tmpdir@1.0.2:
|
||||
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -3696,6 +3826,48 @@ packages:
|
||||
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
prosemirror-commands@1.5.2:
|
||||
resolution: {integrity: sha512-hgLcPaakxH8tu6YvVAaILV2tXYsW3rAdDR8WNkeKGcgeMVQg3/TMhPdVoh7iAmfgVjZGtcOSjKiQaoeKjzd2mQ==}
|
||||
|
||||
prosemirror-dropcursor@1.8.1:
|
||||
resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==}
|
||||
|
||||
prosemirror-example-setup@1.2.2:
|
||||
resolution: {integrity: sha512-pHJc656IgYm249RNp0eQaWNmnyWGk6OrzysWfYI4+NwE14HQ7YNYOlRBLErUS6uCAHIYJLNXf0/XCmf1OCtNbQ==}
|
||||
|
||||
prosemirror-gapcursor@1.3.2:
|
||||
resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==}
|
||||
|
||||
prosemirror-history@1.4.0:
|
||||
resolution: {integrity: sha512-UUiGzDVcqo1lovOPdi9YxxUps3oBFWAIYkXLu3Ot+JPv1qzVogRbcizxK3LhHmtaUxclohgiOVesRw5QSlMnbQ==}
|
||||
|
||||
prosemirror-inputrules@1.4.0:
|
||||
resolution: {integrity: sha512-6ygpPRuTJ2lcOXs9JkefieMst63wVJBgHZGl5QOytN7oSZs3Co/BYbc3Yx9zm9H37Bxw8kVzCnDsihsVsL4yEg==}
|
||||
|
||||
prosemirror-keymap@1.2.2:
|
||||
resolution: {integrity: sha512-EAlXoksqC6Vbocqc0GtzCruZEzYgrn+iiGnNjsJsH4mrnIGex4qbLdWWNza3AW5W36ZRrlBID0eM6bdKH4OStQ==}
|
||||
|
||||
prosemirror-menu@1.2.4:
|
||||
resolution: {integrity: sha512-S/bXlc0ODQup6aiBbWVsX/eM+xJgCTAfMq/nLqaO5ID/am4wS0tTCIkzwytmao7ypEtjj39i7YbJjAgO20mIqA==}
|
||||
|
||||
prosemirror-model@1.21.1:
|
||||
resolution: {integrity: sha512-IVBAuMqOfltTr7yPypwpfdGT+6rGAteVOw2FO6GEvCGGa1ZwxLseqC1Eax/EChDvG/xGquB2d/hLdgh3THpsYg==}
|
||||
|
||||
prosemirror-schema-basic@1.2.2:
|
||||
resolution: {integrity: sha512-/dT4JFEGyO7QnNTe9UaKUhjDXbTNkiWTq/N4VpKaF79bBjSExVV2NXmJpcM7z/gD7mbqNjxbmWW5nf1iNSSGnw==}
|
||||
|
||||
prosemirror-schema-list@1.3.0:
|
||||
resolution: {integrity: sha512-Hz/7gM4skaaYfRPNgr421CU4GSwotmEwBVvJh5ltGiffUJwm7C8GfN/Bc6DR1EKEp5pDKhODmdXXyi9uIsZl5A==}
|
||||
|
||||
prosemirror-state@1.4.3:
|
||||
resolution: {integrity: sha512-goFKORVbvPuAQaXhpbemJFRKJ2aixr+AZMGiquiqKxaucC6hlpHNZHWgz5R7dS4roHiwq9vDctE//CZ++o0W1Q==}
|
||||
|
||||
prosemirror-transform@1.9.0:
|
||||
resolution: {integrity: sha512-5UXkr1LIRx3jmpXXNKDhv8OyAOeLTGuXNwdVfg8x27uASna/wQkr9p6fD3eupGOi4PLJfbezxTyi/7fSJypXHg==}
|
||||
|
||||
prosemirror-view@1.33.7:
|
||||
resolution: {integrity: sha512-jo6eMQCtPRwcrA2jISBCnm0Dd2B+szS08BU1Ay+XGiozHo5EZMHfLQE8R5nO4vb1spTH2RW1woZIYXRiQsuP8g==}
|
||||
|
||||
proxy-agent@6.3.0:
|
||||
resolution: {integrity: sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==}
|
||||
engines: {node: '>= 14'}
|
||||
@@ -3889,6 +4061,9 @@ packages:
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
rope-sequence@1.3.4:
|
||||
resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
|
||||
|
||||
rtl-css-js@1.16.1:
|
||||
resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==}
|
||||
|
||||
@@ -4560,6 +4735,9 @@ packages:
|
||||
vscode-textmate@8.0.0:
|
||||
resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==}
|
||||
|
||||
w3c-keyname@2.2.8:
|
||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||
|
||||
wait-port@1.1.0:
|
||||
resolution: {integrity: sha512-3e04qkoN3LxTMLakdqeWth8nih8usyg+sf1Bgdf9wwUkp05iuK1eSY/QpLvscT/+F/gA89+LpUmmgBtesbqI2Q==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -6397,6 +6575,8 @@ snapshots:
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cross-fetch@4.0.0:
|
||||
dependencies:
|
||||
node-fetch: 2.7.0
|
||||
@@ -7725,6 +7905,8 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
orderedmap@2.1.1: {}
|
||||
|
||||
os-tmpdir@1.0.2: {}
|
||||
|
||||
outdent@0.5.0: {}
|
||||
@@ -7925,6 +8107,91 @@ snapshots:
|
||||
|
||||
progress@2.0.3: {}
|
||||
|
||||
prosemirror-commands@1.5.2:
|
||||
dependencies:
|
||||
prosemirror-model: 1.21.1
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-transform: 1.9.0
|
||||
|
||||
prosemirror-dropcursor@1.8.1:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-transform: 1.9.0
|
||||
prosemirror-view: 1.33.7
|
||||
|
||||
prosemirror-example-setup@1.2.2:
|
||||
dependencies:
|
||||
prosemirror-commands: 1.5.2
|
||||
prosemirror-dropcursor: 1.8.1
|
||||
prosemirror-gapcursor: 1.3.2
|
||||
prosemirror-history: 1.4.0
|
||||
prosemirror-inputrules: 1.4.0
|
||||
prosemirror-keymap: 1.2.2
|
||||
prosemirror-menu: 1.2.4
|
||||
prosemirror-schema-list: 1.3.0
|
||||
prosemirror-state: 1.4.3
|
||||
|
||||
prosemirror-gapcursor@1.3.2:
|
||||
dependencies:
|
||||
prosemirror-keymap: 1.2.2
|
||||
prosemirror-model: 1.21.1
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-view: 1.33.7
|
||||
|
||||
prosemirror-history@1.4.0:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-transform: 1.9.0
|
||||
prosemirror-view: 1.33.7
|
||||
rope-sequence: 1.3.4
|
||||
|
||||
prosemirror-inputrules@1.4.0:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-transform: 1.9.0
|
||||
|
||||
prosemirror-keymap@1.2.2:
|
||||
dependencies:
|
||||
prosemirror-state: 1.4.3
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
prosemirror-menu@1.2.4:
|
||||
dependencies:
|
||||
crelt: 1.0.6
|
||||
prosemirror-commands: 1.5.2
|
||||
prosemirror-history: 1.4.0
|
||||
prosemirror-state: 1.4.3
|
||||
|
||||
prosemirror-model@1.21.1:
|
||||
dependencies:
|
||||
orderedmap: 2.1.1
|
||||
|
||||
prosemirror-schema-basic@1.2.2:
|
||||
dependencies:
|
||||
prosemirror-model: 1.21.1
|
||||
|
||||
prosemirror-schema-list@1.3.0:
|
||||
dependencies:
|
||||
prosemirror-model: 1.21.1
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-transform: 1.9.0
|
||||
|
||||
prosemirror-state@1.4.3:
|
||||
dependencies:
|
||||
prosemirror-model: 1.21.1
|
||||
prosemirror-transform: 1.9.0
|
||||
prosemirror-view: 1.33.7
|
||||
|
||||
prosemirror-transform@1.9.0:
|
||||
dependencies:
|
||||
prosemirror-model: 1.21.1
|
||||
|
||||
prosemirror-view@1.33.7:
|
||||
dependencies:
|
||||
prosemirror-model: 1.21.1
|
||||
prosemirror-state: 1.4.3
|
||||
prosemirror-transform: 1.9.0
|
||||
|
||||
proxy-agent@6.3.0:
|
||||
dependencies:
|
||||
agent-base: 7.1.0
|
||||
@@ -8190,6 +8457,8 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc': 4.9.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
rope-sequence@1.3.4: {}
|
||||
|
||||
rtl-css-js@1.16.1:
|
||||
dependencies:
|
||||
'@babel/runtime': 7.23.6
|
||||
@@ -8946,6 +9215,8 @@ snapshots:
|
||||
|
||||
vscode-textmate@8.0.0: {}
|
||||
|
||||
w3c-keyname@2.2.8: {}
|
||||
|
||||
wait-port@1.1.0:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
|
||||
Reference in New Issue
Block a user