@tldraw/tlschema 4.6.0-next.1f489710ee41 → 4.6.0-next.4dde09fa17ab
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-cjs/TLStore.js +13 -0
- package/dist-cjs/TLStore.js.map +2 -2
- package/dist-cjs/createPresenceStateDerivation.js +6 -4
- package/dist-cjs/createPresenceStateDerivation.js.map +2 -2
- package/dist-cjs/createTLSchema.js +8 -1
- package/dist-cjs/createTLSchema.js.map +2 -2
- package/dist-cjs/index.d.ts +211 -28
- package/dist-cjs/index.js +9 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/records/TLRecord.js.map +1 -1
- package/dist-cjs/records/TLUser.js +101 -0
- package/dist-cjs/records/TLUser.js.map +7 -0
- package/dist-cjs/shapes/TLNoteShape.js +13 -2
- package/dist-cjs/shapes/TLNoteShape.js.map +2 -2
- package/dist-esm/TLStore.mjs +13 -0
- package/dist-esm/TLStore.mjs.map +2 -2
- package/dist-esm/createPresenceStateDerivation.mjs +6 -4
- package/dist-esm/createPresenceStateDerivation.mjs.map +2 -2
- package/dist-esm/createTLSchema.mjs +8 -1
- package/dist-esm/createTLSchema.mjs.map +2 -2
- package/dist-esm/index.d.mts +211 -28
- package/dist-esm/index.mjs +17 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/records/TLUser.mjs +85 -0
- package/dist-esm/records/TLUser.mjs.map +7 -0
- package/dist-esm/shapes/TLNoteShape.mjs +13 -2
- package/dist-esm/shapes/TLNoteShape.mjs.map +2 -2
- package/package.json +6 -6
- package/src/TLStore.test.ts +5 -0
- package/src/TLStore.ts +95 -1
- package/src/createPresenceStateDerivation.test.ts +33 -20
- package/src/createPresenceStateDerivation.ts +20 -25
- package/src/createTLSchema.ts +52 -0
- package/src/index.ts +13 -1
- package/src/migrations.test.ts +22 -0
- package/src/records/TLRecord.ts +2 -0
- package/src/records/TLUser.ts +148 -0
- package/src/shapes/TLNoteShape.ts +13 -0
package/src/migrations.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { instancePageStateVersions } from './records/TLPageState'
|
|
|
15
15
|
import { pointerVersions } from './records/TLPointer'
|
|
16
16
|
import { instancePresenceVersions } from './records/TLPresence'
|
|
17
17
|
import { TLShape, rootShapeVersions } from './records/TLShape'
|
|
18
|
+
import { userVersions } from './records/TLUser'
|
|
18
19
|
import { arrowShapeVersions } from './shapes/TLArrowShape'
|
|
19
20
|
import { bookmarkShapeVersions } from './shapes/TLBookmarkShape'
|
|
20
21
|
import { drawShapeVersions } from './shapes/TLDrawShape'
|
|
@@ -2603,6 +2604,27 @@ describe('LegacyPointsConversion migration for highlight shape', () => {
|
|
|
2603
2604
|
})
|
|
2604
2605
|
})
|
|
2605
2606
|
|
|
2607
|
+
describe('Adding textFirstEditedBy to note shape', () => {
|
|
2608
|
+
const { up, down } = getTestMigration(noteShapeVersions.AddFirstEditedBy)
|
|
2609
|
+
|
|
2610
|
+
test('up works as expected', () => {
|
|
2611
|
+
expect(up({ props: {} })).toEqual({ props: { textFirstEditedBy: null } })
|
|
2612
|
+
})
|
|
2613
|
+
|
|
2614
|
+
test('down works as expected', () => {
|
|
2615
|
+
expect(down({ props: { textFirstEditedBy: null } })).toEqual({ props: {} })
|
|
2616
|
+
})
|
|
2617
|
+
})
|
|
2618
|
+
|
|
2619
|
+
describe('TLUser initial migration', () => {
|
|
2620
|
+
const { up } = getTestMigration(userVersions.Initial)
|
|
2621
|
+
|
|
2622
|
+
test('up is a no-op', () => {
|
|
2623
|
+
const record = { id: 'user:123', name: 'Test', color: '#000', imageUrl: '', meta: {} }
|
|
2624
|
+
expect(up(record)).toEqual(record)
|
|
2625
|
+
})
|
|
2626
|
+
})
|
|
2627
|
+
|
|
2606
2628
|
/* --- PUT YOUR MIGRATIONS TESTS ABOVE HERE --- */
|
|
2607
2629
|
|
|
2608
2630
|
// check that all migrator fns were called at least once
|
package/src/records/TLRecord.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { TLInstancePageState } from './TLPageState'
|
|
|
8
8
|
import { TLPointer } from './TLPointer'
|
|
9
9
|
import { TLInstancePresence } from './TLPresence'
|
|
10
10
|
import { TLShape } from './TLShape'
|
|
11
|
+
import { TLUser } from './TLUser'
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Interface for extending tldraw with custom record types via TypeScript module augmentation.
|
|
@@ -63,6 +64,7 @@ export type TLDefaultRecord =
|
|
|
63
64
|
| TLShape
|
|
64
65
|
| TLInstancePresence
|
|
65
66
|
| TLPointer
|
|
67
|
+
| TLUser
|
|
66
68
|
|
|
67
69
|
/**
|
|
68
70
|
* Index type that maps custom record type names to their record types.
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BaseRecord,
|
|
3
|
+
createMigrationIds,
|
|
4
|
+
createRecordMigrationSequence,
|
|
5
|
+
createRecordType,
|
|
6
|
+
RecordId,
|
|
7
|
+
} from '@tldraw/store'
|
|
8
|
+
import { JsonObject } from '@tldraw/utils'
|
|
9
|
+
import { T } from '@tldraw/validate'
|
|
10
|
+
import { idValidator } from '../misc/id-validator'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A user record in a tldraw store. User records are document-scoped and
|
|
14
|
+
* persist alongside shapes, assets, and pages. They are automatically
|
|
15
|
+
* included in snapshots, clipboard content, and `.tldr` files so that
|
|
16
|
+
* attribution display names survive across boards and sessions.
|
|
17
|
+
*
|
|
18
|
+
* User records are populated from the {@link @tldraw/tlschema#TLUserStore}
|
|
19
|
+
* when the editor stamps attribution metadata onto shapes.
|
|
20
|
+
*
|
|
21
|
+
* Extend user records with custom metadata by passing validators to
|
|
22
|
+
* {@link @tldraw/tlschema#createTLSchema} or {@link createUserRecordType}.
|
|
23
|
+
*
|
|
24
|
+
* @public
|
|
25
|
+
*/
|
|
26
|
+
export interface TLUser extends BaseRecord<'user', TLUserId> {
|
|
27
|
+
name: string
|
|
28
|
+
color: string
|
|
29
|
+
imageUrl: string
|
|
30
|
+
meta: JsonObject
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @public */
|
|
34
|
+
export type TLUserId = RecordId<TLUser>
|
|
35
|
+
|
|
36
|
+
/** @public */
|
|
37
|
+
export const userIdValidator = idValidator<TLUserId>('user')
|
|
38
|
+
|
|
39
|
+
/** @public */
|
|
40
|
+
export const userVersions = createMigrationIds('com.tldraw.user', {
|
|
41
|
+
Initial: 1,
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
/** @public */
|
|
45
|
+
export const userMigrations = createRecordMigrationSequence({
|
|
46
|
+
sequenceId: 'com.tldraw.user',
|
|
47
|
+
recordType: 'user',
|
|
48
|
+
sequence: [
|
|
49
|
+
{
|
|
50
|
+
id: userVersions.Initial,
|
|
51
|
+
up: (_record: any) => {
|
|
52
|
+
// initial version — nothing to migrate
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Creates a user record type with optional custom meta validation.
|
|
60
|
+
*
|
|
61
|
+
* When `meta` validators are provided, the user record's `meta` field will
|
|
62
|
+
* validate those specific fields (when present) while still allowing
|
|
63
|
+
* arbitrary additional JSON properties. Custom meta fields are treated as
|
|
64
|
+
* optional so that user records created without them remain valid.
|
|
65
|
+
*
|
|
66
|
+
* @param config - Optional configuration for custom meta validators
|
|
67
|
+
* @returns A configured user record type
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* import { createUserRecordType } from '@tldraw/tlschema'
|
|
72
|
+
* import { T } from '@tldraw/validate'
|
|
73
|
+
*
|
|
74
|
+
* const CustomUserRecordType = createUserRecordType({
|
|
75
|
+
* meta: {
|
|
76
|
+
* isAdmin: T.boolean,
|
|
77
|
+
* department: T.string,
|
|
78
|
+
* },
|
|
79
|
+
* })
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* @public
|
|
83
|
+
*/
|
|
84
|
+
export function createUserRecordType(config?: { meta?: Record<string, T.Validatable<any>> }) {
|
|
85
|
+
const metaConfig = config?.meta
|
|
86
|
+
|
|
87
|
+
const metaValidator = metaConfig
|
|
88
|
+
? T.object(
|
|
89
|
+
Object.fromEntries(
|
|
90
|
+
Object.entries(metaConfig).map(([key, v]) => [
|
|
91
|
+
key,
|
|
92
|
+
new T.Validator((value) => {
|
|
93
|
+
if (value === undefined) return undefined
|
|
94
|
+
return (v as T.Validator<unknown>).validate(value)
|
|
95
|
+
}),
|
|
96
|
+
])
|
|
97
|
+
)
|
|
98
|
+
).allowUnknownProperties()
|
|
99
|
+
: (T.jsonValue as T.ObjectValidator<JsonObject>)
|
|
100
|
+
|
|
101
|
+
const validator: T.Validator<TLUser> = T.model(
|
|
102
|
+
'user',
|
|
103
|
+
T.object({
|
|
104
|
+
typeName: T.literal('user'),
|
|
105
|
+
id: userIdValidator,
|
|
106
|
+
name: T.string,
|
|
107
|
+
color: T.string,
|
|
108
|
+
imageUrl: T.string,
|
|
109
|
+
meta: metaValidator as T.ObjectValidator<JsonObject>,
|
|
110
|
+
})
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
return createRecordType<TLUser>('user', {
|
|
114
|
+
validator,
|
|
115
|
+
scope: 'document',
|
|
116
|
+
}).withDefaultProperties(() => ({
|
|
117
|
+
name: '',
|
|
118
|
+
color: '',
|
|
119
|
+
imageUrl: '',
|
|
120
|
+
meta: {},
|
|
121
|
+
}))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** @public */
|
|
125
|
+
export const userValidator: T.Validator<TLUser> = T.model(
|
|
126
|
+
'user',
|
|
127
|
+
T.object({
|
|
128
|
+
typeName: T.literal('user'),
|
|
129
|
+
id: userIdValidator,
|
|
130
|
+
name: T.string,
|
|
131
|
+
color: T.string,
|
|
132
|
+
imageUrl: T.string,
|
|
133
|
+
meta: T.jsonValue as T.ObjectValidator<JsonObject>,
|
|
134
|
+
})
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
/** @public */
|
|
138
|
+
export const UserRecordType = createUserRecordType()
|
|
139
|
+
|
|
140
|
+
/** @public */
|
|
141
|
+
export function isUserId(id: string): id is TLUserId {
|
|
142
|
+
return UserRecordType.isId(id)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @public */
|
|
146
|
+
export function createUserId(id: string): TLUserId {
|
|
147
|
+
return UserRecordType.createId(id)
|
|
148
|
+
}
|
|
@@ -64,6 +64,8 @@ export interface TLNoteShapeProps {
|
|
|
64
64
|
richText: TLRichText
|
|
65
65
|
/** Scale factor applied to the note shape for display */
|
|
66
66
|
scale: number
|
|
67
|
+
/** User ID of the person who first edited the note text */
|
|
68
|
+
textFirstEditedBy: string | null
|
|
67
69
|
}
|
|
68
70
|
|
|
69
71
|
/**
|
|
@@ -130,6 +132,7 @@ export const noteShapeProps: RecordProps<TLNoteShape> = {
|
|
|
130
132
|
url: T.linkUrl,
|
|
131
133
|
richText: richTextValidator,
|
|
132
134
|
scale: T.nonZeroNumber,
|
|
135
|
+
textFirstEditedBy: T.string.nullable(),
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
const Versions = createShapePropsMigrationIds('note', {
|
|
@@ -143,6 +146,7 @@ const Versions = createShapePropsMigrationIds('note', {
|
|
|
143
146
|
AddLabelColor: 8,
|
|
144
147
|
AddRichText: 9,
|
|
145
148
|
AddRichTextAttrs: 10,
|
|
149
|
+
AddFirstEditedBy: 11,
|
|
146
150
|
})
|
|
147
151
|
|
|
148
152
|
/**
|
|
@@ -265,5 +269,14 @@ export const noteShapeMigrations = createShapePropsMigrationSequence({
|
|
|
265
269
|
}
|
|
266
270
|
},
|
|
267
271
|
},
|
|
272
|
+
{
|
|
273
|
+
id: Versions.AddFirstEditedBy,
|
|
274
|
+
up: (props) => {
|
|
275
|
+
props.textFirstEditedBy = null
|
|
276
|
+
},
|
|
277
|
+
down: (props) => {
|
|
278
|
+
delete props.textFirstEditedBy
|
|
279
|
+
},
|
|
280
|
+
},
|
|
268
281
|
],
|
|
269
282
|
})
|