@xnetjs/react 1.0.0 → 2.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/dist/{chunk-MEIYFD6R.js → chunk-ANCTAEML.js} +2 -2
- package/dist/{chunk-RIN2MXZK.js → chunk-H44ZSIH4.js} +43 -7
- package/dist/{chunk-WL7OCTSM.js → chunk-HTFAHJQK.js} +1 -1
- package/dist/{chunk-BDBDZXYS.js → chunk-SZQNGRTO.js} +1 -1
- package/dist/{chunk-Q3NDLI6Z.js → chunk-VZMZQTR7.js} +2 -2
- package/dist/{chunk-EJEGIEIC.js → chunk-ZGP56IIJ.js} +1 -1
- package/dist/core.js +3 -3
- package/dist/database.js +2 -2
- package/dist/{experimental-B2FrBnkV.d.ts → experimental-DyPoZcJT.d.ts} +39 -1
- package/dist/experimental.d.ts +1 -1
- package/dist/experimental.js +7 -3
- package/dist/index.d.ts +166 -5
- package/dist/index.js +555 -26
- package/dist/internal.js +2 -2
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useInfiniteQuery,
|
|
6
6
|
useIsOffline,
|
|
7
7
|
useNode
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VZMZQTR7.js";
|
|
9
9
|
import {
|
|
10
10
|
useCell,
|
|
11
11
|
useDatabase,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useDatabaseSchema,
|
|
15
15
|
useRelatedRows,
|
|
16
16
|
useReverseRelations
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HTFAHJQK.js";
|
|
18
18
|
import {
|
|
19
19
|
AuthErrorScreen,
|
|
20
20
|
AuthenticatingScreen,
|
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
useUndo,
|
|
95
95
|
useVerification,
|
|
96
96
|
useVisibleComments
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-ANCTAEML.js";
|
|
98
98
|
import {
|
|
99
99
|
EMPTY_PAGE_INFO,
|
|
100
100
|
computeFallbackPageInfo,
|
|
@@ -106,10 +106,10 @@ import {
|
|
|
106
106
|
useMutate,
|
|
107
107
|
useQuery,
|
|
108
108
|
useSyncManager
|
|
109
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-ZGP56IIJ.js";
|
|
110
110
|
import {
|
|
111
111
|
useUndoScope
|
|
112
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-SZQNGRTO.js";
|
|
113
113
|
import {
|
|
114
114
|
InstrumentationContext,
|
|
115
115
|
useInstrumentation
|
|
@@ -122,6 +122,7 @@ import {
|
|
|
122
122
|
TracingContext,
|
|
123
123
|
XNetContext,
|
|
124
124
|
XNetProvider,
|
|
125
|
+
mergeEditorContributions,
|
|
125
126
|
useCommand,
|
|
126
127
|
useCommands,
|
|
127
128
|
useContributions,
|
|
@@ -129,6 +130,7 @@ import {
|
|
|
129
130
|
useEditorExtensions,
|
|
130
131
|
useEditorExtensionsSafe,
|
|
131
132
|
useImporters,
|
|
133
|
+
useMergedEditorContributions,
|
|
132
134
|
useNodeStore,
|
|
133
135
|
usePluginRegistry,
|
|
134
136
|
usePluginRegistryOptional,
|
|
@@ -142,7 +144,7 @@ import {
|
|
|
142
144
|
useView,
|
|
143
145
|
useViews,
|
|
144
146
|
useXNet
|
|
145
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-H44ZSIH4.js";
|
|
146
148
|
|
|
147
149
|
// src/hooks/useEffectiveSchema.ts
|
|
148
150
|
import {
|
|
@@ -4929,8 +4931,531 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4929
4931
|
};
|
|
4930
4932
|
}
|
|
4931
4933
|
|
|
4934
|
+
// src/hooks/useTimeMachine.ts
|
|
4935
|
+
import { CHECKPOINT_SCHEMA_IRI } from "@xnetjs/data";
|
|
4936
|
+
import {
|
|
4937
|
+
HistoryEngine,
|
|
4938
|
+
HistoryHorizonError,
|
|
4939
|
+
MemorySnapshotStorage,
|
|
4940
|
+
ScopeTimeline,
|
|
4941
|
+
SnapshotCache,
|
|
4942
|
+
createCheckpoint,
|
|
4943
|
+
listCheckpoints,
|
|
4944
|
+
restoreToFrontier
|
|
4945
|
+
} from "@xnetjs/history";
|
|
4946
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef5, useState as useState7 } from "react";
|
|
4947
|
+
function useTimeMachine(nodeId, options) {
|
|
4948
|
+
const { store, isReady } = useNodeStore();
|
|
4949
|
+
const memberKey = options?.memberIds?.join(",") ?? "";
|
|
4950
|
+
const members = useMemo6(() => {
|
|
4951
|
+
if (!nodeId) return [];
|
|
4952
|
+
const extra = options?.memberIds ?? [];
|
|
4953
|
+
return [nodeId, ...extra.filter((id) => id !== nodeId)];
|
|
4954
|
+
}, [nodeId, memberKey]);
|
|
4955
|
+
const [timeline, setTimeline] = useState7([]);
|
|
4956
|
+
const [checkpoints, setCheckpoints] = useState7([]);
|
|
4957
|
+
const [horizon, setHorizon] = useState7(null);
|
|
4958
|
+
const [docSnapshotCount, setDocSnapshotCount] = useState7(null);
|
|
4959
|
+
const [loading, setLoading] = useState7(false);
|
|
4960
|
+
const [error, setError] = useState7(null);
|
|
4961
|
+
const [rawPosition, setRawPosition] = useState7(null);
|
|
4962
|
+
const position = rawPosition === null ? Math.max(0, timeline.length - 1) : Math.max(0, Math.min(rawPosition, timeline.length - 1));
|
|
4963
|
+
const atLatest = timeline.length === 0 || position === timeline.length - 1;
|
|
4964
|
+
const [preview, setPreview] = useState7(null);
|
|
4965
|
+
const [diffs, setDiffs] = useState7([]);
|
|
4966
|
+
const engineRef = useRef5(null);
|
|
4967
|
+
const getEngines = useCallback4(() => {
|
|
4968
|
+
if (!store) return null;
|
|
4969
|
+
const storage = store.getStorageAdapter();
|
|
4970
|
+
if (!storage) return null;
|
|
4971
|
+
if (!engineRef.current || engineRef.current.storage !== storage) {
|
|
4972
|
+
const snapshots = new SnapshotCache(new MemorySnapshotStorage(), { interval: 50 });
|
|
4973
|
+
engineRef.current = {
|
|
4974
|
+
engine: new HistoryEngine(storage, snapshots),
|
|
4975
|
+
scope: new ScopeTimeline(storage),
|
|
4976
|
+
storage
|
|
4977
|
+
};
|
|
4978
|
+
}
|
|
4979
|
+
return engineRef.current;
|
|
4980
|
+
}, [store]);
|
|
4981
|
+
const reload = useCallback4(async () => {
|
|
4982
|
+
if (!nodeId || !isReady || !store) return;
|
|
4983
|
+
const engines = getEngines();
|
|
4984
|
+
if (!engines) return;
|
|
4985
|
+
setLoading(true);
|
|
4986
|
+
setError(null);
|
|
4987
|
+
try {
|
|
4988
|
+
const [line, nodeHorizon, named] = await Promise.all([
|
|
4989
|
+
engines.scope.getMergedTimeline(members),
|
|
4990
|
+
engines.engine.getHorizon(nodeId),
|
|
4991
|
+
listCheckpoints(store, nodeId)
|
|
4992
|
+
]);
|
|
4993
|
+
setTimeline(line);
|
|
4994
|
+
setHorizon(nodeHorizon);
|
|
4995
|
+
setCheckpoints(named);
|
|
4996
|
+
const snapshotStorage = engines.storage;
|
|
4997
|
+
if (typeof snapshotStorage.getYjsSnapshots === "function") {
|
|
4998
|
+
const snapshots = await snapshotStorage.getYjsSnapshots(nodeId);
|
|
4999
|
+
setDocSnapshotCount(snapshots.length);
|
|
5000
|
+
} else {
|
|
5001
|
+
setDocSnapshotCount(null);
|
|
5002
|
+
}
|
|
5003
|
+
} catch (err) {
|
|
5004
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5005
|
+
} finally {
|
|
5006
|
+
setLoading(false);
|
|
5007
|
+
}
|
|
5008
|
+
}, [nodeId, isReady, store, members, getEngines]);
|
|
5009
|
+
useEffect7(() => {
|
|
5010
|
+
setRawPosition(null);
|
|
5011
|
+
void reload();
|
|
5012
|
+
}, [reload]);
|
|
5013
|
+
useEffect7(() => {
|
|
5014
|
+
if (!store || !nodeId) return;
|
|
5015
|
+
const memberSet = new Set(members);
|
|
5016
|
+
const unsub = store.subscribe((event) => {
|
|
5017
|
+
if (memberSet.has(event.change.payload.nodeId) || event.change.payload.schemaId === CHECKPOINT_SCHEMA_IRI) {
|
|
5018
|
+
void reload();
|
|
5019
|
+
}
|
|
5020
|
+
});
|
|
5021
|
+
return unsub;
|
|
5022
|
+
}, [store, nodeId, members, reload]);
|
|
5023
|
+
useEffect7(() => {
|
|
5024
|
+
let cancelled = false;
|
|
5025
|
+
const engines = getEngines();
|
|
5026
|
+
if (!nodeId || !engines || timeline.length === 0) {
|
|
5027
|
+
setPreview(null);
|
|
5028
|
+
setDiffs([]);
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
let hash = null;
|
|
5032
|
+
for (let i = position; i >= 0; i--) {
|
|
5033
|
+
if (timeline[i].nodeId === nodeId) {
|
|
5034
|
+
hash = timeline[i].change.hash;
|
|
5035
|
+
break;
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
if (!hash) {
|
|
5039
|
+
setPreview(null);
|
|
5040
|
+
setDiffs([]);
|
|
5041
|
+
return;
|
|
5042
|
+
}
|
|
5043
|
+
const target = { type: "hash", hash };
|
|
5044
|
+
Promise.all([
|
|
5045
|
+
engines.engine.materializeAt(nodeId, target),
|
|
5046
|
+
engines.engine.diff(nodeId, target, { type: "latest" })
|
|
5047
|
+
]).then(([state, propertyDiffs]) => {
|
|
5048
|
+
if (cancelled) return;
|
|
5049
|
+
setPreview(state.node);
|
|
5050
|
+
setDiffs(propertyDiffs);
|
|
5051
|
+
}).catch((err) => {
|
|
5052
|
+
if (cancelled) return;
|
|
5053
|
+
setPreview(null);
|
|
5054
|
+
setDiffs([]);
|
|
5055
|
+
if (err instanceof HistoryHorizonError) {
|
|
5056
|
+
setHorizon(err.horizon);
|
|
5057
|
+
} else {
|
|
5058
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5059
|
+
}
|
|
5060
|
+
});
|
|
5061
|
+
return () => {
|
|
5062
|
+
cancelled = true;
|
|
5063
|
+
};
|
|
5064
|
+
}, [nodeId, timeline, position, getEngines]);
|
|
5065
|
+
const setPosition = useCallback4(
|
|
5066
|
+
(index) => {
|
|
5067
|
+
const max = timeline.length - 1;
|
|
5068
|
+
const clamped = Math.max(0, Math.min(index, max));
|
|
5069
|
+
setRawPosition(clamped >= max ? null : clamped);
|
|
5070
|
+
},
|
|
5071
|
+
[timeline.length]
|
|
5072
|
+
);
|
|
5073
|
+
const stepBack = useCallback4(() => setPosition(position - 1), [setPosition, position]);
|
|
5074
|
+
const stepForward = useCallback4(() => setPosition(position + 1), [setPosition, position]);
|
|
5075
|
+
const goToLatest = useCallback4(() => setRawPosition(null), []);
|
|
5076
|
+
const createNamedVersion = useCallback4(
|
|
5077
|
+
async (name, note) => {
|
|
5078
|
+
if (!nodeId || !store) return null;
|
|
5079
|
+
const engines = getEngines();
|
|
5080
|
+
if (!engines) return null;
|
|
5081
|
+
try {
|
|
5082
|
+
const checkpoint = await createCheckpoint(store, engines.storage, {
|
|
5083
|
+
name,
|
|
5084
|
+
...note !== void 0 ? { note } : {},
|
|
5085
|
+
nodeIds: members,
|
|
5086
|
+
scopeId: nodeId
|
|
5087
|
+
});
|
|
5088
|
+
await reload();
|
|
5089
|
+
return checkpoint;
|
|
5090
|
+
} catch (err) {
|
|
5091
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5092
|
+
return null;
|
|
5093
|
+
}
|
|
5094
|
+
},
|
|
5095
|
+
[nodeId, store, members, getEngines, reload]
|
|
5096
|
+
);
|
|
5097
|
+
const positionOfCheckpoint = useCallback4(
|
|
5098
|
+
(checkpoint) => {
|
|
5099
|
+
const frontier = checkpoint.properties.frontier;
|
|
5100
|
+
if (!frontier) return null;
|
|
5101
|
+
let best = -1;
|
|
5102
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
5103
|
+
const pinned = frontier[timeline[i].nodeId];
|
|
5104
|
+
if (pinned && pinned.hash === timeline[i].change.hash) {
|
|
5105
|
+
best = Math.max(best, i);
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
return best >= 0 ? best : null;
|
|
5109
|
+
},
|
|
5110
|
+
[timeline]
|
|
5111
|
+
);
|
|
5112
|
+
const frontierAt = useCallback4(
|
|
5113
|
+
(index) => {
|
|
5114
|
+
const scope = getEngines()?.scope;
|
|
5115
|
+
if (!scope) return {};
|
|
5116
|
+
return scope.frontierAtPosition(timeline, index);
|
|
5117
|
+
},
|
|
5118
|
+
[getEngines, timeline]
|
|
5119
|
+
);
|
|
5120
|
+
const restore = useCallback4(async () => {
|
|
5121
|
+
if (!nodeId || !store || timeline.length === 0) return null;
|
|
5122
|
+
const engines = getEngines();
|
|
5123
|
+
if (!engines) return null;
|
|
5124
|
+
try {
|
|
5125
|
+
const frontier = engines.scope.frontierAtPosition(timeline, position);
|
|
5126
|
+
const result = await restoreToFrontier(store, engines.engine, frontier, members);
|
|
5127
|
+
setRawPosition(null);
|
|
5128
|
+
await reload();
|
|
5129
|
+
return result;
|
|
5130
|
+
} catch (err) {
|
|
5131
|
+
if (err instanceof HistoryHorizonError) setHorizon(err.horizon);
|
|
5132
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5133
|
+
return null;
|
|
5134
|
+
}
|
|
5135
|
+
}, [nodeId, store, timeline, position, members, getEngines, reload]);
|
|
5136
|
+
return {
|
|
5137
|
+
timeline,
|
|
5138
|
+
changeCount: timeline.length,
|
|
5139
|
+
position,
|
|
5140
|
+
atLatest,
|
|
5141
|
+
setPosition,
|
|
5142
|
+
stepBack,
|
|
5143
|
+
stepForward,
|
|
5144
|
+
goToLatest,
|
|
5145
|
+
preview,
|
|
5146
|
+
diffs,
|
|
5147
|
+
horizon,
|
|
5148
|
+
docSnapshotCount,
|
|
5149
|
+
checkpoints,
|
|
5150
|
+
createNamedVersion,
|
|
5151
|
+
positionOfCheckpoint,
|
|
5152
|
+
frontierAt,
|
|
5153
|
+
restore,
|
|
5154
|
+
loading,
|
|
5155
|
+
error,
|
|
5156
|
+
reload
|
|
5157
|
+
};
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
// src/hooks/useDraft.ts
|
|
5161
|
+
import { DRAFT_SCHEMA_IRI, DatabaseRowSchema as DatabaseRowSchema2, DatabaseSchema as DatabaseSchema2 } from "@xnetjs/data";
|
|
5162
|
+
import {
|
|
5163
|
+
HistoryEngine as HistoryEngine2,
|
|
5164
|
+
MemorySnapshotStorage as MemorySnapshotStorage2,
|
|
5165
|
+
SnapshotCache as SnapshotCache2,
|
|
5166
|
+
createDraft as createDraftNode,
|
|
5167
|
+
discardDraft,
|
|
5168
|
+
draftEntries,
|
|
5169
|
+
forkNodeIntoDraft,
|
|
5170
|
+
listDrafts,
|
|
5171
|
+
mergeDraft,
|
|
5172
|
+
refreshDraftFromMain,
|
|
5173
|
+
threeWayPropertyMerge
|
|
5174
|
+
} from "@xnetjs/history";
|
|
5175
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef6, useState as useState8 } from "react";
|
|
5176
|
+
var baseIRI = (schemaId) => schemaId.split("@")[0];
|
|
5177
|
+
async function resolveMembers(store, hostId) {
|
|
5178
|
+
const members = [hostId];
|
|
5179
|
+
const host = await store.getRaw(hostId);
|
|
5180
|
+
if (host && baseIRI(host.schemaId) === baseIRI(DatabaseSchema2.schema["@id"])) {
|
|
5181
|
+
const rows = await store.list({ schemaId: DatabaseRowSchema2.schema["@id"] });
|
|
5182
|
+
for (const row of rows) {
|
|
5183
|
+
if (row.properties.database === hostId) members.push(row.id);
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
return members;
|
|
5187
|
+
}
|
|
5188
|
+
function clonesFromEntries(draft) {
|
|
5189
|
+
const clones = {};
|
|
5190
|
+
for (const [originalId, entry] of Object.entries(draftEntries(draft))) {
|
|
5191
|
+
clones[originalId] = entry.cloneId;
|
|
5192
|
+
}
|
|
5193
|
+
return clones;
|
|
5194
|
+
}
|
|
5195
|
+
function useDraft(hostId) {
|
|
5196
|
+
const { store, isReady } = useNodeStore();
|
|
5197
|
+
const [drafts, setDrafts] = useState8([]);
|
|
5198
|
+
const [checkedOut, setCheckedOut] = useState8(null);
|
|
5199
|
+
const [loading, setLoading] = useState8(false);
|
|
5200
|
+
const [error, setError] = useState8(null);
|
|
5201
|
+
const engineRef = useRef6(null);
|
|
5202
|
+
const getEngines = useCallback5(() => {
|
|
5203
|
+
if (!store) return null;
|
|
5204
|
+
const storage = store.getStorageAdapter();
|
|
5205
|
+
if (!storage) return null;
|
|
5206
|
+
if (!engineRef.current || engineRef.current.storage !== storage) {
|
|
5207
|
+
engineRef.current = {
|
|
5208
|
+
engine: new HistoryEngine2(
|
|
5209
|
+
storage,
|
|
5210
|
+
new SnapshotCache2(new MemorySnapshotStorage2(), { interval: 50 })
|
|
5211
|
+
),
|
|
5212
|
+
storage
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5215
|
+
return engineRef.current;
|
|
5216
|
+
}, [store]);
|
|
5217
|
+
const draftIdsRef = useRef6(/* @__PURE__ */ new Set());
|
|
5218
|
+
const reload = useCallback5(async () => {
|
|
5219
|
+
if (!hostId || !isReady || !store) return;
|
|
5220
|
+
setLoading(true);
|
|
5221
|
+
setError(null);
|
|
5222
|
+
try {
|
|
5223
|
+
const open = await listDrafts(store, hostId);
|
|
5224
|
+
setDrafts(open);
|
|
5225
|
+
for (const draft of open) draftIdsRef.current.add(draft.id);
|
|
5226
|
+
const overlay = store.getCheckedOutDraft();
|
|
5227
|
+
if (overlay) {
|
|
5228
|
+
const draftNode = await store.getRaw(overlay.draftId);
|
|
5229
|
+
setCheckedOut(
|
|
5230
|
+
draftNode && draftNode.properties.status === "open" && draftNode.properties.target === hostId ? draftNode : null
|
|
5231
|
+
);
|
|
5232
|
+
} else {
|
|
5233
|
+
setCheckedOut(null);
|
|
5234
|
+
}
|
|
5235
|
+
} catch (err) {
|
|
5236
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5237
|
+
} finally {
|
|
5238
|
+
setLoading(false);
|
|
5239
|
+
}
|
|
5240
|
+
}, [hostId, isReady, store]);
|
|
5241
|
+
useEffect8(() => {
|
|
5242
|
+
void reload();
|
|
5243
|
+
}, [reload]);
|
|
5244
|
+
useEffect8(() => {
|
|
5245
|
+
if (!store || !hostId) return;
|
|
5246
|
+
const unsubStore = store.subscribe((event) => {
|
|
5247
|
+
if (event.change.payload.schemaId === DRAFT_SCHEMA_IRI || draftIdsRef.current.has(event.change.payload.nodeId)) {
|
|
5248
|
+
void reload();
|
|
5249
|
+
}
|
|
5250
|
+
});
|
|
5251
|
+
const unsubOverlay = store.subscribeToDraftOverlay(() => void reload());
|
|
5252
|
+
return () => {
|
|
5253
|
+
unsubStore();
|
|
5254
|
+
unsubOverlay();
|
|
5255
|
+
};
|
|
5256
|
+
}, [store, hostId, reload]);
|
|
5257
|
+
const checkout = useCallback5(
|
|
5258
|
+
async (draftId) => {
|
|
5259
|
+
if (!hostId || !store) return false;
|
|
5260
|
+
const engines = getEngines();
|
|
5261
|
+
if (!engines) return false;
|
|
5262
|
+
try {
|
|
5263
|
+
const draft = await store.getRaw(draftId);
|
|
5264
|
+
if (!draft || draft.properties.status !== "open") return false;
|
|
5265
|
+
const members = await resolveMembers(store, hostId);
|
|
5266
|
+
store.setCheckedOutDraft({
|
|
5267
|
+
draftId,
|
|
5268
|
+
members,
|
|
5269
|
+
clones: clonesFromEntries(draft),
|
|
5270
|
+
// Lazy copy-on-write: the first write to an unforked member clones
|
|
5271
|
+
// it. Returning null declines — the write then targets the
|
|
5272
|
+
// original (never-fork schemas stay live by policy).
|
|
5273
|
+
onMissingMember: async (originalId) => {
|
|
5274
|
+
try {
|
|
5275
|
+
const entry = await forkNodeIntoDraft(store, engines.storage, draftId, originalId);
|
|
5276
|
+
return entry.cloneId;
|
|
5277
|
+
} catch {
|
|
5278
|
+
return null;
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
});
|
|
5282
|
+
await reload();
|
|
5283
|
+
return true;
|
|
5284
|
+
} catch (err) {
|
|
5285
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5286
|
+
return false;
|
|
5287
|
+
}
|
|
5288
|
+
},
|
|
5289
|
+
[hostId, store, getEngines, reload]
|
|
5290
|
+
);
|
|
5291
|
+
const returnToMain = useCallback5(() => {
|
|
5292
|
+
if (!store) return;
|
|
5293
|
+
store.setCheckedOutDraft(null);
|
|
5294
|
+
}, [store]);
|
|
5295
|
+
const createDraft = useCallback5(
|
|
5296
|
+
async (name) => {
|
|
5297
|
+
if (!hostId || !store) return null;
|
|
5298
|
+
try {
|
|
5299
|
+
const draft = await createDraftNode(store, { name, targetId: hostId });
|
|
5300
|
+
await checkout(draft.id);
|
|
5301
|
+
return draft;
|
|
5302
|
+
} catch (err) {
|
|
5303
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5304
|
+
return null;
|
|
5305
|
+
}
|
|
5306
|
+
},
|
|
5307
|
+
[hostId, store, checkout]
|
|
5308
|
+
);
|
|
5309
|
+
const discard = useCallback5(
|
|
5310
|
+
async (draftId) => {
|
|
5311
|
+
if (!store) return false;
|
|
5312
|
+
const engines = getEngines();
|
|
5313
|
+
if (!engines) return false;
|
|
5314
|
+
try {
|
|
5315
|
+
if (store.getCheckedOutDraft()?.draftId === draftId) {
|
|
5316
|
+
store.setCheckedOutDraft(null);
|
|
5317
|
+
}
|
|
5318
|
+
await discardDraft(store, engines.storage, draftId);
|
|
5319
|
+
await reload();
|
|
5320
|
+
return true;
|
|
5321
|
+
} catch (err) {
|
|
5322
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5323
|
+
return false;
|
|
5324
|
+
}
|
|
5325
|
+
},
|
|
5326
|
+
[store, getEngines, reload]
|
|
5327
|
+
);
|
|
5328
|
+
const merge = useCallback5(
|
|
5329
|
+
async (draftId) => {
|
|
5330
|
+
if (!store) return null;
|
|
5331
|
+
const engines = getEngines();
|
|
5332
|
+
if (!engines) return null;
|
|
5333
|
+
try {
|
|
5334
|
+
const result = await mergeDraft(store, engines.storage, engines.engine, draftId);
|
|
5335
|
+
if (result.status === "merged" && store.getCheckedOutDraft()?.draftId === draftId) {
|
|
5336
|
+
store.setCheckedOutDraft(null);
|
|
5337
|
+
}
|
|
5338
|
+
await reload();
|
|
5339
|
+
return result;
|
|
5340
|
+
} catch (err) {
|
|
5341
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5342
|
+
return null;
|
|
5343
|
+
}
|
|
5344
|
+
},
|
|
5345
|
+
[store, getEngines, reload]
|
|
5346
|
+
);
|
|
5347
|
+
const refresh = useCallback5(
|
|
5348
|
+
async (draftId) => {
|
|
5349
|
+
if (!store) return null;
|
|
5350
|
+
const engines = getEngines();
|
|
5351
|
+
if (!engines) return null;
|
|
5352
|
+
try {
|
|
5353
|
+
const result = await refreshDraftFromMain(store, engines.storage, engines.engine, draftId);
|
|
5354
|
+
await reload();
|
|
5355
|
+
return result;
|
|
5356
|
+
} catch (err) {
|
|
5357
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5358
|
+
return null;
|
|
5359
|
+
}
|
|
5360
|
+
},
|
|
5361
|
+
[store, getEngines, reload]
|
|
5362
|
+
);
|
|
5363
|
+
const setReviewRequested = useCallback5(
|
|
5364
|
+
async (draftId, requested) => {
|
|
5365
|
+
if (!store) return;
|
|
5366
|
+
try {
|
|
5367
|
+
await store.update(draftId, { properties: { reviewRequested: requested } });
|
|
5368
|
+
} catch (err) {
|
|
5369
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5370
|
+
}
|
|
5371
|
+
},
|
|
5372
|
+
[store]
|
|
5373
|
+
);
|
|
5374
|
+
const computeReview = useCallback5(
|
|
5375
|
+
async (draftId) => {
|
|
5376
|
+
if (!store) return null;
|
|
5377
|
+
const engines = getEngines();
|
|
5378
|
+
if (!engines) return null;
|
|
5379
|
+
try {
|
|
5380
|
+
const draft = await store.getRaw(draftId);
|
|
5381
|
+
if (!draft) return null;
|
|
5382
|
+
const cards = [];
|
|
5383
|
+
const members = [];
|
|
5384
|
+
for (const [id, entry] of Object.entries(draftEntries(draft))) {
|
|
5385
|
+
const originalId = id;
|
|
5386
|
+
const cloneId = entry.cloneId;
|
|
5387
|
+
const [base, ours, theirs] = await Promise.all([
|
|
5388
|
+
engines.engine.materializeAt(originalId, {
|
|
5389
|
+
type: "hash",
|
|
5390
|
+
hash: entry.forkedAtHash
|
|
5391
|
+
}),
|
|
5392
|
+
store.getRaw(originalId),
|
|
5393
|
+
store.getRaw(cloneId)
|
|
5394
|
+
]);
|
|
5395
|
+
if (!ours || !theirs) continue;
|
|
5396
|
+
const result = threeWayPropertyMerge(
|
|
5397
|
+
base.node.properties,
|
|
5398
|
+
ours.properties,
|
|
5399
|
+
theirs.properties
|
|
5400
|
+
);
|
|
5401
|
+
for (const [property, draftValue] of Object.entries(result.patch)) {
|
|
5402
|
+
cards.push({
|
|
5403
|
+
originalId,
|
|
5404
|
+
property,
|
|
5405
|
+
base: base.node.properties[property],
|
|
5406
|
+
main: ours.properties[property],
|
|
5407
|
+
draft: draftValue,
|
|
5408
|
+
conflict: false
|
|
5409
|
+
});
|
|
5410
|
+
}
|
|
5411
|
+
for (const conflict of result.conflicts) {
|
|
5412
|
+
cards.push({
|
|
5413
|
+
originalId,
|
|
5414
|
+
property: conflict.property,
|
|
5415
|
+
base: conflict.base,
|
|
5416
|
+
main: conflict.ours,
|
|
5417
|
+
draft: conflict.theirs,
|
|
5418
|
+
conflict: true
|
|
5419
|
+
});
|
|
5420
|
+
}
|
|
5421
|
+
const hasDocument = Boolean(entry.forkedAtYjsStateVector);
|
|
5422
|
+
let documentDiffers = false;
|
|
5423
|
+
if (hasDocument) {
|
|
5424
|
+
const [cloneBlob, mainBlob] = await Promise.all([
|
|
5425
|
+
engines.storage.getDocumentContent(cloneId),
|
|
5426
|
+
engines.storage.getDocumentContent(originalId)
|
|
5427
|
+
]);
|
|
5428
|
+
documentDiffers = (cloneBlob?.length ?? 0) !== (mainBlob?.length ?? 0);
|
|
5429
|
+
}
|
|
5430
|
+
members.push({ originalId, cloneId, hasDocument, documentDiffers });
|
|
5431
|
+
}
|
|
5432
|
+
return { cards, members };
|
|
5433
|
+
} catch (err) {
|
|
5434
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5435
|
+
return null;
|
|
5436
|
+
}
|
|
5437
|
+
},
|
|
5438
|
+
[store, getEngines]
|
|
5439
|
+
);
|
|
5440
|
+
return {
|
|
5441
|
+
drafts,
|
|
5442
|
+
checkedOut,
|
|
5443
|
+
createDraft,
|
|
5444
|
+
checkout,
|
|
5445
|
+
returnToMain,
|
|
5446
|
+
discard,
|
|
5447
|
+
merge,
|
|
5448
|
+
refresh,
|
|
5449
|
+
setReviewRequested,
|
|
5450
|
+
computeReview,
|
|
5451
|
+
loading,
|
|
5452
|
+
error,
|
|
5453
|
+
reload
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
5456
|
+
|
|
4932
5457
|
// src/hooks/useGlobalUndo.ts
|
|
4933
|
-
import { useCallback as
|
|
5458
|
+
import { useCallback as useCallback6, useContext, useEffect as useEffect9, useReducer } from "react";
|
|
4934
5459
|
function readUndoContext(ctx) {
|
|
4935
5460
|
if (!ctx) return { undoManager: null, nodeStore: null };
|
|
4936
5461
|
return { undoManager: ctx.undoManager, nodeStore: ctx.nodeStore };
|
|
@@ -4939,17 +5464,17 @@ function useGlobalUndo() {
|
|
|
4939
5464
|
const ctx = useContext(XNetContext);
|
|
4940
5465
|
const { undoManager, nodeStore } = readUndoContext(ctx);
|
|
4941
5466
|
const [, bump] = useReducer((n) => n + 1, 0);
|
|
4942
|
-
|
|
5467
|
+
useEffect9(() => {
|
|
4943
5468
|
if (!nodeStore) return;
|
|
4944
5469
|
return nodeStore.subscribe(() => bump());
|
|
4945
5470
|
}, [nodeStore]);
|
|
4946
|
-
const undo =
|
|
5471
|
+
const undo = useCallback6(async () => {
|
|
4947
5472
|
if (!undoManager) return false;
|
|
4948
5473
|
const ok = await undoManager.undoLatest();
|
|
4949
5474
|
bump();
|
|
4950
5475
|
return ok;
|
|
4951
5476
|
}, [undoManager]);
|
|
4952
|
-
const redo =
|
|
5477
|
+
const redo = useCallback6(async () => {
|
|
4953
5478
|
if (!undoManager) return false;
|
|
4954
5479
|
const ok = await undoManager.redoLatest();
|
|
4955
5480
|
bump();
|
|
@@ -4964,7 +5489,7 @@ function useGlobalUndo() {
|
|
|
4964
5489
|
}
|
|
4965
5490
|
|
|
4966
5491
|
// src/hooks/useCanCreate.ts
|
|
4967
|
-
import { useEffect as
|
|
5492
|
+
import { useEffect as useEffect10, useMemo as useMemo7, useRef as useRef7, useState as useState9 } from "react";
|
|
4968
5493
|
var INITIAL_STATE = {
|
|
4969
5494
|
canCreate: false,
|
|
4970
5495
|
loading: true,
|
|
@@ -4972,10 +5497,10 @@ var INITIAL_STATE = {
|
|
|
4972
5497
|
};
|
|
4973
5498
|
function useCanCreate(schemaId, properties) {
|
|
4974
5499
|
const { store, isReady } = useNodeStore();
|
|
4975
|
-
const [state, setState] =
|
|
4976
|
-
const requestRef =
|
|
4977
|
-
const propertiesKey =
|
|
4978
|
-
|
|
5500
|
+
const [state, setState] = useState9(INITIAL_STATE);
|
|
5501
|
+
const requestRef = useRef7(0);
|
|
5502
|
+
const propertiesKey = useMemo7(() => JSON.stringify(properties ?? {}), [properties]);
|
|
5503
|
+
useEffect10(() => {
|
|
4979
5504
|
if (!store || !isReady) {
|
|
4980
5505
|
setState((prev) => ({ ...prev, loading: true }));
|
|
4981
5506
|
return;
|
|
@@ -5044,7 +5569,7 @@ import {
|
|
|
5044
5569
|
} from "@xnetjs/runtime";
|
|
5045
5570
|
|
|
5046
5571
|
// src/hooks/useBilling.ts
|
|
5047
|
-
import { useCallback as
|
|
5572
|
+
import { useCallback as useCallback7, useContext as useContext2, useEffect as useEffect11, useMemo as useMemo8, useState as useState10 } from "react";
|
|
5048
5573
|
var toHttpUrl = (hubUrl) => {
|
|
5049
5574
|
try {
|
|
5050
5575
|
const url = new URL(hubUrl);
|
|
@@ -5081,20 +5606,20 @@ function deriveBilling(state) {
|
|
|
5081
5606
|
}
|
|
5082
5607
|
function useBilling() {
|
|
5083
5608
|
const context = useContext2(XNetContext);
|
|
5084
|
-
const [state, setState] =
|
|
5085
|
-
const [loading, setLoading] =
|
|
5609
|
+
const [state, setState] = useState10(null);
|
|
5610
|
+
const [loading, setLoading] = useState10(
|
|
5086
5611
|
Boolean(context?.hubUrl || context?.billing?.apiBase)
|
|
5087
5612
|
);
|
|
5088
|
-
const [error, setError] =
|
|
5089
|
-
const apiBase =
|
|
5613
|
+
const [error, setError] = useState10(null);
|
|
5614
|
+
const apiBase = useMemo8(() => {
|
|
5090
5615
|
if (context?.billing?.apiBase) return context.billing.apiBase.replace(/\/$/, "");
|
|
5091
5616
|
return context?.hubUrl ? toHttpUrl(context.hubUrl) : null;
|
|
5092
5617
|
}, [context?.billing?.apiBase, context?.hubUrl]);
|
|
5093
|
-
const authHeaders =
|
|
5618
|
+
const authHeaders = useCallback7(async () => {
|
|
5094
5619
|
const token = context?.getHubAuthToken ? await context.getHubAuthToken() : "";
|
|
5095
5620
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
5096
5621
|
}, [context]);
|
|
5097
|
-
const reload =
|
|
5622
|
+
const reload = useCallback7(async () => {
|
|
5098
5623
|
if (!apiBase) {
|
|
5099
5624
|
setState(null);
|
|
5100
5625
|
setLoading(false);
|
|
@@ -5112,10 +5637,10 @@ function useBilling() {
|
|
|
5112
5637
|
setLoading(false);
|
|
5113
5638
|
}
|
|
5114
5639
|
}, [apiBase, authHeaders]);
|
|
5115
|
-
|
|
5640
|
+
useEffect11(() => {
|
|
5116
5641
|
void reload();
|
|
5117
5642
|
}, [reload]);
|
|
5118
|
-
const redirectVia =
|
|
5643
|
+
const redirectVia = useCallback7(
|
|
5119
5644
|
async (path, body) => {
|
|
5120
5645
|
if (!apiBase) throw new Error("Hub URL not configured");
|
|
5121
5646
|
const res = await fetch(`${apiBase}${path}`, {
|
|
@@ -5129,11 +5654,11 @@ function useBilling() {
|
|
|
5129
5654
|
},
|
|
5130
5655
|
[apiBase, authHeaders]
|
|
5131
5656
|
);
|
|
5132
|
-
const openCheckout =
|
|
5657
|
+
const openCheckout = useCallback7(
|
|
5133
5658
|
(priceRef, options = {}) => redirectVia("/billing/checkout", buildCheckoutBody(priceRef, options)),
|
|
5134
5659
|
[redirectVia]
|
|
5135
5660
|
);
|
|
5136
|
-
const openPortal =
|
|
5661
|
+
const openPortal = useCallback7(
|
|
5137
5662
|
(returnUrl) => redirectVia("/billing/portal", returnUrl ? { returnUrl } : {}),
|
|
5138
5663
|
[redirectVia]
|
|
5139
5664
|
);
|
|
@@ -5239,6 +5764,7 @@ export {
|
|
|
5239
5764
|
isReactionVisible,
|
|
5240
5765
|
isSavedViewVisualPreviewEmbeddable,
|
|
5241
5766
|
isTaskOverdue,
|
|
5767
|
+
mergeEditorContributions,
|
|
5242
5768
|
mergeSavedViewFeedEnrichment,
|
|
5243
5769
|
moderateThread,
|
|
5244
5770
|
onboardingReducer,
|
|
@@ -5274,6 +5800,7 @@ export {
|
|
|
5274
5800
|
useDatabaseSchema,
|
|
5275
5801
|
useDemoMode,
|
|
5276
5802
|
useDiff,
|
|
5803
|
+
useDraft,
|
|
5277
5804
|
useEditorExtensions,
|
|
5278
5805
|
useEditorExtensionsSafe,
|
|
5279
5806
|
useEffectiveSchema,
|
|
@@ -5290,6 +5817,7 @@ export {
|
|
|
5290
5817
|
useInfiniteQuery,
|
|
5291
5818
|
useInstrumentation,
|
|
5292
5819
|
useIsOffline,
|
|
5820
|
+
useMergedEditorContributions,
|
|
5293
5821
|
useMessageRequests,
|
|
5294
5822
|
useModeratedThread,
|
|
5295
5823
|
useMutate,
|
|
@@ -5317,6 +5845,7 @@ export {
|
|
|
5317
5845
|
useTaskProjectionSync,
|
|
5318
5846
|
useTasks,
|
|
5319
5847
|
useTelemetryReporter,
|
|
5848
|
+
useTimeMachine,
|
|
5320
5849
|
useTracingReporter,
|
|
5321
5850
|
useUndo,
|
|
5322
5851
|
useVerification,
|