Compare commits

..

10 Commits

Author SHA1 Message Date
Kevin Jahns
8586806932 13.6.6 2023-06-25 19:10:34 +02:00
Kevin Jahns
981340139f skip iterating when there are no formatting items - replaces #547 2023-06-25 12:46:02 +02:00
Kevin Jahns
b792902f17 13.6.5 2023-06-22 17:55:45 +02:00
Kevin Jahns
83b7c6839e Merge pull request #548 from YousefED/fix/equalDeleteSets
fix equalDeleteSets
2023-06-22 17:46:34 +02:00
Kevin Jahns
65c4d40a87 Merge branch 'NilSet-path-cache-invalidation' 2023-06-22 17:48:19 +02:00
Kevin Jahns
942c8a267b remove duplicate Transaction.callAll logic 2023-06-22 17:46:49 +02:00
yousefed
eda085936a keep original imports 2023-06-21 18:29:40 +02:00
yousefed
12be6c006a fix equalDeleteSets 2023-06-21 18:28:53 +02:00
Noel Levy
5d862477cd invalidate cached path when changing currentTarget of event
fixes #544
2023-06-19 11:31:45 -07:00
Kevin Jahns
c398448152 add blocksuite editor by affine 2023-06-16 16:04:30 +02:00
8 changed files with 75 additions and 39 deletions

View File

