jazz-tools 0.19.3 → 0.19.4
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/.svelte-kit/__package__/jazz.class.svelte.d.ts +2 -2
- package/.svelte-kit/__package__/jazz.class.svelte.d.ts.map +1 -1
- package/.svelte-kit/__package__/jazz.class.svelte.js +15 -17
- package/.turbo/turbo-build.log +64 -64
- package/CHANGELOG.md +14 -0
- package/dist/{chunk-JPWM4CS2.js → chunk-PT7FCV26.js} +145 -77
- package/dist/chunk-PT7FCV26.js.map +1 -0
- package/dist/index.js +14 -7
- package/dist/index.js.map +1 -1
- package/dist/inspector/{custom-element-3JAYHXWQ.js → custom-element-P76EIWEV.js} +301 -142
- package/dist/inspector/{custom-element-3JAYHXWQ.js.map → custom-element-P76EIWEV.js.map} +1 -1
- package/dist/inspector/index.js +281 -122
- package/dist/inspector/index.js.map +1 -1
- package/dist/inspector/register-custom-element.js +1 -1
- package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts +2 -0
- package/dist/inspector/tests/viewer/co-plain-text-view.test.d.ts.map +1 -0
- package/dist/inspector/utils/history.d.ts +5 -1
- package/dist/inspector/utils/history.d.ts.map +1 -1
- package/dist/inspector/viewer/co-plain-text-view.d.ts +4 -2
- package/dist/inspector/viewer/co-plain-text-view.d.ts.map +1 -1
- package/dist/inspector/viewer/page.d.ts.map +1 -1
- package/dist/inspector/viewer/use-resolve-covalue.d.ts +0 -1
- package/dist/inspector/viewer/use-resolve-covalue.d.ts.map +1 -1
- package/dist/react-core/hooks.d.ts.map +1 -1
- package/dist/react-core/index.js +4 -17
- package/dist/react-core/index.js.map +1 -1
- package/dist/svelte/jazz.class.svelte.d.ts +2 -2
- package/dist/svelte/jazz.class.svelte.d.ts.map +1 -1
- package/dist/svelte/jazz.class.svelte.js +15 -17
- package/dist/testing.js +1 -1
- package/dist/tools/coValues/coFeed.d.ts.map +1 -1
- package/dist/tools/coValues/group.d.ts.map +1 -1
- package/dist/tools/coValues/interfaces.d.ts +7 -6
- package/dist/tools/coValues/interfaces.d.ts.map +1 -1
- package/dist/tools/coValues/promise.d.ts +9 -0
- package/dist/tools/coValues/promise.d.ts.map +1 -0
- package/dist/tools/coValues/request.d.ts.map +1 -1
- package/dist/tools/exports.d.ts +1 -1
- package/dist/tools/exports.d.ts.map +1 -1
- package/dist/tools/implementation/refs.d.ts +1 -1
- package/dist/tools/implementation/refs.d.ts.map +1 -1
- package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts +3 -1
- package/dist/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.d.ts.map +1 -1
- package/dist/tools/subscribe/SubscriptionScope.d.ts +5 -2
- package/dist/tools/subscribe/SubscriptionScope.d.ts.map +1 -1
- package/dist/tools/subscribe/index.d.ts +1 -1
- package/dist/tools/subscribe/index.d.ts.map +1 -1
- package/dist/tools/subscribe/types.d.ts +2 -1
- package/dist/tools/subscribe/types.d.ts.map +1 -1
- package/dist/tools/tests/SubscriptionScope.test.d.ts +2 -0
- package/dist/tools/tests/SubscriptionScope.test.d.ts.map +1 -0
- package/package.json +4 -4
- package/src/inspector/tests/utils/history.test.ts +233 -2
- package/src/inspector/tests/viewer/co-plain-text-view.test.tsx +125 -0
- package/src/inspector/tests/viewer/history-view.test.tsx +134 -2
- package/src/inspector/utils/history.ts +168 -1
- package/src/inspector/viewer/co-plain-text-view.tsx +102 -3
- package/src/inspector/viewer/history-view.tsx +5 -25
- package/src/inspector/viewer/page.tsx +8 -1
- package/src/inspector/viewer/use-resolve-covalue.ts +2 -6
- package/src/react-core/hooks.ts +5 -29
- package/src/svelte/jazz.class.svelte.ts +16 -34
- package/src/tools/coValues/coFeed.ts +10 -7
- package/src/tools/coValues/coMap.ts +10 -7
- package/src/tools/coValues/group.ts +6 -2
- package/src/tools/coValues/interfaces.ts +48 -28
- package/src/tools/coValues/promise.ts +34 -0
- package/src/tools/coValues/request.ts +12 -8
- package/src/tools/exports.ts +1 -0
- package/src/tools/implementation/refs.ts +9 -17
- package/src/tools/implementation/zodSchema/runtimeConverters/schemaFieldToCoFieldDef.ts +62 -30
- package/src/tools/subscribe/SubscriptionScope.ts +45 -2
- package/src/tools/subscribe/index.ts +28 -13
- package/src/tools/subscribe/types.ts +5 -2
- package/src/tools/tests/SubscriptionScope.test.ts +397 -0
- package/src/tools/tests/deepLoading.test.ts +22 -0
- package/src/tools/tests/subscribe.test.ts +69 -0
- package/dist/chunk-JPWM4CS2.js.map +0 -1
|
@@ -17,6 +17,8 @@ import { HistoryView } from "../../viewer/history-view";
|
|
|
17
17
|
import { setup } from "goober";
|
|
18
18
|
import React from "react";
|
|
19
19
|
|
|
20
|
+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
|
|
20
22
|
function extractAction(row: HTMLElement | null | undefined) {
|
|
21
23
|
if (!row) return "";
|
|
22
24
|
// index 0: author, index 1: action, index 2: timestamp
|
|
@@ -30,7 +32,6 @@ function extractActions(): string[] {
|
|
|
30
32
|
|
|
31
33
|
describe("HistoryView", async () => {
|
|
32
34
|
const account = await setupJazzTestSync();
|
|
33
|
-
const account2 = await createJazzTestAccount();
|
|
34
35
|
|
|
35
36
|
beforeAll(() => {
|
|
36
37
|
// setup goober
|
|
@@ -270,7 +271,138 @@ describe("HistoryView", async () => {
|
|
|
270
271
|
});
|
|
271
272
|
});
|
|
272
273
|
|
|
273
|
-
describe("co.
|
|
274
|
+
describe("co.plaintext", () => {
|
|
275
|
+
it("should render co.plaintext initial append in a single row", async () => {
|
|
276
|
+
const value = co.plainText().create("hello", account);
|
|
277
|
+
render(
|
|
278
|
+
<HistoryView coValue={value.$jazz.raw} node={value.$jazz.localNode} />,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
expect(extractActions()).toEqual(['"hello" has been appended']);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it("should render co.plaintext appends in a single row", async () => {
|
|
285
|
+
const value = co.plainText().create("hello", account);
|
|
286
|
+
value.$jazz.applyDiff("hello world");
|
|
287
|
+
value.$jazz.applyDiff("hello world!");
|
|
288
|
+
|
|
289
|
+
expect(value.$jazz.raw.toString()).toEqual("hello world!");
|
|
290
|
+
|
|
291
|
+
render(
|
|
292
|
+
<HistoryView coValue={value.$jazz.raw} node={value.$jazz.localNode} />,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const history = [
|
|
296
|
+
'"hello" has been appended',
|
|
297
|
+
'" world" has been inserted after "o"',
|
|
298
|
+
'"!" has been inserted after " "', // it is after " " because previous action is reversed
|
|
299
|
+
].toReversed(); // Default sort is descending
|
|
300
|
+
|
|
301
|
+
expect(extractActions()).toEqual(history);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("should render co.plaintext delete in tail", async () => {
|
|
305
|
+
const value = co.plainText().create("hello", account);
|
|
306
|
+
value.$jazz.applyDiff("hell");
|
|
307
|
+
|
|
308
|
+
expect(value.$jazz.raw.toString()).toEqual("hell");
|
|
309
|
+
|
|
310
|
+
render(
|
|
311
|
+
<HistoryView coValue={value.$jazz.raw} node={value.$jazz.localNode} />,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const history = [
|
|
315
|
+
'"hello" has been appended',
|
|
316
|
+
'"o" has been deleted',
|
|
317
|
+
].toReversed(); // Default sort is descending
|
|
318
|
+
|
|
319
|
+
expect(extractActions()).toEqual(history);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it("should render co.plaintext delete in head", async () => {
|
|
323
|
+
const value = co.plainText().create("hello", account);
|
|
324
|
+
value.$jazz.applyDiff("ello");
|
|
325
|
+
|
|
326
|
+
expect(value.$jazz.raw.toString()).toEqual("ello");
|
|
327
|
+
|
|
328
|
+
render(
|
|
329
|
+
<HistoryView coValue={value.$jazz.raw} node={value.$jazz.localNode} />,
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
const history = [
|
|
333
|
+
'"hello" has been appended',
|
|
334
|
+
'"h" has been deleted',
|
|
335
|
+
].toReversed(); // Default sort is descending
|
|
336
|
+
|
|
337
|
+
expect(extractActions()).toEqual(history);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it("should render co.plaintext delete history of multiple old insertions in a single row", async () => {
|
|
341
|
+
const value = co.plainText().create("hello", account);
|
|
342
|
+
await sleep(2);
|
|
343
|
+
value.$jazz.applyDiff("hello world");
|
|
344
|
+
await sleep(2);
|
|
345
|
+
value.$jazz.applyDiff("hed");
|
|
346
|
+
|
|
347
|
+
expect(value.$jazz.raw.toString()).toEqual("hed");
|
|
348
|
+
|
|
349
|
+
render(
|
|
350
|
+
<HistoryView coValue={value.$jazz.raw} node={value.$jazz.localNode} />,
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const history = [
|
|
354
|
+
'"hello" has been appended',
|
|
355
|
+
'" world" has been inserted after "o"',
|
|
356
|
+
'"lod" has been deleted',
|
|
357
|
+
'" worl" has been deleted',
|
|
358
|
+
].toReversed(); // Default sort is descending
|
|
359
|
+
|
|
360
|
+
expect(extractActions()).toEqual(history);
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("should render co.plaintext insertBefore in history", async () => {
|
|
364
|
+
const value = co.plainText().create("world", account);
|
|
365
|
+
await sleep(2);
|
|
366
|
+
value.insertBefore(0, "Hello, ");
|
|
367
|
+
|
|
368
|
+
expect(value.$jazz.raw.toString()).toEqual("Hello, world");
|
|
369
|
+
|
|
370
|
+
render(
|
|
371
|
+
<HistoryView coValue={value.$jazz.raw} node={value.$jazz.localNode} />,
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const history = [
|
|
375
|
+
'"world" has been appended',
|
|
376
|
+
'"H" has been inserted before "w"',
|
|
377
|
+
'"ello, " has been inserted after "H"',
|
|
378
|
+
].toReversed(); // Default sort is descending
|
|
379
|
+
|
|
380
|
+
expect(extractActions()).toEqual(history);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it("should render co.plaintext insertAfter in history", async () => {
|
|
384
|
+
const value = co.plainText().create("world", account);
|
|
385
|
+
await sleep(2);
|
|
386
|
+
value.insertAfter(0, "Hello, ");
|
|
387
|
+
|
|
388
|
+
expect(value.$jazz.raw.toString()).toEqual("wHello, orld");
|
|
389
|
+
|
|
390
|
+
render(
|
|
391
|
+
<HistoryView coValue={value.$jazz.raw} node={value.$jazz.localNode} />,
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const history = [
|
|
395
|
+
'"world" has been appended',
|
|
396
|
+
'"Hello, " has been inserted after "w"',
|
|
397
|
+
].toReversed(); // Default sort is descending
|
|
398
|
+
|
|
399
|
+
expect(extractActions()).toEqual(history);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe("co.group", async () => {
|
|
404
|
+
const account2 = await createJazzTestAccount();
|
|
405
|
+
|
|
274
406
|
it("should render co.group changes", async () => {
|
|
275
407
|
const group = co.group().create(account);
|
|
276
408
|
|
|
@@ -1,5 +1,172 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
JsonObject,
|
|
3
|
+
JsonValue,
|
|
4
|
+
OpID,
|
|
5
|
+
RawCoMap,
|
|
6
|
+
RawCoPlainText,
|
|
7
|
+
RawCoValue,
|
|
8
|
+
Role,
|
|
9
|
+
} from "cojson";
|
|
10
|
+
import { stringifyOpID } from "cojson";
|
|
11
|
+
import type { VerifiedTransaction } from "cojson/dist/coValueCore/coValueCore.js";
|
|
2
12
|
import type { MapOpPayload } from "cojson/dist/coValues/coMap.js";
|
|
13
|
+
import * as TransactionChanges from "./transactions-changes";
|
|
14
|
+
import type {
|
|
15
|
+
DeletionOpPayload,
|
|
16
|
+
InsertionOpPayload,
|
|
17
|
+
} from "cojson/dist/coValues/coList.js";
|
|
18
|
+
|
|
19
|
+
export function areSameOpIds(
|
|
20
|
+
opId1: OpID | string,
|
|
21
|
+
opId2: OpID | string,
|
|
22
|
+
): boolean {
|
|
23
|
+
if (typeof opId1 === "string" || typeof opId2 === "string") {
|
|
24
|
+
return opId1 === opId2;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
opId1.sessionID === opId2.sessionID &&
|
|
29
|
+
opId1.txIndex === opId2.txIndex &&
|
|
30
|
+
opId1.changeIdx === opId2.changeIdx
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isCoPlainText(coValue: RawCoValue): coValue is RawCoPlainText {
|
|
35
|
+
return coValue.type === "coplaintext";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getTransactionChanges(
|
|
39
|
+
tx: VerifiedTransaction,
|
|
40
|
+
coValue: RawCoValue,
|
|
41
|
+
): JsonValue[] {
|
|
42
|
+
if (tx.isValid === false && tx.tx.privacy === "private") {
|
|
43
|
+
const readKey = coValue.core.getReadKey(tx.tx.keyUsed);
|
|
44
|
+
if (!readKey) {
|
|
45
|
+
return [
|
|
46
|
+
`Unable to decrypt transaction: read key ${tx.tx.keyUsed} not found.`,
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
coValue.core.verified.decryptTransaction(
|
|
52
|
+
tx.txID.sessionID,
|
|
53
|
+
tx.txID.txIndex,
|
|
54
|
+
readKey,
|
|
55
|
+
) ?? []
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Trying to collapse multiple changes into a single action in the history
|
|
60
|
+
if (isCoPlainText(coValue)) {
|
|
61
|
+
if (tx.changes === undefined || tx.changes.length === 0) return [];
|
|
62
|
+
const firstChange = tx.changes[0]!;
|
|
63
|
+
|
|
64
|
+
if (
|
|
65
|
+
TransactionChanges.isItemAppend(firstChange) &&
|
|
66
|
+
tx.changes.every(
|
|
67
|
+
(c) =>
|
|
68
|
+
TransactionChanges.isItemAppend(c) &&
|
|
69
|
+
areSameOpIds(c.after, firstChange.after),
|
|
70
|
+
)
|
|
71
|
+
) {
|
|
72
|
+
const changes = tx.changes as InsertionOpPayload<string>[];
|
|
73
|
+
if (firstChange.after !== "start") {
|
|
74
|
+
changes.reverse();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return [
|
|
78
|
+
{
|
|
79
|
+
op: "app",
|
|
80
|
+
value: changes.map((c) => c.value).join(""),
|
|
81
|
+
after: firstChange.after,
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
TransactionChanges.isItemPrepend(firstChange) &&
|
|
88
|
+
tx.changes.every(
|
|
89
|
+
(c) =>
|
|
90
|
+
TransactionChanges.isItemPrepend(c) &&
|
|
91
|
+
areSameOpIds(c.before, firstChange.before),
|
|
92
|
+
)
|
|
93
|
+
) {
|
|
94
|
+
const changes = tx.changes as InsertionOpPayload<string>[];
|
|
95
|
+
if (firstChange.before !== "end") {
|
|
96
|
+
changes.reverse();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return [
|
|
100
|
+
{
|
|
101
|
+
op: "pre",
|
|
102
|
+
value: changes.map((c) => c.value).join(""),
|
|
103
|
+
before: firstChange.before,
|
|
104
|
+
},
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
TransactionChanges.isItemDeletion(firstChange) &&
|
|
110
|
+
tx.changes.every((c) => TransactionChanges.isItemDeletion(c))
|
|
111
|
+
) {
|
|
112
|
+
const coValueBeforeDeletions = coValue.atTime(tx.madeAt - 1);
|
|
113
|
+
|
|
114
|
+
// Verify if the deleted chars are consecutive
|
|
115
|
+
function changesAreConsecutive(changes: DeletionOpPayload[]): boolean {
|
|
116
|
+
if (changes.length < 2) return false;
|
|
117
|
+
const mapping = coValueBeforeDeletions.mapping.idxAfterOpID;
|
|
118
|
+
|
|
119
|
+
for (let i = 1; i < changes.length; ++i) {
|
|
120
|
+
const prevIdx = mapping[stringifyOpID(changes[i - 1]!.insertion)];
|
|
121
|
+
const currIdx = mapping[stringifyOpID(changes[i]!.insertion)];
|
|
122
|
+
if (currIdx !== prevIdx && currIdx !== (prevIdx ?? -2) + 1) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (changesAreConsecutive(tx.changes)) {
|
|
130
|
+
// Group the deletions by insertion.sessionID-txIndex
|
|
131
|
+
// This is to help the readability of deletions that act on different previous transactions
|
|
132
|
+
const groupedBySession: Map<string, DeletionOpPayload[]> = new Map();
|
|
133
|
+
for (const change of tx.changes) {
|
|
134
|
+
const group = `${change.insertion.sessionID}-${change.insertion.txIndex}`;
|
|
135
|
+
if (!groupedBySession.has(group)) groupedBySession.set(group, []);
|
|
136
|
+
groupedBySession.get(group)!.push(change);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return Array.from(groupedBySession.values()).map((changes) => {
|
|
140
|
+
const stringDeleted = changes
|
|
141
|
+
// order by txIndex and changeIdx
|
|
142
|
+
.toSorted((a, b) => {
|
|
143
|
+
if (a.insertion.txIndex === b.insertion.txIndex) {
|
|
144
|
+
return a.insertion.changeIdx - b.insertion.changeIdx;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return a.insertion.txIndex - b.insertion.txIndex;
|
|
148
|
+
})
|
|
149
|
+
// extract the single char from the insertions
|
|
150
|
+
.map((c) =>
|
|
151
|
+
coValueBeforeDeletions.get(
|
|
152
|
+
coValueBeforeDeletions.mapping.idxAfterOpID[
|
|
153
|
+
stringifyOpID(c.insertion)
|
|
154
|
+
]!,
|
|
155
|
+
),
|
|
156
|
+
)
|
|
157
|
+
.join("");
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
op: "custom",
|
|
161
|
+
action: `"${stringDeleted}" has been deleted`,
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return tx.changes ?? (tx.tx as any).changes ?? [];
|
|
169
|
+
}
|
|
3
170
|
|
|
4
171
|
export function restoreCoMapToTimestamp(
|
|
5
172
|
coValue: RawCoMap,
|
|
@@ -1,13 +1,112 @@
|
|
|
1
|
-
import { JsonObject } from "cojson";
|
|
1
|
+
import { JsonObject, LocalNode, RawCoPlainText } from "cojson";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { styled } from "goober";
|
|
4
|
+
import { CoPlainText } from "jazz-tools";
|
|
5
|
+
import { isWriter } from "../utils/permissions.js";
|
|
6
|
+
import { Button } from "../ui/button.js";
|
|
2
7
|
import { RawDataCard } from "./raw-data-card.js";
|
|
8
|
+
import { Icon } from "../ui/icon.js";
|
|
9
|
+
|
|
10
|
+
export function CoPlainTextView({
|
|
11
|
+
data,
|
|
12
|
+
coValue,
|
|
13
|
+
}: {
|
|
14
|
+
data: JsonObject;
|
|
15
|
+
coValue: RawCoPlainText;
|
|
16
|
+
node: LocalNode;
|
|
17
|
+
}) {
|
|
18
|
+
const currentText = Object.values(data).join("");
|
|
19
|
+
const [isEditing, setIsEditing] = useState(false);
|
|
20
|
+
const [editValue, setEditValue] = useState("");
|
|
21
|
+
const canEdit = isWriter(coValue.group.myRole());
|
|
22
|
+
|
|
23
|
+
const handleEditClick = () => {
|
|
24
|
+
setIsEditing(true);
|
|
25
|
+
setEditValue(currentText);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const handleCancel = () => {
|
|
29
|
+
setIsEditing(false);
|
|
30
|
+
setEditValue(currentText);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const handleSave = (e: React.FormEvent) => {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
e.stopPropagation();
|
|
36
|
+
|
|
37
|
+
const coPlainText = CoPlainText.fromRaw(coValue);
|
|
38
|
+
coPlainText.$jazz.applyDiff(editValue);
|
|
39
|
+
|
|
40
|
+
setIsEditing(false);
|
|
41
|
+
};
|
|
3
42
|
|
|
4
|
-
export function CoPlainTextView({ data }: { data: JsonObject }) {
|
|
5
43
|
if (!data) return;
|
|
6
44
|
|
|
45
|
+
if (isEditing) {
|
|
46
|
+
return (
|
|
47
|
+
<>
|
|
48
|
+
<EditForm onSubmit={handleSave}>
|
|
49
|
+
<StyledTextarea
|
|
50
|
+
value={editValue}
|
|
51
|
+
onChange={(e) => setEditValue(e.target.value)}
|
|
52
|
+
onClick={(e) => e.stopPropagation()}
|
|
53
|
+
/>
|
|
54
|
+
<FormActions>
|
|
55
|
+
<Button type="button" variant="secondary" onClick={handleCancel}>
|
|
56
|
+
Cancel
|
|
57
|
+
</Button>
|
|
58
|
+
<Button type="submit" variant="primary">
|
|
59
|
+
Save
|
|
60
|
+
</Button>
|
|
61
|
+
</FormActions>
|
|
62
|
+
</EditForm>
|
|
63
|
+
<RawDataCard data={data} />
|
|
64
|
+
</>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
7
68
|
return (
|
|
8
69
|
<>
|
|
9
|
-
<p>{
|
|
70
|
+
<p>{currentText}</p>
|
|
71
|
+
<div>
|
|
72
|
+
{canEdit && (
|
|
73
|
+
<Button variant="secondary" onClick={handleEditClick} title="Edit">
|
|
74
|
+
<Icon name="edit" />
|
|
75
|
+
</Button>
|
|
76
|
+
)}
|
|
77
|
+
</div>
|
|
10
78
|
<RawDataCard data={data} />
|
|
11
79
|
</>
|
|
12
80
|
);
|
|
13
81
|
}
|
|
82
|
+
|
|
83
|
+
const EditForm = styled("form")`
|
|
84
|
+
display: flex;
|
|
85
|
+
flex-direction: column;
|
|
86
|
+
gap: 0.75rem;
|
|
87
|
+
margin-bottom: 1rem;
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
const StyledTextarea = styled("textarea")`
|
|
91
|
+
width: 100%;
|
|
92
|
+
min-height: 120px;
|
|
93
|
+
border-radius: var(--j-radius-md);
|
|
94
|
+
border: 1px solid var(--j-border-color);
|
|
95
|
+
padding: 0.5rem 0.875rem;
|
|
96
|
+
box-shadow: var(--j-shadow-sm);
|
|
97
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
98
|
+
font-size: 0.875rem;
|
|
99
|
+
background-color: white;
|
|
100
|
+
color: var(--j-text-color-strong);
|
|
101
|
+
resize: vertical;
|
|
102
|
+
|
|
103
|
+
@media (prefers-color-scheme: dark) {
|
|
104
|
+
background-color: var(--j-foreground);
|
|
105
|
+
}
|
|
106
|
+
`;
|
|
107
|
+
|
|
108
|
+
const FormActions = styled("div")`
|
|
109
|
+
display: flex;
|
|
110
|
+
gap: 0.5rem;
|
|
111
|
+
justify-content: flex-end;
|
|
112
|
+
`;
|
|
@@ -3,9 +3,9 @@ import { useMemo } from "react";
|
|
|
3
3
|
import { styled } from "goober";
|
|
4
4
|
import { AccountOrGroupText } from "./account-or-group-text";
|
|
5
5
|
import { DataTable, ColumnDef } from "../ui/data-table";
|
|
6
|
-
import type { VerifiedTransaction } from "cojson/dist/coValueCore/coValueCore.js";
|
|
7
6
|
import { Icon, Accordion } from "../ui";
|
|
8
7
|
import * as TransactionChanges from "../utils/transactions-changes";
|
|
8
|
+
import { getTransactionChanges } from "../utils/history";
|
|
9
9
|
|
|
10
10
|
type HistoryEntry = {
|
|
11
11
|
id: string;
|
|
@@ -108,30 +108,6 @@ export function HistoryView({
|
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
function getTransactionChanges(
|
|
112
|
-
tx: VerifiedTransaction,
|
|
113
|
-
coValue: RawCoValue,
|
|
114
|
-
): JsonValue[] {
|
|
115
|
-
if (tx.isValid === false && tx.tx.privacy === "private") {
|
|
116
|
-
const readKey = coValue.core.getReadKey(tx.tx.keyUsed);
|
|
117
|
-
if (!readKey) {
|
|
118
|
-
return [
|
|
119
|
-
`Unable to decrypt transaction: read key ${tx.tx.keyUsed} not found.`,
|
|
120
|
-
];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
coValue.core.verified.decryptTransaction(
|
|
125
|
-
tx.txID.sessionID,
|
|
126
|
-
tx.txID.txIndex,
|
|
127
|
-
readKey,
|
|
128
|
-
) ?? []
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return tx.changes ?? (tx.tx as any).changes ?? [];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
111
|
function getHistory(coValue: RawCoValue): HistoryEntry[] {
|
|
136
112
|
return coValue.core.verifiedTransactions.flatMap((tx, index) => {
|
|
137
113
|
const changes = getTransactionChanges(tx, coValue);
|
|
@@ -240,6 +216,10 @@ function mapTransactionToAction(
|
|
|
240
216
|
return `Property "${change.key}" has been deleted`;
|
|
241
217
|
}
|
|
242
218
|
|
|
219
|
+
if ((change as any).op === "custom") {
|
|
220
|
+
return (change as any).action;
|
|
221
|
+
}
|
|
222
|
+
|
|
243
223
|
return "Unknown action: " + JSON.stringify(change);
|
|
244
224
|
}
|
|
245
225
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
LocalNode,
|
|
4
4
|
RawCoList,
|
|
5
5
|
RawCoMap,
|
|
6
|
+
RawCoPlainText,
|
|
6
7
|
RawCoStream,
|
|
7
8
|
RawCoValue,
|
|
8
9
|
RawGroup,
|
|
@@ -142,7 +143,13 @@ function View(
|
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
if (type === "coplaintext") {
|
|
145
|
-
return
|
|
146
|
+
return (
|
|
147
|
+
<CoPlainTextView
|
|
148
|
+
data={snapshot}
|
|
149
|
+
coValue={value as RawCoPlainText}
|
|
150
|
+
node={node}
|
|
151
|
+
/>
|
|
152
|
+
);
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
if (type === "colist") {
|
|
@@ -48,10 +48,6 @@ export type ResolvedAccount = {
|
|
|
48
48
|
[key: string]: JSON;
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
export const isAccount = (coValue: JSONObject): coValue is ResolvedAccount => {
|
|
52
|
-
return isGroup(coValue) && "profile" in coValue;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
51
|
export async function resolveCoValue(
|
|
56
52
|
coValueId: CoID<RawCoValue>,
|
|
57
53
|
node: LocalNode,
|
|
@@ -89,7 +85,7 @@ export async function resolveCoValue(
|
|
|
89
85
|
if (type === "comap") {
|
|
90
86
|
if (isBrowserImage(snapshot)) {
|
|
91
87
|
extendedType = "image";
|
|
92
|
-
} else if (
|
|
88
|
+
} else if (value.headerMeta?.type === "account") {
|
|
93
89
|
extendedType = "account";
|
|
94
90
|
} else if (value.core.isGroup()) {
|
|
95
91
|
extendedType = "group";
|
|
@@ -125,7 +121,7 @@ function subscribeToCoValue(
|
|
|
125
121
|
if (type === "comap") {
|
|
126
122
|
if (isBrowserImage(snapshot)) {
|
|
127
123
|
extendedType = "image";
|
|
128
|
-
} else if (
|
|
124
|
+
} else if (value.headerMeta?.type === "account") {
|
|
129
125
|
extendedType = "account";
|
|
130
126
|
} else if (value.core.isGroup()) {
|
|
131
127
|
extendedType = "group";
|
package/src/react-core/hooks.ts
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
SchemaResolveQuery,
|
|
27
27
|
SubscriptionScope,
|
|
28
28
|
coValueClassFromCoValueClassOrSchema,
|
|
29
|
-
|
|
29
|
+
getUnloadedCoValueWithoutId,
|
|
30
30
|
type BranchDefinition,
|
|
31
31
|
} from "jazz-tools";
|
|
32
32
|
import { JazzContext, JazzContextManagerContext } from "./provider.js";
|
|
@@ -172,39 +172,15 @@ export function useCoValueSubscription<
|
|
|
172
172
|
return subscription.subscription as CoValueSubscription<S, R>;
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
function getSubscriptionValue<C extends CoValue>(
|
|
176
|
-
subscription: SubscriptionScope<C> | null,
|
|
177
|
-
): MaybeLoaded<C> {
|
|
178
|
-
if (!subscription) {
|
|
179
|
-
return createUnloadedCoValue("", CoValueLoadingState.UNAVAILABLE);
|
|
180
|
-
}
|
|
181
|
-
const value = subscription.getCurrentValue();
|
|
182
|
-
if (typeof value === "string") {
|
|
183
|
-
return createUnloadedCoValue(subscription.id, value);
|
|
184
|
-
}
|
|
185
|
-
return value;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
175
|
function useGetCurrentValue<C extends CoValue>(
|
|
189
176
|
subscription: SubscriptionScope<C> | null,
|
|
190
177
|
) {
|
|
191
|
-
const previousValue = useRef<MaybeLoaded<CoValue> | undefined>(undefined);
|
|
192
|
-
|
|
193
178
|
return useCallback(() => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (
|
|
197
|
-
previousValue.current !== undefined &&
|
|
198
|
-
previousValue.current.$jazz.id === currentValue.$jazz.id &&
|
|
199
|
-
!previousValue.current.$isLoaded &&
|
|
200
|
-
!currentValue.$isLoaded &&
|
|
201
|
-
previousValue.current.$jazz.loadingState ===
|
|
202
|
-
currentValue.$jazz.loadingState
|
|
203
|
-
) {
|
|
204
|
-
return previousValue.current as MaybeLoaded<C>;
|
|
179
|
+
if (!subscription) {
|
|
180
|
+
return getUnloadedCoValueWithoutId(CoValueLoadingState.UNAVAILABLE);
|
|
205
181
|
}
|
|
206
|
-
|
|
207
|
-
return
|
|
182
|
+
|
|
183
|
+
return subscription.getCurrentValue();
|
|
208
184
|
}, [subscription]);
|
|
209
185
|
}
|
|
210
186
|
|