@tldraw/editor 5.3.0-next.299378752aaf → 5.3.0-next.75e2874c4b3f
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/index.d.ts +23 -0
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/editor/Editor.js +7 -2
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +3 -0
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +14 -5
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +23 -0
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/editor/Editor.mjs +7 -2
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +3 -0
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +14 -5
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +10 -0
- package/package.json +7 -7
- package/src/lib/editor/Editor.ts +14 -2
- package/src/lib/editor/managers/TextManager/TextManager.ts +3 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +24 -0
- package/src/lib/license/LicenseManager.test.ts +99 -29
- package/src/lib/license/LicenseManager.ts +16 -4
- package/src/version.ts +3 -3
|
@@ -183,6 +183,9 @@ export class TextManager extends EditorManager {
|
|
|
183
183
|
'font-weight': opts.fontWeight,
|
|
184
184
|
'font-size': opts.fontSize + 'px',
|
|
185
185
|
'line-height': `${resolveLineHeightPx(opts.fontSize, opts.lineHeight)}px`,
|
|
186
|
+
// Unitless multiplier consumed by the .tl-rich-text h1–h6 rule (see editor.css),
|
|
187
|
+
// so heading measurement matches on-canvas rendering.
|
|
188
|
+
'--tl-rich-text-heading-line-height': `${opts.lineHeight}`,
|
|
186
189
|
padding: opts.padding,
|
|
187
190
|
'max-width': opts.maxWidth ? opts.maxWidth + 'px' : undefined,
|
|
188
191
|
'min-width': opts.minWidth ? opts.minWidth + 'px' : undefined,
|
|
@@ -669,6 +669,30 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
|
|
|
669
669
|
*/
|
|
670
670
|
onBeforeCreate?(next: Shape): Shape | void
|
|
671
671
|
|
|
672
|
+
/**
|
|
673
|
+
* A callback called when a shape is reproduced from an existing shape, either by duplicating
|
|
674
|
+
* ({@link Editor.duplicateShapes}) or by pasting/putting content onto the page
|
|
675
|
+
* ({@link Editor.putContentOntoCurrentPage}). This provides a last chance to modify the copy
|
|
676
|
+
* before it's created — for example, to re-stamp attribution so the copy is credited to the
|
|
677
|
+
* current user rather than the original author. It is not called when content is put with
|
|
678
|
+
* `preserveIds` (e.g. {@link Editor.moveShapesToPage}), since the shape keeps its identity
|
|
679
|
+
* and no copy is made.
|
|
680
|
+
*
|
|
681
|
+
* @example
|
|
682
|
+
*
|
|
683
|
+
* ```ts
|
|
684
|
+
* onBeforeDuplicate = (source, duplicate) => {
|
|
685
|
+
* return { ...duplicate, props: { ...duplicate.props, editedBy: this.editor.getAttributionUserId() } }
|
|
686
|
+
* }
|
|
687
|
+
* ```
|
|
688
|
+
*
|
|
689
|
+
* @param source - The shape being copied from.
|
|
690
|
+
* @param duplicate - The new copy (with its own id), before it's created.
|
|
691
|
+
* @returns The next shape or void.
|
|
692
|
+
* @public
|
|
693
|
+
*/
|
|
694
|
+
onBeforeDuplicate?(source: Shape, duplicate: Shape): Shape | void
|
|
695
|
+
|
|
672
696
|
/**
|
|
673
697
|
* A callback called just before a shape is updated. This method provides a last chance to modify
|
|
674
698
|
* the updated shape.
|
|
@@ -510,6 +510,60 @@ describe('LicenseManager', () => {
|
|
|
510
510
|
})
|
|
511
511
|
})
|
|
512
512
|
|
|
513
|
+
describe('Expiry date timezone handling', () => {
|
|
514
|
+
afterEach(() => {
|
|
515
|
+
vi.useRealTimers()
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// The named expiry date is minted as a date-only string and should mean "the license
|
|
519
|
+
// is fully usable through the end of that day in UTC", giving every user the same
|
|
520
|
+
// cutoff regardless of their local timezone.
|
|
521
|
+
async function makeEvaluationLicenseWithExpiry(expiry: string) {
|
|
522
|
+
const licenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
523
|
+
licenseInfo[PROPERTIES.FLAGS] = FLAGS.EVALUATION_LICENSE
|
|
524
|
+
licenseInfo[PROPERTIES.EXPIRY_DATE] = expiry
|
|
525
|
+
return generateLicenseKey(JSON.stringify(licenseInfo), keyPair)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
it('Keeps the license valid through the end of the named expiry day (UTC)', async () => {
|
|
529
|
+
const key = await makeEvaluationLicenseWithExpiry('2026-06-26')
|
|
530
|
+
|
|
531
|
+
// The final moment of the named day is still valid.
|
|
532
|
+
vi.useFakeTimers()
|
|
533
|
+
vi.setSystemTime(new Date('2026-06-26T23:59:59.000Z'))
|
|
534
|
+
expect(
|
|
535
|
+
((await licenseManager.getLicenseFromKey(key)) as ValidLicenseKeyResult)
|
|
536
|
+
.isEvaluationLicenseExpired
|
|
537
|
+
).toBe(false)
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
it('Expires the license at the start of the day after the named expiry day (UTC)', async () => {
|
|
541
|
+
const key = await makeEvaluationLicenseWithExpiry('2026-06-26')
|
|
542
|
+
|
|
543
|
+
vi.useFakeTimers()
|
|
544
|
+
vi.setSystemTime(new Date('2026-06-27T00:00:01.000Z'))
|
|
545
|
+
expect(
|
|
546
|
+
((await licenseManager.getLicenseFromKey(key)) as ValidLicenseKeyResult)
|
|
547
|
+
.isEvaluationLicenseExpired
|
|
548
|
+
).toBe(true)
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
it('Does not expire early for users west of UTC who are still on the named day locally', async () => {
|
|
552
|
+
const key = await makeEvaluationLicenseWithExpiry('2026-06-26')
|
|
553
|
+
|
|
554
|
+
// Under the old logic a user west of UTC parsed the date one calendar day early and
|
|
555
|
+
// expired at the *start* of that day, losing most of a day. With the uniform UTC
|
|
556
|
+
// cutoff the license stays valid all through June 26 for everyone, no matter their
|
|
557
|
+
// local offset. 1pm UTC on June 26 is well before the cutoff, so it is still valid.
|
|
558
|
+
vi.useFakeTimers()
|
|
559
|
+
vi.setSystemTime(new Date('2026-06-26T13:00:00.000Z'))
|
|
560
|
+
expect(
|
|
561
|
+
((await licenseManager.getLicenseFromKey(key)) as ValidLicenseKeyResult)
|
|
562
|
+
.isEvaluationLicenseExpired
|
|
563
|
+
).toBe(false)
|
|
564
|
+
})
|
|
565
|
+
})
|
|
566
|
+
|
|
513
567
|
describe('License expiry and grace period', () => {
|
|
514
568
|
it('Fails if the license key has expired beyond grace period', async () => {
|
|
515
569
|
const expiredLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
@@ -540,38 +594,54 @@ describe('LicenseManager', () => {
|
|
|
540
594
|
})
|
|
541
595
|
|
|
542
596
|
it('Handles grace period correctly - 20 days expired should still be within grace period', async () => {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
597
|
+
// Pin the clock and use a date-only UTC expiry so the calculation is timezone
|
|
598
|
+
// independent. The named day is fully usable, so an expiry 20 calendar days before
|
|
599
|
+
// "now" has been past its usable window for 19 full days.
|
|
600
|
+
vi.useFakeTimers()
|
|
601
|
+
vi.setSystemTime(new Date('2026-06-26T12:00:00.000Z'))
|
|
602
|
+
try {
|
|
603
|
+
const expiredLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
604
|
+
expiredLicenseInfo[PROPERTIES.EXPIRY_DATE] = '2026-06-06' // 20 days before now
|
|
605
|
+
|
|
606
|
+
const expiredLicenseKey = await generateLicenseKey(
|
|
607
|
+
JSON.stringify(expiredLicenseInfo),
|
|
608
|
+
keyPair
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
// Test the getLicenseFromKey method to verify grace period calculation
|
|
612
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
613
|
+
expiredLicenseKey
|
|
614
|
+
)) as ValidLicenseKeyResult
|
|
615
|
+
expect(result.isAnnualLicense).toBe(true)
|
|
616
|
+
expect(result.isAnnualLicenseExpired).toBe(false) // Within 30-day grace period
|
|
617
|
+
expect(result.daysSinceExpiry).toBe(19)
|
|
618
|
+
} finally {
|
|
619
|
+
vi.useRealTimers()
|
|
620
|
+
}
|
|
559
621
|
})
|
|
560
622
|
|
|
561
623
|
it('Calculates days since expiry correctly', async () => {
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
624
|
+
// Pin the clock and use a date-only UTC expiry so the calculation is timezone
|
|
625
|
+
// independent. The named day is fully usable, so an expiry 15 calendar days before
|
|
626
|
+
// "now" has been past its usable window for 14 full days.
|
|
627
|
+
vi.useFakeTimers()
|
|
628
|
+
vi.setSystemTime(new Date('2026-06-26T12:00:00.000Z'))
|
|
629
|
+
try {
|
|
630
|
+
const expiredLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
631
|
+
expiredLicenseInfo[PROPERTIES.EXPIRY_DATE] = '2026-06-11' // 15 days before now
|
|
632
|
+
|
|
633
|
+
const expiredLicenseKey = await generateLicenseKey(
|
|
634
|
+
JSON.stringify(expiredLicenseInfo),
|
|
635
|
+
keyPair
|
|
636
|
+
)
|
|
637
|
+
|
|
638
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
639
|
+
expiredLicenseKey
|
|
640
|
+
)) as ValidLicenseKeyResult
|
|
641
|
+
expect(result.daysSinceExpiry).toBe(14)
|
|
642
|
+
} finally {
|
|
643
|
+
vi.useRealTimers()
|
|
644
|
+
}
|
|
575
645
|
})
|
|
576
646
|
})
|
|
577
647
|
|
|
@@ -372,14 +372,26 @@ export class LicenseManager {
|
|
|
372
372
|
}
|
|
373
373
|
|
|
374
374
|
private getExpirationDateWithoutGracePeriod(expiryDate: Date) {
|
|
375
|
-
|
|
375
|
+
// The named expiry date is the last day the license is fully usable, so the license
|
|
376
|
+
// expires at the end of that day, i.e. the start of the following day. We work in UTC
|
|
377
|
+
// (the expiry date is minted and parsed as a UTC date-only string) so the cutoff is a
|
|
378
|
+
// single predictable instant for every user regardless of their local timezone.
|
|
379
|
+
return new Date(
|
|
380
|
+
Date.UTC(
|
|
381
|
+
expiryDate.getUTCFullYear(),
|
|
382
|
+
expiryDate.getUTCMonth(),
|
|
383
|
+
expiryDate.getUTCDate() + 1 // Add 1 day so the named date is fully usable
|
|
384
|
+
)
|
|
385
|
+
)
|
|
376
386
|
}
|
|
377
387
|
|
|
378
388
|
private getExpirationDateWithGracePeriod(expiryDate: Date) {
|
|
379
389
|
return new Date(
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
390
|
+
Date.UTC(
|
|
391
|
+
expiryDate.getUTCFullYear(),
|
|
392
|
+
expiryDate.getUTCMonth(),
|
|
393
|
+
expiryDate.getUTCDate() + GRACE_PERIOD_DAYS + 1 // Add 1 day to include the expiration day
|
|
394
|
+
)
|
|
383
395
|
)
|
|
384
396
|
}
|
|
385
397
|
|
package/src/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '5.3.0-next.
|
|
4
|
+
export const version = '5.3.0-next.75e2874c4b3f'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2026-05-06T16:28:18.473Z',
|
|
7
|
-
minor: '2026-07-
|
|
8
|
-
patch: '2026-07-
|
|
7
|
+
minor: '2026-07-24T17:23:32.181Z',
|
|
8
|
+
patch: '2026-07-24T17:23:32.181Z',
|
|
9
9
|
}
|