@templatical/core 0.47.0 → 0.49.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 CHANGED
@@ -45,6 +45,8 @@ watch(editor.content, (next) => {
45
45
 
46
46
  - `useEditor` — reactive state (content, selection, viewport, darkMode, isDirty) + mutation methods (`addBlock`, `removeBlock`, `moveBlock`, `updateBlock`, `updateSettings`, `selectBlock`, …)
47
47
  - `useHistory` — undo/redo stack
48
+ - `useCollaboration` — the real-time collaboration controller over a `CollaborationProvider`: ordered operation batches, block locks, presence, and undo/redo as inverse operations
49
+ - `diffContent` / `invertOperations` — the operations between two contents, and the guarded operations that undo a batch
48
50
  - `useHistoryInterceptor` — wraps mutation methods so they push onto the history stack automatically
49
51
  - `useBlockActions` — higher-level helpers built on top of `useEditor` (`createAndAddBlock(type)`, `duplicateBlock`, `deleteBlock`, `updateBlockProperty`)
50
52
  - `useAutoSave` — debounced save with onSave callback
@@ -55,7 +57,7 @@ watch(editor.content, (next) => {
55
57
 
56
58
  ## Cloud subpath
57
59
 
58
- The `@templatical/core/cloud` subpath provides Templatical Cloud integrations (Auth, API client, WebSocket, AI chat/rewrite, collaboration, comments, scoring, and `createCloudSavedBlocksProvider`). Used by `@templatical/editor`'s `initCloud()`.
60
+ The `@templatical/core/cloud` subpath provides Templatical Cloud integrations (Auth, API client, WebSocket, AI chat/rewrite, comments, scoring, and `createCloudSavedBlocksProvider`). Used by `@templatical/editor`'s `initCloud()`.
59
61
 
60
62
  ## Documentation
61
63
 
@@ -1,5 +1,4 @@
1
- import { r as UseEditorReturn } from "../editor-Bl4WsPU3.js";
2
- import { AiChatMessage, AiConfig, AuthConfig, AuthRequestOptions, Block, Collaborator, CommentResponse, CommentsProvider, CustomFont, ExportResult, FontsConfig, HealthCheckResult, MediaProvider, MergeTag, PlanConfig, PlanFeatures, RenderProvider, SavedBlock, SavedBlocksProvider, ScoringCategory, ScoringFinding, ScoringResult, SdkAuthConfig, Template, TemplateContent, TemplateOperationPayload, TemplatePatch, TemplateVersionResponse, TemplatesProvider, TestEmailConfig, TestEmailProvider, UserConfig, VersionHistoryProvider, WebSocketServerConfig } from "@templatical/types";
1
+ import { AiChatMessage, AiConfig, AuthConfig, AuthRequestOptions, Block, CommentResponse, CommentsProvider, CustomFont, ExportResult, FontsConfig, HealthCheckResult, MediaProvider, MergeTag, PlanConfig, PlanFeatures, RenderProvider, SavedBlock, SavedBlocksProvider, ScoringCategory, ScoringFinding, ScoringResult, SdkAuthConfig, Template, TemplateContent, TemplatePatch, TemplateVersionResponse, TemplatesProvider, TestEmailConfig, TestEmailProvider, UserConfig, VersionHistoryProvider, WebSocketServerConfig } from "@templatical/types";
3
2
  import { ComputedRef, Ref, ref } from "vue";
4
3
  import { Channel, PresenceChannel } from "pusher-js";
5
4
  //#region src/cloud/auth.d.ts
@@ -292,9 +291,6 @@ export declare class WebSocketClient {
292
291
  get isConnected(): boolean;
293
292
  }
294
293
  //#endregion
295
- //#region src/cloud/mcp-operation-handler.d.ts
296
- export declare function handleOperation(editor: UseEditorReturn, payload: TemplateOperationPayload): void;
297
- //#endregion
298
294
  //#region src/cloud/ai-chat.d.ts
299
295
  interface UseAiChatOptions {
300
296
  authManager: AuthManager;
@@ -439,45 +435,6 @@ interface CreateCloudCommentsProviderOptions {
439
435
  */
440
436
  export declare function createCloudCommentsProvider(options: CreateCloudCommentsProviderOptions): CommentsProvider;
441
437
  //#endregion
442
- //#region src/cloud/collaboration.d.ts
443
- interface UseCollaborationOptions {
444
- authManager: AuthManager;
445
- editor: UseEditorReturn;
446
- channel: Ref<PresenceChannel | null>;
447
- onError?: (error: Error) => void;
448
- onCollaboratorJoined?: (collaborator: Collaborator) => void;
449
- onCollaboratorLeft?: (collaborator: Collaborator) => void;
450
- onBlockLocked?: (event: {
451
- blockId: string;
452
- collaborator: Collaborator;
453
- }) => void;
454
- onBlockUnlocked?: (event: {
455
- blockId: string;
456
- collaborator: Collaborator;
457
- }) => void;
458
- }
459
- interface UseCollaborationReturn {
460
- collaborators: Ref<Collaborator[]>;
461
- lockedBlocks: Ref<Map<string, Collaborator>>;
462
- }
463
- export declare function useCollaboration(options: UseCollaborationOptions): UseCollaborationReturn & {
464
- _broadcastOperation: (payload: TemplateOperationPayload) => void;
465
- _isProcessingRemoteOperation: () => boolean;
466
- };
467
- //#endregion
468
- //#region src/cloud/collaboration-broadcast.d.ts
469
- interface BroadcastTarget {
470
- _broadcastOperation: (payload: TemplateOperationPayload) => void;
471
- }
472
- /**
473
- * Wraps editor mutation methods to broadcast operations to collaboration
474
- * peers after each mutation executes. Mutates the editor object in place.
475
- *
476
- * Must be applied **before** the history interceptor so the call chain is:
477
- * history.record() → broadcast + original mutation.
478
- */
479
- export declare function useCollaborationBroadcast(editor: Pick<UseEditorReturn, "addBlock" | "updateBlock" | "removeBlock" | "moveBlock" | "updateSettings" | "setContent">, collaboration: BroadcastTarget): void;
480
- //#endregion
481
438
  //#region src/cloud/web-socket.d.ts
482
439
  interface UseWebSocketOptions {
483
440
  authManager: AuthManager;
@@ -733,13 +690,5 @@ interface HealthCheckOptions {
733
690
  }
734
691
  export declare function performHealthCheck(options?: HealthCheckOptions): Promise<HealthCheckResult>;
735
692
  //#endregion
736
- //#region src/cloud/mcp-listener.d.ts
737
- interface UseMcpListenerOptions {
738
- editor: UseEditorReturn;
739
- channel: Ref<PresenceChannel | null>;
740
- onOperation?: (payload: TemplateOperationPayload) => void;
741
- }
742
- export declare function useMcpListener(options: UseMcpListenerOptions): void;
743
- //#endregion
744
- export type { AuthConfig, AuthRequestOptions, CloudMediaBrowseParams, CloudMediaBrowseResponse, CloudMediaFolder, CloudMediaItem, CloudMediaUsageInfo, CloudMediaUsageResponse, CreateCloudCommentsProviderOptions, CreateCloudRenderProviderOptions, CreateCloudTestEmailProviderOptions, DesignReferenceInput, ExportFontsPayload, PresenceMember, RealtimeChannel, SdkAuthConfig, TestEmailConfig, UseAiChatOptions, UseAiChatReturn, UseAiConfigReturn, UseAiRewriteOptions, UseAiRewriteReturn, UseCollaborationOptions, UseCollaborationReturn, UseDesignReferenceOptions, UseDesignReferenceReturn, UseExportOptions, UseExportReturn, UseMcpListenerOptions, UsePlanConfigOptions, UsePlanConfigReturn, UseTemplateScoringOptions, UseTemplateScoringReturn, UseTestEmailOptions, UseTestEmailReturn, UseWebSocketOptions, UseWebSocketReturn, UserConfig, WebSocketClientOptions, WebSocketConfig };
693
+ export type { AuthConfig, AuthRequestOptions, CloudMediaBrowseParams, CloudMediaBrowseResponse, CloudMediaFolder, CloudMediaItem, CloudMediaUsageInfo, CloudMediaUsageResponse, CreateCloudCommentsProviderOptions, CreateCloudRenderProviderOptions, CreateCloudTestEmailProviderOptions, DesignReferenceInput, ExportFontsPayload, PresenceMember, RealtimeChannel, SdkAuthConfig, TestEmailConfig, UseAiChatOptions, UseAiChatReturn, UseAiConfigReturn, UseAiRewriteOptions, UseAiRewriteReturn, UseDesignReferenceOptions, UseDesignReferenceReturn, UseExportOptions, UseExportReturn, UsePlanConfigOptions, UsePlanConfigReturn, UseTemplateScoringOptions, UseTemplateScoringReturn, UseTestEmailOptions, UseTestEmailReturn, UseWebSocketOptions, UseWebSocketReturn, UserConfig, WebSocketClientOptions, WebSocketConfig };
745
694
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { SdkError } from "@templatical/types";
2
- import { computed, onScopeDispose, ref, watch } from "vue";
2
+ import { computed, ref, watch } from "vue";
3
3
  //#region src/cloud/auth.ts
4
4
  /**
5
5
  * Whether a token-refresh failure is worth interrupting the user over.
@@ -693,32 +693,6 @@ var WebSocketClient = class {
693
693
  }
694
694
  };
695
695
  //#endregion
696
- //#region src/cloud/mcp-operation-handler.ts
697
- function handleOperation(editor, payload) {
698
- const { operation, data } = payload;
699
- switch (operation) {
700
- case "addBlock":
701
- editor.addBlock(data.block, data.sectionId, data.columnIndex, data.index);
702
- break;
703
- case "updateBlock":
704
- editor.updateBlock(data.blockId, data.updates);
705
- break;
706
- case "deleteBlock":
707
- editor.removeBlock(data.blockId);
708
- break;
709
- case "moveBlock":
710
- editor.moveBlock(data.blockId, data.index, data.sectionId, data.columnIndex);
711
- break;
712
- case "updateSettings":
713
- editor.updateSettings(data.updates);
714
- break;
715
- case "setContent":
716
- editor.setContent(data.content);
717
- break;
718
- case "updateBlockStyle": editor.updateBlock(data.blockId, { styles: data.styles });
719
- }
720
- }
721
- //#endregion
722
696
  //#region src/cloud/ai-chat.ts
723
697
  let messageIdCounter = 0;
724
698
  function generateMessageId() {
@@ -1461,247 +1435,6 @@ function createCloudCommentsProvider(options) {
1461
1435
  };
1462
1436
  }
1463
1437
  //#endregion
1464
- //#region src/cloud/collaboration.ts
1465
- const COLLAB_EVENTS = [
1466
- "pusher:member_added",
1467
- "pusher:member_removed",
1468
- "client-blockLocked",
1469
- "client-blockUnlocked",
1470
- "client-operation",
1471
- "mcp-operation"
1472
- ];
1473
- function unbindCollabEvents(channel) {
1474
- for (const event of COLLAB_EVENTS) channel.unbind(event);
1475
- }
1476
- const COLLABORATOR_COLORS = [
1477
- "#3b82f6",
1478
- "#ef4444",
1479
- "#10b981",
1480
- "#f59e0b",
1481
- "#8b5cf6",
1482
- "#ec4899",
1483
- "#06b6d4",
1484
- "#f97316",
1485
- "#6366f1",
1486
- "#14b8a6"
1487
- ];
1488
- function useCollaboration(options) {
1489
- const { authManager, editor, channel } = options;
1490
- const collaborators = ref([]);
1491
- const lockedBlocks = ref(/* @__PURE__ */ new Map());
1492
- let colorIndex = 0;
1493
- let isProcessingRemoteOperation = false;
1494
- const myUserId = computed(() => authManager.userConfig?.id ?? "");
1495
- function assignColor() {
1496
- const color = COLLABORATOR_COLORS[colorIndex % COLLABORATOR_COLORS.length];
1497
- colorIndex++;
1498
- return color;
1499
- }
1500
- function addCollaborator(member) {
1501
- if (member.id === myUserId.value) return;
1502
- if (collaborators.value.some((c) => c.id === member.id)) return;
1503
- const collaborator = {
1504
- id: member.id,
1505
- name: member.name,
1506
- color: assignColor(),
1507
- selectedBlockId: null
1508
- };
1509
- collaborators.value = [...collaborators.value, collaborator];
1510
- return collaborator;
1511
- }
1512
- function removeCollaborator(memberId) {
1513
- const newLockedBlocks = new Map(lockedBlocks.value);
1514
- for (const [blockId, collaborator] of newLockedBlocks) if (collaborator.id === memberId) newLockedBlocks.delete(blockId);
1515
- lockedBlocks.value = newLockedBlocks;
1516
- collaborators.value = collaborators.value.filter((c) => c.id !== memberId);
1517
- }
1518
- function handleBlockLocked(data) {
1519
- const collaborator = collaborators.value.find((c) => c.id === data.userId);
1520
- if (!collaborator) return;
1521
- collaborators.value = collaborators.value.map((c) => c.id === data.userId ? {
1522
- ...c,
1523
- selectedBlockId: data.blockId
1524
- } : c);
1525
- const newLockedBlocks = new Map(lockedBlocks.value);
1526
- for (const [blockId, holder] of newLockedBlocks) if (holder.id === data.userId) newLockedBlocks.delete(blockId);
1527
- newLockedBlocks.set(data.blockId, {
1528
- ...collaborator,
1529
- selectedBlockId: data.blockId
1530
- });
1531
- lockedBlocks.value = newLockedBlocks;
1532
- if (editor.state.selectedBlockId === data.blockId) editor.selectBlock(null);
1533
- }
1534
- function handleBlockUnlocked(data) {
1535
- const newLockedBlocks = new Map(lockedBlocks.value);
1536
- const holder = newLockedBlocks.get(data.blockId);
1537
- newLockedBlocks.delete(data.blockId);
1538
- lockedBlocks.value = newLockedBlocks;
1539
- if (holder) collaborators.value = collaborators.value.map((c) => c.id === holder.id ? {
1540
- ...c,
1541
- selectedBlockId: null
1542
- } : c);
1543
- }
1544
- function handleRemoteOperation(payload) {
1545
- isProcessingRemoteOperation = true;
1546
- try {
1547
- handleOperation(editor, payload);
1548
- } finally {
1549
- isProcessingRemoteOperation = false;
1550
- }
1551
- }
1552
- function broadcastOperation(payload) {
1553
- if (!channel.value || isProcessingRemoteOperation) return;
1554
- channel.value.trigger("client-operation", payload);
1555
- }
1556
- function broadcastBlockLocked(blockId) {
1557
- if (!channel.value) return;
1558
- channel.value.trigger("client-blockLocked", {
1559
- blockId,
1560
- userId: myUserId.value
1561
- });
1562
- }
1563
- function broadcastBlockUnlocked(blockId) {
1564
- if (!channel.value) return;
1565
- channel.value.trigger("client-blockUnlocked", { blockId });
1566
- }
1567
- watch(() => editor.state.selectedBlockId, (newBlockId, oldBlockId) => {
1568
- if (isProcessingRemoteOperation) return;
1569
- if (oldBlockId) broadcastBlockUnlocked(oldBlockId);
1570
- if (newBlockId) broadcastBlockLocked(newBlockId);
1571
- });
1572
- watch(channel, (newChannel, oldChannel) => {
1573
- if (oldChannel) unbindCollabEvents(oldChannel);
1574
- if (!newChannel) {
1575
- collaborators.value = [];
1576
- lockedBlocks.value = /* @__PURE__ */ new Map();
1577
- colorIndex = 0;
1578
- return;
1579
- }
1580
- const members = newChannel.members;
1581
- if (members) members.each((member) => {
1582
- addCollaborator(member.info);
1583
- });
1584
- newChannel.bind("pusher:member_added", (member) => {
1585
- const collaborator = addCollaborator(member.info);
1586
- if (collaborator) options.onCollaboratorJoined?.(collaborator);
1587
- });
1588
- newChannel.bind("pusher:member_removed", (member) => {
1589
- const collaborator = collaborators.value.find((c) => c.id === member.id);
1590
- removeCollaborator(member.id);
1591
- if (collaborator) options.onCollaboratorLeft?.(collaborator);
1592
- });
1593
- newChannel.bind("client-blockLocked", (data) => {
1594
- handleBlockLocked(data);
1595
- const collaborator = collaborators.value.find((c) => c.id === data.userId);
1596
- if (collaborator) options.onBlockLocked?.({
1597
- blockId: data.blockId,
1598
- collaborator
1599
- });
1600
- });
1601
- newChannel.bind("client-blockUnlocked", (data) => {
1602
- const holder = lockedBlocks.value.get(data.blockId);
1603
- handleBlockUnlocked(data);
1604
- if (holder) options.onBlockUnlocked?.({
1605
- blockId: data.blockId,
1606
- collaborator: holder
1607
- });
1608
- });
1609
- newChannel.bind("client-operation", (payload) => {
1610
- handleRemoteOperation(payload);
1611
- });
1612
- newChannel.bind("mcp-operation", (payload) => {
1613
- handleRemoteOperation(payload);
1614
- });
1615
- });
1616
- onScopeDispose(() => {
1617
- if (channel.value) unbindCollabEvents(channel.value);
1618
- });
1619
- return {
1620
- collaborators,
1621
- lockedBlocks,
1622
- _broadcastOperation: broadcastOperation,
1623
- _isProcessingRemoteOperation: () => isProcessingRemoteOperation
1624
- };
1625
- }
1626
- //#endregion
1627
- //#region src/cloud/collaboration-broadcast.ts
1628
- /**
1629
- * Wraps editor mutation methods to broadcast operations to collaboration
1630
- * peers after each mutation executes. Mutates the editor object in place.
1631
- *
1632
- * Must be applied **before** the history interceptor so the call chain is:
1633
- * history.record() → broadcast + original mutation.
1634
- */
1635
- function useCollaborationBroadcast(editor, collaboration) {
1636
- const originalAddBlock = editor.addBlock;
1637
- const originalUpdateBlock = editor.updateBlock;
1638
- const originalRemoveBlock = editor.removeBlock;
1639
- const originalMoveBlock = editor.moveBlock;
1640
- const originalUpdateSettings = editor.updateSettings;
1641
- const originalSetContent = editor.setContent;
1642
- editor.addBlock = (block, targetSectionId, columnIndex, index) => {
1643
- originalAddBlock(block, targetSectionId, columnIndex, index);
1644
- collaboration._broadcastOperation({
1645
- operation: "addBlock",
1646
- data: {
1647
- block,
1648
- sectionId: targetSectionId,
1649
- columnIndex,
1650
- index
1651
- },
1652
- timestamp: Date.now()
1653
- });
1654
- };
1655
- editor.updateBlock = (blockId, updates) => {
1656
- originalUpdateBlock(blockId, updates);
1657
- collaboration._broadcastOperation({
1658
- operation: "updateBlock",
1659
- data: {
1660
- blockId,
1661
- updates
1662
- },
1663
- timestamp: Date.now()
1664
- });
1665
- };
1666
- editor.removeBlock = (blockId) => {
1667
- originalRemoveBlock(blockId);
1668
- collaboration._broadcastOperation({
1669
- operation: "deleteBlock",
1670
- data: { blockId },
1671
- timestamp: Date.now()
1672
- });
1673
- };
1674
- editor.moveBlock = (blockId, newIndex, targetSectionId, columnIndex) => {
1675
- originalMoveBlock(blockId, newIndex, targetSectionId, columnIndex);
1676
- collaboration._broadcastOperation({
1677
- operation: "moveBlock",
1678
- data: {
1679
- blockId,
1680
- index: newIndex,
1681
- sectionId: targetSectionId,
1682
- columnIndex
1683
- },
1684
- timestamp: Date.now()
1685
- });
1686
- };
1687
- editor.updateSettings = (updates) => {
1688
- originalUpdateSettings(updates);
1689
- collaboration._broadcastOperation({
1690
- operation: "updateSettings",
1691
- data: { updates },
1692
- timestamp: Date.now()
1693
- });
1694
- };
1695
- editor.setContent = (content, markDirty) => {
1696
- originalSetContent(content, markDirty);
1697
- collaboration._broadcastOperation({
1698
- operation: "setContent",
1699
- data: { content },
1700
- timestamp: Date.now()
1701
- });
1702
- };
1703
- }
1704
- //#endregion
1705
1438
  //#region src/cloud/web-socket.ts
1706
1439
  function useWebSocket(options) {
1707
1440
  const { authManager, onError } = options;
@@ -2357,18 +2090,6 @@ async function performHealthCheck(options = {}) {
2357
2090
  };
2358
2091
  }
2359
2092
  //#endregion
2360
- //#region src/cloud/mcp-listener.ts
2361
- function useMcpListener(options) {
2362
- const { editor, channel, onOperation } = options;
2363
- watch(channel, (newChannel, oldChannel) => {
2364
- if (oldChannel) oldChannel.unbind("mcp-operation");
2365
- if (newChannel) newChannel.bind("mcp-operation", (payload) => {
2366
- handleOperation(editor, payload);
2367
- onOperation?.(payload);
2368
- });
2369
- });
2370
- }
2371
- //#endregion
2372
- export { API_ROUTES, ApiClient, AuthManager, MediaApiClient, WebSocketClient, buildUrl, createCloudCommentsProvider, createCloudMediaProvider, createCloudRenderProvider, createCloudSavedBlocksProvider, createCloudTemplatesProvider, createCloudTestEmailProvider, createCloudVersionHistoryProvider, createSdkAuthManager, handleOperation, isFatalAuthError, performHealthCheck, resolveExportFonts, resolveWebSocketConfig, useAiChat, useAiConfig, useAiRewrite, useCollaboration, useCollaborationBroadcast, useDesignReference, useExport, useMcpListener, usePlanConfig, useTemplateScoring, useTestEmail, useWebSocket };
2093
+ export { API_ROUTES, ApiClient, AuthManager, MediaApiClient, WebSocketClient, buildUrl, createCloudCommentsProvider, createCloudMediaProvider, createCloudRenderProvider, createCloudSavedBlocksProvider, createCloudTemplatesProvider, createCloudTestEmailProvider, createCloudVersionHistoryProvider, createSdkAuthManager, isFatalAuthError, performHealthCheck, resolveExportFonts, resolveWebSocketConfig, useAiChat, useAiConfig, useAiRewrite, useDesignReference, useExport, usePlanConfig, useTemplateScoring, useTestEmail, useWebSocket };
2373
2094
 
2374
2095
  //# sourceMappingURL=index.js.map