edytor 1.0.0-rc.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -25
- package/dist/block/block.svelte.d.ts +10 -85
- package/dist/block/block.svelte.js +47 -141
- package/dist/block/colors.d.ts +0 -9
- package/dist/block/colors.js +1 -18
- package/dist/block/contentRange.d.ts +5 -1
- package/dist/block/contentRange.js +15 -16
- package/dist/block/inlineBlock.svelte.d.ts +8 -10
- package/dist/block/inlineBlock.svelte.js +14 -22
- package/dist/clipboard/createClipboardFragment.js +22 -64
- package/dist/clipboard/fragmentData.js +17 -42
- package/dist/clipboard/htmlFlow.d.ts +6 -1
- package/dist/clipboard/htmlFlow.js +26 -13
- package/dist/clipboard/insertClipboardFragment.js +5 -4
- package/dist/clipboard/moveText.js +16 -8
- package/dist/clipboard/serializeClipboardFragment.d.ts +2 -0
- package/dist/clipboard/serializeClipboardFragment.js +32 -28
- package/dist/cloudflare/DocumentRoom.d.ts +22 -17
- package/dist/cloudflare/DocumentRoom.js +10 -5
- package/dist/cloudflare/index.d.ts +1 -1
- package/dist/cloudflare/index.js +1 -1
- package/dist/cloudflare/move.d.ts +1 -18
- package/dist/cloudflare/move.js +2 -48
- package/dist/cloudflare/room/admission.d.ts +1 -60
- package/dist/cloudflare/room/admission.js +25 -29
- package/dist/cloudflare/room/comments.d.ts +1 -3
- package/dist/cloudflare/room/comments.js +6 -6
- package/dist/cloudflare/room/context.d.ts +20 -82
- package/dist/cloudflare/room/context.js +8 -8
- package/dist/cloudflare/room/forged.d.ts +1 -1
- package/dist/cloudflare/room/forged.js +2 -12
- package/dist/cloudflare/room/frames.d.ts +1 -1
- package/dist/cloudflare/room/frames.js +1 -1
- package/dist/cloudflare/room/history.d.ts +1 -25
- package/dist/cloudflare/room/history.js +39 -54
- package/dist/cloudflare/room/moves.d.ts +1 -13
- package/dist/cloudflare/room/moves.js +33 -57
- package/dist/cloudflare/room/presence.d.ts +1 -11
- package/dist/cloudflare/room/presence.js +4 -4
- package/dist/cloudflare/room/purge.d.ts +1 -2
- package/dist/cloudflare/room/purge.js +2 -2
- package/dist/cloudflare/room/replicas.d.ts +1 -7
- package/dist/cloudflare/room/replicas.js +2 -2
- package/dist/cloudflare/room/storage.d.ts +9 -30
- package/dist/cloudflare/room/storage.js +52 -104
- package/dist/cloudflare/room/updates.d.ts +4 -4
- package/dist/cloudflare/room/updates.js +17 -24
- package/dist/cloudflare/room/validation.d.ts +1 -5
- package/dist/cloudflare/room/validation.js +16 -37
- package/dist/collaboration/RemoteSelections.svelte +13 -17
- package/dist/collaboration/comments/client.d.ts +2 -2
- package/dist/collaboration/comments/client.js +1 -1
- package/dist/collaboration/history/index.d.ts +5 -5
- package/dist/collaboration/history/index.js +5 -5
- package/dist/collaboration/history/panel.svelte.d.ts +91 -0
- package/dist/collaboration/history/panel.svelte.js +223 -0
- package/dist/collaboration/providers.d.ts +12 -3
- package/dist/collaboration/providers.js +5 -4
- package/dist/components/Block.svelte +160 -119
- package/dist/components/Block.svelte.d.ts +8 -3
- package/dist/components/Content.svelte +91 -44
- package/dist/components/CustomMark.svelte +46 -0
- package/dist/components/CustomMark.svelte.d.ts +11 -0
- package/dist/components/Edytor.svelte +80 -95
- package/dist/components/Edytor.svelte.d.ts +8 -12
- package/dist/components/InlineBlock.svelte +4 -9
- package/dist/components/Mark.svelte +46 -35
- package/dist/components/Mark.svelte.d.ts +7 -0
- package/dist/components/Ranges.svelte +96 -0
- package/dist/components/Ranges.svelte.d.ts +19 -0
- package/dist/components/Suggestion.svelte +3 -2
- package/dist/components/Text.svelte +48 -14
- package/dist/components/Text.svelte.d.ts +1 -1
- package/dist/crdt/admission.d.ts +2 -9
- package/dist/crdt/admission.js +3 -9
- package/dist/crdt/attribution/attribution.d.ts +11 -72
- package/dist/crdt/attribution/attribution.js +5 -52
- package/dist/crdt/attribution/block.d.ts +0 -78
- package/dist/crdt/attribution/block.js +1 -144
- package/dist/crdt/data.d.ts +8 -13
- package/dist/crdt/data.js +30 -70
- package/dist/crdt/doc/anchors.js +4 -4
- package/dist/crdt/doc/content.d.ts +5 -1
- package/dist/crdt/doc/content.js +3 -1
- package/dist/crdt/doc/delete.d.ts +1 -0
- package/dist/crdt/doc/funnel.d.ts +0 -23
- package/dist/crdt/doc/funnel.js +34 -85
- package/dist/crdt/doc/gate.d.ts +8 -9
- package/dist/crdt/doc/gate.js +7 -17
- package/dist/crdt/doc/history.d.ts +87 -14
- package/dist/crdt/doc/history.js +334 -149
- package/dist/crdt/doc/layout.d.ts +0 -1
- package/dist/crdt/doc/layout.js +2 -3
- package/dist/crdt/doc/meta.d.ts +4 -4
- package/dist/crdt/doc/meta.js +10 -21
- package/dist/crdt/doc/moves.d.ts +4 -4
- package/dist/crdt/doc/moves.js +5 -5
- package/dist/crdt/doc/plan.d.ts +2 -0
- package/dist/crdt/doc/plan.js +2 -0
- package/dist/crdt/doc/reads.d.ts +1 -5
- package/dist/crdt/doc/reads.js +38 -49
- package/dist/crdt/doc/seed.d.ts +15 -6
- package/dist/crdt/doc/seed.js +27 -26
- package/dist/crdt/doc/split-merge.d.ts +1 -3
- package/dist/crdt/doc/split-merge.js +4 -5
- package/dist/crdt/doc/steps.d.ts +3 -7
- package/dist/crdt/doc/steps.js +20 -9
- package/dist/crdt/doc/types.d.ts +0 -10
- package/dist/crdt/document.d.ts +24 -115
- package/dist/crdt/document.js +207 -305
- package/dist/crdt/edytor-doc.d.ts +671 -90
- package/dist/crdt/edytor-doc.js +73 -149
- package/dist/crdt/engine-api.d.ts +15 -47
- package/dist/crdt/engine.d.ts +7 -17
- package/dist/crdt/engine.js +9 -19
- package/dist/crdt/flow.d.ts +9 -37
- package/dist/crdt/flow.js +260 -238
- package/dist/crdt/index.d.ts +26 -85
- package/dist/crdt/index.js +21 -47
- package/dist/crdt/operations.d.ts +24 -33
- package/dist/crdt/operations.js +7 -17
- package/dist/crdt/placement/display.d.ts +0 -2
- package/dist/crdt/placement/display.js +0 -15
- package/dist/crdt/placement/model.d.ts +160 -12
- package/dist/crdt/placement/model.js +413 -414
- package/dist/crdt/placement/rank.d.ts +1 -5
- package/dist/crdt/placement/rank.js +28 -38
- package/dist/crdt/protocol.d.ts +4 -5
- package/dist/crdt/protocol.js +3 -4
- package/dist/crdt/protocols/awareness.d.ts +3 -7
- package/dist/crdt/protocols/awareness.js +17 -33
- package/dist/crdt/protocols/envelope.d.ts +9 -29
- package/dist/crdt/protocols/envelope.js +8 -25
- package/dist/crdt/protocols/sync.d.ts +15 -30
- package/dist/crdt/protocols/sync.js +27 -89
- package/dist/crdt/providers/bound.d.ts +3 -1
- package/dist/crdt/providers/bound.js +3 -1
- package/dist/crdt/providers/container.d.ts +11 -17
- package/dist/crdt/providers/container.js +39 -27
- package/dist/crdt/providers/index.d.ts +34 -6
- package/dist/crdt/providers/index.js +106 -111
- package/dist/crdt/providers/indexeddb.d.ts +2 -12
- package/dist/crdt/providers/indexeddb.js +29 -90
- package/dist/crdt/providers/room.d.ts +58 -2
- package/dist/crdt/providers/room.js +38 -21
- package/dist/crdt/providers/websocket.d.ts +1 -1
- package/dist/crdt/providers/websocket.js +3 -4
- package/dist/crdt/purge.d.ts +2 -2
- package/dist/crdt/purge.js +69 -6
- package/dist/crdt/rangeDelete.d.ts +4 -42
- package/dist/crdt/rangeDelete.js +7 -6
- package/dist/crdt/restore.d.ts +21 -1
- package/dist/crdt/restore.js +54 -59
- package/dist/crdt/schema.d.ts +2 -2
- package/dist/crdt/schema.js +4 -5
- package/dist/crdt/semantics.d.ts +10 -15
- package/dist/crdt/semantics.js +22 -21
- package/dist/crdt/storage.d.ts +7 -13
- package/dist/crdt/storage.js +10 -24
- package/dist/crdt/structs.d.ts +42 -47
- package/dist/crdt/structs.js +76 -67
- package/dist/crdt/tables.d.ts +9 -0
- package/dist/crdt/tables.js +20 -0
- package/dist/crdt/text/delete-index.d.ts +0 -13
- package/dist/crdt/text/delete-index.js +7 -14
- package/dist/crdt/text/deletes.d.ts +9 -22
- package/dist/crdt/text/deletes.js +11 -74
- package/dist/crdt/text/index/cache.js +3 -3
- package/dist/crdt/text/index/fold.d.ts +1 -2
- package/dist/crdt/text/index/fold.js +15 -10
- package/dist/crdt/text/index/layout.d.ts +0 -10
- package/dist/crdt/text/index/layout.js +6 -20
- package/dist/crdt/text/index/records.js +2 -1
- package/dist/crdt/text/index/report.js +5 -6
- package/dist/crdt/text/index/shared.d.ts +11 -4
- package/dist/crdt/text/index/shared.js +8 -7
- package/dist/crdt/text/index/state.d.ts +3 -33
- package/dist/crdt/text/index/state.js +2 -4
- package/dist/crdt/text/model.d.ts +82 -2
- package/dist/crdt/text/model.js +344 -354
- package/dist/crdt/text/rows.d.ts +0 -2
- package/dist/crdt/text/rows.js +0 -11
- package/dist/crdt/text/runs.d.ts +6 -3
- package/dist/crdt/text/runs.js +132 -137
- package/dist/crdt/text/streams.d.ts +1 -1
- package/dist/crdt/text/streams.js +1 -1
- package/dist/crdt/vendor/yjs/API-NOTES.md +25 -8
- package/dist/crdt/vendor/yjs/UPSTREAM.md +288 -8
- package/dist/crdt/vendor/yjs/dts/global.d.ts +5 -26
- package/dist/crdt/vendor/yjs/dts/index.d.ts +10 -15
- package/dist/crdt/vendor/yjs/dts/structs/AbstractStruct.d.ts +2 -2
- package/dist/crdt/vendor/yjs/dts/structs/Item.d.ts +14 -219
- package/dist/crdt/vendor/yjs/dts/structs/Skip.d.ts +2 -2
- package/dist/crdt/vendor/yjs/dts/utils/BlockSet.d.ts +2 -2
- package/dist/crdt/vendor/yjs/dts/utils/Doc.d.ts +12 -123
- package/dist/crdt/vendor/yjs/dts/utils/ID.d.ts +1 -1
- package/dist/crdt/vendor/yjs/dts/utils/RangeCursor.d.ts +37 -58
- package/dist/crdt/vendor/yjs/dts/utils/Transaction.d.ts +3 -78
- package/dist/crdt/vendor/yjs/dts/utils/UpdateDecoder.d.ts +2 -81
- package/dist/crdt/vendor/yjs/dts/utils/UpdateEncoder.d.ts +2 -74
- package/dist/crdt/vendor/yjs/dts/utils/encoding-helpers.d.ts +4 -4
- package/dist/crdt/vendor/yjs/dts/utils/encoding.d.ts +6 -18
- package/dist/crdt/vendor/yjs/dts/utils/ids.d.ts +12 -237
- package/dist/crdt/vendor/yjs/dts/utils/positions.d.ts +5 -0
- package/dist/crdt/vendor/yjs/dts/utils/redo.d.ts +3 -0
- package/dist/crdt/vendor/yjs/dts/utils/transaction-helpers.d.ts +1 -3
- package/dist/crdt/vendor/yjs/dts/utils/updates.d.ts +6 -23
- package/dist/crdt/vendor/yjs/dts/ynode.d.ts +73 -498
- package/dist/crdt/vendor/yjs/global.d.ts +5 -26
- package/dist/crdt/vendor/yjs/src/index.js +11 -25
- package/dist/crdt/vendor/yjs/src/structs/AbstractStruct.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/GC.js +1 -1
- package/dist/crdt/vendor/yjs/src/structs/Item.js +10 -372
- package/dist/crdt/vendor/yjs/src/structs/Skip.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/BlockSet.js +2 -2
- package/dist/crdt/vendor/yjs/src/utils/Doc.js +14 -170
- package/dist/crdt/vendor/yjs/src/utils/ID.js +1 -1
- package/dist/crdt/vendor/yjs/src/utils/RangeCursor.js +61 -128
- package/dist/crdt/vendor/yjs/src/utils/StructStore.js +0 -22
- package/dist/crdt/vendor/yjs/src/utils/Transaction.js +17 -282
- package/dist/crdt/vendor/yjs/src/utils/UpdateDecoder.js +3 -130
- package/dist/crdt/vendor/yjs/src/utils/UpdateEncoder.js +2 -132
- package/dist/crdt/vendor/yjs/src/utils/encoding-helpers.js +4 -4
- package/dist/crdt/vendor/yjs/src/utils/encoding.js +49 -146
- package/dist/crdt/vendor/yjs/src/utils/ids.js +23 -900
- package/dist/crdt/vendor/yjs/src/utils/positions.js +115 -0
- package/dist/crdt/vendor/yjs/src/utils/redo.js +143 -0
- package/dist/crdt/vendor/yjs/src/utils/transaction-helpers.js +10 -127
- package/dist/crdt/vendor/yjs/src/utils/updates.js +7 -50
- package/dist/crdt/vendor/yjs/src/ynode.js +261 -2226
- package/dist/{plugins/blockHandles → dnd}/autoScroll.d.ts +2 -1
- package/dist/{plugins/blockHandles → dnd}/autoScroll.js +3 -3
- package/dist/dnd/pragmatic.d.ts +4 -22
- package/dist/dnd/pragmatic.js +7 -26
- package/dist/dnd/track.d.ts +14 -0
- package/dist/dnd/track.js +55 -0
- package/dist/dnd/vendor/pragmatic/UPSTREAM.md +103 -0
- package/dist/dnd/vendor/pragmatic/bind-event-listener/LICENSE +21 -0
- package/dist/dnd/vendor/pragmatic/bind-event-listener/bind-all.js +46 -0
- package/dist/dnd/vendor/pragmatic/bind-event-listener/bind.js +7 -0
- package/dist/dnd/vendor/pragmatic/bind-event-listener/index.js +2 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/LICENSE.md +11 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter-native-data-key.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.d.ts +57 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.js +306 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/entry-point/element/adapter.d.ts +8 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/entry-point/element/adapter.js +10 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/entry-point/element/set-custom-native-drag-preview.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/entry-point/element/set-custom-native-drag-preview.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/entry-point/types.d.ts +1 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/get-element-from-point-without-honey-pot.js +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/honey-pot-data-attribute.js +3 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/is-honey-pot-element.js +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/make-honey-pot-fix.js +333 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/internal-types.d.ts +350 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/ledger/dispatch-consumer-event.js +128 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/ledger/lifecycle-manager.js +340 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/ledger/usage-ledger.js +32 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-adapter.js +59 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-drop-target.js +288 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-monitor.js +85 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/combine.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.d.ts +36 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js +153 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/types.d.ts +16 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/once.js +13 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/add-attribute.js +7 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/android.js +10 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/count-events-for-safari.js +104 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-from-another-window.js +16 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-leaving-window-in-safari.js +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-leaving-window.js +55 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/detect-broken-drag.js +48 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/get-input.js +14 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/is-firefox.js +13 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/is-safari.js +19 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/max-z-index.js +3 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/media-types/text-media-type.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/media-types/url-media-type.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/popover-reset-styles.js +40 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/supports-popover.js +16 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/LICENSE.md +11 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/element.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/element.js +8 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/internal-types.d.ts +50 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/data-attributes.js +9 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/get-scroll-by.js +117 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/make-api.js +90 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/try-scroll.js +183 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/axis.js +22 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/can-scroll-on-edge.js +17 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/configuration.js +49 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/edges.js +9 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/engagement-history.js +43 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-over-element-hitbox.js +50 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-percentage-in-range.js +19 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-scroll-change.js +106 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/is-axis-allowed.js +3 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/is-within.js +11 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/scheduler.js +104 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/side.js +6 -0
- package/dist/dnd/vendor/pragmatic/raf-schd/LICENSE +21 -0
- package/dist/dnd/vendor/pragmatic/raf-schd/raf-schd.esm.js +34 -0
- package/dist/edytor.svelte.d.ts +34 -64
- package/dist/edytor.svelte.js +118 -180
- package/dist/events/beforeInputCommands.d.ts +6 -11
- package/dist/events/beforeInputCommands.js +189 -184
- package/dist/events/beforeInputDeleteCommands.d.ts +20 -1
- package/dist/events/beforeInputDeleteCommands.js +153 -204
- package/dist/events/domTextOffset.d.ts +9 -9
- package/dist/events/domTextOffset.js +20 -48
- package/dist/events/events.utils.d.ts +0 -2
- package/dist/events/events.utils.js +0 -4
- package/dist/events/nativeInteractiveControl.d.ts +13 -13
- package/dist/events/nativeInteractiveControl.js +28 -56
- package/dist/events/onBeforeInput.d.ts +6 -1
- package/dist/events/onBeforeInput.js +207 -331
- package/dist/events/onCopy.js +1 -5
- package/dist/events/onDrop.d.ts +7 -1
- package/dist/events/onDrop.js +33 -72
- package/dist/events/onFocus.d.ts +9 -4
- package/dist/events/onFocus.js +92 -72
- package/dist/events/onInput.d.ts +8 -4
- package/dist/events/onInput.js +34 -147
- package/dist/events/onKeyDown.js +55 -122
- package/dist/events/onPaste.d.ts +13 -1
- package/dist/events/onPaste.js +46 -76
- package/dist/index.d.ts +10 -13
- package/dist/index.js +3 -3
- package/dist/kinds.d.ts +4 -6
- package/dist/kinds.js +28 -27
- package/dist/labels.d.ts +91 -314
- package/dist/labels.js +180 -396
- package/dist/plugins/Chrome.svelte +29 -0
- package/dist/plugins/Chrome.svelte.d.ts +32 -0
- package/dist/plugins/Control.svelte +23 -0
- package/dist/plugins/Control.svelte.d.ts +30 -0
- package/dist/plugins/anchored.svelte.d.ts +47 -0
- package/dist/plugins/anchored.svelte.js +73 -0
- package/dist/plugins/arrowMove/arrowMove.d.ts +2 -1
- package/dist/plugins/arrowMove/arrowMove.js +9 -5
- package/dist/plugins/bands.svelte.d.ts +57 -0
- package/dist/plugins/bands.svelte.js +95 -0
- package/dist/plugins/blockHandles/BlockHandle.svelte +14 -151
- package/dist/plugins/blockHandles/BlockHandle.svelte.d.ts +2 -2
- package/dist/plugins/blockHandles/BlockHandleController.svelte.d.ts +63 -97
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +247 -268
- package/dist/plugins/blockHandles/BlockHandles.svelte +46 -29
- package/dist/plugins/blockHandles/BlockHandles.svelte.d.ts +2 -2
- package/dist/plugins/blockHandles/blockHandlesPlugin.d.ts +29 -23
- package/dist/plugins/blockHandles/blockHandlesPlugin.js +130 -162
- package/dist/plugins/blockHandles/dragPreview.d.ts +8 -6
- package/dist/plugins/blockHandles/dragPreview.js +20 -17
- package/dist/plugins/blockHandles/dropIndicator.js +52 -60
- package/dist/plugins/blockHandles/geometry.d.ts +46 -1
- package/dist/plugins/blockHandles/geometry.js +51 -4
- package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +59 -28
- package/dist/plugins/blockMenu/BlockMenuController.svelte.js +180 -105
- package/dist/plugins/blockMenu/blockMenuPlugin.d.ts +1 -3
- package/dist/plugins/blockMenu/blockMenuPlugin.js +12 -29
- package/dist/plugins/brand.d.ts +8 -0
- package/dist/plugins/brand.js +8 -0
- package/dist/plugins/chrome.d.ts +79 -0
- package/dist/plugins/chrome.js +87 -0
- package/dist/plugins/code/CodeHeader.svelte +12 -58
- package/dist/plugins/code/CodeHeader.svelte.d.ts +1 -0
- package/dist/plugins/code/CodePlugin.svelte +75 -56
- package/dist/plugins/code/CodePlugin.svelte.d.ts +0 -2
- package/dist/plugins/code/LanguageMenu.svelte +25 -0
- package/dist/plugins/code/LanguageMenu.svelte.d.ts +9 -0
- package/dist/plugins/code/code.css +5 -100
- package/dist/plugins/code/header.svelte.d.ts +47 -0
- package/dist/plugins/code/header.svelte.js +141 -0
- package/dist/plugins/code/languageMenu.svelte.d.ts +84 -0
- package/dist/plugins/code/languageMenu.svelte.js +199 -0
- package/dist/plugins/code/languages.d.ts +21 -10
- package/dist/plugins/code/languages.js +39 -126
- package/dist/plugins/columns/ColumnResize.svelte +13 -25
- package/dist/plugins/columns/ColumnResize.svelte.d.ts +1 -1
- package/dist/plugins/columns/ColumnsPlugin.svelte +46 -78
- package/dist/plugins/columns/ColumnsPlugin.svelte.d.ts +0 -12
- package/dist/plugins/columns/columns.css +7 -26
- package/dist/plugins/columns/resize.svelte.d.ts +14 -51
- package/dist/plugins/columns/resize.svelte.js +70 -164
- package/dist/plugins/comments/CommentsController.svelte.d.ts +1 -4
- package/dist/plugins/comments/CommentsController.svelte.js +14 -25
- package/dist/plugins/comments/CommentsSidebar.svelte +82 -430
- package/dist/plugins/comments/CommentsSidebar.svelte.d.ts +6 -7
- package/dist/plugins/comments/commentsPlugin.d.ts +31 -10
- package/dist/plugins/comments/commentsPlugin.js +12 -10
- package/dist/plugins/equation/EquationEditor.svelte +18 -129
- package/dist/plugins/equation/EquationEditor.svelte.d.ts +2 -1
- package/dist/plugins/equation/EquationPlugin.svelte +123 -168
- package/dist/plugins/equation/EquationPlugin.svelte.d.ts +5 -3
- package/dist/plugins/equation/EquationView.svelte +20 -11
- package/dist/plugins/equation/equation.svelte.d.ts +74 -36
- package/dist/plugins/equation/equation.svelte.js +74 -66
- package/dist/plugins/equation/importUrl.d.ts +6 -0
- package/dist/plugins/equation/importUrl.js +6 -0
- package/dist/plugins/equation/katex.d.ts +8 -0
- package/dist/plugins/equation/katex.js +77 -0
- package/dist/plugins/find/FindController.svelte.d.ts +8 -13
- package/dist/plugins/find/FindController.svelte.js +34 -55
- package/dist/plugins/find/findPlugin.d.ts +4 -8
- package/dist/plugins/find/findPlugin.js +29 -9
- package/dist/plugins/image/ImageChrome.svelte +5 -151
- package/dist/plugins/image/ImagePlugin.svelte +49 -117
- package/dist/plugins/image/ImagePlugin.svelte.d.ts +1 -24
- package/dist/plugins/image/ImageToolbarHost.svelte +37 -0
- package/dist/plugins/image/ImageToolbarHost.svelte.d.ts +9 -0
- package/dist/plugins/image/controls.svelte.d.ts +6 -57
- package/dist/plugins/image/controls.svelte.js +51 -174
- package/dist/plugins/image/image.d.ts +0 -5
- package/dist/plugins/image/image.js +0 -3
- package/dist/plugins/image/toolbar.svelte.d.ts +69 -0
- package/dist/plugins/image/toolbar.svelte.js +145 -0
- package/dist/plugins/index.d.ts +39 -24
- package/dist/plugins/index.js +30 -22
- package/dist/plugins/list.svelte.d.ts +128 -0
- package/dist/plugins/list.svelte.js +217 -0
- package/dist/plugins/markdownShortcuts.d.ts +1 -1
- package/dist/plugins/markdownShortcuts.js +6 -5
- package/dist/plugins/marquee/MarqueeBox.svelte +15 -0
- package/dist/plugins/marquee/MarqueeBox.svelte.d.ts +9 -0
- package/dist/plugins/marquee/MarqueeController.svelte.d.ts +46 -0
- package/dist/plugins/marquee/MarqueeController.svelte.js +243 -0
- package/dist/plugins/marquee/marquee.d.ts +33 -0
- package/dist/plugins/marquee/marquee.js +89 -0
- package/dist/plugins/marquee/marqueePlugin.d.ts +32 -0
- package/dist/plugins/marquee/marqueePlugin.js +68 -0
- package/dist/plugins/media/AudioPlugin.svelte +3 -88
- package/dist/plugins/media/AudioPlugin.svelte.d.ts +1 -4
- package/dist/plugins/media/BookmarkPlugin.svelte +124 -112
- package/dist/plugins/media/BookmarkPlugin.svelte.d.ts +20 -7
- package/dist/plugins/media/EmbedPlugin.svelte +93 -77
- package/dist/plugins/media/EmbedPlugin.svelte.d.ts +21 -9
- package/dist/plugins/media/EmptySlot.svelte +44 -0
- package/dist/plugins/media/EmptySlot.svelte.d.ts +25 -0
- package/dist/plugins/media/FilePlugin.svelte +35 -111
- package/dist/plugins/media/FilePlugin.svelte.d.ts +12 -4
- package/dist/plugins/media/MediaEmpty.svelte +55 -112
- package/dist/plugins/media/MediaEmpty.svelte.d.ts +26 -10
- package/dist/plugins/media/SourceBlock.svelte +164 -0
- package/dist/plugins/media/SourceBlock.svelte.d.ts +33 -0
- package/dist/plugins/media/VideoPlugin.svelte +4 -97
- package/dist/plugins/media/VideoPlugin.svelte.d.ts +2 -5
- package/dist/plugins/media/empty.svelte.d.ts +69 -0
- package/dist/plugins/media/empty.svelte.js +111 -0
- package/dist/plugins/media/media.d.ts +93 -30
- package/dist/plugins/media/media.js +79 -86
- package/dist/plugins/media/urlPaste.svelte.d.ts +9 -32
- package/dist/plugins/media/urlPaste.svelte.js +46 -75
- package/dist/plugins/mention/MentionPlugin.svelte +47 -124
- package/dist/plugins/mention/MentionPlugin.svelte.d.ts +11 -23
- package/dist/plugins/page/PageLink.svelte +54 -38
- package/dist/plugins/page/PagePlugin.svelte +20 -18
- package/dist/plugins/page/PagePlugin.svelte.d.ts +2 -4
- package/dist/plugins/page/page.d.ts +35 -9
- package/dist/plugins/page/page.js +3 -4
- package/dist/plugins/pageLink/PageLinkPlugin.svelte +52 -108
- package/dist/plugins/pageLink/PageLinkPlugin.svelte.d.ts +11 -23
- package/dist/plugins/planned.d.ts +10 -0
- package/dist/plugins/planned.js +12 -0
- package/dist/plugins/richtext/CalloutIcon.svelte +25 -0
- package/dist/plugins/richtext/CalloutIcon.svelte.d.ts +9 -0
- package/dist/plugins/richtext/CalloutIconPicker.svelte +26 -0
- package/dist/plugins/richtext/CalloutIconPicker.svelte.d.ts +9 -0
- package/dist/plugins/richtext/EmptyBody.svelte +25 -0
- package/dist/plugins/richtext/EmptyBody.svelte.d.ts +8 -0
- package/dist/plugins/richtext/RichTextControl.svelte +39 -0
- package/dist/plugins/richtext/RichTextControl.svelte.d.ts +8 -0
- package/dist/plugins/richtext/RichTextPlugin.svelte +272 -148
- package/dist/plugins/richtext/RichTextPlugin.svelte.d.ts +37 -18
- package/dist/plugins/richtext/TodoCheckbox.svelte +10 -21
- package/dist/plugins/richtext/TodoCheckbox.svelte.d.ts +0 -2
- package/dist/plugins/richtext/body.d.ts +9 -0
- package/dist/plugins/richtext/body.js +16 -0
- package/dist/plugins/richtext/calloutIcons.svelte.d.ts +97 -0
- package/dist/plugins/richtext/calloutIcons.svelte.js +214 -0
- package/dist/plugins/richtext/callouts.d.ts +15 -0
- package/dist/plugins/richtext/callouts.js +23 -0
- package/dist/plugins/richtext/controls.svelte.d.ts +110 -0
- package/dist/plugins/richtext/controls.svelte.js +126 -0
- package/dist/plugins/richtext/richTextOperations.js +37 -28
- package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +18 -11
- package/dist/plugins/slashMenu/SlashMenuController.svelte.js +93 -35
- package/dist/plugins/slashMenu/slashMenuPlugin.d.ts +11 -24
- package/dist/plugins/slashMenu/slashMenuPlugin.js +27 -68
- package/dist/plugins/suggestions/SuggestionBars.svelte +32 -84
- package/dist/plugins/suggestions/SuggestionBars.svelte.d.ts +9 -3
- package/dist/plugins/suggestions/suggestionsPlugin.d.ts +10 -16
- package/dist/plugins/suggestions/suggestionsPlugin.js +9 -24
- package/dist/plugins/table/TableChrome.svelte +96 -127
- package/dist/plugins/table/TableChrome.svelte.d.ts +5 -1
- package/dist/plugins/table/TablePlugin.svelte +116 -171
- package/dist/plugins/table/TablePlugin.svelte.d.ts +0 -4
- package/dist/plugins/table/chrome.svelte.d.ts +110 -117
- package/dist/plugins/table/chrome.svelte.js +643 -289
- package/dist/plugins/table/ops.d.ts +47 -0
- package/dist/plugins/table/ops.js +233 -0
- package/dist/plugins/table/table.css +55 -134
- package/dist/plugins/table/table.d.ts +26 -7
- package/dist/plugins/table/table.js +23 -49
- package/dist/plugins/toc/TableOfContents.svelte +32 -38
- package/dist/plugins/toc/TableOfContents.svelte.d.ts +1 -5
- package/dist/plugins/toc/TocPlugin.svelte +22 -22
- package/dist/plugins/toc/TocPlugin.svelte.d.ts +4 -6
- package/dist/plugins/toc/toc.d.ts +41 -10
- package/dist/plugins/toc/toc.js +2 -3
- package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +29 -66
- package/dist/plugins/toolbar/ToolbarController.svelte.js +151 -76
- package/dist/plugins/toolbar/toolbarPlugin.d.ts +14 -13
- package/dist/plugins/toolbar/toolbarPlugin.js +28 -43
- package/dist/plugins/triggers/TriggerController.svelte.d.ts +6 -45
- package/dist/plugins/triggers/TriggerController.svelte.js +51 -96
- package/dist/plugins/triggers/place.d.ts +0 -9
- package/dist/plugins/triggers/place.js +1 -17
- package/dist/plugins/triggers/triggers.d.ts +31 -9
- package/dist/plugins/triggers/triggers.js +82 -62
- package/dist/plugins/uploads.svelte.d.ts +38 -20
- package/dist/plugins/uploads.svelte.js +39 -32
- package/dist/plugins.d.ts +167 -60
- package/dist/selection/domSelection.d.ts +13 -4
- package/dist/selection/domSelection.js +110 -183
- package/dist/selection/pointer.svelte.d.ts +12 -10
- package/dist/selection/pointer.svelte.js +154 -71
- package/dist/selection/replaceSelection.d.ts +21 -22
- package/dist/selection/replaceSelection.js +60 -69
- package/dist/selection/selection.svelte.d.ts +5 -73
- package/dist/selection/selection.svelte.js +263 -542
- package/dist/selection/selection.utils.d.ts +34 -11
- package/dist/selection/selection.utils.js +128 -247
- package/dist/selection/visibility.d.ts +11 -8
- package/dist/selection/visibility.js +23 -8
- package/dist/session/announcer.svelte.d.ts +2 -1
- package/dist/session/announcer.svelte.js +7 -3
- package/dist/session/attempt.d.ts +33 -39
- package/dist/session/attempt.js +61 -53
- package/dist/session/bindings.js +108 -139
- package/dist/session/commands.d.ts +47 -22
- package/dist/session/commands.js +206 -208
- package/dist/session/composition.svelte.d.ts +2 -4
- package/dist/session/composition.svelte.js +55 -65
- package/dist/session/editing/text.d.ts +6 -0
- package/dist/session/editing/text.js +17 -3
- package/dist/session/handles.d.ts +10 -1
- package/dist/session/handles.js +28 -0
- package/dist/session/history.d.ts +0 -4
- package/dist/session/history.js +15 -21
- package/dist/session/inputRules.js +75 -84
- package/dist/session/keymap.d.ts +1 -6
- package/dist/session/keymap.js +17 -20
- package/dist/session/moves.d.ts +10 -10
- package/dist/session/moves.js +31 -59
- package/dist/session/navigation.d.ts +18 -3
- package/dist/session/navigation.js +194 -153
- package/dist/session/ports.d.ts +7 -0
- package/dist/session/selection.d.ts +4 -3
- package/dist/session/selection.js +8 -0
- package/dist/session/suggestions.svelte.d.ts +1 -1
- package/dist/session/suggestions.svelte.js +64 -47
- package/dist/session/virtual.d.ts +3 -3
- package/dist/session/virtual.js +18 -40
- package/dist/surface/attributes.d.ts +1 -0
- package/dist/surface/attributes.js +3 -26
- package/dist/surface/cells.d.ts +5 -0
- package/dist/surface/cells.js +5 -0
- package/dist/surface/lines.d.ts +32 -0
- package/dist/surface/lines.js +43 -0
- package/dist/surface/observer.svelte.d.ts +3 -20
- package/dist/surface/observer.svelte.js +73 -77
- package/dist/surface/overlay.d.ts +48 -6
- package/dist/surface/overlay.js +133 -23
- package/dist/surface/pin.svelte.d.ts +5 -0
- package/dist/surface/pin.svelte.js +5 -0
- package/dist/surface/projector.svelte.d.ts +4 -6
- package/dist/surface/projector.svelte.js +42 -69
- package/dist/text/text.svelte.d.ts +14 -40
- package/dist/text/text.svelte.js +35 -51
- package/dist/text/wordBoundary.d.ts +19 -0
- package/dist/text/wordBoundary.js +29 -44
- package/dist/themes/notion.css +315 -126
- package/dist/utils/json.d.ts +3 -1
- package/dist/utils/json.js +52 -51
- package/dist/utils/listen.d.ts +8 -0
- package/dist/utils/listen.js +19 -0
- package/package.json +4 -5
- package/dist/block/block.utils.d.ts +0 -340
- package/dist/block/block.utils.js +0 -356
- package/dist/collaboration/history/HistoryPanel.svelte +0 -525
- package/dist/collaboration/history/HistoryPanel.svelte.d.ts +0 -7
- package/dist/collaboration/history/panel.d.ts +0 -43
- package/dist/collaboration/history/panel.js +0 -3
- package/dist/components/SuggestionRanges.svelte +0 -58
- package/dist/components/SuggestionRanges.svelte.d.ts +0 -7
- package/dist/crdt/doc/table.d.ts +0 -21
- package/dist/crdt/doc/table.js +0 -288
- package/dist/crdt/migration/generation.d.ts +0 -58
- package/dist/crdt/migration/generation.js +0 -70
- package/dist/crdt/migration/legacy-schema.d.ts +0 -61
- package/dist/crdt/migration/legacy-schema.js +0 -151
- package/dist/crdt/migration/migrate.d.ts +0 -56
- package/dist/crdt/migration/migrate.js +0 -227
- package/dist/crdt/nodes.d.ts +0 -121
- package/dist/crdt/nodes.js +0 -56
- package/dist/crdt/text/index/checks.d.ts +0 -15
- package/dist/crdt/text/index/checks.js +0 -156
- package/dist/crdt/vendor/yjs/dts/utils/EventHandler.d.ts +0 -17
- package/dist/crdt/vendor/yjs/dts/utils/RelativePosition.d.ts +0 -80
- package/dist/crdt/vendor/yjs/dts/utils/UndoManager.d.ts +0 -205
- package/dist/crdt/vendor/yjs/dts/utils/YEvent.d.ts +0 -93
- package/dist/crdt/vendor/yjs/dts/utils/meta.d.ts +0 -25
- package/dist/crdt/vendor/yjs/dts/utils/renderer-helpers.d.ts +0 -98
- package/dist/crdt/vendor/yjs/dts/utils/schemas.d.ts +0 -2
- package/dist/crdt/vendor/yjs/src/utils/EventHandler.js +0 -75
- package/dist/crdt/vendor/yjs/src/utils/RelativePosition.js +0 -301
- package/dist/crdt/vendor/yjs/src/utils/UndoManager.js +0 -554
- package/dist/crdt/vendor/yjs/src/utils/YEvent.js +0 -158
- package/dist/crdt/vendor/yjs/src/utils/meta.js +0 -74
- package/dist/crdt/vendor/yjs/src/utils/renderer-helpers.js +0 -186
- package/dist/crdt/vendor/yjs/src/utils/schemas.js +0 -3
- package/dist/edytor.utils.d.ts +0 -50
- package/dist/edytor.utils.js +0 -67
- package/dist/plugins/blockMenu/BlockMenu.svelte +0 -447
- package/dist/plugins/blockMenu/BlockMenu.svelte.d.ts +0 -9
- package/dist/plugins/find/FindBar.svelte +0 -170
- package/dist/plugins/find/FindBar.svelte.d.ts +0 -9
- package/dist/plugins/find/FindHighlights.svelte +0 -75
- package/dist/plugins/find/FindHighlights.svelte.d.ts +0 -9
- package/dist/plugins/icons.d.ts +0 -2
- package/dist/plugins/icons.js +0 -63
- package/dist/plugins/image/ImageEmpty.svelte +0 -116
- package/dist/plugins/image/ImageEmpty.svelte.d.ts +0 -12
- package/dist/plugins/keepInView.d.ts +0 -4
- package/dist/plugins/keepInView.js +0 -6
- package/dist/plugins/media/UrlPasteMenu.svelte +0 -83
- package/dist/plugins/media/UrlPasteMenu.svelte.d.ts +0 -7
- package/dist/plugins/richtext/labels.d.ts +0 -5
- package/dist/plugins/richtext/labels.js +0 -3
- package/dist/plugins/slashMenu/SlashMenu.svelte +0 -360
- package/dist/plugins/slashMenu/SlashMenu.svelte.d.ts +0 -11
- package/dist/plugins/toolbar/LinkCard.svelte +0 -118
- package/dist/plugins/toolbar/LinkCard.svelte.d.ts +0 -7
- package/dist/plugins/toolbar/Toolbar.svelte +0 -400
- package/dist/plugins/toolbar/Toolbar.svelte.d.ts +0 -9
- package/dist/plugins/triggers/TriggerMenu.svelte +0 -144
- package/dist/plugins/triggers/TriggerMenu.svelte.d.ts +0 -7
- package/dist/session/props.d.ts +0 -28
- package/dist/session/props.js +0 -162
- package/dist/text/deltas.d.ts +0 -22
- package/dist/text/deltas.js +0 -46
- package/dist/text/text.utils.d.ts +0 -45
- package/dist/text/text.utils.js +0 -199
|
@@ -1,92 +1,30 @@
|
|
|
1
1
|
import * as binary from 'lib0-v14/binary'
|
|
2
|
-
import * as array from 'lib0-v14/array'
|
|
3
|
-
import * as delta from 'lib0-v14/delta'
|
|
4
2
|
import * as error from 'lib0-v14/error'
|
|
5
|
-
import * as iterator from 'lib0-v14/iterator'
|
|
6
|
-
import * as log from 'lib0-v14/logging'
|
|
7
3
|
import * as map from 'lib0-v14/map'
|
|
8
|
-
import * as set from 'lib0-v14/set'
|
|
9
4
|
import * as math from 'lib0-v14/math'
|
|
10
|
-
import * as object from 'lib0-v14/object'
|
|
11
|
-
import * as s from 'lib0-v14/schema'
|
|
12
|
-
import * as traits from 'lib0-v14/traits'
|
|
13
|
-
import { ObservableV2 } from 'lib0-v14/observable'
|
|
14
5
|
import {
|
|
15
6
|
Item,
|
|
16
7
|
ContentAny,
|
|
17
8
|
ContentBinary,
|
|
18
9
|
ContentDeleted,
|
|
19
|
-
ContentEmbed,
|
|
20
10
|
ContentFormat,
|
|
21
|
-
ContentJSON,
|
|
22
11
|
ContentString,
|
|
23
12
|
ContentType,
|
|
24
13
|
YXmlFragmentRefID,
|
|
25
14
|
YXmlElementRefID,
|
|
26
|
-
YXmlHookRefID
|
|
27
|
-
ContentDoc,
|
|
28
|
-
createContentDocFromDoc
|
|
15
|
+
YXmlHookRefID
|
|
29
16
|
} from './structs/Item.js'
|
|
30
|
-
import { AttributedContent, rendererContentLength, readItemPieces } from './utils/renderer-helpers.js'
|
|
31
|
-
import { removeEventHandlerListener, callEventHandlerListeners, addEventHandlerListener, createEventHandler } from './utils/EventHandler.js'
|
|
32
17
|
import { createID } from './utils/ID.js'
|
|
33
|
-
import {
|
|
34
|
-
import { getItemCleanStart, cleanupFormattingGap } from './utils/transaction-helpers.js'
|
|
18
|
+
import { getItemCleanStart, liveLength, updateCurrentFormats } from './utils/transaction-helpers.js'
|
|
35
19
|
import { transact } from './utils/Transaction.js'
|
|
36
|
-
import {
|
|
37
|
-
import { $doc } from './utils/schemas.js'
|
|
38
|
-
import { foldPaired, formatValue, pairedRole, markClass, MARK_START, MARK_END, SIDE_LEFT } from './utils/marks.js' // YP13
|
|
20
|
+
import { markClass, MARK_START, MARK_END, SIDE_LEFT } from './utils/marks.js' // YP13
|
|
39
21
|
|
|
40
22
|
/**
|
|
41
|
-
* @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt|YNode
|
|
23
|
+
* @typedef {Object<string,any>|Array<any>|number|null|string|Uint8Array|BigInt|YNode} YValue
|
|
42
24
|
*/
|
|
43
25
|
|
|
44
|
-
/**
|
|
45
|
-
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
|
|
46
|
-
*/
|
|
47
|
-
export const warnPrematureAccess = () => { log.warn('Invalid access: Add Yjs type to a document before reading data.') }
|
|
48
|
-
|
|
49
26
|
const maxSearchMarker = 80
|
|
50
27
|
|
|
51
|
-
/**
|
|
52
|
-
* @todo SHOULD NOT RETURN AN OBJECT!
|
|
53
|
-
* @param {Array<ContentAttribute<any>>?} attrs
|
|
54
|
-
* @param {boolean} deleted - whether the attributed item is deleted
|
|
55
|
-
* @return {Attribution|undefined} `undefined` when there is no attribution — under lib0's tri-state
|
|
56
|
-
* that means "skip / inherit the builder's attribution context" (NOT `null`, which would clear it).
|
|
57
|
-
*/
|
|
58
|
-
export const createAttributionFromAttributionItems = (attrs, deleted) => {
|
|
59
|
-
if (attrs == null) {
|
|
60
|
-
return undefined
|
|
61
|
-
}
|
|
62
|
-
/**
|
|
63
|
-
* @type {Attribution}
|
|
64
|
-
*/
|
|
65
|
-
const attribution = {}
|
|
66
|
-
if (deleted) {
|
|
67
|
-
attribution.delete = []
|
|
68
|
-
} else {
|
|
69
|
-
attribution.insert = []
|
|
70
|
-
}
|
|
71
|
-
attrs.forEach(attr => {
|
|
72
|
-
switch (attr.name) {
|
|
73
|
-
// eslint-disable-next-line no-fallthrough
|
|
74
|
-
case 'insert':
|
|
75
|
-
case 'delete': {
|
|
76
|
-
// needs to be non-ambiguous: don't add existing attr if it doesn't match the actual status
|
|
77
|
-
attribution[attr.name]?.push(attr.val)
|
|
78
|
-
break
|
|
79
|
-
}
|
|
80
|
-
default: {
|
|
81
|
-
if (attr.name[0] !== '_') {
|
|
82
|
-
/** @type {any} */ (attribution)[attr.name] = attr.val
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
return attribution
|
|
88
|
-
}
|
|
89
|
-
|
|
90
28
|
/**
|
|
91
29
|
* A unique timestamp that identifies each marker.
|
|
92
30
|
*
|
|
@@ -102,14 +40,12 @@ export class ItemTextListPosition {
|
|
|
102
40
|
* @param {Item|null} right
|
|
103
41
|
* @param {number} index
|
|
104
42
|
* @param {Map<string,any>} currentFormats
|
|
105
|
-
* @param {AbstractRenderer?} renderer
|
|
106
43
|
*/
|
|
107
|
-
constructor (left, right, index, currentFormats
|
|
44
|
+
constructor (left, right, index, currentFormats) {
|
|
108
45
|
this.left = left
|
|
109
46
|
this.right = right
|
|
110
47
|
this.index = index
|
|
111
48
|
this.currentFormats = currentFormats
|
|
112
|
-
this.renderer = renderer
|
|
113
49
|
}
|
|
114
50
|
|
|
115
51
|
/**
|
|
@@ -126,7 +62,7 @@ export class ItemTextListPosition {
|
|
|
126
62
|
}
|
|
127
63
|
break
|
|
128
64
|
default:
|
|
129
|
-
this.index +=
|
|
65
|
+
this.index += liveLength(this.right)
|
|
130
66
|
break
|
|
131
67
|
}
|
|
132
68
|
this.left = this.right
|
|
@@ -134,213 +70,42 @@ export class ItemTextListPosition {
|
|
|
134
70
|
}
|
|
135
71
|
|
|
136
72
|
/**
|
|
73
|
+
* Move past `length` live units, splitting the item the last one ends in,
|
|
74
|
+
* and past the deleted items around them (YP19: the positioning of every
|
|
75
|
+
* write; mark items are folded, never rewritten).
|
|
76
|
+
*
|
|
137
77
|
* @param {Transaction} transaction
|
|
138
|
-
* @param {YNode} parent
|
|
139
78
|
* @param {number} length
|
|
140
|
-
* @param {Object<string,any>} formats
|
|
141
|
-
*
|
|
142
|
-
* @function
|
|
143
79
|
*/
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
(
|
|
155
|
-
negatedFormats.size > 0 &&
|
|
156
|
-
((this.right.deleted && rendererContentLength(this.renderer, this.right) === 0) || this.right.content.constructor === ContentFormat)
|
|
157
|
-
)
|
|
158
|
-
)
|
|
159
|
-
) {
|
|
160
|
-
switch (this.right.content.constructor) {
|
|
161
|
-
case ContentFormat: {
|
|
162
|
-
if (!this.right.deleted && pairedRole(/** @type {ContentFormat} */ (this.right.content)) >= 0) {
|
|
163
|
-
// YP13: a paired mark is folded, never rewritten by a plain format
|
|
164
|
-
updateCurrentFormats(this.currentFormats, /** @type {ContentFormat} */ (this.right.content))
|
|
165
|
-
} else if (!this.right.deleted) {
|
|
166
|
-
const { key, value } = /** @type {ContentFormat} */ (this.right.content)
|
|
167
|
-
const attr = formats[key]
|
|
168
|
-
if (attr !== undefined) {
|
|
169
|
-
if (equalFormats(attr, value)) {
|
|
170
|
-
negatedFormats.delete(key)
|
|
171
|
-
} else {
|
|
172
|
-
if (length === 0) {
|
|
173
|
-
// no need to further extend negatedFormats
|
|
174
|
-
// eslint-disable-next-line no-labels
|
|
175
|
-
break iterationLoop
|
|
176
|
-
}
|
|
177
|
-
negatedFormats.set(key, value)
|
|
178
|
-
}
|
|
179
|
-
this.right.delete(transaction)
|
|
180
|
-
} else {
|
|
181
|
-
this.currentFormats.set(key, value)
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
break
|
|
185
|
-
}
|
|
186
|
-
default: {
|
|
187
|
-
const item = this.right
|
|
188
|
-
const rightLen = rendererContentLength(this.renderer, item)
|
|
189
|
-
if (length < rightLen) {
|
|
190
|
-
if (this.renderer !== null && this.renderer.hasItem(item)) {
|
|
191
|
-
/**
|
|
192
|
-
* @type {Array<AttributedContent<any>>}
|
|
193
|
-
*/
|
|
194
|
-
const contents = []
|
|
195
|
-
this.renderer.readContent(contents, item.id.client, item.id.clock, item.deleted, item.content, 0)
|
|
196
|
-
let i = 0
|
|
197
|
-
for (; i < contents.length && length > 0; i++) {
|
|
198
|
-
const c = contents[i]
|
|
199
|
-
if ((!c.deleted || c.attrs != null) && c.content.isCountable()) {
|
|
200
|
-
length -= c.content.getLength()
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
if (length < 0 || (length === 0 && i !== contents.length)) {
|
|
204
|
-
const c = contents[--i]
|
|
205
|
-
getItemCleanStart(transaction, createID(item.id.client, c.clock + c.content.getLength() + length))
|
|
206
|
-
}
|
|
207
|
-
} else {
|
|
208
|
-
// plain content: split directly at the offset
|
|
209
|
-
getItemCleanStart(transaction, createID(item.id.client, item.id.clock + length))
|
|
210
|
-
length = 0
|
|
211
|
-
}
|
|
212
|
-
} else {
|
|
213
|
-
length -= rightLen
|
|
214
|
-
}
|
|
215
|
-
break
|
|
216
|
-
}
|
|
80
|
+
advance (transaction, length) {
|
|
81
|
+
while (this.right !== null && this.right.deleted) this.forward()
|
|
82
|
+
while (this.right !== null && length > 0) {
|
|
83
|
+
const item = this.right
|
|
84
|
+
const len = liveLength(item)
|
|
85
|
+
if (length < len) {
|
|
86
|
+
getItemCleanStart(transaction, createID(item.id.client, item.id.clock + length))
|
|
87
|
+
length = 0
|
|
88
|
+
} else {
|
|
89
|
+
length -= len
|
|
217
90
|
}
|
|
218
91
|
this.forward()
|
|
219
92
|
}
|
|
220
93
|
if (length > 0) {
|
|
221
94
|
throw new Error('Exceeded content range')
|
|
222
95
|
}
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/**
|
|
228
|
-
* Negate applied formats
|
|
229
|
-
*
|
|
230
|
-
* @param {Transaction} transaction
|
|
231
|
-
* @param {YNode} parent
|
|
232
|
-
* @param {ItemTextListPosition} currPos
|
|
233
|
-
* @param {Map<string,any>} negatedFormats
|
|
234
|
-
*
|
|
235
|
-
* @private
|
|
236
|
-
* @function
|
|
237
|
-
*/
|
|
238
|
-
const insertNegatedFormats = (transaction, parent, currPos, negatedFormats) => {
|
|
239
|
-
// check if we really need to remove formats
|
|
240
|
-
while (
|
|
241
|
-
currPos.right !== null && (
|
|
242
|
-
(currPos.right.deleted && rendererContentLength(currPos.renderer, currPos.right) === 0) || (
|
|
243
|
-
currPos.right.content.constructor === ContentFormat &&
|
|
244
|
-
equalFormats(negatedFormats.get(/** @type {ContentFormat} */ (currPos.right.content).key), /** @type {ContentFormat} */ (currPos.right.content).value)
|
|
245
|
-
)
|
|
246
|
-
)
|
|
247
|
-
) {
|
|
248
|
-
if (!currPos.right.deleted) {
|
|
249
|
-
negatedFormats.delete(/** @type {ContentFormat} */ (currPos.right.content).key)
|
|
250
|
-
}
|
|
251
|
-
currPos.forward()
|
|
252
|
-
}
|
|
253
|
-
const doc = transaction.doc
|
|
254
|
-
const ownClientId = doc.clientID
|
|
255
|
-
negatedFormats.forEach((val, key) => {
|
|
256
|
-
const left = currPos.left
|
|
257
|
-
const right = currPos.right
|
|
258
|
-
const nextFormat = new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
|
|
259
|
-
nextFormat.integrate(transaction, 0)
|
|
260
|
-
currPos.right = nextFormat
|
|
261
|
-
currPos.forward()
|
|
262
|
-
updateMarkerFormats(parent, currPos, /** @type {ContentFormat} */ (nextFormat.content))
|
|
263
|
-
})
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* @param {Map<string,any>} currentFormats
|
|
268
|
-
* @param {ContentFormat} format
|
|
269
|
-
*
|
|
270
|
-
* @private
|
|
271
|
-
* @function
|
|
272
|
-
*/
|
|
273
|
-
const updateCurrentFormats = (currentFormats, format) => {
|
|
274
|
-
if (foldPaired(currentFormats, format)) return // YP13
|
|
275
|
-
const { key, value } = format
|
|
276
|
-
if (value === null) {
|
|
277
|
-
currentFormats.delete(key)
|
|
278
|
-
} else {
|
|
279
|
-
currentFormats.set(key, value)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* @param {ItemTextListPosition} currPos
|
|
285
|
-
* @param {Object<string,any>} formats
|
|
286
|
-
*
|
|
287
|
-
* @private
|
|
288
|
-
* @function
|
|
289
|
-
*/
|
|
290
|
-
const minimizeFormatChanges = (currPos, formats) => {
|
|
291
|
-
// go right while formats[right.key] === right.value (or right is deleted)
|
|
292
|
-
while (true) {
|
|
293
|
-
if (currPos.right === null) {
|
|
294
|
-
break
|
|
295
|
-
} else if (currPos.right.deleted ? (rendererContentLength(currPos.renderer, currPos.right) === 0) : (!currPos.right.deleted && currPos.right.content.constructor === ContentFormat && equalFormats(formats[(/** @type {ContentFormat} */ (currPos.right.content)).key] ?? null, /** @type {ContentFormat} */ (currPos.right.content).value))) {
|
|
296
|
-
//
|
|
297
|
-
} else {
|
|
298
|
-
break
|
|
299
|
-
}
|
|
300
|
-
currPos.forward()
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* @param {Transaction} transaction
|
|
306
|
-
* @param {YNode} parent
|
|
307
|
-
* @param {ItemTextListPosition} currPos
|
|
308
|
-
* @param {Object<string,any>} formats
|
|
309
|
-
* @return {Map<string,any>}
|
|
310
|
-
*
|
|
311
|
-
* @private
|
|
312
|
-
* @function
|
|
313
|
-
**/
|
|
314
|
-
const insertFormats = (transaction, parent, currPos, formats) => {
|
|
315
|
-
const doc = transaction.doc
|
|
316
|
-
const ownClientId = doc.clientID
|
|
317
|
-
const negatedFormats = new Map()
|
|
318
|
-
// insert format-start items
|
|
319
|
-
for (const key in formats) {
|
|
320
|
-
const val = formats[key]
|
|
321
|
-
const currentVal = currPos.currentFormats.get(key) ?? null
|
|
322
|
-
if (!equalFormats(currentVal, val)) {
|
|
323
|
-
// save negated format (set null if currentVal undefined)
|
|
324
|
-
negatedFormats.set(key, currentVal)
|
|
325
|
-
const { left, right } = currPos
|
|
326
|
-
currPos.right = new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentFormat(key, val))
|
|
327
|
-
currPos.right.integrate(transaction, 0)
|
|
328
|
-
currPos.forward()
|
|
329
|
-
updateMarkerFormats(parent, currPos, /** @type {ContentFormat} */ ((/** @type {Item} */ (currPos.left)).content))
|
|
330
|
-
}
|
|
96
|
+
while (this.right !== null && this.right.deleted) this.forward()
|
|
331
97
|
}
|
|
332
|
-
return negatedFormats
|
|
333
98
|
}
|
|
334
99
|
|
|
335
100
|
// YP13 begin (edytor fork: paired marks — see UPSTREAM.md YP13)
|
|
336
101
|
/**
|
|
337
|
-
* The contents an
|
|
102
|
+
* The contents an insert writes: a string, or an array split into its nodes and its runs of JSON values.
|
|
338
103
|
*
|
|
339
|
-
* @param {Array<any
|
|
104
|
+
* @param {Array<any>|string} insert
|
|
340
105
|
* @return {Array<import('./structs/Item.js').AbstractContent>}
|
|
341
106
|
*/
|
|
342
107
|
const contentsOf = insert => {
|
|
343
|
-
|
|
108
|
+
if (typeof insert === 'string') return [new ContentString(insert)]
|
|
344
109
|
/** @type {Array<import('./structs/Item.js').AbstractContent>} */
|
|
345
110
|
const out = []
|
|
346
111
|
for (let i = 0; i < insert.length;) {
|
|
@@ -348,12 +113,9 @@ const contentsOf = insert => {
|
|
|
348
113
|
if (first instanceof YNode) {
|
|
349
114
|
out.push(new ContentType(first))
|
|
350
115
|
i++
|
|
351
|
-
} else if ($doc.check(first)) {
|
|
352
|
-
out.push(createContentDocFromDoc(first))
|
|
353
|
-
i++
|
|
354
116
|
} else {
|
|
355
117
|
let j = i + 1
|
|
356
|
-
for (; j < insert.length && !(insert[j] instanceof YNode
|
|
118
|
+
for (; j < insert.length && !(insert[j] instanceof YNode); j++) { /* nop */ }
|
|
357
119
|
out.push(new ContentAny((i === 0 && j === insert.length) ? insert : insert.slice(i, j)))
|
|
358
120
|
i = j
|
|
359
121
|
}
|
|
@@ -494,77 +256,13 @@ const markHelper = (transaction, parent, index, length, mark, value, startSide,
|
|
|
494
256
|
}
|
|
495
257
|
// YP13 end
|
|
496
258
|
|
|
497
|
-
/**
|
|
498
|
-
* @param {Transaction} transaction
|
|
499
|
-
* @param {YNode} parent
|
|
500
|
-
* @param {ItemTextListPosition} currPos
|
|
501
|
-
* @param {import('./structs/Item.js').AbstractContent} content
|
|
502
|
-
* @param {Object<string,any>} formats
|
|
503
|
-
*
|
|
504
|
-
* @private
|
|
505
|
-
* @function
|
|
506
|
-
**/
|
|
507
|
-
export const insertContent = (transaction, parent, currPos, content, formats) => {
|
|
508
|
-
currPos.currentFormats.forEach((_val, key) => {
|
|
509
|
-
if (formats[key] === undefined) {
|
|
510
|
-
formats[key] = null
|
|
511
|
-
}
|
|
512
|
-
})
|
|
513
|
-
const doc = transaction.doc
|
|
514
|
-
const ownClientId = doc.clientID
|
|
515
|
-
minimizeFormatChanges(currPos, formats)
|
|
516
|
-
const negatedFormats = insertFormats(transaction, parent, currPos, formats)
|
|
517
|
-
let { left, right, index } = currPos
|
|
518
|
-
if (parent._searchMarker) {
|
|
519
|
-
updateMarkerChanges(parent._searchMarker, currPos.index, content.getLength())
|
|
520
|
-
}
|
|
521
|
-
right = new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, content)
|
|
522
|
-
right.integrate(transaction, 0)
|
|
523
|
-
currPos.right = right
|
|
524
|
-
currPos.index = index
|
|
525
|
-
currPos.forward()
|
|
526
|
-
insertNegatedFormats(transaction, parent, currPos, negatedFormats)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
/**
|
|
530
|
-
* @param {Transaction} transaction
|
|
531
|
-
* @param {YNode} parent
|
|
532
|
-
* @param {ItemTextListPosition} currPos
|
|
533
|
-
* @param {Array<any>|string} insert
|
|
534
|
-
* @param {Object<string,any>} formats
|
|
535
|
-
*/
|
|
536
|
-
export const insertContentHelper = (transaction, parent, currPos, insert, formats) => {
|
|
537
|
-
if (s.$string.check(insert)) {
|
|
538
|
-
insertContent(transaction, parent, currPos, new ContentString(insert), formats)
|
|
539
|
-
} else {
|
|
540
|
-
insert = insert.map(ins => delta.$deltaAny.check(ins) ? YNode.from(ins) : ins)
|
|
541
|
-
for (let i = 0; i < insert.length;) {
|
|
542
|
-
const first = insert[i]
|
|
543
|
-
if (first instanceof YNode) {
|
|
544
|
-
insertContent(transaction, parent, currPos, new ContentType(first), formats)
|
|
545
|
-
i++
|
|
546
|
-
} else if ($doc.check(first)) {
|
|
547
|
-
insertContent(transaction, parent, currPos, createContentDocFromDoc(first), formats)
|
|
548
|
-
i++
|
|
549
|
-
} else {
|
|
550
|
-
// insert "any" content
|
|
551
|
-
// compute slice len
|
|
552
|
-
let j = i + 1
|
|
553
|
-
for (; j < insert.length && !(insert[j] instanceof YNode || $doc.check(insert[j])); j++) { /* nop */ }
|
|
554
|
-
insertContent(transaction, parent, currPos, new ContentAny((i === 0 && j === insert.length) ? insert : insert.slice(i, j)), formats)
|
|
555
|
-
i = j
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
}
|
|
560
|
-
|
|
561
259
|
// YP7 begin (edytor fork: insertAtGapEnd — see UPSTREAM.md YP7)
|
|
562
260
|
/**
|
|
563
261
|
* Insert `content` at the END of the gap at live index `index`: walk `index`
|
|
564
262
|
* countable live units, then pass every deleted item and every format item
|
|
565
263
|
* before the next live content item, and integrate `content` there. The
|
|
566
264
|
* content takes exactly the formats in effect at that point — no format item
|
|
567
|
-
* is added.
|
|
265
|
+
* is added. Indices are live-content space.
|
|
568
266
|
*
|
|
569
267
|
* @param {Transaction} transaction
|
|
570
268
|
* @param {YNode} parent
|
|
@@ -572,7 +270,7 @@ export const insertContentHelper = (transaction, parent, currPos, insert, format
|
|
|
572
270
|
* @param {import('./structs/Item.js').AbstractContent} content
|
|
573
271
|
*/
|
|
574
272
|
export const insertAtGapEndHelper = (transaction, parent, index, content) => {
|
|
575
|
-
const currPos = new ItemTextListPosition(null, parent._start, 0, new Map()
|
|
273
|
+
const currPos = new ItemTextListPosition(null, parent._start, 0, new Map())
|
|
576
274
|
let n = index
|
|
577
275
|
while (n > 0 && currPos.right !== null) {
|
|
578
276
|
const item = currPos.right
|
|
@@ -605,8 +303,6 @@ export const insertAtGapEndHelper = (transaction, parent, index, content) => {
|
|
|
605
303
|
*/
|
|
606
304
|
export const deleteText = (transaction, currPos, length) => {
|
|
607
305
|
const startLength = length
|
|
608
|
-
const startFormats = map.copy(currPos.currentFormats)
|
|
609
|
-
const start = currPos.right
|
|
610
306
|
while (length > 0 && currPos.right !== null) {
|
|
611
307
|
const item = currPos.right
|
|
612
308
|
if (!item.deleted && item.countable) {
|
|
@@ -615,52 +311,10 @@ export const deleteText = (transaction, currPos, length) => {
|
|
|
615
311
|
}
|
|
616
312
|
length -= item.length
|
|
617
313
|
item.delete(transaction)
|
|
618
|
-
} else if (currPos.renderer !== null && currPos.renderer.hasItem(item)) {
|
|
619
|
-
/**
|
|
620
|
-
* @type {Array<AttributedContent<any>>}
|
|
621
|
-
*/
|
|
622
|
-
const contents = []
|
|
623
|
-
currPos.renderer.readContent(contents, item.id.client, item.id.clock, true, item.content, 0)
|
|
624
|
-
let splitClock = -1
|
|
625
|
-
for (let i = 0; i < contents.length; i++) {
|
|
626
|
-
const c = contents[i]
|
|
627
|
-
if (c.content.isCountable() && c.attrs != null) {
|
|
628
|
-
if (length === 0) {
|
|
629
|
-
// the delete is exhausted but this item renders more content — split so the cursor
|
|
630
|
-
// advances only past the consumed part (mirrors formatText's renderer branch);
|
|
631
|
-
// otherwise every following op of the same delta targets too far right
|
|
632
|
-
splitClock = c.clock
|
|
633
|
-
break
|
|
634
|
-
}
|
|
635
|
-
// deleting already deleted content. store that information in a meta property, but do
|
|
636
|
-
// nothing
|
|
637
|
-
const pieceLen = c.content.getLength()
|
|
638
|
-
const contentLen = math.min(pieceLen, length)
|
|
639
|
-
map.setIfUndefined(transaction.meta, 'attributedDeletes', createIdSet).add(item.id.client, c.clock, contentLen)
|
|
640
|
-
length -= contentLen
|
|
641
|
-
if (contentLen < pieceLen) {
|
|
642
|
-
splitClock = c.clock + contentLen
|
|
643
|
-
break
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
if (splitClock >= 0) {
|
|
648
|
-
getItemCleanStart(transaction, createID(item.id.client, splitClock))
|
|
649
|
-
} else {
|
|
650
|
-
const lastContent = contents.length > 0 ? contents[contents.length - 1] : null
|
|
651
|
-
const nextItemClock = item.id.clock + item.length
|
|
652
|
-
const nextContentClock = lastContent != null ? lastContent.clock + lastContent.content.getLength() : nextItemClock
|
|
653
|
-
if (nextContentClock < nextItemClock) {
|
|
654
|
-
getItemCleanStart(transaction, createID(item.id.client, nextContentClock))
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
314
|
}
|
|
658
315
|
currPos.forward()
|
|
659
316
|
}
|
|
660
|
-
|
|
661
|
-
cleanupFormattingGap(transaction, start, currPos.right, startFormats, currPos.currentFormats)
|
|
662
|
-
}
|
|
663
|
-
const parent = /** @type {YNode<any>} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
|
|
317
|
+
const parent = /** @type {YNode} */ (/** @type {Item} */ (currPos.left || currPos.right).parent)
|
|
664
318
|
if (parent._searchMarker) {
|
|
665
319
|
updateMarkerChanges(parent._searchMarker, currPos.index, -startLength + length)
|
|
666
320
|
}
|
|
@@ -711,101 +365,6 @@ export class ArraySearchMarker {
|
|
|
711
365
|
*/
|
|
712
366
|
const refreshMarkerTimestamp = marker => { marker.timestamp = globalSearchMarkerTimestamp++ }
|
|
713
367
|
|
|
714
|
-
/**
|
|
715
|
-
* After a format marker item is integrated at `currPos` (it is `currPos.left`;
|
|
716
|
-
* `currPos.right` is the item right of it), fold it into the format snapshot of
|
|
717
|
-
* every search marker positioned at-or-right of the insertion — exactly what a
|
|
718
|
-
* cursor passing the marker would do.
|
|
719
|
-
*
|
|
720
|
-
* Positions compare in *list order*, not just index space: markers anchored
|
|
721
|
-
* at items sharing `currPos.index` but sitting left of the insertion (a
|
|
722
|
-
* same-index run of non-countable/deleted items ending at `currPos.left`)
|
|
723
|
-
* must NOT observe the new format — index equality alone is ambiguous there.
|
|
724
|
-
*
|
|
725
|
-
* Same-key boundary (R1 repair): a marker's `formats` snapshot is the fold of
|
|
726
|
-
* the live format items left of its anchor. The new item changes that fold
|
|
727
|
-
* only while it is the LAST live item setting its key before the anchor — a
|
|
728
|
-
* later live format item that sets the same key again shadows it, so the
|
|
729
|
-
* snapshot's value for that key still comes from that later marker and must
|
|
730
|
-
* not be overwritten. The fold therefore walks right from the insertion and
|
|
731
|
-
* stops at the first live same-key `ContentFormat`: markers anchored
|
|
732
|
-
* at-or-before it are updated, everything past it keeps its (still valid)
|
|
733
|
-
* snapshot.
|
|
734
|
-
*
|
|
735
|
-
* Candidates whose anchor the walk never reaches (stale record — anchor
|
|
736
|
-
* unlinked/merged, or index lying about the real position) are invalidated
|
|
737
|
-
* (`formats = null`) instead of trusting a position that can't be verified.
|
|
738
|
-
*
|
|
739
|
-
* @param {YNode<any>} parent
|
|
740
|
-
* @param {ItemTextListPosition} currPos
|
|
741
|
-
* @param {ContentFormat} format
|
|
742
|
-
*/
|
|
743
|
-
const updateMarkerFormats = (parent, currPos, format) => {
|
|
744
|
-
const ms = parent._searchMarker
|
|
745
|
-
if (ms === null) return
|
|
746
|
-
/**
|
|
747
|
-
* Anchors of the same-index run ending at `currPos.left`: deleted or
|
|
748
|
-
* non-countable items sharing `currPos.index` but sitting left of the
|
|
749
|
-
* insertion (the run always starts with the new format item itself, which
|
|
750
|
-
* is non-countable). Built lazily — only needed when a marker shares the
|
|
751
|
-
* insertion index.
|
|
752
|
-
*
|
|
753
|
-
* @type {Set<Item>?}
|
|
754
|
-
*/
|
|
755
|
-
let leftRun = null
|
|
756
|
-
/**
|
|
757
|
-
* Snapshots at-or-right of the insertion — candidates for the fold.
|
|
758
|
-
*
|
|
759
|
-
* @type {Array<ArraySearchMarker>?}
|
|
760
|
-
*/
|
|
761
|
-
let cands = null
|
|
762
|
-
let maxIndex = -1
|
|
763
|
-
for (let i = 0; i < ms.length; i++) {
|
|
764
|
-
const m = ms[i]
|
|
765
|
-
if (m.formats === null || m.index < currPos.index) continue
|
|
766
|
-
if (m.index === currPos.index) {
|
|
767
|
-
if (leftRun === null) {
|
|
768
|
-
leftRun = new Set()
|
|
769
|
-
for (let p = currPos.left; p !== null && (p.deleted || !p.countable); p = p.left) {
|
|
770
|
-
leftRun.add(p)
|
|
771
|
-
}
|
|
772
|
-
}
|
|
773
|
-
// fold only if m.p is at-or-right of currPos.right — i.e. not part of
|
|
774
|
-
// the same-index run terminating at currPos.left
|
|
775
|
-
if (leftRun.has(m.p)) continue
|
|
776
|
-
}
|
|
777
|
-
;(cands ?? (cands = [])).push(m)
|
|
778
|
-
if (m.index > maxIndex) maxIndex = m.index
|
|
779
|
-
}
|
|
780
|
-
if (cands === null) return
|
|
781
|
-
let pindex = currPos.index
|
|
782
|
-
for (let p = currPos.right; p !== null; p = p.right) {
|
|
783
|
-
if (p.marker) {
|
|
784
|
-
for (let i = cands.length - 1; i >= 0; i--) {
|
|
785
|
-
if (cands[i].p === p) {
|
|
786
|
-
updateCurrentFormats(/** @type {Map<string,any>} */ (cands[i].formats), format)
|
|
787
|
-
cands.splice(i, 1)
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
if (cands.length === 0) return
|
|
791
|
-
}
|
|
792
|
-
if (!p.deleted && p.content.constructor === ContentFormat && /** @type {ContentFormat} */ (p.content).key === format.key) {
|
|
793
|
-
// Same-key boundary: snapshots past this item still draw this key from
|
|
794
|
-
// it — the new marker's reach ends here. (Markers anchored ON it were
|
|
795
|
-
// already updated: their snapshot is the state at their left edge,
|
|
796
|
-
// which precedes the item's own effect.)
|
|
797
|
-
return
|
|
798
|
-
}
|
|
799
|
-
if (!p.deleted && p.countable) {
|
|
800
|
-
pindex += p.length
|
|
801
|
-
if (pindex > maxIndex) break // every remaining anchor lies behind us — stale records
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
// Candidates never reached by the walk can't be verified at-or-right of the
|
|
805
|
-
// insertion — drop their snapshots rather than risk a stale read.
|
|
806
|
-
for (let i = 0; i < cands.length; i++) cands[i].formats = null
|
|
807
|
-
}
|
|
808
|
-
|
|
809
368
|
/**
|
|
810
369
|
* This is rather complex so this function is the only thing that should overwrite a marker
|
|
811
370
|
*
|
|
@@ -850,10 +409,10 @@ const markPosition = (searchMarker, p, index) => {
|
|
|
850
409
|
* of `currPos.right`. Never mid-mutation: in-flight `currentFormats` is
|
|
851
410
|
* transient.
|
|
852
411
|
*
|
|
853
|
-
* @param {YNode
|
|
412
|
+
* @param {YNode} parent
|
|
854
413
|
* @param {ItemTextListPosition} currPos
|
|
855
414
|
*/
|
|
856
|
-
const plantMarker = (parent, currPos) => {
|
|
415
|
+
export const plantMarker = (parent, currPos) => {
|
|
857
416
|
const ms = parent._searchMarker
|
|
858
417
|
if (ms === null) return
|
|
859
418
|
let p = currPos.right
|
|
@@ -892,9 +451,9 @@ const plantMarker = (parent, currPos) => {
|
|
|
892
451
|
*
|
|
893
452
|
* The marker's `index` is `p`'s left edge in the raw countable space — the
|
|
894
453
|
* same space every marker consumer records — so callers must only plant
|
|
895
|
-
* while walking the
|
|
454
|
+
* while walking the live (countable, non-deleted) interpretation.
|
|
896
455
|
*
|
|
897
|
-
* @param {YNode
|
|
456
|
+
* @param {YNode} parent
|
|
898
457
|
* @param {Item} p - item the marker anchors on (the marker's `index` is its left edge)
|
|
899
458
|
* @param {number} index
|
|
900
459
|
* @param {Map<string,any>} formats - format state at `p`'s left edge (copied)
|
|
@@ -1038,61 +597,162 @@ export const updateMarkerChanges = (searchMarker, index, len) => {
|
|
|
1038
597
|
}
|
|
1039
598
|
}
|
|
1040
599
|
|
|
600
|
+
// YP16 begin (edytor fork: writes without the delta layer — see UPSTREAM.md YP16)
|
|
1041
601
|
/**
|
|
1042
|
-
*
|
|
1043
|
-
*
|
|
602
|
+
* What a detached node was given, replayed when it integrates: its content
|
|
603
|
+
* (adjacent strings, and adjacent arrays, merged) and its attrs in
|
|
604
|
+
* first-write order (`null`: removed).
|
|
1044
605
|
*
|
|
1045
|
-
* @
|
|
1046
|
-
|
|
1047
|
-
|
|
606
|
+
* @typedef {{ runs: Array<string|Array<any>>, attrs: Object<string,{ value: any }|null> }} Prelim
|
|
607
|
+
*/
|
|
608
|
+
|
|
609
|
+
/**
|
|
610
|
+
* @param {YNode} node
|
|
611
|
+
* @return {Prelim}
|
|
612
|
+
*/
|
|
613
|
+
const prelimOf = node => node._prelim ?? (node._prelim = { runs: [], attrs: Object.create(null) })
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Split `runs` so that a run starts at `index`; returns that run's position.
|
|
617
|
+
*
|
|
618
|
+
* @param {Array<string|Array<any>>} runs
|
|
619
|
+
* @param {number} index
|
|
620
|
+
* @return {number}
|
|
1048
621
|
*/
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
// @ts-ignore
|
|
1057
|
-
map.setIfUndefined(changedParentTypes, type, () => []).push(event)
|
|
1058
|
-
if (type._item === null) {
|
|
1059
|
-
break
|
|
622
|
+
const splitRuns = (runs, index) => {
|
|
623
|
+
let i = 0
|
|
624
|
+
for (; i < runs.length && index > 0; i++) {
|
|
625
|
+
const run = runs[i]
|
|
626
|
+
if (index < run.length) {
|
|
627
|
+
runs.splice(i, 1, run.slice(0, index), run.slice(index))
|
|
628
|
+
return i + 1
|
|
1060
629
|
}
|
|
1061
|
-
|
|
630
|
+
index -= run.length
|
|
1062
631
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
632
|
+
if (index > 0) throw new Error('Exceeded content range')
|
|
633
|
+
return i
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/**
|
|
637
|
+
* Drop empty runs and merge adjacent runs of one kind.
|
|
638
|
+
*
|
|
639
|
+
* @param {Array<string|Array<any>>} runs
|
|
640
|
+
*/
|
|
641
|
+
const mergeRuns = runs => {
|
|
642
|
+
for (let i = runs.length - 1; i >= 0; i--) {
|
|
643
|
+
if (runs[i].length === 0) runs.splice(i, 1)
|
|
644
|
+
}
|
|
645
|
+
for (let i = runs.length - 1; i > 0; i--) {
|
|
646
|
+
const a = runs[i - 1]
|
|
647
|
+
const b = runs[i]
|
|
648
|
+
if (typeof a === typeof b) {
|
|
649
|
+
runs.splice(i - 1, 2, typeof a === 'string' ? a + b : a.concat(b))
|
|
650
|
+
}
|
|
1068
651
|
}
|
|
1069
652
|
}
|
|
1070
653
|
|
|
1071
654
|
/**
|
|
1072
|
-
*
|
|
655
|
+
* A cursor at live index `index` of `node` — the positioning step of a delete. A distant index
|
|
656
|
+
* seeds the cursor from a format-aware search marker (UPSTREAM.md YP4): a marker at-or-left of the
|
|
657
|
+
* target carrying a formats snapshot starts the walk there, provided its anchor is the first item
|
|
658
|
+
* at its index (the walk from `_start` stops on the first unprocessed item at the target, format
|
|
659
|
+
* markers and deleted items included).
|
|
1073
660
|
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
661
|
+
* @param {Transaction} transaction
|
|
662
|
+
* @param {YNode} node
|
|
663
|
+
* @param {number} index
|
|
664
|
+
* @return {ItemTextListPosition}
|
|
665
|
+
*/
|
|
666
|
+
export const cursorAt = (transaction, node, index) => {
|
|
667
|
+
const currPos = new ItemTextListPosition(null, node._start, 0, new Map())
|
|
668
|
+
if (!(index > 0)) return currPos
|
|
669
|
+
let adjust = 0
|
|
670
|
+
const ms = node._searchMarker
|
|
671
|
+
if (ms !== null) {
|
|
672
|
+
// A marker anchored right of the target can still seed when the gap is crossed by stepping
|
|
673
|
+
// left over countable/non-deleted items only — format state does not change across pure
|
|
674
|
+
// content (a format marker or tombstone in the gap stops the walk and rejects the marker).
|
|
675
|
+
// Anchor linkage is verified because records can outlive their items.
|
|
676
|
+
let best = null
|
|
677
|
+
/** @type {Item|null} */
|
|
678
|
+
let bestP = null
|
|
679
|
+
let bestIndex = 0
|
|
680
|
+
for (let i = 0; i < ms.length; i++) {
|
|
681
|
+
const m = ms[i]
|
|
682
|
+
if (m.formats === null || m.index <= 0) continue
|
|
683
|
+
let p = m.p
|
|
684
|
+
let idx = m.index
|
|
685
|
+
let steps = 0
|
|
686
|
+
while (idx > index && p.left !== null && !p.left.deleted && p.left.countable && steps < 64) {
|
|
687
|
+
p = p.left
|
|
688
|
+
idx -= p.length
|
|
689
|
+
steps++
|
|
690
|
+
}
|
|
691
|
+
if (
|
|
692
|
+
idx <= index && (best === null || idx > bestIndex) &&
|
|
693
|
+
(p.left === null ? node._start === p : p.left.right === p) &&
|
|
694
|
+
(p.right === null || p.right.left === p) &&
|
|
695
|
+
(p.left === null || (!p.left.deleted && p.left.countable))
|
|
696
|
+
) {
|
|
697
|
+
best = m
|
|
698
|
+
bestP = p
|
|
699
|
+
bestIndex = idx
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (best !== null) {
|
|
703
|
+
currPos.left = /** @type {Item} */ (bestP).left
|
|
704
|
+
currPos.right = bestP
|
|
705
|
+
currPos.index = bestIndex
|
|
706
|
+
currPos.currentFormats = map.copy(/** @type {Map<string,any>} */ (best.formats))
|
|
707
|
+
adjust = bestIndex
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
currPos.advance(transaction, index - adjust)
|
|
711
|
+
// quiescent point at a positioning boundary — a later write near it seeds from this marker
|
|
712
|
+
plantMarker(node, currPos)
|
|
713
|
+
return currPos
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Write the content and attrs a detached node was given: the content runs in order, then the
|
|
718
|
+
* attrs.
|
|
1082
719
|
*
|
|
1083
|
-
* @
|
|
1084
|
-
* @
|
|
720
|
+
* @param {Transaction} transaction
|
|
721
|
+
* @param {YNode} node
|
|
722
|
+
* @param {Prelim} prelim
|
|
723
|
+
*/
|
|
724
|
+
const writePrelim = (transaction, node, prelim) => {
|
|
725
|
+
let index = 0
|
|
726
|
+
for (const run of prelim.runs) {
|
|
727
|
+
for (const part of contentsOf(run)) {
|
|
728
|
+
insertInGapHelper(transaction, node, index, part)
|
|
729
|
+
index += part.getLength()
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
for (const key in prelim.attrs) {
|
|
733
|
+
const attr = prelim.attrs[key]
|
|
734
|
+
if (attr === null) {
|
|
735
|
+
nodeMapDelete(transaction, node, key)
|
|
736
|
+
} else {
|
|
737
|
+
nodeMapSet(transaction, node, key, attr.value)
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
// YP16 end
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* The engine's node: a sequence (text, elements, nested nodes) and a map of attributes. YP19: the
|
|
745
|
+
* reads and writes edytor calls — no observers, no plain formats, no JSON or string renderings.
|
|
1085
746
|
*/
|
|
1086
|
-
export class YNode
|
|
747
|
+
export class YNode {
|
|
1087
748
|
/**
|
|
1088
|
-
* @param {
|
|
749
|
+
* @param {string?} name
|
|
1089
750
|
*/
|
|
1090
751
|
constructor (name = null) {
|
|
1091
|
-
super()
|
|
1092
752
|
/**
|
|
1093
|
-
* @type {
|
|
753
|
+
* @type {string|null}
|
|
1094
754
|
*/
|
|
1095
|
-
this.name =
|
|
755
|
+
this.name = name
|
|
1096
756
|
/**
|
|
1097
757
|
* @type {Item|null}
|
|
1098
758
|
*/
|
|
@@ -1110,239 +770,45 @@ export class YNode extends ObservableV2 {
|
|
|
1110
770
|
*/
|
|
1111
771
|
this.doc = null
|
|
1112
772
|
this._length = 0
|
|
1113
|
-
/**
|
|
1114
|
-
* Event handlers
|
|
1115
|
-
* @type {EventHandler<YEvent<DeltaToYNode<DConf>>,Transaction>}
|
|
1116
|
-
*/
|
|
1117
|
-
this._eH = createEventHandler()
|
|
1118
|
-
/**
|
|
1119
|
-
* Deep event handlers
|
|
1120
|
-
* @type {EventHandler<YEvent<DConf>,Transaction>}
|
|
1121
|
-
*/
|
|
1122
|
-
this._dEH = createEventHandler()
|
|
1123
|
-
/**
|
|
1124
|
-
* @type {null | Array<ArraySearchMarker>}
|
|
1125
|
-
*/
|
|
1126
|
-
this._searchMarker = null
|
|
1127
|
-
/**
|
|
1128
|
-
* Maintained deep-delta cache backing {@link YNode#delta}. `null` until `delta` is first
|
|
1129
|
-
* accessed; thereafter kept current on every event of this type (incrementally, by applying the
|
|
1130
|
-
* deep change) and re-diffed by {@link YNode#useRenderer}. Cleared by {@link YNode#clearCache}.
|
|
1131
|
-
*
|
|
1132
|
-
* INVARIANT (fingerprint-memo safety): consumers (e.g. y-sync) diff against this LIVE object
|
|
1133
|
-
* via {@link YNode#delta}, which memoizes `_fingerprint` on its delta/op nodes at every depth.
|
|
1134
|
-
* Every in-place patch — the per-transaction `apply` in `cleanupTransactions` and the
|
|
1135
|
-
* renderer-overlay `apply` in {@link typeApplyRendererChange} — MUST route through the lib0
|
|
1136
|
-
* delta builder API (`apply`), which invalidates those memos. NEVER write delta/op fields
|
|
1137
|
-
* directly: a direct write leaves stale memos, corrupting every subsequent fingerprint read
|
|
1138
|
-
* and diff. Pinned by the `testRdtFingerprintMemo*CacheDrift` tests.
|
|
1139
|
-
* @type {delta.DeltaBuilderAny | null}
|
|
1140
|
-
*/
|
|
1141
|
-
this._delta = null
|
|
1142
773
|
this._legacyTypeRef = this.name == null ? YXmlFragmentRefID : YXmlElementRefID
|
|
1143
774
|
/**
|
|
1144
775
|
* @type {Array<ArraySearchMarker>|null}
|
|
1145
776
|
*/
|
|
1146
777
|
this._searchMarker = []
|
|
1147
778
|
/**
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1150
|
-
*/
|
|
1151
|
-
this._hasFormatting = false
|
|
1152
|
-
/**
|
|
1153
|
-
* The active default renderer. Used by `toDelta`, `applyDelta`, and the events whenever no
|
|
1154
|
-
* explicit renderer is passed. `null` = no renderer: content renders as-is via the generic
|
|
1155
|
-
* fast path. Change it via {@link YNode#useRenderer}.
|
|
1156
|
-
* @type {AbstractRenderer?}
|
|
1157
|
-
*/
|
|
1158
|
-
this._renderer = null
|
|
1159
|
-
/**
|
|
1160
|
-
* Bound listener on the active renderer's `'change'` event — attribution corrections that
|
|
1161
|
-
* happen without a Y transaction on this doc (e.g. a suggestion is accepted and the
|
|
1162
|
-
* renderer's attribution overlay updates). `null` while no renderer is active.
|
|
1163
|
-
* Managed by {@link YNode#useRenderer}; see {@link typeApplyRendererChange}.
|
|
1164
|
-
* @type {((changes: IdSet, origin: any, local: boolean) => void) | null}
|
|
779
|
+
* The writes made while this node is detached, replayed when it integrates (YP16).
|
|
780
|
+
* @type {Prelim|null}
|
|
1165
781
|
*/
|
|
1166
|
-
this.
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
/**
|
|
1170
|
-
* Schema of the deltas this type produces — part of the lib0 `RDT` interface.
|
|
1171
|
-
*
|
|
1172
|
-
* @type {s.Schema<delta.Delta<DConf>>}
|
|
1173
|
-
*/
|
|
1174
|
-
get $delta () {
|
|
1175
|
-
return /** @type {any} */ (delta.$deltaAny)
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
/**
|
|
1179
|
-
* The deep delta of this type (the full nested content tree, children rendered as their own
|
|
1180
|
-
* deltas).
|
|
1181
|
-
*
|
|
1182
|
-
* The returned value is the type's **live** maintained cache: it is materialized on first access
|
|
1183
|
-
* and then kept current on every event fired on this type (and re-diffed by
|
|
1184
|
-
* {@link YNode#useRenderer}), so a reference held across edits keeps updating in place. Clone it
|
|
1185
|
-
* (e.g. `type.delta.clone()`) if you need a stable snapshot, and call {@link YNode#clearCache} to
|
|
1186
|
-
* drop the cache.
|
|
1187
|
-
*
|
|
1188
|
-
* Consider the returned delta **done** — it must not be edited from the outside. It is
|
|
1189
|
-
* deliberately typed as a `Delta` (not a `DeltaBuilder`) so the mutating builder API is not
|
|
1190
|
-
* reachable; editing it anyway would corrupt the cache without changing the CRDT. The proper way
|
|
1191
|
-
* to change this type is {@link YNode#applyDelta}.
|
|
1192
|
-
*
|
|
1193
|
-
* @type {delta.Delta<DConf>}
|
|
1194
|
-
*/
|
|
1195
|
-
get delta () {
|
|
1196
|
-
if (this._delta === null) {
|
|
1197
|
-
this._delta = this._renderDelta()
|
|
1198
|
-
}
|
|
1199
|
-
return /** @type {any} */ (this._delta)
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
/**
|
|
1203
|
-
* Render the full deep current state into a fresh `isFinal` builder (so subsequent `.apply`s of
|
|
1204
|
-
* deep changes update content in place). Uses this type's active renderer.
|
|
1205
|
-
*
|
|
1206
|
-
* @return {delta.DeltaBuilderAny}
|
|
1207
|
-
*/
|
|
1208
|
-
_renderDelta () {
|
|
1209
|
-
const state = /** @type {delta.DeltaBuilderAny} */ (delta.create(this.name))
|
|
1210
|
-
state.isFinal = true
|
|
1211
|
-
state.apply(this.toDelta({ deep: true }))
|
|
1212
|
-
return state
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
/**
|
|
1216
|
-
* Discard the cached deep delta backing {@link YNode#delta}.
|
|
1217
|
-
*
|
|
1218
|
-
* After `delta` is first accessed, the cache is updated on every event fired on this type (and
|
|
1219
|
-
* re-diffed by {@link YNode#useRenderer}). Call this to drop it — e.g. to reclaim memory, or to
|
|
1220
|
-
* force an exact recomputation after editing while a non-base renderer is active (the incremental
|
|
1221
|
-
* updates can drift from a fresh deep render in that case).
|
|
1222
|
-
*/
|
|
1223
|
-
clearCache () {
|
|
1224
|
-
this._delta = null
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
/**
|
|
1228
|
-
* Change the default renderer used by this type. After calling `useRenderer(renderer)`, the
|
|
1229
|
-
* `toDelta`, `applyDelta`, and event methods all use `renderer` whenever no explicit renderer is
|
|
1230
|
-
* passed (an explicit `{ renderer }` argument still overrides it per call).
|
|
1231
|
-
*
|
|
1232
|
-
* If the deep-delta cache ({@link YNode#delta}) is being maintained, or a `'delta'` listener is
|
|
1233
|
-
* attached, the content is re-rendered with the new renderer and the difference is emitted on the
|
|
1234
|
-
* `'delta'` channel only (a renderer switch is not a CRDT change, so no `YEvent` is produced, and
|
|
1235
|
-
* the emitted origin is `null` as no transaction is involved).
|
|
1236
|
-
*
|
|
1237
|
-
* @param {AbstractRenderer?} renderer - `null` detaches: content renders as-is again
|
|
1238
|
-
* @return {this}
|
|
1239
|
-
*/
|
|
1240
|
-
useRenderer (renderer) {
|
|
1241
|
-
const prev = this._renderer
|
|
1242
|
-
if (renderer === prev) return this
|
|
1243
|
-
if (this._rendererChangeHandler !== null) {
|
|
1244
|
-
/** @type {AbstractRenderer} */ (prev).off('change', this._rendererChangeHandler)
|
|
1245
|
-
this._rendererChangeHandler = null
|
|
1246
|
-
}
|
|
1247
|
-
if (renderer !== null && this._rendererChangeHandler === null) {
|
|
1248
|
-
// attribution corrections (e.g. accepting a suggestion) reach the renderer without a Y
|
|
1249
|
-
// transaction on this doc — subscribe so the RDT surface (maintained cache + 'delta'
|
|
1250
|
-
// channel) stays current. Without a renderer there are no attributions, hence no
|
|
1251
|
-
// subscription. Not gated on `renderer !== prev`: after `destroy()` the handler is removed
|
|
1252
|
-
// while `_renderer` keeps pointing at the renderer, so re-activating with the same renderer
|
|
1253
|
-
// must re-subscribe.
|
|
1254
|
-
this._rendererChangeHandler = (changes, origin) => typeApplyRendererChange(this, changes, origin)
|
|
1255
|
-
renderer.on('change', this._rendererChangeHandler)
|
|
1256
|
-
}
|
|
1257
|
-
const hasDeltaListeners = (this._observers.get('delta')?.size ?? 0) > 0
|
|
1258
|
-
if (this._delta !== null || hasDeltaListeners) {
|
|
1259
|
-
const oldState = this._delta ?? this._renderDelta()
|
|
1260
|
-
this._renderer = renderer
|
|
1261
|
-
const newState = this._renderDelta()
|
|
1262
|
-
if (this._delta !== null) this._delta = newState
|
|
1263
|
-
if (hasDeltaListeners) {
|
|
1264
|
-
const d = /** @type {any} */ (delta.diff(/** @type {any} */ (oldState), /** @type {any} */ (newState)))
|
|
1265
|
-
if (!d.isEmpty()) this.emit('delta', [d, null])
|
|
1266
|
-
}
|
|
1267
|
-
} else {
|
|
1268
|
-
this._renderer = renderer
|
|
1269
|
-
}
|
|
1270
|
-
return this
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
/**
|
|
1274
|
-
* Tear down this type as an `RDT`: emit the `'destroy'` event, unregister all `'delta'` /
|
|
1275
|
-
* `'destroy'` listeners, and reset the RDT surface (active renderer detached, the
|
|
1276
|
-
* maintained {@link YNode#delta} cache dropped). The CRDT content and the
|
|
1277
|
-
* `observe`/`observeDeep` handlers are left untouched — this only releases the RDT/binding
|
|
1278
|
-
* observers. Without the reset, a still-materialized cache would keep being maintained through
|
|
1279
|
-
* the transaction path but no longer receive the renderer's attribution corrections — reading
|
|
1280
|
-
* `delta` after `destroy()` would silently drift. Re-activating is fully supported:
|
|
1281
|
-
* `useRenderer(renderer)` re-subscribes and `delta` re-materializes.
|
|
1282
|
-
*/
|
|
1283
|
-
destroy () {
|
|
1284
|
-
if (this._rendererChangeHandler !== null) {
|
|
1285
|
-
/** @type {AbstractRenderer} */ (this._renderer).off('change', this._rendererChangeHandler)
|
|
1286
|
-
this._rendererChangeHandler = null
|
|
1287
|
-
}
|
|
1288
|
-
this._renderer = null
|
|
1289
|
-
this._delta = null
|
|
1290
|
-
this.emit('destroy', [this])
|
|
1291
|
-
super.destroy()
|
|
782
|
+
this._prelim = null
|
|
1292
783
|
}
|
|
1293
784
|
|
|
1294
785
|
/**
|
|
1295
|
-
*
|
|
1296
|
-
* @param {delta.Delta<DC>} d
|
|
1297
|
-
* @return {YNode<DC>}
|
|
786
|
+
* The live length of the sequence (`0` while detached).
|
|
1298
787
|
*/
|
|
1299
|
-
static from (d) {
|
|
1300
|
-
const yt = new YNode(d.name)
|
|
1301
|
-
yt.applyDelta(d)
|
|
1302
|
-
return yt
|
|
1303
|
-
}
|
|
1304
|
-
|
|
1305
788
|
get length () {
|
|
1306
|
-
this.doc ?? warnPrematureAccess()
|
|
1307
789
|
return this._length
|
|
1308
790
|
}
|
|
1309
791
|
|
|
1310
|
-
/**
|
|
1311
|
-
* Returns a fresh delta that can be used to change this YNode.
|
|
1312
|
-
* @type {delta.DeltaBuilder<DeltaToYNode<DConf>>}
|
|
1313
|
-
*/
|
|
1314
|
-
get change () {
|
|
1315
|
-
return /** @type {any} */ (delta.create())
|
|
1316
|
-
}
|
|
1317
|
-
|
|
1318
|
-
/**
|
|
1319
|
-
* @return {YNode<any>?}
|
|
1320
|
-
*/
|
|
1321
|
-
get parent () {
|
|
1322
|
-
return /** @type {YNode<any>?} */ (this._item ? this._item.parent : null)
|
|
1323
|
-
}
|
|
1324
|
-
|
|
1325
792
|
/**
|
|
1326
793
|
* Integrate this type into the Yjs instance.
|
|
1327
794
|
*
|
|
1328
|
-
* * Save this struct in the os
|
|
1329
|
-
* * This type is sent to other client
|
|
1330
|
-
* * Observer functions are fired
|
|
1331
|
-
*
|
|
1332
795
|
* @param {Doc} y The Yjs instance
|
|
1333
796
|
* @param {Item|null} item
|
|
1334
797
|
*/
|
|
1335
798
|
_integrate (y, item) {
|
|
1336
799
|
this.doc = y
|
|
1337
800
|
this._item = item
|
|
1338
|
-
|
|
1339
|
-
|
|
801
|
+
const prelim = this._prelim
|
|
802
|
+
if (prelim !== null) {
|
|
1340
803
|
this._prelim = null
|
|
804
|
+
if ((prelim.runs.length > 0 || Object.keys(prelim.attrs).length > 0) && (item === null || !item.deleted)) {
|
|
805
|
+
transact(y, transaction => writePrelim(transaction, this, prelim))
|
|
806
|
+
}
|
|
1341
807
|
}
|
|
1342
808
|
}
|
|
1343
809
|
|
|
1344
810
|
/**
|
|
1345
|
-
* @return {YNode
|
|
811
|
+
* @return {YNode}
|
|
1346
812
|
*/
|
|
1347
813
|
_copy () {
|
|
1348
814
|
const ynode = new YNode(this.name)
|
|
@@ -1351,972 +817,63 @@ export class YNode extends ObservableV2 {
|
|
|
1351
817
|
}
|
|
1352
818
|
|
|
1353
819
|
/**
|
|
1354
|
-
*
|
|
1355
|
-
* Must be implemented by each type.
|
|
820
|
+
* Removes an attribute. A deleted node is left as it is.
|
|
1356
821
|
*
|
|
1357
|
-
* @param {
|
|
1358
|
-
* @param {Set<null|string>} parentSubs Keys changed on this type. `null` if list was modified.
|
|
822
|
+
* @param {string} attributeName The attribute name that is to be removed.
|
|
1359
823
|
*/
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
// `observeDeep`, not here where only the directly-changed type is visible.
|
|
1366
|
-
if (!transaction.local && this._searchMarker) {
|
|
1367
|
-
this._searchMarker.length = 0
|
|
1368
|
-
}
|
|
1369
|
-
// If a remote change happened, we try to cleanup potential formatting duplicates.
|
|
1370
|
-
if (!transaction.local && this._hasFormatting) {
|
|
1371
|
-
transaction._needFormattingCleanup = true
|
|
824
|
+
deleteAttr (attributeName) {
|
|
825
|
+
if (this.doc == null) {
|
|
826
|
+
prelimOf(this).attrs[attributeName] = null
|
|
827
|
+
} else if (this._item === null || !this._item.deleted) {
|
|
828
|
+
transact(this.doc, transaction => nodeMapDelete(transaction, this, attributeName))
|
|
1372
829
|
}
|
|
1373
830
|
}
|
|
1374
831
|
|
|
1375
832
|
/**
|
|
1376
|
-
*
|
|
833
|
+
* Sets or updates an attribute. A deleted node is left as it is.
|
|
1377
834
|
*
|
|
1378
|
-
* @template
|
|
1379
|
-
* @param {
|
|
1380
|
-
* @return {F}
|
|
1381
|
-
*/
|
|
1382
|
-
observe (f) {
|
|
1383
|
-
addEventHandlerListener(this._eH, f)
|
|
1384
|
-
return f
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
/**
|
|
1388
|
-
* Observe all events that are created by this type and its children.
|
|
1389
|
-
*
|
|
1390
|
-
* @template {function(YEvent<DConf>,Transaction):void} F
|
|
1391
|
-
* @param {F} f Observer function
|
|
1392
|
-
* @return {F}
|
|
1393
|
-
*/
|
|
1394
|
-
observeDeep (f) {
|
|
1395
|
-
addEventHandlerListener(this._dEH, f)
|
|
1396
|
-
return f
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
/**
|
|
1400
|
-
* Unregister an observer function.
|
|
1401
|
-
*
|
|
1402
|
-
* @param {(type:YEvent<DeltaToYNode<DConf>>,tr:Transaction)=>void} f Observer function
|
|
1403
|
-
*/
|
|
1404
|
-
unobserve (f) {
|
|
1405
|
-
removeEventHandlerListener(this._eH, f)
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
/**
|
|
1409
|
-
* Unregister an observer function.
|
|
1410
|
-
*
|
|
1411
|
-
* @param {function(YEvent<DConf>,Transaction):void} f Observer function
|
|
1412
|
-
*/
|
|
1413
|
-
unobserveDeep (f) {
|
|
1414
|
-
removeEventHandlerListener(this._dEH, f)
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
/**
|
|
1418
|
-
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
|
1419
|
-
* attributions.
|
|
1420
|
-
*
|
|
1421
|
-
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
|
1422
|
-
* attribution `{ isDeleted: true, .. }`.
|
|
1423
|
-
*
|
|
1424
|
-
* @template {boolean} [Deep=false]
|
|
1425
|
-
*
|
|
1426
|
-
* @param {Object} [opts]
|
|
1427
|
-
* @param {AbstractRenderer?} [opts.renderer] - renders the content (with attributions); defaults to this type's active renderer (see {@link YNode#useRenderer}), i.e. `null` (render as-is) unless changed
|
|
1428
|
-
* @param {IdSet?} [opts.itemsToRender]
|
|
1429
|
-
* @param {boolean} [opts.retainInserts] - if true, retain rendered inserts with attributions
|
|
1430
|
-
* @param {boolean} [opts.retainDeletes] - if true, retain rendered+attributed deletes only
|
|
1431
|
-
* @param {IdSet?} [opts.insertedItems] - ids inserted by the change being rendered; content that is both in `insertedItems` and deleted renders as a *fresh* insert even under `retainDeletes` (it was never part of the consuming state)
|
|
1432
|
-
* @param {Map<YNode,Set<string|null>>|null} [opts.modified] - set of types that should be rendered as modified children
|
|
1433
|
-
* @param {Deep} [opts.deep] - render child types as delta
|
|
1434
|
-
* @return {Deep extends true ? delta.Delta<DConf> : delta.Delta<DeltaConfDeltaToYNode<DConf>>} The Delta representation of this type.
|
|
1435
|
-
*
|
|
1436
|
-
* @public
|
|
1437
|
-
*/
|
|
1438
|
-
toDelta (opts = {}) {
|
|
1439
|
-
const { renderer = this._renderer, itemsToRender = null, retainInserts = false, retainDeletes = false, insertedItems = null, deep = false } = opts
|
|
1440
|
-
const { modified = (deep && itemsToRender) ? computeModifiedFromItems(/** @type {Doc} */ (this.doc).store, itemsToRender) : null } = opts
|
|
1441
|
-
const renderAttrs = modified?.get(this) || null
|
|
1442
|
-
const renderChildren = modified == null || !modified.has(this) || /** @type {Set<string|null>} */ (modified.get(this)).has(null)
|
|
1443
|
-
/**
|
|
1444
|
-
* @type {delta.DeltaBuilderAny}
|
|
1445
|
-
*/
|
|
1446
|
-
const d = /** @type {any} */ (delta.create(this.name))
|
|
1447
|
-
const optsAll = object.assign({}, opts, { renderer, modified })
|
|
1448
|
-
// opts has been re-computed - do not use opts after this point!
|
|
1449
|
-
nodeMapGetDelta(d, /** @type {any} */ (this), renderAttrs, renderer, deep, modified, itemsToRender, optsAll, optsAll)
|
|
1450
|
-
if (renderChildren) {
|
|
1451
|
-
/**
|
|
1452
|
-
* @type {delta.Formats}
|
|
1453
|
-
*/
|
|
1454
|
-
let currentFormats = {} // saves all current formats for insert
|
|
1455
|
-
/**
|
|
1456
|
-
* YP13: the open paired mark operations, by mark.
|
|
1457
|
-
* @type {Map<string,any>}
|
|
1458
|
-
*/
|
|
1459
|
-
const pairedFormats = new Map()
|
|
1460
|
-
let usingCurrentFormats = false
|
|
1461
|
-
/**
|
|
1462
|
-
* @type {delta.Formats}
|
|
1463
|
-
*/
|
|
1464
|
-
let changedFormats = {} // saves changed formats for retain
|
|
1465
|
-
let usingChangedFormats = false
|
|
1466
|
-
/**
|
|
1467
|
-
* Logic for format attribution
|
|
1468
|
-
* Everything that comes after a format is formatted by the user that created it.
|
|
1469
|
-
* Two exceptions:
|
|
1470
|
-
* - the user resets formatting to the previously known formatting that is not attributed
|
|
1471
|
-
* - the user deletes a format and hence restores the previously known formatting
|
|
1472
|
-
* that is not attributed.
|
|
1473
|
-
* @type {delta.Formats}
|
|
1474
|
-
*/
|
|
1475
|
-
const previousUnattributedFormats = {} // contains previously known unattributed formatting
|
|
1476
|
-
/**
|
|
1477
|
-
* @type {delta.Formats}
|
|
1478
|
-
*/
|
|
1479
|
-
const previousFormats = {} // The value before changes
|
|
1480
|
-
/**
|
|
1481
|
-
* Per-key provenance of `currentFormats`: `true` iff the marker currently governing the key
|
|
1482
|
-
* opened a *pending attributed* range (the attributor-push branch below). Consulted only at
|
|
1483
|
-
* deleted markers in change renders (`isDeletedFormatClear`) to distinguish a re-exposed
|
|
1484
|
-
* committed enclosing value (must clear the stale attribution from the cache) from a
|
|
1485
|
-
* re-exposed pending suggested value (must preserve it — the re-bolding case).
|
|
1486
|
-
* @type {{ [key: string]: boolean }}
|
|
1487
|
-
*/
|
|
1488
|
-
const currentFormatsAttributed = {}
|
|
1489
|
-
/**
|
|
1490
|
-
* Attribution argument for a change-render retain whose content lost its *own* attribution.
|
|
1491
|
-
* `null` clears everything — correct in an attribution-free context — but inside an ambient
|
|
1492
|
-
* format attribution (installed via `useAttribution` by a surrounding format marker) the
|
|
1493
|
-
* fresh render still stamps that ambient format on this content. An object argument merges
|
|
1494
|
-
* over the ambient context (lib0 `combineInstr`), so emit per-key removals instead: they
|
|
1495
|
-
* clear only the op's own insert/delete attribution while re-asserting the ambient format.
|
|
1496
|
-
*/
|
|
1497
|
-
const clearedOwnAttribution = () => d.usedAttribution == null ? null : /** @type {any} */ ({ insert: null, insertAt: null, delete: null, deleteAt: null })
|
|
1498
|
-
/**
|
|
1499
|
-
* Format keys touched by a *rendered* format marker during an attributing change render. A
|
|
1500
|
-
* merely retained attributed marker normally must not emit ops — but when a rendered
|
|
1501
|
-
* same-key marker preceded it in this walk, the change is what made the retained boundary
|
|
1502
|
-
* reset-to-previous (e.g. a base-doc format arriving under a same-key format suggestion),
|
|
1503
|
-
* and the spans it governs must drop their now-stale attribution from the cache. Per-key
|
|
1504
|
-
* tracking is required (a plain "any format rendered" flag would over-clear other keys),
|
|
1505
|
-
* but the set is only ever consulted at format markers — never per character — and stays
|
|
1506
|
-
* `null` on all hot paths: full renders, base-renderer renders, and change renders without
|
|
1507
|
-
* format markers never allocate it.
|
|
1508
|
-
* @type {Set<string>?}
|
|
1509
|
-
*/
|
|
1510
|
-
let renderedFormatKeys = null
|
|
1511
|
-
/**
|
|
1512
|
-
* Renderer output for a single item — a renderer may split an item into multiple
|
|
1513
|
-
* attributed pieces.
|
|
1514
|
-
* @type {Array<AttributedContent<any>>}
|
|
1515
|
-
*/
|
|
1516
|
-
const cs = []
|
|
1517
|
-
/**
|
|
1518
|
-
* Reusable trivial piece for the `readItemPieces` current-state walk —
|
|
1519
|
-
* `cs` is drained per item, so a single allocation (captured lazily
|
|
1520
|
-
* from the first emitted piece) serves the whole render.
|
|
1521
|
-
* @type {AttributedContent<any>?}
|
|
1522
|
-
*/
|
|
1523
|
-
let csScratch = null
|
|
1524
|
-
/**
|
|
1525
|
-
* Process one piece of (possibly attributed) content — the shared op-emission and format
|
|
1526
|
-
* state machine. The renderer path feeds it every piece produced by `readContent`; the
|
|
1527
|
-
* generic path feeds it `ContentFormat` markers only (the state machine must see every
|
|
1528
|
-
* marker, attributed or not — a plain marker can e.g. close a formerly attributed range)
|
|
1529
|
-
* and emits ops for plain content directly.
|
|
1530
|
-
* @param {AttributedContent<any>} c
|
|
1531
|
-
*/
|
|
1532
|
-
const processContent = c => {
|
|
1533
|
-
// render (attributed) content even if it was deleted
|
|
1534
|
-
const renderContent = c.render && (!c.deleted || c.attrs != null)
|
|
1535
|
-
// content that was just deleted. It is not rendered as an insertion, because it doesn't
|
|
1536
|
-
// have any formats.
|
|
1537
|
-
const renderDelete = c.render && c.deleted
|
|
1538
|
-
// existing content that should be retained, only adding changed formats
|
|
1539
|
-
const retainContent = !c.render && (!c.deleted || c.attrs != null)
|
|
1540
|
-
const attribution = (renderContent || c.content.constructor === ContentFormat) ? createAttributionFromAttributionItems(c.attrs, c.deleted) : undefined
|
|
1541
|
-
switch (c.content.constructor) {
|
|
1542
|
-
case ContentDeleted: {
|
|
1543
|
-
// fresh (mode 3) content renders as nothing here: the consuming state never saw it,
|
|
1544
|
-
// and gc'd content cannot render as an insert — a delete op would misapply
|
|
1545
|
-
if (renderDelete && !c.fresh) d.delete(c.content.getLength())
|
|
1546
|
-
break
|
|
1547
|
-
}
|
|
1548
|
-
case ContentString:
|
|
1549
|
-
if (renderContent) {
|
|
1550
|
-
if (c.deleted ? (retainDeletes && !c.fresh) : retainInserts) {
|
|
1551
|
-
// a retain expresses the format *diff* against existing (cached) content, so use
|
|
1552
|
-
// `changedFormats`: a format removed this change (e.g. its marker was deleted)
|
|
1553
|
-
// is present there as a `null` clear, whereas `currentFormats` (absolute) can
|
|
1554
|
-
// only re-assert present formats and would silently keep a stale one.
|
|
1555
|
-
// (`c.fresh` content is exempt from `retainDeletes`: it was inserted *and*
|
|
1556
|
-
// deleted by this very change, so the consuming state holds nothing to retain —
|
|
1557
|
-
// it renders as an insert below, carrying its attribution.)
|
|
1558
|
-
d.useFormats(changedFormats)
|
|
1559
|
-
usingChangedFormats = true
|
|
1560
|
-
// change render: a retained item with no attribution means its attribution was
|
|
1561
|
-
// removed → emit a clear rather than `{}` (skip). Present attribution merges.
|
|
1562
|
-
d.retain(/** @type {ContentString} */ (c.content).str.length, undefined, attribution ?? clearedOwnAttribution())
|
|
1563
|
-
} else {
|
|
1564
|
-
d.useFormats(currentFormats)
|
|
1565
|
-
usingCurrentFormats = true
|
|
1566
|
-
d.insert(/** @type {ContentString} */ (c.content).str, undefined, attribution)
|
|
1567
|
-
}
|
|
1568
|
-
} else if (renderDelete) {
|
|
1569
|
-
d.delete(c.content.getLength())
|
|
1570
|
-
} else if (retainContent) {
|
|
1571
|
-
d.useFormats(changedFormats)
|
|
1572
|
-
usingChangedFormats = true
|
|
1573
|
-
d.retain(c.content.getLength())
|
|
1574
|
-
}
|
|
1575
|
-
break
|
|
1576
|
-
case ContentEmbed:
|
|
1577
|
-
case ContentAny:
|
|
1578
|
-
case ContentJSON:
|
|
1579
|
-
case ContentType:
|
|
1580
|
-
case ContentBinary:
|
|
1581
|
-
case ContentDoc:
|
|
1582
|
-
if (renderContent) {
|
|
1583
|
-
if (c.deleted ? (retainDeletes && !c.fresh) : retainInserts) {
|
|
1584
|
-
// a retain expresses the format *diff* → use `changedFormats` (see ContentString)
|
|
1585
|
-
d.useFormats(changedFormats)
|
|
1586
|
-
usingChangedFormats = true
|
|
1587
|
-
if (c.deleted && c.content.constructor === ContentType) {
|
|
1588
|
-
// @todo use current transaction instead
|
|
1589
|
-
d.modify(/** @type {any} */ (c.content).type.toDelta(optsAll), undefined, attribution ?? null)
|
|
1590
|
-
} else if (c.content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (c.content).type)) {
|
|
1591
|
-
// a de/re-attributed node the renderer still claims (e.g. a partial accept):
|
|
1592
|
-
// merge/clear its own attribution and descend so the nested walk heals exactly
|
|
1593
|
-
// the child ids in `itemsToRender` — other children keep their attribution
|
|
1594
|
-
d.modify(/** @type {any} */ (c.content).type.toDelta(optsAll), undefined, attribution ?? clearedOwnAttribution())
|
|
1595
|
-
} else {
|
|
1596
|
-
d.retain(c.content.getLength(), undefined, attribution ?? clearedOwnAttribution())
|
|
1597
|
-
}
|
|
1598
|
-
} else if (deep && c.content.constructor === ContentType) {
|
|
1599
|
-
d.useFormats(currentFormats)
|
|
1600
|
-
usingCurrentFormats = true
|
|
1601
|
-
d.insert([/** @type {any} */(c.content).type.toDelta(optsAll)], undefined, attribution)
|
|
1602
|
-
} else {
|
|
1603
|
-
d.useFormats(currentFormats)
|
|
1604
|
-
usingCurrentFormats = true
|
|
1605
|
-
d.insert(c.content.getContent(), undefined, attribution)
|
|
1606
|
-
}
|
|
1607
|
-
} else if (renderDelete) {
|
|
1608
|
-
d.delete(1)
|
|
1609
|
-
} else if (retainContent) {
|
|
1610
|
-
if (c.content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (c.content).type)) {
|
|
1611
|
-
// @todo use current transaction instead
|
|
1612
|
-
d.modify(/** @type {any} */ (c.content).type.toDelta(optsAll))
|
|
1613
|
-
} else {
|
|
1614
|
-
d.useFormats(changedFormats)
|
|
1615
|
-
usingChangedFormats = true
|
|
1616
|
-
d.retain(1)
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
break
|
|
1620
|
-
case ContentFormat: {
|
|
1621
|
-
let { key, value } = /** @type {ContentFormat} */ (c.content)
|
|
1622
|
-
// YP13: a live paired mark item renders as its mark's winning value there
|
|
1623
|
-
if (pairedRole(/** @type {ContentFormat} */ (c.content)) >= 0) {
|
|
1624
|
-
if (c.deleted) break
|
|
1625
|
-
foldPaired(pairedFormats, /** @type {ContentFormat} */ (c.content))
|
|
1626
|
-
key = key.slice(1)
|
|
1627
|
-
value = formatValue(pairedFormats.get(key) ?? null)
|
|
1628
|
-
}
|
|
1629
|
-
const currFormatVal = currentFormats[key] ?? null
|
|
1630
|
-
if (attribution != null && (c.deleted || !object.hasProperty(previousUnattributedFormats, key))) {
|
|
1631
|
-
previousUnattributedFormats[key] = c.deleted ? value : currFormatVal
|
|
1632
|
-
}
|
|
1633
|
-
// @todo write a function "updateCurrentFormats" and "updateChangedFormats"
|
|
1634
|
-
// # Update Formats
|
|
1635
|
-
if (renderContent || renderDelete) {
|
|
1636
|
-
// create fresh references
|
|
1637
|
-
if (usingCurrentFormats) {
|
|
1638
|
-
currentFormats = object.assign({}, currentFormats)
|
|
1639
|
-
usingCurrentFormats = false
|
|
1640
|
-
}
|
|
1641
|
-
if (usingChangedFormats) {
|
|
1642
|
-
usingChangedFormats = false
|
|
1643
|
-
changedFormats = object.assign({}, changedFormats)
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
if (renderContent || renderDelete) {
|
|
1647
|
-
if (itemsToRender !== null && renderer !== null) {
|
|
1648
|
-
// only attributing change renders consult this (see the reset branch below)
|
|
1649
|
-
if (renderedFormatKeys === null) renderedFormatKeys = new Set()
|
|
1650
|
-
renderedFormatKeys.add(key)
|
|
1651
|
-
}
|
|
1652
|
-
// `previousFormats` tracks the value governing the current walk position in the
|
|
1653
|
-
// *consuming cache state*, so the retain diff below stays a plain cache → new-state
|
|
1654
|
-
// comparison. Contributors: markers deleted by this change in event renders (their
|
|
1655
|
-
// value governed the cache until now; heal-rendered deletes are PRE-deleted markers
|
|
1656
|
-
// whose removal the cache already rendered — they must not contribute), alive
|
|
1657
|
-
// markers re-rendered by heal renders (`retainInserts` — their value was in the
|
|
1658
|
-
// cache), and retained alive markers (the branch below). A gated update here is not
|
|
1659
|
-
// enough: a marker deleted by the change whose value coincides with the walk's
|
|
1660
|
-
// current value would silently drop out of the cache-reference tracking, and a later
|
|
1661
|
-
// marker would be misread as a restore-to-previous, swallowing a needed format diff.
|
|
1662
|
-
if (c.deleted) {
|
|
1663
|
-
if (itemsToRender !== null && !retainInserts) previousFormats[key] = value
|
|
1664
|
-
} else { // !c.deleted
|
|
1665
|
-
if (retainInserts) previousFormats[key] = value
|
|
1666
|
-
if (value == null) {
|
|
1667
|
-
delete currentFormats[key]
|
|
1668
|
-
} else {
|
|
1669
|
-
currentFormats[key] = value
|
|
1670
|
-
}
|
|
1671
|
-
currentFormatsAttributed[key] = false
|
|
1672
|
-
}
|
|
1673
|
-
// the retain diff for following spans is exactly cache (previousFormats) → new
|
|
1674
|
-
// state (currentFormats), recomputed per key at every rendered marker
|
|
1675
|
-
if (equalFormats(currentFormats[key] ?? null, previousFormats[key] ?? null)) {
|
|
1676
|
-
delete changedFormats[key]
|
|
1677
|
-
} else {
|
|
1678
|
-
changedFormats[key] = currentFormats[key] ?? null
|
|
1679
|
-
}
|
|
1680
|
-
} else if (retainContent && !c.deleted) {
|
|
1681
|
-
// fresh reference to currentFormats only
|
|
1682
|
-
if (usingCurrentFormats) {
|
|
1683
|
-
currentFormats = object.assign({}, currentFormats)
|
|
1684
|
-
usingCurrentFormats = false
|
|
1685
|
-
}
|
|
1686
|
-
if (usingChangedFormats && changedFormats[key] !== undefined) {
|
|
1687
|
-
usingChangedFormats = false
|
|
1688
|
-
changedFormats = object.assign({}, changedFormats)
|
|
1689
|
-
}
|
|
1690
|
-
if (value == null) {
|
|
1691
|
-
delete currentFormats[key]
|
|
1692
|
-
} else {
|
|
1693
|
-
currentFormats[key] = value
|
|
1694
|
-
}
|
|
1695
|
-
currentFormatsAttributed[key] = false
|
|
1696
|
-
delete changedFormats[key]
|
|
1697
|
-
previousFormats[key] = value
|
|
1698
|
-
}
|
|
1699
|
-
// # Update Attributions
|
|
1700
|
-
// A format marker deleted in a change render under an *attributing* renderer nets to no
|
|
1701
|
-
// attribution (its insert+delete suggestion cancels → `attribution == null`), yet it ends
|
|
1702
|
-
// the attributed range it opened: the following retained content must drop the stale
|
|
1703
|
-
// `{ format: { [key]: [] } }` the marker's insertion wrote to the cache. Emit an explicit
|
|
1704
|
-
// `null` leaf for the key (a context-wide `useAttribution(null)` cannot carry a per-key
|
|
1705
|
-
// clear). Conditions: only an attributing render (`renderer !== null`; without a
|
|
1706
|
-
// renderer there are no attributions to clear), and only when the deletion reverts to
|
|
1707
|
-
// a value that is not itself governed by a pending suggestion: either no value at all
|
|
1708
|
-
// (`currFormatVal == null`) or a *committed* enclosing value (provenance tracked in
|
|
1709
|
-
// `currentFormatsAttributed` — e.g. an accept/reject deleting a marker re-exposes the
|
|
1710
|
-
// enclosing committed `bold:true`, and the span's stale suggested-format attribution
|
|
1711
|
-
// must be cleared). If it reverts to a still-pending *attributed* value (deleting a
|
|
1712
|
-
// `bold:null` marker re-exposes an enclosing attributed `bold:true`, as when
|
|
1713
|
-
// re-bolding), the attribution is preserved. Suppressed entirely while an attributed
|
|
1714
|
-
// same-key range is open (`previousUnattributedFormats` has the key): there the fresh
|
|
1715
|
-
// render skips this marker — its governance belongs to the still-open attributed
|
|
1716
|
-
// range, whose ambient context re-stamps the correct attribution on the following
|
|
1717
|
-
// retains — mirroring the identical guard on `isAcceptedFormatClear`.
|
|
1718
|
-
const isDeletedFormatClear = attribution == null && renderer !== null && renderDelete && c.deleted && itemsToRender != null && (currFormatVal == null || currentFormatsAttributed[key] !== true) && !equalFormats(value, currFormatVal) && !object.hasProperty(previousUnattributedFormats, key)
|
|
1719
|
-
// The alive-marker analogue of `isDeletedFormatClear`: a format marker whose
|
|
1720
|
-
// *attribution* was removed while the marker survives — its suggestion was accepted
|
|
1721
|
-
// (the marker's id arrives via the renderer's `'change'` event). The original change
|
|
1722
|
-
// render stamped `{ format: { [key]: [] } }` on the spans this marker governs, so the
|
|
1723
|
-
// heal render must emit a per-key `null` leaf over those spans (a blunt
|
|
1724
|
-
// `attribution: null` would also wipe unrelated attributions such as a co-located
|
|
1725
|
-
// `{ delete: [] }`). Gated on `retainInserts` — the diff-against-existing-content
|
|
1726
|
-
// mode used by attribution-heal renders: in a plain event render an unattributed
|
|
1727
|
-
// alive marker is just new unattributed content and must not emit spurious clears.
|
|
1728
|
-
// When the key is tracked as an open attributed range (`previousUnattributedFormats`)
|
|
1729
|
-
// the accepted marker instead defers to the regular branch chain below, which mirrors
|
|
1730
|
-
// the fresh render exactly: mid-range it *keeps* the ambient attribution (relative to
|
|
1731
|
-
// the base doc the span's format is still a suggested change), and at the range end
|
|
1732
|
-
// (`sameAsPreviousAttributions`) it closes the attribution context.
|
|
1733
|
-
const isAcceptedFormatClear = attribution == null && renderer !== null && renderContent && !c.deleted && itemsToRender != null && retainInserts && !object.hasProperty(previousUnattributedFormats, key)
|
|
1734
|
-
if (attribution != null || isDeletedFormatClear || isAcceptedFormatClear || object.hasProperty(previousUnattributedFormats, key)) {
|
|
1735
|
-
/**
|
|
1736
|
-
* @type {Attribution}
|
|
1737
|
-
*/
|
|
1738
|
-
const formattingAttribution = object.assign({}, d.usedAttribution)
|
|
1739
|
-
const changedAttributedFormats = /** @type {{ [key: string]: Array<any>|null }} */ (formattingAttribution.format = object.assign({}, formattingAttribution.format ?? {}))
|
|
1740
|
-
const sameAsPreviousAttributions = equalFormats(previousUnattributedFormats[key], currentFormats[key] ?? null)
|
|
1741
|
-
// An *unattributed* marker rendered by this change (e.g. a fresh base-doc marker)
|
|
1742
|
-
// that closes an open attributed same-key range by restoring the previous
|
|
1743
|
-
// unattributed value: the spans it governs must drop their stale attribution from
|
|
1744
|
-
// the cache, exactly like the attributed range-end below. Mirrors the fresh render
|
|
1745
|
-
// precisely: there, the reset only closes the ambient attribution when removing the
|
|
1746
|
-
// key leaves the format context empty (the `useAttribution(null)` case below) —
|
|
1747
|
-
// while another attributed key holds the context open, the removal happens on a
|
|
1748
|
-
// copy that is never installed, so the key survives on the following spans and the
|
|
1749
|
-
// change render must NOT clear it.
|
|
1750
|
-
const isRangeEndClear = attribution == null && sameAsPreviousAttributions && renderContent && !c.deleted && itemsToRender != null && renderer !== null && object.hasProperty(previousUnattributedFormats, key) && object.every(changedAttributedFormats, (_v, k) => k === key)
|
|
1751
|
-
if (isDeletedFormatClear || (isAcceptedFormatClear && changedAttributedFormats[key] == null)) {
|
|
1752
|
-
// uniformly emit the per-key clear — also for a marker that *ends* a formerly
|
|
1753
|
-
// attributed range: the attributed render emits a trailing per-key `null` past
|
|
1754
|
-
// the closing marker as well (see the `attribution != null` case below), so the
|
|
1755
|
-
// heal render mirrors that exact span; where the cache holds no stale
|
|
1756
|
-
// attribution the clear merges as a no-op.
|
|
1757
|
-
changedAttributedFormats[key] = null
|
|
1758
|
-
delete previousUnattributedFormats[key]
|
|
1759
|
-
} else if (isAcceptedFormatClear) {
|
|
1760
|
-
// accepted marker inside a *still-attributed* (pending) range for the same key —
|
|
1761
|
-
// the ambient context holds an attributor list, not a clear. Relative to the base
|
|
1762
|
-
// doc the effective format of this span is still a suggested change, so the fresh
|
|
1763
|
-
// render keeps attributing it (the `skip` case below): keep the ambient
|
|
1764
|
-
// attribution rather than clearing it.
|
|
1765
|
-
} else if (attribution == null && !sameAsPreviousAttributions) {
|
|
1766
|
-
// skip
|
|
1767
|
-
} else if (attribution == null || sameAsPreviousAttributions) {
|
|
1768
|
-
// an unattributed format was found or an attributed format
|
|
1769
|
-
// was found that resets to the previous status. When this format item is
|
|
1770
|
-
// itself rendered this transaction (`renderContent || renderDelete`) in a change/diff
|
|
1771
|
-
// render (`itemsToRender != null`), it is the END of an attributed format range:
|
|
1772
|
-
// emit an explicit clear (a `null` leaf) so the retained content drops any stale
|
|
1773
|
-
// `{ format: { [key]: [] } }` from the maintained `delta` cache — a bare context-skip
|
|
1774
|
-
// (`delete`) would leave it in place. The same applies to a *retained* attributed
|
|
1775
|
-
// boundary when a rendered same-key marker preceded it in this walk
|
|
1776
|
-
// (`renderedFormatKeys`): the boundary only resets-to-previous *because of* the
|
|
1777
|
-
// rendered change (e.g. a base-doc format arriving under an equal same-key format
|
|
1778
|
-
// suggestion — which marker comes first depends on client ids), so the spans it
|
|
1779
|
-
// governs must drop their now-stale attribution too. For a merely-retained boundary
|
|
1780
|
-
// with no rendered same-key marker, or a full insert render (removal is already
|
|
1781
|
-
// modeled as absence in `currentFormats`), just drop the key: a change render must
|
|
1782
|
-
// not emit ops for unchanged ranges, and inserts must stay free of a spurious
|
|
1783
|
-
// `{ format: { [key]: null } }`.
|
|
1784
|
-
// `c.fresh`: a marker inserted (and possibly auto-deleted) by this very change —
|
|
1785
|
-
// e.g. a base-doc format clear arriving inside a suggestion-deleted paragraph —
|
|
1786
|
-
// stays on the retained path (mode 0, the fresh-format exemption), but when it
|
|
1787
|
-
// resets-to-previous it is the range END of the attributed run in the new state
|
|
1788
|
-
// and must clear the stale attribution from the cache like a rendered marker.
|
|
1789
|
-
if (isRangeEndClear || (attribution != null && itemsToRender != null && (renderContent || renderDelete || c.fresh || renderedFormatKeys?.has(key) === true))) {
|
|
1790
|
-
changedAttributedFormats[key] = null
|
|
1791
|
-
} else {
|
|
1792
|
-
delete changedAttributedFormats[key]
|
|
1793
|
-
// pending per-key clears force this copy to install (the all-null disjunct
|
|
1794
|
-
// below); installing after silently dropping this key would close its
|
|
1795
|
-
// governance without the clear the fresh render's context-close implies,
|
|
1796
|
-
// leaving a stale `{ format: { [key]: [] } }` on the following retained
|
|
1797
|
-
// spans — re-add the key as an explicit clear. Inert outside attributing
|
|
1798
|
-
// change renders: null leaves cannot exist in fresh contexts.
|
|
1799
|
-
if (itemsToRender != null && !object.isEmpty(changedAttributedFormats) && object.every(changedAttributedFormats, v => v == null)) {
|
|
1800
|
-
changedAttributedFormats[key] = null
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
delete previousUnattributedFormats[key]
|
|
1804
|
-
} else {
|
|
1805
|
-
// an attributed marker opens pending governance for the key — a deleted attributed
|
|
1806
|
-
// marker never writes `currentFormats`, hence the alive gate; an attributed marker
|
|
1807
|
-
// that value-equals the previous unattributed value takes the reset branch above
|
|
1808
|
-
// and correctly yields committed governance
|
|
1809
|
-
if (!c.deleted) currentFormatsAttributed[key] = true
|
|
1810
|
-
const by = changedAttributedFormats[key] = (changedAttributedFormats[key]?.slice() ?? [])
|
|
1811
|
-
by.push(...((c.deleted ? attribution.delete : attribution.insert) ?? []))
|
|
1812
|
-
const attributedAt = (c.deleted ? attribution.deleteAt : attribution.insertAt)
|
|
1813
|
-
if (attributedAt) formattingAttribution.formatAt = attributedAt
|
|
1814
|
-
}
|
|
1815
|
-
if (object.isEmpty(changedAttributedFormats)) {
|
|
1816
|
-
d.useAttribution(null)
|
|
1817
|
-
} else if (attribution != null || isDeletedFormatClear || isAcceptedFormatClear || isRangeEndClear || (itemsToRender != null && object.every(changedAttributedFormats, v => v == null))) {
|
|
1818
|
-
// the last disjunct: a change-render copy whose remaining leaves are ALL per-key
|
|
1819
|
-
// null clears is the analogue of the fresh render's empty copy (fresh contexts
|
|
1820
|
-
// never contain null leaves — they are produced only by the change-render-only
|
|
1821
|
-
// predicates above) — install it, so an unattributed close marker's key deletion
|
|
1822
|
-
// closes the governance while the pending cache clears still propagate; discarding
|
|
1823
|
-
// it would re-merge the stale key into the cache from the surviving ambient
|
|
1824
|
-
const attributedAt = (c.deleted ? attribution?.deleteAt : attribution?.insertAt)
|
|
1825
|
-
if (attributedAt != null) formattingAttribution.formatAt = attributedAt
|
|
1826
|
-
d.useAttribution(formattingAttribution)
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
break
|
|
1830
|
-
}
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
for (let item = this._start; item !== null; item = item.right) {
|
|
1834
|
-
const content = item.content
|
|
1835
|
-
if (itemsToRender === null && !retainInserts) {
|
|
1836
|
-
// Current-state render — the same physical-sequence interpretation
|
|
1837
|
-
// `RangeCursor`'s bounded reads consume (UPSTREAM.md YP5):
|
|
1838
|
-
// `readItemPieces` produces the item's AttributedContent pieces
|
|
1839
|
-
// (renderer-claimed → readContent mode 1; ContentFormat → one
|
|
1840
|
-
// marker piece; tombstoned → nothing; live → whole item) and the
|
|
1841
|
-
// shared state machine folds formats + emits ops for them —
|
|
1842
|
-
// op-identical to the inlined fast-path dispatch it replaces.
|
|
1843
|
-
cs.length = 0
|
|
1844
|
-
readItemPieces(cs, renderer, item, csScratch)
|
|
1845
|
-
if (csScratch === null) csScratch = cs[0] ?? null
|
|
1846
|
-
for (let i = 0; i < cs.length; i++) {
|
|
1847
|
-
processContent(cs[i])
|
|
1848
|
-
}
|
|
1849
|
-
continue
|
|
1850
|
-
}
|
|
1851
|
-
if (renderer === null || !renderer.hasItem(item)) {
|
|
1852
|
-
// generic fast path: content the renderer doesn't claim renders as-is — no attribution
|
|
1853
|
-
// lookups, no AttributedContent wrappers and, in the common full-coverage case, no
|
|
1854
|
-
// content slicing
|
|
1855
|
-
if (content.constructor === ContentFormat) {
|
|
1856
|
-
// format markers always flow through the shared state machine
|
|
1857
|
-
processContent(new AttributedContent(content, item.id.clock, item.deleted, null,
|
|
1858
|
-
itemsToRender == null ? 1 : (itemsToRender.has(item.id.client, item.id.clock) ? 2 : 0)))
|
|
1859
|
-
} else if (item.deleted) {
|
|
1860
|
-
// plain deleted content is invisible; in a change render, the ranges deleted by this
|
|
1861
|
-
// change emit `delete` ops — position-only, the content itself is not needed
|
|
1862
|
-
if (itemsToRender !== null && itemsToRender.intersects(item.id.client, item.id.clock, item.length)) {
|
|
1863
|
-
const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
|
|
1864
|
-
for (let ir = 0; ir < rslice.length; ir++) {
|
|
1865
|
-
const idrange = rslice[ir]
|
|
1866
|
-
if (idrange.exists) {
|
|
1867
|
-
// mirror the piece-wise op sizes of the renderer path: string-ish content
|
|
1868
|
-
// deletes by length, other content deletes one element per piece
|
|
1869
|
-
d.delete((content.constructor === ContentString || content.constructor === ContentDeleted) ? idrange.len : 1)
|
|
1870
|
-
}
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
} else if (itemsToRender == null) {
|
|
1874
|
-
if (retainInserts) {
|
|
1875
|
-
// attribution-overlay render (e.g. `toDelta({ renderer, retainInserts: true })`):
|
|
1876
|
-
// existing content is retained, clearing any formerly cached own-attribution
|
|
1877
|
-
d.useFormats(changedFormats)
|
|
1878
|
-
usingChangedFormats = true
|
|
1879
|
-
d.retain(content.getLength(), undefined, clearedOwnAttribution())
|
|
1880
|
-
} else {
|
|
1881
|
-
// full render: a plain insert of the whole item
|
|
1882
|
-
d.useFormats(currentFormats)
|
|
1883
|
-
usingCurrentFormats = true
|
|
1884
|
-
if (deep && content.constructor === ContentType) {
|
|
1885
|
-
d.insert([/** @type {any} */(content).type.toDelta(optsAll)])
|
|
1886
|
-
} else if (content.constructor === ContentString) {
|
|
1887
|
-
d.insert(/** @type {ContentString} */ (content).str)
|
|
1888
|
-
} else {
|
|
1889
|
-
d.insert(content.getContent())
|
|
1890
|
-
}
|
|
1891
|
-
}
|
|
1892
|
-
} else {
|
|
1893
|
-
// change render on alive plain content: inserted ranges become inserts, everything
|
|
1894
|
-
// else retains (position-only). `retainInserts` (attribution-heal renders) retains
|
|
1895
|
-
// previously inserted content while clearing its former attribution.
|
|
1896
|
-
const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
|
|
1897
|
-
let itemContent = rslice.length > 1 ? content.copy() : content
|
|
1898
|
-
for (let ir = 0; ir < rslice.length; ir++) {
|
|
1899
|
-
const idrange = rslice[ir]
|
|
1900
|
-
const c = itemContent
|
|
1901
|
-
if (ir !== rslice.length - 1) {
|
|
1902
|
-
itemContent = itemContent.splice(idrange.len)
|
|
1903
|
-
}
|
|
1904
|
-
if (!idrange.exists) {
|
|
1905
|
-
if (content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (content).type)) {
|
|
1906
|
-
// @todo use current transaction instead
|
|
1907
|
-
d.modify(/** @type {ContentType} */ (content).type.toDelta(optsAll))
|
|
1908
|
-
} else {
|
|
1909
|
-
d.useFormats(changedFormats)
|
|
1910
|
-
usingChangedFormats = true
|
|
1911
|
-
// mirror the piece-wise op sizes of the renderer path (see the delete branch)
|
|
1912
|
-
d.retain(content.constructor === ContentString ? idrange.len : 1)
|
|
1913
|
-
}
|
|
1914
|
-
} else if (retainInserts) {
|
|
1915
|
-
if (content.constructor === ContentType && modified?.has(/** @type {ContentType} */ (content).type)) {
|
|
1916
|
-
// a de/re-attributed node (e.g. its insert-suggestion was accepted): clear its
|
|
1917
|
-
// own attribution and descend so the nested walk heals exactly the child ids
|
|
1918
|
-
// present in `itemsToRender` — children outside the change set keep their
|
|
1919
|
-
// attribution (id-scoped, never a blanket subtree clear)
|
|
1920
|
-
d.modify(/** @type {ContentType} */ (content).type.toDelta(optsAll), undefined, clearedOwnAttribution())
|
|
1921
|
-
} else {
|
|
1922
|
-
d.useFormats(changedFormats)
|
|
1923
|
-
usingChangedFormats = true
|
|
1924
|
-
d.retain(idrange.len, undefined, clearedOwnAttribution())
|
|
1925
|
-
}
|
|
1926
|
-
} else {
|
|
1927
|
-
d.useFormats(currentFormats)
|
|
1928
|
-
usingCurrentFormats = true
|
|
1929
|
-
if (deep && content.constructor === ContentType) {
|
|
1930
|
-
d.insert([/** @type {any} */(content).type.toDelta(optsAll)])
|
|
1931
|
-
} else if (content.constructor === ContentString) {
|
|
1932
|
-
d.insert(/** @type {ContentString} */ (c).str)
|
|
1933
|
-
} else {
|
|
1934
|
-
d.insert(c.getContent())
|
|
1935
|
-
}
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
}
|
|
1939
|
-
} else {
|
|
1940
|
-
cs.length = 0
|
|
1941
|
-
if (itemsToRender != null) {
|
|
1942
|
-
const rslice = itemsToRender.slice(item.id.client, item.id.clock, item.length)
|
|
1943
|
-
// fresh content deleted by the very change being rendered gets mode 3: it renders as
|
|
1944
|
-
// an insert even under `retainDeletes` — the consuming state (e.g. the maintained
|
|
1945
|
-
// `delta` cache) has never seen it, so there is nothing to retain. Freshness is
|
|
1946
|
-
// decided per id range, NOT per item: structs merge across cleanups (e.g. a queued
|
|
1947
|
-
// transaction's render runs after an earlier finally-block merged its fresh item into
|
|
1948
|
-
// an older neighbor), so one item can span fresh and pre-existing ids. ContentFormat
|
|
1949
|
-
// markers are exempt and keep mode 0 (the retained-marker path; they never merge, so
|
|
1950
|
-
// the whole-item check is exact): the format state machine treats fresh-deleted
|
|
1951
|
-
// markers exactly like before this change rendered.
|
|
1952
|
-
const checkFresh = item.deleted && insertedItems !== null && content.constructor !== ContentFormat
|
|
1953
|
-
const freshFormat = item.deleted && insertedItems !== null && content.constructor === ContentFormat && insertedItems.hasId(item.id)
|
|
1954
|
-
let itemContent = rslice.length > 1 ? content.copy() : content
|
|
1955
|
-
for (let ir = 0; ir < rslice.length; ir++) {
|
|
1956
|
-
const idrange = rslice[ir]
|
|
1957
|
-
let c = itemContent
|
|
1958
|
-
if (ir !== rslice.length - 1) {
|
|
1959
|
-
itemContent = itemContent.splice(idrange.len)
|
|
1960
|
-
}
|
|
1961
|
-
if (!idrange.exists || !checkFresh) {
|
|
1962
|
-
renderer.readContent(cs, item.id.client, idrange.clock, item.deleted, c,
|
|
1963
|
-
idrange.exists ? (freshFormat ? 0 : 2) : 0)
|
|
1964
|
-
} else {
|
|
1965
|
-
// an exists range may itself straddle fresh and pre-existing ids (ranges from
|
|
1966
|
-
// different sources merge in `itemsToRender`) — split it against `insertedItems`
|
|
1967
|
-
const fslice = insertedItems.slice(item.id.client, idrange.clock, idrange.len)
|
|
1968
|
-
let subContent = fslice.length > 1 ? c.copy() : c
|
|
1969
|
-
for (let fi = 0; fi < fslice.length; fi++) {
|
|
1970
|
-
const frange = fslice[fi]
|
|
1971
|
-
c = subContent
|
|
1972
|
-
if (fi !== fslice.length - 1) {
|
|
1973
|
-
subContent = subContent.splice(frange.len)
|
|
1974
|
-
}
|
|
1975
|
-
renderer.readContent(cs, item.id.client, frange.clock, item.deleted, c, frange.exists ? 3 : 2)
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
if (freshFormat) {
|
|
1980
|
-
// fresh format markers stay on the retained path (mode 0, see `checkFresh`), but
|
|
1981
|
-
// the format state machine must still see their freshness: a fresh clear marker
|
|
1982
|
-
// that resets-to-previous ends an attributed range and must emit the per-key
|
|
1983
|
-
// attribution clear (`cs` is reset per item; a freshFormat item is a single
|
|
1984
|
-
// len-1 ContentFormat, so this marks exactly its pieces)
|
|
1985
|
-
for (let i = 0; i < cs.length; i++) { cs[i].fresh = true }
|
|
1986
|
-
}
|
|
1987
|
-
} else {
|
|
1988
|
-
renderer.readContent(cs, item.id.client, item.id.clock, item.deleted, content, 1)
|
|
1989
|
-
}
|
|
1990
|
-
for (let i = 0; i < cs.length; i++) {
|
|
1991
|
-
processContent(cs[i])
|
|
1992
|
-
}
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
}
|
|
1996
|
-
return /** @type {any} */ (d.done(false))
|
|
1997
|
-
}
|
|
1998
|
-
|
|
1999
|
-
/**
|
|
2000
|
-
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
|
2001
|
-
* attributions.
|
|
2002
|
-
*
|
|
2003
|
-
* @param {Object} [opts]
|
|
2004
|
-
* @param {AbstractRenderer?} [opts.renderer] - renders the content (with attributions); defaults to this type's active renderer (see {@link YNode#useRenderer}), i.e. `null` (render as-is) unless changed
|
|
2005
|
-
* @return {delta.Delta<DConf>}
|
|
2006
|
-
*/
|
|
2007
|
-
toDeltaDeep (opts = {}) {
|
|
2008
|
-
return /** @type {any} */ (this.toDelta({ ...opts, deep: true }))
|
|
2009
|
-
}
|
|
2010
|
-
|
|
2011
|
-
/**
|
|
2012
|
-
* Apply a {@link Delta} on this shared type.
|
|
2013
|
-
*
|
|
2014
|
-
* @param {delta.DeltaAny} d The changes to apply on this element.
|
|
2015
|
-
* @param {any} [origin] Origin of the transaction that applies this delta (stored on
|
|
2016
|
-
* `transaction.origin` and forwarded verbatim on the emitted `'delta'` event, so listeners can
|
|
2017
|
-
* recognize — and skip — changes they produced themselves; see the lib0 `RDT` spec). Defaults to `null`.
|
|
2018
|
-
* @param {Object} [opts]
|
|
2019
|
-
* @param {AbstractRenderer?} [opts.renderer] - renders the content (with attributions); defaults to this type's active renderer (see {@link YNode#useRenderer}), i.e. `null` (render as-is) unless changed
|
|
2020
|
-
* @return {delta.DeltaBuilder<any>?} The lib0 `RDT` "fix" of this apply — a change measured against the
|
|
2021
|
-
* caller's expected state (`old.apply(d)`) that transforms it into the actual state, or `null`
|
|
2022
|
-
* when `d` applied cleanly. A fix is produced when `d` (or a nested `modify`/`modifyAttr`)
|
|
2023
|
-
* addresses a *deleted but rendered* node (e.g. a suggestion-deleted paragraph under a
|
|
2024
|
-
* DiffRenderer): that part of the change is not applied to the document, and its inverse
|
|
2025
|
-
* (`lib0-v14/delta` `inverse` against the node's rendered state) is returned — the change is
|
|
2026
|
-
* immediately reverted.
|
|
2027
|
-
*
|
|
2028
|
-
* @public
|
|
2029
|
-
*/
|
|
2030
|
-
applyDelta (d, origin = null, { renderer = this._renderer } = {}) {
|
|
2031
|
-
if (d.isEmpty()) return null
|
|
2032
|
-
if (this.doc == null) {
|
|
2033
|
-
(this._prelim || (this._prelim = /** @type {any} */ (delta.create()))).apply(d)
|
|
2034
|
-
return null
|
|
2035
|
-
}
|
|
2036
|
-
const titem = this._item
|
|
2037
|
-
if (titem !== null && titem.deleted) {
|
|
2038
|
-
if (rendererContentLength(renderer, titem) > 0) {
|
|
2039
|
-
// deleted, but still rendered (e.g. a suggestion-deleted node): apply nothing — revert the
|
|
2040
|
-
// whole change and return its inverse (against the rendered state the caller addressed)
|
|
2041
|
-
const inv = delta.inverse(d, /** @type {any} */ (this.toDeltaDeep({ renderer })))
|
|
2042
|
-
return inv.isEmpty() ? null : /** @type {any} */ (inv)
|
|
2043
|
-
}
|
|
2044
|
-
return null // invisible deleted type: the caller's view shows nothing here — silently drop
|
|
2045
|
-
}
|
|
2046
|
-
// @todo this was moved here from ytext. Make this more generic
|
|
2047
|
-
return transact(this.doc, transaction => {
|
|
2048
|
-
/**
|
|
2049
|
-
* The accumulated fix. Its coordinates live in the caller's *expected* space, so they are
|
|
2050
|
-
* tracked from `d`'s own ops (`expectedIndex`) — not `currPos.index`, which also counts
|
|
2051
|
-
* content that a delete over attributed-deleted ranges leaves rendered.
|
|
2052
|
-
*
|
|
2053
|
-
* @type {delta.DeltaBuilder<any>?}
|
|
2054
|
-
*/
|
|
2055
|
-
let fix = null
|
|
2056
|
-
let fixLen = 0
|
|
2057
|
-
let expectedIndex = 0
|
|
2058
|
-
/**
|
|
2059
|
-
* @param {delta.DeltaAny?} childFix
|
|
2060
|
-
* @param {{ [k:string]: any }} [invFormat]
|
|
2061
|
-
*/
|
|
2062
|
-
const appendModifyFix = (childFix, invFormat) => {
|
|
2063
|
-
const f = fix ?? (fix = /** @type {any} */ (delta.create()))
|
|
2064
|
-
expectedIndex > fixLen && f.retain(expectedIndex - fixLen)
|
|
2065
|
-
f.modify(/** @type {any} */ (childFix ?? delta.create().done(false)), invFormat)
|
|
2066
|
-
fixLen = expectedIndex + 1
|
|
2067
|
-
}
|
|
2068
|
-
const currPos = new ItemTextListPosition(null, this._start, 0, new Map(), renderer)
|
|
2069
|
-
/**
|
|
2070
|
-
* Seed the cursor from a format-aware search marker when the first op
|
|
2071
|
-
* retains a distant position. When a marker at-or-left of the target
|
|
2072
|
-
* carries a formats snapshot the cursor can start there directly, and
|
|
2073
|
-
* the leading retain is shortened by the seeded index — the only op
|
|
2074
|
-
* needing adjustment, since every later op is position-relative anyway.
|
|
2075
|
-
* Seeding is restricted to `renderer === null`, where
|
|
2076
|
-
* `rendererContentLength` counts countable/non-deleted length — exactly
|
|
2077
|
-
* the space `marker.index` records.
|
|
2078
|
-
*/
|
|
2079
|
-
let firstRetainAdjust = 0
|
|
2080
|
-
const op0 = d.children[Symbol.iterator]().next().value
|
|
2081
|
-
if (
|
|
2082
|
-
// `op0.format == null && op0.attribution == null`: only a *pure*
|
|
2083
|
-
// positioning retain may be shortened (lib0's `_isPlainRetain`
|
|
2084
|
-
// convention) — a retain carrying formats/attribution applies them
|
|
2085
|
-
// across its whole traversed range, so skipping its head would
|
|
2086
|
-
// silently drop that work for `[0, seededIndex)`.
|
|
2087
|
-
renderer === null && op0 !== undefined && delta.$retainOp.check(op0) && op0.retain > 0 &&
|
|
2088
|
-
op0.format == null && op0.attribution == null && this._searchMarker !== null
|
|
2089
|
-
) {
|
|
2090
|
-
// Best format-aware checkpoint at-or-left of the retained position.
|
|
2091
|
-
// A marker anchored *right* of the target can still seed if the gap is
|
|
2092
|
-
// crossed by stepping left over countable/non-deleted items only —
|
|
2093
|
-
// format state doesn't change across pure content (a format marker or
|
|
2094
|
-
// tombstone in the gap stops the walk and rejects the marker). Anchor
|
|
2095
|
-
// linkage is verified because records can outlive their items.
|
|
2096
|
-
let best = null
|
|
2097
|
-
let bestP = null
|
|
2098
|
-
let bestIndex = 0
|
|
2099
|
-
for (let i = 0; i < this._searchMarker.length; i++) {
|
|
2100
|
-
const m = this._searchMarker[i]
|
|
2101
|
-
if (m.formats === null || m.index <= 0) continue
|
|
2102
|
-
// step left over pure content until at-or-below the target
|
|
2103
|
-
let p = m.p
|
|
2104
|
-
let idx = m.index
|
|
2105
|
-
let steps = 0
|
|
2106
|
-
while (idx > op0.retain && p.left !== null && !p.left.deleted && p.left.countable && steps < 64) {
|
|
2107
|
-
p = p.left
|
|
2108
|
-
idx -= p.length
|
|
2109
|
-
steps++
|
|
2110
|
-
}
|
|
2111
|
-
if (
|
|
2112
|
-
idx <= op0.retain && (best === null || idx > bestIndex) &&
|
|
2113
|
-
(p.left === null ? this._start === p : p.left.right === p) &&
|
|
2114
|
-
(p.right === null || p.right.left === p) &&
|
|
2115
|
-
// `p` must be the *first* item at its index: upstream's walk
|
|
2116
|
-
// stops on the first unprocessed item at the target — including
|
|
2117
|
-
// format markers and deleted items sharing the index — so an
|
|
2118
|
-
// anchor after same-index non-countable/deleted items would seed
|
|
2119
|
-
// a different list position (correct state, wrong item order).
|
|
2120
|
-
(p.left === null || (!p.left.deleted && p.left.countable))
|
|
2121
|
-
) {
|
|
2122
|
-
best = m
|
|
2123
|
-
bestP = p
|
|
2124
|
-
bestIndex = idx
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
if (best !== null) {
|
|
2128
|
-
currPos.left = (/** @type {Item} */ (bestP)).left
|
|
2129
|
-
currPos.right = bestP
|
|
2130
|
-
currPos.index = bestIndex
|
|
2131
|
-
currPos.currentFormats = map.copy(/** @type {Map<string,any>} */ (best.formats))
|
|
2132
|
-
firstRetainAdjust = bestIndex
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
for (const op of d.children) {
|
|
2136
|
-
if (delta.$textOp.check(op)) {
|
|
2137
|
-
insertContent(transaction, /** @type {any} */ (this), currPos, new ContentString(op.insert), op.format || {})
|
|
2138
|
-
expectedIndex += op.length
|
|
2139
|
-
} else if (delta.$insertOp.check(op)) {
|
|
2140
|
-
insertContentHelper(transaction, this, currPos, op.insert, op.format || {})
|
|
2141
|
-
expectedIndex += op.length
|
|
2142
|
-
} else if (delta.$retainOp.check(op)) {
|
|
2143
|
-
currPos.formatText(transaction, /** @type {any} */ (this), op.retain - firstRetainAdjust, op.format || {})
|
|
2144
|
-
firstRetainAdjust = 0
|
|
2145
|
-
expectedIndex += op.length
|
|
2146
|
-
// quiescent point at a positioning boundary — plant a marker so a
|
|
2147
|
-
// subsequent delta targeting this position can seed its cursor
|
|
2148
|
-
if (renderer === null) plantMarker(/** @type {any} */ (this), currPos)
|
|
2149
|
-
} else if (delta.$deleteOp.check(op)) {
|
|
2150
|
-
deleteText(transaction, currPos, op.delete)
|
|
2151
|
-
} else if (delta.$modifyOp.check(op)) {
|
|
2152
|
-
let item = currPos.right
|
|
2153
|
-
while (item !== null && rendererContentLength(renderer, item) === 0) { item = item.right }
|
|
2154
|
-
if (item == null || item.content.constructor !== ContentType) { error.unexpectedCase() }
|
|
2155
|
-
if (item.deleted) {
|
|
2156
|
-
// deleted but rendered: revert instead of apply. Advance the cursor first (populating
|
|
2157
|
-
// `currentFormats` with any markers up to the node) without applying `op.format`, then
|
|
2158
|
-
// recurse — the child's deleted-guard applies nothing and returns the inverse.
|
|
2159
|
-
currPos.formatText(transaction, /** @type {any} */ (this), 1, {})
|
|
2160
|
-
/** @type {{ [k:string]: any }|undefined} */
|
|
2161
|
-
let invFormat
|
|
2162
|
-
for (const k in op.format) {
|
|
2163
|
-
(invFormat ?? (invFormat = {}))[k] = currPos.currentFormats.get(k) ?? null
|
|
2164
|
-
}
|
|
2165
|
-
const childFix = /** @type {ContentType} */ (item.content).type.applyDelta(op.value, origin, { renderer })
|
|
2166
|
-
if (childFix !== null || invFormat !== undefined) {
|
|
2167
|
-
appendModifyFix(childFix, invFormat)
|
|
2168
|
-
}
|
|
2169
|
-
} else {
|
|
2170
|
-
const childFix = /** @type {ContentType} */ (item.content).type.applyDelta(op.value, origin, { renderer })
|
|
2171
|
-
currPos.formatText(transaction, /** @type {any} */ (this), 1, op.format || {})
|
|
2172
|
-
if (childFix !== null) {
|
|
2173
|
-
appendModifyFix(childFix)
|
|
2174
|
-
}
|
|
2175
|
-
}
|
|
2176
|
-
expectedIndex += 1
|
|
2177
|
-
} else {
|
|
2178
|
-
error.unexpectedCase()
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
// Leave a format-aware marker at the deepest position this delta
|
|
2182
|
-
// reached — subsequent deltas targeting nearby positions seed their
|
|
2183
|
-
// cursor from it.
|
|
2184
|
-
if (renderer === null) plantMarker(/** @type {any} */ (this), currPos)
|
|
2185
|
-
for (const op of d.attrs) {
|
|
2186
|
-
if (delta.$setAttrOp.check(op)) {
|
|
2187
|
-
nodeMapSet(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key), op.value)
|
|
2188
|
-
} else if (delta.$deleteAttrOp.check(op)) {
|
|
2189
|
-
nodeMapDelete(transaction, /** @type {any} */ (this), /** @type {any} */ (op.key))
|
|
2190
|
-
} else {
|
|
2191
|
-
// modifyAttr — locate the target renderer-aware: a deleted map value may still be rendered
|
|
2192
|
-
const mapItem = this._map.get(/** @type {any} */ (op.key))
|
|
2193
|
-
const sub = mapItem === undefined
|
|
2194
|
-
? undefined
|
|
2195
|
-
: (mapItem.deleted
|
|
2196
|
-
? (mapItem.content.constructor === ContentType && rendererContentLength(renderer, mapItem) > 0
|
|
2197
|
-
? /** @type {ContentType} */ (mapItem.content).type
|
|
2198
|
-
: undefined)
|
|
2199
|
-
: mapItem.content.getContent()[mapItem.length - 1])
|
|
2200
|
-
if (!(sub instanceof YNode)) {
|
|
2201
|
-
error.unexpectedCase()
|
|
2202
|
-
}
|
|
2203
|
-
const subFix = sub.applyDelta(op.value, origin, { renderer })
|
|
2204
|
-
if (subFix !== null) {
|
|
2205
|
-
const f = fix ?? (fix = /** @type {any} */ (delta.create()))
|
|
2206
|
-
f.modifyAttr(/** @type {any} */ (op.key), /** @type {any} */ (subFix))
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
}
|
|
2210
|
-
return fix !== null && !(/** @type {delta.DeltaBuilder<any>} */ (fix).done(false).isEmpty()) ? fix : null
|
|
2211
|
-
}, origin)
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
/**
|
|
2215
|
-
* Makes a copy of this data type that can be included somewhere else.
|
|
2216
|
-
*
|
|
2217
|
-
* Note that the content is only readable _after_ it has been included somewhere in the Ydoc.
|
|
2218
|
-
*
|
|
2219
|
-
* @return {YNode<DConf>}
|
|
2220
|
-
*/
|
|
2221
|
-
clone () {
|
|
2222
|
-
const cpy = this._copy()
|
|
2223
|
-
cpy.applyDelta(this.toDeltaDeep())
|
|
2224
|
-
return cpy
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
/**
|
|
2228
|
-
* Removes all elements from this YMap.
|
|
2229
|
-
*/
|
|
2230
|
-
clearAttrs () {
|
|
2231
|
-
const d = delta.create()
|
|
2232
|
-
this.forEachAttr((_, key) => {
|
|
2233
|
-
d.deleteAttr(/** @type {any} */ (key))
|
|
2234
|
-
})
|
|
2235
|
-
this.applyDelta(d)
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
/**
|
|
2239
|
-
* Removes an attribute from this YXmlElement.
|
|
2240
|
-
*
|
|
2241
|
-
* @param {string} attributeName The attribute name that is to be removed.
|
|
2242
|
-
*
|
|
2243
|
-
* @public
|
|
2244
|
-
*/
|
|
2245
|
-
deleteAttr (attributeName) {
|
|
2246
|
-
this.applyDelta(delta.create().deleteAttr(attributeName).done())
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
/**
|
|
2250
|
-
* Sets or updates an attribute.
|
|
2251
|
-
*
|
|
2252
|
-
* @template {Exclude<keyof delta.DeltaConfGetAttrs<DConf>,symbol>} KEY
|
|
2253
|
-
* @template {delta.DeltaConfGetAttrs<DConf>[KEY]} VAL
|
|
2254
|
-
*
|
|
2255
|
-
* @param {KEY} attributeName The attribute name that is to be set.
|
|
835
|
+
* @template VAL
|
|
836
|
+
* @param {string} attributeName The attribute name that is to be set.
|
|
2256
837
|
* @param {VAL} attributeValue The attribute value that is to be set.
|
|
2257
838
|
* @return {VAL}
|
|
2258
|
-
*
|
|
2259
|
-
* @public
|
|
2260
839
|
*/
|
|
2261
840
|
setAttr (attributeName, attributeValue) {
|
|
2262
|
-
this.
|
|
841
|
+
if (this.doc == null) {
|
|
842
|
+
prelimOf(this).attrs[attributeName] = { value: attributeValue }
|
|
843
|
+
} else if (this._item === null || !this._item.deleted) {
|
|
844
|
+
transact(this.doc, transaction => nodeMapSet(transaction, this, attributeName, /** @type {any} */ (attributeValue)))
|
|
845
|
+
}
|
|
2263
846
|
return attributeValue
|
|
2264
847
|
}
|
|
2265
848
|
|
|
2266
849
|
/**
|
|
2267
850
|
* Returns an attribute value that belongs to the attribute name.
|
|
2268
851
|
*
|
|
2269
|
-
* @
|
|
2270
|
-
* @
|
|
2271
|
-
* @return {delta.DeltaConfGetAttrs<DConf>[KEY]|undefined} The queried attribute value.
|
|
2272
|
-
* @public
|
|
852
|
+
* @param {string} attributeName The attribute name that identifies the queried value.
|
|
853
|
+
* @return {any} The queried attribute value.
|
|
2273
854
|
*/
|
|
2274
855
|
getAttr (attributeName) {
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
/**
|
|
2279
|
-
* Returns whether an attribute exists
|
|
2280
|
-
*
|
|
2281
|
-
* @param {string} attributeName The attribute name to check for existence.
|
|
2282
|
-
* @return {boolean} whether the attribute exists.
|
|
2283
|
-
*
|
|
2284
|
-
* @public
|
|
2285
|
-
*/
|
|
2286
|
-
hasAttr (attributeName) {
|
|
2287
|
-
return /** @type {any} */ (nodeMapHas(this, attributeName))
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
/**
|
|
2291
|
-
* Returns all attribute name/value pairs in a JSON Object.
|
|
2292
|
-
*
|
|
2293
|
-
* @return {{ [Key in Extract<keyof delta.DeltaConfGetAttrs<DConf>,string>]?: delta.DeltaConfGetAttrs<DConf>[Key]}} A JSON Object that describes the attributes.
|
|
2294
|
-
*
|
|
2295
|
-
* @public
|
|
2296
|
-
*/
|
|
2297
|
-
getAttrs () {
|
|
2298
|
-
return /** @type {any} */ (nodeMapGetAll(this))
|
|
856
|
+
const val = this._map.get(attributeName)
|
|
857
|
+
return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
|
|
2299
858
|
}
|
|
2300
859
|
|
|
2301
860
|
/**
|
|
2302
|
-
* Inserts new content at an index
|
|
2303
|
-
*
|
|
2304
|
-
* Important: This function expects an array of content. Not just a content
|
|
2305
|
-
* object. The reason for this "weirdness" is that inserting several elements
|
|
2306
|
-
* is very efficient when it is done as a single operation.
|
|
2307
|
-
*
|
|
2308
|
-
* @example
|
|
2309
|
-
* // Insert character 'a' at position 0
|
|
2310
|
-
* yarray.insert(0, ['a'])
|
|
2311
|
-
* // Insert numbers 1, 2 at position 1
|
|
2312
|
-
* yarray.insert(1, [1, 2])
|
|
861
|
+
* Inserts new content at an index — a string, or an array of elements (JSON values, nodes) —
|
|
862
|
+
* as `insertInGap` places it (YP19: one sequence write). A deleted node is left as it is.
|
|
2313
863
|
*
|
|
2314
864
|
* @param {number} index The index to insert content at.
|
|
2315
|
-
* @param {Array<
|
|
2316
|
-
* @param {delta.Formats} [format]
|
|
865
|
+
* @param {Array<any>|string} content
|
|
2317
866
|
*/
|
|
2318
|
-
insert (index, content
|
|
2319
|
-
|
|
867
|
+
insert (index, content) {
|
|
868
|
+
if ((typeof content !== 'string' && !Array.isArray(content)) || content.length === 0) return
|
|
869
|
+
if (this.doc == null) {
|
|
870
|
+
const runs = prelimOf(this).runs
|
|
871
|
+
runs.splice(splitRuns(runs, index), 0, content.slice())
|
|
872
|
+
mergeRuns(runs)
|
|
873
|
+
return
|
|
874
|
+
}
|
|
875
|
+
if (this._item !== null && this._item.deleted) return
|
|
876
|
+
this.insertInGap(index, content)
|
|
2320
877
|
}
|
|
2321
878
|
|
|
2322
879
|
// YP7 begin (edytor fork: insertAtGapEnd — see UPSTREAM.md YP7)
|
|
@@ -2350,9 +907,8 @@ export class YNode extends ObservableV2 {
|
|
|
2350
907
|
insertInGap (index, content) {
|
|
2351
908
|
if (this.doc == null) throw new Error('insertInGap: node is not integrated')
|
|
2352
909
|
transact(this.doc, transaction => {
|
|
2353
|
-
const parts = typeof content === 'string' ? [new ContentString(content)] : contentsOf(content)
|
|
2354
910
|
let at = index
|
|
2355
|
-
for (const part of
|
|
911
|
+
for (const part of contentsOf(content)) {
|
|
2356
912
|
insertInGapHelper(transaction, this, at, part)
|
|
2357
913
|
at += part.getLength()
|
|
2358
914
|
}
|
|
@@ -2382,408 +938,73 @@ export class YNode extends ObservableV2 {
|
|
|
2382
938
|
// YP13 end
|
|
2383
939
|
|
|
2384
940
|
/**
|
|
2385
|
-
*
|
|
2386
|
-
*
|
|
2387
|
-
* Important: This function expects an array of content. Not just a content
|
|
2388
|
-
* object. The reason for this "weirdness" is that inserting several elements
|
|
2389
|
-
* is very efficient when it is done as a single operation.
|
|
2390
|
-
*
|
|
2391
|
-
* @example
|
|
2392
|
-
* // Insert character 'a' at position 0
|
|
2393
|
-
* yarray.insert(0, ['a'])
|
|
2394
|
-
* // Insert numbers 1, 2 at position 1
|
|
2395
|
-
* yarray.insert(1, [1, 2])
|
|
2396
|
-
*
|
|
2397
|
-
* @param {number} index The index to insert content at.
|
|
2398
|
-
* @param {number} length The index to insert content at.
|
|
2399
|
-
* @param {delta.Formats} formats
|
|
2400
|
-
*
|
|
2401
|
-
*/
|
|
2402
|
-
format (index, length, formats) {
|
|
2403
|
-
this.applyDelta(delta.create().retain(index).retain(length, formats))
|
|
2404
|
-
}
|
|
2405
|
-
|
|
2406
|
-
/**
|
|
2407
|
-
* Appends content to this YArray.
|
|
2408
|
-
*
|
|
2409
|
-
* @param {Array<delta.DeltaConfGetChildren<DConf>>|delta.DeltaConfGetText<DConf>} content Array of content to append.
|
|
2410
|
-
*
|
|
2411
|
-
* @todo Use the following implementation in all types.
|
|
2412
|
-
*/
|
|
2413
|
-
push (content) {
|
|
2414
|
-
this.insert(this.length, content)
|
|
2415
|
-
}
|
|
2416
|
-
|
|
2417
|
-
/**
|
|
2418
|
-
* Prepends content to this YArray.
|
|
2419
|
-
*
|
|
2420
|
-
* @param {delta.DeltaConfGetText<DConf>} content Array of content to prepend.
|
|
2421
|
-
*/
|
|
2422
|
-
unshift (content) {
|
|
2423
|
-
this.insert(0, content)
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
/**
|
|
2427
|
-
* Deletes elements starting from an index.
|
|
941
|
+
* Deletes elements starting from an index. A deleted node is left as it is.
|
|
2428
942
|
*
|
|
2429
943
|
* @param {number} index Index at which to start deleting elements
|
|
2430
944
|
* @param {number} length The number of elements to remove. Defaults to 1.
|
|
2431
945
|
*/
|
|
2432
946
|
delete (index, length = 1) {
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
*/
|
|
2442
|
-
get (index) {
|
|
2443
|
-
return typeListGet(this, index)
|
|
2444
|
-
}
|
|
2445
|
-
|
|
2446
|
-
/**
|
|
2447
|
-
* Returns a portion of this YXmlFragment into a JavaScript Array selected
|
|
2448
|
-
* from start to end (end not included).
|
|
2449
|
-
*
|
|
2450
|
-
* @param {number} [start]
|
|
2451
|
-
* @param {number} [end]
|
|
2452
|
-
* @return {Array<delta.DeltaConfGetChildren<DConf>>}
|
|
2453
|
-
*/
|
|
2454
|
-
slice (start = 0, end = this.length) {
|
|
2455
|
-
return typeListSlice(this, start, end)
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
/**
|
|
2459
|
-
* @todo refactor this, this should use getContent only!
|
|
2460
|
-
*
|
|
2461
|
-
* Transforms this YArray to a JavaScript Array.
|
|
2462
|
-
*
|
|
2463
|
-
* @return {Array<delta.DeltaConfGetChildren<DConf> | delta.DeltaConfGetText<DConf>>}
|
|
2464
|
-
*/
|
|
2465
|
-
toArray () {
|
|
2466
|
-
const dcontent = this.toDelta()
|
|
2467
|
-
/**
|
|
2468
|
-
* @type {Array<any>}
|
|
2469
|
-
*/
|
|
2470
|
-
const children = []
|
|
2471
|
-
for (const child of dcontent.children) {
|
|
2472
|
-
if (delta.$insertOp.check(child)) {
|
|
2473
|
-
for (let i = 0; i < child.insert.length; i++) {
|
|
2474
|
-
children.push(child.insert[i])
|
|
2475
|
-
}
|
|
2476
|
-
} else if (delta.$textOp.check(child)) {
|
|
2477
|
-
children.push(child.insert)
|
|
947
|
+
if (!(length > 0)) return
|
|
948
|
+
if (this.doc == null) {
|
|
949
|
+
const runs = prelimOf(this).runs
|
|
950
|
+
let n = length
|
|
951
|
+
for (let i = splitRuns(runs, index); i < runs.length && n > 0; i++) {
|
|
952
|
+
const cut = Math.min(n, runs[i].length)
|
|
953
|
+
runs[i] = runs[i].slice(cut)
|
|
954
|
+
n -= cut
|
|
2478
955
|
}
|
|
956
|
+
mergeRuns(runs)
|
|
957
|
+
return
|
|
2479
958
|
}
|
|
2480
|
-
return
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
* @return {{ name?: string, attrs?: { [K:string|number]: any }, children?: Array<any> }}
|
|
2486
|
-
*/
|
|
2487
|
-
toJSON () {
|
|
2488
|
-
/**
|
|
2489
|
-
* @type {{[K:string]:any}}
|
|
2490
|
-
*/
|
|
2491
|
-
const attrs = this.getAttrs()
|
|
2492
|
-
for (const k in attrs) {
|
|
2493
|
-
const attr = attrs[k]
|
|
2494
|
-
attrs[k] = attr instanceof YNode ? attr.toJSON() : attr
|
|
2495
|
-
}
|
|
2496
|
-
const children = this.toArray().map(child => child instanceof YNode ? /** @type {any} */ (child.toJSON()) : child)
|
|
2497
|
-
/**
|
|
2498
|
-
* @type {any}
|
|
2499
|
-
*/
|
|
2500
|
-
const res = {}
|
|
2501
|
-
if (this.name != null) {
|
|
2502
|
-
res.name = this.name
|
|
2503
|
-
}
|
|
2504
|
-
if (this.length > 0) {
|
|
2505
|
-
res.children = children
|
|
2506
|
-
}
|
|
2507
|
-
if (this.attrSize > 0) {
|
|
2508
|
-
res.attrs = attrs
|
|
2509
|
-
}
|
|
2510
|
-
return res
|
|
2511
|
-
}
|
|
2512
|
-
|
|
2513
|
-
/**
|
|
2514
|
-
* @param {object} opts
|
|
2515
|
-
* @param {boolean} [opts.forceTag] enforce creating a surrouning <name /> tag, even if it is null.
|
|
2516
|
-
*/
|
|
2517
|
-
toString ({ forceTag = false } = {}) {
|
|
2518
|
-
/**
|
|
2519
|
-
* @type {Array<[string|number,string]>}
|
|
2520
|
-
*/
|
|
2521
|
-
const attrs = []
|
|
2522
|
-
this.forEachAttr((attr, key) => {
|
|
2523
|
-
attrs.push([(key), /** @type {any} */ (attr) instanceof YNode ? attr.toString({ forceTag: true }) : JSON.stringify(attr)])
|
|
959
|
+
if (this._item !== null && this._item.deleted) return
|
|
960
|
+
transact(this.doc, transaction => {
|
|
961
|
+
const currPos = cursorAt(transaction, this, index)
|
|
962
|
+
deleteText(transaction, currPos, length)
|
|
963
|
+
plantMarker(this, currPos)
|
|
2524
964
|
})
|
|
2525
|
-
const attrsString = (attrs.length > 0 ? ' ' : '') + attrs.sort((a, b) => a[0].toString() < b[0].toString() ? -1 : 1).map(attr => attr[0] + '=' + attr[1]).join(' ')
|
|
2526
|
-
/**
|
|
2527
|
-
* @type {string}
|
|
2528
|
-
*/
|
|
2529
|
-
const children = this.toArray().map(c => s.$string.check(c) ? c : (c instanceof YNode ? c.toString({ forceTag: true }) : JSON.stringify(c))).join('')
|
|
2530
|
-
if (this.name == null && !forceTag && attrs.length === 0) {
|
|
2531
|
-
return children
|
|
2532
|
-
}
|
|
2533
|
-
if (this.length === 0) {
|
|
2534
|
-
return `<${this.name ?? ''}${attrsString} />`
|
|
2535
|
-
}
|
|
2536
|
-
return `<${this.name ?? ''}${attrsString}>${children}</${this.name ?? ''}>`
|
|
2537
|
-
}
|
|
2538
|
-
|
|
2539
|
-
/**
|
|
2540
|
-
* Returns an Array with the result of calling a provided function on every
|
|
2541
|
-
* child-element.
|
|
2542
|
-
*
|
|
2543
|
-
* @template M
|
|
2544
|
-
* @param {(child:delta.DeltaConfGetChildren<DConf>|delta.DeltaConfGetText<DConf>,index:number)=>M} f Function that produces an element of the new Array
|
|
2545
|
-
* @return {Array<M>} A new array with each element being the result of the
|
|
2546
|
-
* callback function
|
|
2547
|
-
*/
|
|
2548
|
-
map (f) {
|
|
2549
|
-
return this.toArray().map(f)
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
/**
|
|
2553
|
-
* Executes a provided function once on every element of this YArray.
|
|
2554
|
-
*
|
|
2555
|
-
* @param {(child:delta.DeltaConfGetChildren<DConf>|delta.DeltaConfGetText<DConf>,index:number)=>any} f Function that produces an element of the new Array
|
|
2556
|
-
*/
|
|
2557
|
-
forEach (f) {
|
|
2558
|
-
return this.toArray().forEach(f)
|
|
2559
965
|
}
|
|
2560
966
|
|
|
2561
967
|
/**
|
|
2562
968
|
* Executes a provided function on once on every key-value pair.
|
|
2563
969
|
*
|
|
2564
|
-
* @param {(val:
|
|
970
|
+
* @param {(val:any,key:string,ynode:this)=>any} f
|
|
2565
971
|
*/
|
|
2566
972
|
forEachAttr (f) {
|
|
2567
973
|
this._map.forEach((item, key) => {
|
|
2568
974
|
if (!item.deleted) {
|
|
2569
|
-
f(item.content.getContent()[item.length - 1],
|
|
975
|
+
f(item.content.getContent()[item.length - 1], key, this)
|
|
2570
976
|
}
|
|
2571
977
|
})
|
|
2572
978
|
}
|
|
2573
979
|
|
|
2574
980
|
/**
|
|
2575
|
-
*
|
|
981
|
+
* The keys of the live attributes.
|
|
2576
982
|
*
|
|
2577
|
-
* @return {IterableIterator<
|
|
983
|
+
* @return {IterableIterator<string>}
|
|
2578
984
|
*/
|
|
2579
|
-
attrKeys () {
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
/**
|
|
2584
|
-
* Returns the values for each element in the YMap Type.
|
|
2585
|
-
*
|
|
2586
|
-
* @return {IterableIterator<delta.DeltaConfGetAttrs<DConf>[any]>}
|
|
2587
|
-
*/
|
|
2588
|
-
attrValues () {
|
|
2589
|
-
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
/**
|
|
2593
|
-
* Returns an Iterator of [key, value] pairs
|
|
2594
|
-
*
|
|
2595
|
-
* @return {IterableIterator<{ [K in keyof delta.DeltaConfGetAttrs<DConf>]: [K,delta.DeltaConfGetAttrs<DConf>[K]] }[any]>}
|
|
2596
|
-
*/
|
|
2597
|
-
attrEntries () {
|
|
2598
|
-
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]]))
|
|
2599
|
-
}
|
|
2600
|
-
|
|
2601
|
-
/**
|
|
2602
|
-
* Returns the number of stored attributes (count of key/value pairs)
|
|
2603
|
-
*
|
|
2604
|
-
* @return {number}
|
|
2605
|
-
*/
|
|
2606
|
-
get attrSize () {
|
|
2607
|
-
return [...createMapIterator(this)].length
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
/**
|
|
2611
|
-
* @param {this} other
|
|
2612
|
-
*/
|
|
2613
|
-
[traits.EqualityTraitSymbol] (other) {
|
|
2614
|
-
return this.toDelta().equals(other.toDelta())
|
|
985
|
+
* attrKeys () {
|
|
986
|
+
for (const [key, item] of this._map) {
|
|
987
|
+
if (!item.deleted) yield key
|
|
988
|
+
}
|
|
2615
989
|
}
|
|
2616
990
|
|
|
2617
991
|
/**
|
|
2618
|
-
*
|
|
2619
|
-
*
|
|
2620
|
-
* Transform the properties of this type to binary and write it to an
|
|
2621
|
-
* BinaryEncoder.
|
|
992
|
+
* Write this node's type reference (and name), sent with the item that holds it.
|
|
2622
993
|
*
|
|
2623
|
-
*
|
|
2624
|
-
*
|
|
2625
|
-
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to.
|
|
994
|
+
* @param {UpdateEncoder} encoder The encoder to write data to.
|
|
2626
995
|
*/
|
|
2627
996
|
_write (encoder) {
|
|
2628
997
|
encoder.writeTypeRef(this._legacyTypeRef)
|
|
2629
998
|
switch (this._legacyTypeRef) {
|
|
2630
999
|
case YXmlElementRefID:
|
|
2631
1000
|
case YXmlHookRefID: {
|
|
2632
|
-
encoder.writeKey(this.name)
|
|
1001
|
+
encoder.writeKey(/** @type {string} */ (this.name))
|
|
2633
1002
|
break
|
|
2634
1003
|
}
|
|
2635
1004
|
}
|
|
2636
1005
|
}
|
|
2637
1006
|
}
|
|
2638
1007
|
|
|
2639
|
-
/**
|
|
2640
|
-
* Schema of a {@link YNode} with any delta configuration. This is where the nominal `y:node` tag
|
|
2641
|
-
* lives - {@link $node} builds on it. Mirrors lib0's `$deltaAny` / `$delta` split.
|
|
2642
|
-
*/
|
|
2643
|
-
export const $nodeAny = /** @type {s.Schema<YNode<any>>} */ (YNode.prototype.$type = s.$type('y:node', YNode))
|
|
2644
|
-
|
|
2645
|
-
/**
|
|
2646
|
-
* @param {StructStore} store
|
|
2647
|
-
* @param {IdSet} items
|
|
2648
|
-
*/
|
|
2649
|
-
export const computeModifiedFromItems = (store, items) => {
|
|
2650
|
-
/**
|
|
2651
|
-
* @type {Map<YNode,Set<null|string>>}
|
|
2652
|
-
*/
|
|
2653
|
-
const modified = new Map()
|
|
2654
|
-
iterateStructsByIdSetWithoutSplits(store, items, /** @param {Item | GC | Skip | null} item */ item => {
|
|
2655
|
-
while (item instanceof Item) {
|
|
2656
|
-
const parent = /** @type {YNode} */ (item.parent)
|
|
2657
|
-
const conf = map.setIfUndefined(modified, parent, set.create)
|
|
2658
|
-
if (conf.has(item.parentSub)) break // has already been marked as modified
|
|
2659
|
-
conf.add(item.parentSub)
|
|
2660
|
-
item = parent._item
|
|
2661
|
-
}
|
|
2662
|
-
})
|
|
2663
|
-
return modified
|
|
2664
|
-
}
|
|
2665
|
-
|
|
2666
|
-
/**
|
|
2667
|
-
* The active renderer's attribution overlay changed (e.g. a suggestion was accepted or rejected)
|
|
2668
|
-
* without a Y transaction on the type's doc, so no `YEvent` fires. Re-render the affected id
|
|
2669
|
-
* ranges and keep the RDT surface current, mirroring the per-transaction upkeep in
|
|
2670
|
-
* `cleanupTransactions`: patch the maintained {@link YNode#delta} cache and emit the change on
|
|
2671
|
-
* the `'delta'` channel. Wired to the renderer's `'change'` event by {@link YNode#useRenderer}.
|
|
2672
|
-
*
|
|
2673
|
-
* Ranges in `changes` that the doc has not (yet) integrated — e.g. a base-doc edit whose update
|
|
2674
|
-
* flows in only after the renderer event — simply render to nothing here; the later transaction
|
|
2675
|
-
* covers them through the regular event path.
|
|
2676
|
-
*
|
|
2677
|
-
* The cache patch must stay on the builder `apply` — see the {@link YNode#_delta}
|
|
2678
|
-
* fingerprint-memo invariant.
|
|
2679
|
-
*
|
|
2680
|
-
* @param {YNode<any>} type
|
|
2681
|
-
* @param {IdSet} changes
|
|
2682
|
-
* @param {any} origin
|
|
2683
|
-
*
|
|
2684
|
-
* @private
|
|
2685
|
-
* @function
|
|
2686
|
-
*/
|
|
2687
|
-
export const typeApplyRendererChange = (type, changes, origin) => {
|
|
2688
|
-
const hasDeltaListeners = (type._observers.get('delta')?.size ?? 0) > 0
|
|
2689
|
-
// no deleted-type guard: this handler only exists while a custom renderer is attached (see
|
|
2690
|
-
// `useRenderer`), and a custom renderer may still render a deleted type — its cache and 'delta'
|
|
2691
|
-
// channel must stay current through accept/reject overlay changes.
|
|
2692
|
-
if ((type._delta === null && !hasDeltaListeners) || type.doc == null) return
|
|
2693
|
-
const change = type.toDelta({ renderer: type._renderer, deep: true, itemsToRender: changes, retainInserts: true, retainDeletes: true })
|
|
2694
|
-
if (!change.isEmpty()) {
|
|
2695
|
-
type._delta?.apply(change)
|
|
2696
|
-
if (hasDeltaListeners) type.emit('delta', [change, origin])
|
|
2697
|
-
}
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
/**
|
|
2701
|
-
* @param {any} a
|
|
2702
|
-
* @param {any} b
|
|
2703
|
-
* @return {boolean}
|
|
2704
|
-
*/
|
|
2705
|
-
export const equalFormats = (a, b) => a === b || (typeof a === 'object' && typeof b === 'object' && a && b && object.equalFlat(a, b))
|
|
2706
|
-
|
|
2707
|
-
/**
|
|
2708
|
-
* @template {delta.DeltaConf} DConf
|
|
2709
|
-
* @typedef {delta.DeltaConfOverwrite<DConf, {
|
|
2710
|
-
* attrs: { [K in keyof delta.DeltaConfGetAttrs<DConf>]: DeltaToYNode<delta.DeltaConfGetAttrs<DConf>[K]> },
|
|
2711
|
-
* children: DeltaToYNode<delta.DeltaConfGetChildren<DConf>>
|
|
2712
|
-
* }>
|
|
2713
|
-
* } DeltaConfDeltaToYNode
|
|
2714
|
-
*/
|
|
2715
|
-
|
|
2716
|
-
/**
|
|
2717
|
-
* @template {any} Data
|
|
2718
|
-
* @typedef {Exclude<Data,delta.DeltaAny> | (Extract<Data,delta.DeltaAny> extends delta.Delta<infer DConf> ? (unknown extends DConf ? YNode<DConf> : never) : never)} DeltaToYNode
|
|
2719
|
-
*/
|
|
2720
|
-
|
|
2721
|
-
/**
|
|
2722
|
-
* @param {YNode<any>} type
|
|
2723
|
-
* @param {number} start
|
|
2724
|
-
* @param {number} end
|
|
2725
|
-
* @return {Array<any>}
|
|
2726
|
-
*
|
|
2727
|
-
* @private
|
|
2728
|
-
* @function
|
|
2729
|
-
*/
|
|
2730
|
-
export const typeListSlice = (type, start, end) => {
|
|
2731
|
-
type.doc ?? warnPrematureAccess()
|
|
2732
|
-
if (start < 0) {
|
|
2733
|
-
start = type._length + start
|
|
2734
|
-
}
|
|
2735
|
-
if (end < 0) {
|
|
2736
|
-
end = type._length + end
|
|
2737
|
-
}
|
|
2738
|
-
let len = end - start
|
|
2739
|
-
const cs = []
|
|
2740
|
-
let n = type._start
|
|
2741
|
-
while (n !== null && len > 0) {
|
|
2742
|
-
if (n.countable && !n.deleted) {
|
|
2743
|
-
const c = n.content.getContent()
|
|
2744
|
-
if (c.length <= start) {
|
|
2745
|
-
start -= c.length
|
|
2746
|
-
} else {
|
|
2747
|
-
for (let i = start; i < c.length && len > 0; i++) {
|
|
2748
|
-
cs.push(c[i])
|
|
2749
|
-
len--
|
|
2750
|
-
}
|
|
2751
|
-
start = 0
|
|
2752
|
-
}
|
|
2753
|
-
}
|
|
2754
|
-
n = n.right
|
|
2755
|
-
}
|
|
2756
|
-
return cs
|
|
2757
|
-
}
|
|
2758
|
-
|
|
2759
|
-
/**
|
|
2760
|
-
* @todo remove / inline this
|
|
2761
|
-
*
|
|
2762
|
-
* @param {YNode} type
|
|
2763
|
-
* @param {number} index
|
|
2764
|
-
* @return {any}
|
|
2765
|
-
*
|
|
2766
|
-
* @private
|
|
2767
|
-
* @function
|
|
2768
|
-
*/
|
|
2769
|
-
export const typeListGet = (type, index) => {
|
|
2770
|
-
type.doc ?? warnPrematureAccess()
|
|
2771
|
-
const marker = findMarker(type, index)
|
|
2772
|
-
let n = type._start
|
|
2773
|
-
if (marker !== null) {
|
|
2774
|
-
n = marker.p
|
|
2775
|
-
index -= marker.index
|
|
2776
|
-
}
|
|
2777
|
-
for (; n !== null; n = n.right) {
|
|
2778
|
-
if (!n.deleted && n.countable) {
|
|
2779
|
-
if (index < n.length) {
|
|
2780
|
-
return n.content.getContent()[index]
|
|
2781
|
-
}
|
|
2782
|
-
index -= n.length
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
}
|
|
2786
|
-
|
|
2787
1008
|
/**
|
|
2788
1009
|
* @todo inline this code
|
|
2789
1010
|
*
|
|
@@ -2832,9 +1053,7 @@ export const nodeMapSet = (transaction, parent, key, value) => {
|
|
|
2832
1053
|
content = new ContentBinary(/** @type {Uint8Array} */ (value))
|
|
2833
1054
|
break
|
|
2834
1055
|
default:
|
|
2835
|
-
if (
|
|
2836
|
-
content = createContentDocFromDoc(/** @type {Doc} */ (value))
|
|
2837
|
-
} else if (value instanceof YNode) {
|
|
1056
|
+
if (value instanceof YNode) {
|
|
2838
1057
|
content = new ContentType(/** @type {any} */ (value))
|
|
2839
1058
|
} else {
|
|
2840
1059
|
throw new Error('Unexpected content type')
|
|
@@ -2844,162 +1063,10 @@ export const nodeMapSet = (transaction, parent, key, value) => {
|
|
|
2844
1063
|
new Item(createID(ownClientId, doc.store.getClock(ownClientId)), left, left && left.lastId, null, null, parent, key, content).integrate(transaction, 0)
|
|
2845
1064
|
}
|
|
2846
1065
|
|
|
2847
|
-
/**
|
|
2848
|
-
* @param {YNode<any>} parent
|
|
2849
|
-
* @param {string} key
|
|
2850
|
-
* @return {Object<string,any>|number|null|Array<any>|string|Uint8Array|YNode<any>|undefined}
|
|
2851
|
-
*
|
|
2852
|
-
* @private
|
|
2853
|
-
* @function
|
|
2854
|
-
*/
|
|
2855
|
-
export const nodeMapGet = (parent, key) => {
|
|
2856
|
-
parent.doc ?? warnPrematureAccess()
|
|
2857
|
-
const val = parent._map.get(key)
|
|
2858
|
-
return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
|
|
2859
|
-
}
|
|
2860
|
-
|
|
2861
|
-
/**
|
|
2862
|
-
* @param {YNode<any>} parent
|
|
2863
|
-
* @return {Object<string,Object<string,any>|number|null|Array<any>|string|Uint8Array|YNode<any>|undefined>}
|
|
2864
|
-
*
|
|
2865
|
-
* @private
|
|
2866
|
-
* @function
|
|
2867
|
-
*/
|
|
2868
|
-
export const nodeMapGetAll = (parent) => {
|
|
2869
|
-
/**
|
|
2870
|
-
* @type {Object<string,any>}
|
|
2871
|
-
*/
|
|
2872
|
-
const res = {}
|
|
2873
|
-
parent.doc ?? warnPrematureAccess()
|
|
2874
|
-
parent._map.forEach((value, key) => {
|
|
2875
|
-
if (!value.deleted) {
|
|
2876
|
-
res[key] = value.content.getContent()[value.length - 1]
|
|
2877
|
-
}
|
|
2878
|
-
})
|
|
2879
|
-
return res
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
|
-
/**
|
|
2883
|
-
* @todo move this to getContent/getDelta
|
|
2884
|
-
*
|
|
2885
|
-
* Render the difference to another ydoc (which can be empty) and highlight the differences with
|
|
2886
|
-
* attributions.
|
|
2887
|
-
*
|
|
2888
|
-
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
|
|
2889
|
-
* attribution `{ isDeleted: true, .. }`.
|
|
2890
|
-
*
|
|
2891
|
-
* @template {delta.DeltaBuilderAny} TypeDelta
|
|
2892
|
-
* @param {TypeDelta} d
|
|
2893
|
-
* @param {YNode} parent
|
|
2894
|
-
* @param {Set<string|null>?} attrsToRender
|
|
2895
|
-
* @param {AbstractRenderer?} renderer
|
|
2896
|
-
* @param {boolean} deep
|
|
2897
|
-
* @param {Set<YNode>|Map<YNode,any>|null} [modified] - set of types that should be rendered as modified children
|
|
2898
|
-
* @param {IdSet?} [itemsToRender]
|
|
2899
|
-
* @param {any} [opts]
|
|
2900
|
-
* @param {any} [optsAll]
|
|
2901
|
-
*
|
|
2902
|
-
* @private
|
|
2903
|
-
* @function
|
|
2904
|
-
*/
|
|
2905
|
-
export const nodeMapGetDelta = (d, parent, attrsToRender, renderer, deep, modified, itemsToRender, opts, optsAll) => {
|
|
2906
|
-
// @todo support modified ops!
|
|
2907
|
-
/**
|
|
2908
|
-
* @param {Item} item
|
|
2909
|
-
* @param {string} key
|
|
2910
|
-
*/
|
|
2911
|
-
const renderAttrs = (item, key) => {
|
|
2912
|
-
/**
|
|
2913
|
-
* @type {{ deleted: boolean, attrs: Array<ContentAttribute<any>>?, content: AbstractContent }}
|
|
2914
|
-
*/
|
|
2915
|
-
let piece
|
|
2916
|
-
if (renderer === null || !renderer.hasItem(item)) {
|
|
2917
|
-
// generic fast path — the item's own fields, no renderer involved
|
|
2918
|
-
piece = { deleted: item.deleted, attrs: null, content: item.content }
|
|
2919
|
-
} else {
|
|
2920
|
-
/**
|
|
2921
|
-
* @type {Array<AttributedContent<any>>}
|
|
2922
|
-
*/
|
|
2923
|
-
const cs = []
|
|
2924
|
-
renderer.readContent(cs, item.id.client, item.id.clock, item.deleted, item.content, 1)
|
|
2925
|
-
if (cs.length === 0) return // the renderer surfaces nothing for this attribute (e.g. a diff renderer hiding an unchanged delete)
|
|
2926
|
-
piece = cs[cs.length - 1]
|
|
2927
|
-
}
|
|
2928
|
-
const { deleted, attrs, content } = piece
|
|
2929
|
-
const attribution = createAttributionFromAttributionItems(attrs, deleted)
|
|
2930
|
-
let c = array.last(content.getContent())
|
|
2931
|
-
if (deleted) {
|
|
2932
|
-
if (attribution != null) {
|
|
2933
|
-
// Item surfaced under attribution (suggestion view / diff renderer, either in snapshot mode
|
|
2934
|
-
// or in an event-driven render). The attribute is still observable in the rendered state, so
|
|
2935
|
-
// emit a positive `SetAttrOp` carrying the attribution metadata - matching how content
|
|
2936
|
-
// children are rendered for the same case (positive `InsertOp` with attribution, never
|
|
2937
|
-
// `DeleteOp`).
|
|
2938
|
-
// also re-emit when the change happened *inside* the still-rendered value (`modified`
|
|
2939
|
-
// contains the value type but the attr's own map item is not part of the change) — the
|
|
2940
|
-
// deleted value has no modifyAttr path, and a full-state `setAttr` replace is idempotent
|
|
2941
|
-
if (itemsToRender == null || itemsToRender.hasId(item.lastId) || (c instanceof YNode && modified != null && modified.has(c))) {
|
|
2942
|
-
if (deep && c instanceof YNode) {
|
|
2943
|
-
// full-state value render: a positive `setAttr` *replaces* the attr value on the
|
|
2944
|
-
// consuming side, so the nested type must render as its full attributed state
|
|
2945
|
-
// (change-scoped opts like `itemsToRender` would render bare retains here)
|
|
2946
|
-
c = /** @type {any} */(c).toDelta({ renderer, deep: true })
|
|
2947
|
-
}
|
|
2948
|
-
d.setAttr(key, c, attribution)
|
|
2949
|
-
}
|
|
2950
|
-
} else if (itemsToRender != null && itemsToRender.hasId(item.lastId)) {
|
|
2951
|
-
// Hard-deleted attribute within a change render: emit the `deleteAttr` op so consumers (the
|
|
2952
|
-
// `YEvent` delta, RDT bindings, the maintained `delta` cache) can apply the removal. In
|
|
2953
|
-
// full-state mode (`itemsToRender == null`) the attribute is simply omitted (above renders
|
|
2954
|
-
// run with `render === false` for such items, so nothing was emitted before either).
|
|
2955
|
-
d.deleteAttr(key, attribution)
|
|
2956
|
-
}
|
|
2957
|
-
} else if (deep && c instanceof YNode && modified?.has(c)) {
|
|
2958
|
-
d.modifyAttr(key, c.toDelta(opts))
|
|
2959
|
-
} else {
|
|
2960
|
-
if (deep && c instanceof YNode) {
|
|
2961
|
-
c = /** @type {any} */(c).toDelta(optsAll)
|
|
2962
|
-
}
|
|
2963
|
-
d.setAttr(key, c, attribution)
|
|
2964
|
-
}
|
|
2965
|
-
}
|
|
2966
|
-
if (attrsToRender == null) {
|
|
2967
|
-
parent._map.forEach(renderAttrs)
|
|
2968
|
-
} else {
|
|
2969
|
-
attrsToRender.forEach(key => key != null && renderAttrs(/** @type {Item} */ (parent._map.get(key)), key))
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
/**
|
|
2974
|
-
* @param {YNode<any>} parent
|
|
2975
|
-
* @param {string} key
|
|
2976
|
-
* @return {boolean}
|
|
2977
|
-
*
|
|
2978
|
-
* @private
|
|
2979
|
-
* @function
|
|
2980
|
-
*/
|
|
2981
|
-
export const nodeMapHas = (parent, key) => {
|
|
2982
|
-
parent.doc ?? warnPrematureAccess()
|
|
2983
|
-
const val = parent._map.get(key)
|
|
2984
|
-
return val !== undefined && !val.deleted
|
|
2985
|
-
}
|
|
2986
|
-
|
|
2987
|
-
/**
|
|
2988
|
-
* @param {YNode<any> & { _map: Map<string, Item> }} type
|
|
2989
|
-
* @return {IterableIterator<Array<any>>}
|
|
2990
|
-
*
|
|
2991
|
-
* @private
|
|
2992
|
-
* @function
|
|
2993
|
-
*/
|
|
2994
|
-
export const createMapIterator = type => {
|
|
2995
|
-
type.doc ?? warnPrematureAccess()
|
|
2996
|
-
return iterator.iteratorFilter(type._map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
|
|
2997
|
-
}
|
|
2998
|
-
|
|
2999
1066
|
/**
|
|
3000
1067
|
* @private
|
|
3001
1068
|
*
|
|
3002
|
-
* @param {
|
|
1069
|
+
* @param {UpdateDecoder} decoder
|
|
3003
1070
|
* @return {ContentType}
|
|
3004
1071
|
*/
|
|
3005
1072
|
export const readContentType = decoder => new ContentType(readYNode(decoder))
|
|
@@ -3007,55 +1074,19 @@ export const readContentType = decoder => new ContentType(readYNode(decoder))
|
|
|
3007
1074
|
/**
|
|
3008
1075
|
* @private
|
|
3009
1076
|
*
|
|
3010
|
-
* @param {
|
|
1077
|
+
* @param {UpdateDecoder} decoder
|
|
3011
1078
|
* @return {ContentString}
|
|
3012
1079
|
*/
|
|
3013
1080
|
export const readContentString = decoder => new ContentString(decoder.readString())
|
|
3014
1081
|
|
|
3015
1082
|
/**
|
|
3016
|
-
* @
|
|
3017
|
-
*
|
|
3018
|
-
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
3019
|
-
* @return {ContentJSON}
|
|
3020
|
-
*/
|
|
3021
|
-
export const readContentJSON = decoder => {
|
|
3022
|
-
const len = decoder.readLen()
|
|
3023
|
-
const cs = []
|
|
3024
|
-
for (let i = 0; i < len; i++) {
|
|
3025
|
-
const c = decoder.readString()
|
|
3026
|
-
if (c === 'undefined') {
|
|
3027
|
-
cs.push(undefined)
|
|
3028
|
-
} else {
|
|
3029
|
-
cs.push(JSON.parse(c))
|
|
3030
|
-
}
|
|
3031
|
-
}
|
|
3032
|
-
return new ContentJSON(cs)
|
|
3033
|
-
}
|
|
3034
|
-
|
|
3035
|
-
/**
|
|
3036
|
-
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
1083
|
+
* @param {UpdateDecoder} decoder
|
|
3037
1084
|
* @return {ContentFormat}
|
|
3038
1085
|
*/
|
|
3039
1086
|
export const readContentFormat = decoder => new ContentFormat(decoder.readKey(), decoder.readJSON())
|
|
3040
1087
|
|
|
3041
1088
|
/**
|
|
3042
|
-
* @
|
|
3043
|
-
*
|
|
3044
|
-
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
3045
|
-
* @return {ContentEmbed}
|
|
3046
|
-
*/
|
|
3047
|
-
export const readContentEmbed = decoder => new ContentEmbed(decoder.readJSON())
|
|
3048
|
-
|
|
3049
|
-
/**
|
|
3050
|
-
* @private
|
|
3051
|
-
*
|
|
3052
|
-
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
3053
|
-
* @return {ContentDoc}
|
|
3054
|
-
*/
|
|
3055
|
-
export const readContentDoc = decoder => new ContentDoc(decoder.readString(), decoder.readAny())
|
|
3056
|
-
|
|
3057
|
-
/**
|
|
3058
|
-
* @param {UpdateDecoderV1 | UpdateDecoderV2} decoder
|
|
1089
|
+
* @param {UpdateDecoder} decoder
|
|
3059
1090
|
* @return {ContentAny}
|
|
3060
1091
|
*/
|
|
3061
1092
|
export const readContentAny = decoder => {
|
|
@@ -3068,7 +1099,7 @@ export const readContentAny = decoder => {
|
|
|
3068
1099
|
}
|
|
3069
1100
|
|
|
3070
1101
|
/**
|
|
3071
|
-
* @param {
|
|
1102
|
+
* @param {UpdateDecoder} decoder
|
|
3072
1103
|
* @return {ContentBinary}
|
|
3073
1104
|
*/
|
|
3074
1105
|
export const readContentBinary = decoder => new ContentBinary(decoder.readBuf())
|
|
@@ -3076,38 +1107,42 @@ export const readContentBinary = decoder => new ContentBinary(decoder.readBuf())
|
|
|
3076
1107
|
/**
|
|
3077
1108
|
* @private
|
|
3078
1109
|
*
|
|
3079
|
-
* @param {
|
|
1110
|
+
* @param {UpdateDecoder} decoder
|
|
3080
1111
|
* @return {ContentDeleted}
|
|
3081
1112
|
*/
|
|
3082
1113
|
export const readContentDeleted = decoder => new ContentDeleted(decoder.readLen())
|
|
3083
1114
|
|
|
1115
|
+
const refused = () => { error.unexpectedCase() }
|
|
1116
|
+
|
|
3084
1117
|
/**
|
|
3085
|
-
* A lookup map for reading Item content.
|
|
1118
|
+
* A lookup map for reading Item content. YP19: JSON (2), embed (5) and
|
|
1119
|
+
* subdocument (9) contents are refused like GC (0) and Skip (10): edytor
|
|
1120
|
+
* writes none, so an update holding one is malformed.
|
|
3086
1121
|
*
|
|
3087
|
-
* @type {Array<function(
|
|
1122
|
+
* @type {Array<function(UpdateDecoder):AbstractContent>}
|
|
3088
1123
|
*/
|
|
3089
1124
|
export const contentRefs = [
|
|
3090
|
-
|
|
1125
|
+
refused, // 0 - GC is not ItemContent
|
|
3091
1126
|
readContentDeleted, // 1
|
|
3092
|
-
|
|
1127
|
+
refused, // 2 - JSON
|
|
3093
1128
|
readContentBinary, // 3
|
|
3094
1129
|
readContentString, // 4
|
|
3095
|
-
|
|
1130
|
+
refused, // 5 - embed
|
|
3096
1131
|
readContentFormat, // 6
|
|
3097
1132
|
readContentType, // 7
|
|
3098
1133
|
readContentAny, // 8
|
|
3099
|
-
|
|
3100
|
-
|
|
1134
|
+
refused, // 9 - subdocument
|
|
1135
|
+
refused // 10 - Skip is not ItemContent
|
|
3101
1136
|
]
|
|
3102
1137
|
|
|
3103
1138
|
/**
|
|
3104
|
-
* @param {
|
|
1139
|
+
* @param {UpdateDecoder} decoder
|
|
3105
1140
|
* @param {number} info
|
|
3106
1141
|
*/
|
|
3107
1142
|
export const readItemContent = (decoder, info) => contentRefs[info & binary.BITS5](decoder)
|
|
3108
1143
|
|
|
3109
1144
|
/**
|
|
3110
|
-
* @param {
|
|
1145
|
+
* @param {UpdateDecoder} decoder
|
|
3111
1146
|
* @return {YNode}
|
|
3112
1147
|
*
|
|
3113
1148
|
* @private
|