@@ -91,6 +91,7 @@ are implemented in separate modules.
| [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](https://github.com/yjs/y-codemirror) | [demo](https://demos.yjs.dev/codemirror/codemirror.html) | | [CodeMirror](https://codemirror.net/) | ✔ | [y-codemirror](https://github.com/yjs/y-codemirror) | [demo](https://demos.yjs.dev/codemirror/codemirror.html) |
| [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](https://github.com/yjs/y-monaco) | [demo](https://demos.yjs.dev/monaco/monaco.html) | | [Monaco](https://microsoft.github.io/monaco-editor/) | ✔ | [y-monaco](https://github.com/yjs/y-monaco) | [demo](https://demos.yjs.dev/monaco/monaco.html) |
| [Slate](https://github.com/ianstormtaylor/slate) | ✔ | [slate-yjs](https://github.com/bitphinix/slate-yjs) | [demo](https://bitphinix.github.io/slate-yjs-example) | | [Slate](https://github.com/ianstormtaylor/slate) | ✔ | [slate-yjs](https://github.com/bitphinix/slate-yjs) | [demo](https://bitphinix.github.io/slate-yjs-example) |
| [BlockSuite](https://github.com/toeverything/blocksuite) | ✔ | (native) | [demo](https://blocksuite-toeverything.vercel.app/?init) |
| [valtio](https://github.com/pmndrs/valtio) | | [valtio-yjs](https://github.com/dai-shi/valtio-yjs) | [demo](https://codesandbox.io/s/valtio-yjs-demo-ox3iy) | | [valtio](https://github.com/pmndrs/valtio) | | [valtio-yjs](https://github.com/dai-shi/valtio-yjs) | [demo](https://codesandbox.io/s/valtio-yjs-demo-ox3iy) |
| [immer](https://github.com/immerjs/immer) | | [immer-yjs](https://github.com/sep2/immer-yjs) | [demo](https://codesandbox.io/s/immer-yjs-demo-6e0znb) | | [immer](https://github.com/immerjs/immer) | | [immer-yjs](https://github.com/sep2/immer-yjs) | [demo](https://codesandbox.io/s/immer-yjs-demo-6e0znb) |
| React / Vue / Svelte / MobX | | [SyncedStore](https://syncedstore.org) | [demo](https://syncedstore.org/docs/react) | | React / Vue / Svelte / MobX | | [SyncedStore](https://syncedstore.org) | [demo](https://syncedstore.org/docs/react) |

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.6.4", "version": "13.6.6",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "yjs", "name": "yjs",
"version": "13.6.4", "version": "13.6.6",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lib0": "^0.2.74" "lib0": "^0.2.74"

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.6.4", "version": "13.6.6",
"description": "Shared Editing Library", "description": "Shared Editing Library",
"main": "./dist/yjs.cjs", "main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs", "module": "./dist/yjs.mjs",

View File

@@ -1,6 +1,6 @@
import { import {
AbstractType, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Item, StructStore, Transaction // eslint-disable-line YText, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Item, StructStore, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error' import * as error from 'lib0/error'
@@ -47,28 +47,30 @@ export class ContentFormat {
} }
/** /**
* @param {number} offset * @param {number} _offset
* @return {ContentFormat} * @return {ContentFormat}
*/ */
splice (offset) { splice (_offset) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/** /**
* @param {ContentFormat} right * @param {ContentFormat} _right
* @return {boolean} * @return {boolean}
*/ */
mergeWith (right) { mergeWith (_right) {
return false return false
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} _transaction
* @param {Item} item * @param {Item} item
*/ */
integrate (transaction, item) { integrate (_transaction, item) {
// @todo searchmarker are currently unsupported for rich text documents // @todo searchmarker are currently unsupported for rich text documents
/** @type {AbstractType<any>} */ (item.parent)._searchMarker = null const p = /** @type {YText} */ (item.parent)
p._searchMarker = null
p._hasFormatting = true
} }
/** /**

View File

@@ -505,7 +505,7 @@ export const cleanupYTextAfterTransaction = transaction => {
// cleanup in a new transaction // cleanup in a new transaction
transact(doc, (t) => { transact(doc, (t) => {
iterateDeletedStructs(transaction, transaction.deleteSet, item => { iterateDeletedStructs(transaction, transaction.deleteSet, item => {
if (item instanceof GC || needFullCleanup.has(/** @type {YText} */ (item.parent))) { if (item instanceof GC || !(/** @type {YText} */ (item.parent)._hasFormatting) || needFullCleanup.has(/** @type {YText} */ (item.parent))) {
return return
} }
const parent = /** @type {YText} */ (item.parent) const parent = /** @type {YText} */ (item.parent)
@@ -859,9 +859,14 @@ export class YText extends AbstractType {
*/ */
this._pending = string !== undefined ? [() => this.insert(0, string)] : [] this._pending = string !== undefined ? [() => this.insert(0, string)] : []
/** /**
* @type {Array<ArraySearchMarker>} * @type {Array<ArraySearchMarker>|null}
*/ */
this._searchMarker = [] this._searchMarker = []
/**
* Whether this YText contains formatting attributes.
* This flag is updated when a formatting item is integrated (see ContentFormat.integrate)
*/
this._hasFormatting = false
} }
/** /**
@@ -911,7 +916,7 @@ export class YText extends AbstractType {
const event = new YTextEvent(this, transaction, parentSubs) const event = new YTextEvent(this, transaction, parentSubs)
callTypeObservers(this, transaction, event) callTypeObservers(this, transaction, event)
// If a remote change happened, we try to cleanup potential formatting duplicates. // If a remote change happened, we try to cleanup potential formatting duplicates.
if (!transaction.local) { if (!transaction.local && this._hasFormatting) {
transaction._needFormattingCleanup = true transaction._needFormattingCleanup = true
} }
} }

View File

@@ -335,7 +335,7 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
*/ */
export const equalDeleteSets = (ds1, ds2) => { export const equalDeleteSets = (ds1, ds2) => {
if (ds1.clients.size !== ds2.clients.size) return false if (ds1.clients.size !== ds2.clients.size) return false
ds1.clients.forEach((deleteItems1, client) => { for (const [client, deleteItems1] of ds1.clients.entries()) {
const deleteItems2 = /** @type {Array<import('../internals.js').DeleteItem>} */ (ds2.clients.get(client)) const deleteItems2 = /** @type {Array<import('../internals.js').DeleteItem>} */ (ds2.clients.get(client))
if (deleteItems2 === undefined || deleteItems1.length !== deleteItems2.length) return false if (deleteItems2 === undefined || deleteItems1.length !== deleteItems2.length) return false
for (let i = 0; i < deleteItems1.length; i++) { for (let i = 0; i < deleteItems1.length; i++) {
@@ -345,6 +345,6 @@ export const equalDeleteSets = (ds1, ds2) => {
return false return false
} }
} }
}) }
return true return true
} }

View File

@@ -275,30 +275,30 @@ const cleanupTransactions = (transactionCleanups, i) => {
) )
fs.push(() => { fs.push(() => {
// deep observe events // deep observe events
transaction.changedParentTypes.forEach((events, type) => transaction.changedParentTypes.forEach((events, type) => {
fs.push(() => { // We need to think about the possibility that the user transforms the
// We need to think about the possibility that the user transforms the // Y.Doc in the event.
// Y.Doc in the event. if (type._dEH.l.length > 0 && (type._item === null || !type._item.deleted)) {
if (type._item === null || !type._item.deleted) { events = events
events = events .filter(event =>
.filter(event => event.target._item === null || !event.target._item.deleted
event.target._item === null || !event.target._item.deleted )
) events
events .forEach(event => {
.forEach(event => { event.currentTarget = type
event.currentTarget = type // path is relative to the current target
}) event._path = null
// sort events by path length so that top-level events are fired first. })
events // sort events by path length so that top-level events are fired first.
.sort((event1, event2) => event1.path.length - event2.path.length) events
// We don't need to check for events.length .sort((event1, event2) => event1.path.length - event2.path.length)
// because we know it has at least one element // We don't need to check for events.length
callEventHandlerListeners(type._dEH, events, transaction) // because we know it has at least one element
} callEventHandlerListeners(type._dEH, events, transaction)
}) }
) })
fs.push(() => doc.emit('afterTransaction', [transaction, doc]))
}) })
fs.push(() => doc.emit('afterTransaction', [transaction, doc]))
callAll(fs, []) callAll(fs, [])
if (transaction._needFormattingCleanup) { if (transaction._needFormattingCleanup) {
cleanupYTextAfterTransaction(transaction) cleanupYTextAfterTransaction(transaction)

View File

@@ -337,6 +337,34 @@ export const testObserversUsingObservedeep = tc => {
compare(users) compare(users)
} }
/**
* @param {t.TestCase} tc
*/
export const testPathsOfSiblingEvents = tc => {
const { users, map0 } = init(tc, { users: 2 })
/**
* @type {Array<Array<string|number>>}
*/
const pathes = []
let calls = 0
const doc = users[0]
map0.set('map', new Y.Map())
map0.get('map').set('text1', new Y.Text('initial'))
map0.observeDeep(events => {
events.forEach(event => {
pathes.push(event.path)
})
calls++
})
doc.transact(() => {
map0.get('map').get('text1').insert(0, 'post-')
map0.get('map').set('text2', new Y.Text('new'))
})
t.assert(calls === 1)
t.compare(pathes, [['map'], ['map', 'text1']])
compare(users)
}
// TODO: Test events in Y.Map // TODO: Test events in Y.Map
/** /**
* @param {Object<string,any>} is * @param {Object<string,any>} is