From ac7003c651ae457d722400d182e2bc7ca25bb3d4 Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Thu, 16 Apr 2026 14:23:16 +0530 Subject: [PATCH] Fixed timeline preview issue --- Contentstack.Core/ContentstackClient.cs | 82 +++++++++++++++++++++++++ Contentstack.Core/Models/Entry.cs | 41 +++++++++---- Contentstack.Core/Models/Query.cs | 44 ++++++++----- 3 files changed, 138 insertions(+), 29 deletions(-) diff --git a/Contentstack.Core/ContentstackClient.cs b/Contentstack.Core/ContentstackClient.cs index 85ebccdb..b69eaf79 100644 --- a/Contentstack.Core/ContentstackClient.cs +++ b/Contentstack.Core/ContentstackClient.cs @@ -61,6 +61,88 @@ private string _Url private string currentContenttypeUid = null; private string currentEntryUid = null; public List Plugins { get; set; } = new List(); + + private static LivePreviewConfig CloneLivePreviewConfig(LivePreviewConfig source) + { + if (source == null) return null; + + return new LivePreviewConfig + { + Enable = source.Enable, + Host = source.Host, + ManagementToken = source.ManagementToken, + PreviewToken = source.PreviewToken, + ReleaseId = source.ReleaseId, + PreviewTimestamp = source.PreviewTimestamp, + + // internal state (same assembly) + LivePreview = source.LivePreview, + ContentTypeUID = source.ContentTypeUID, + EntryUID = source.EntryUID, + PreviewResponse = source.PreviewResponse + }; + } + + private static ContentstackOptions CloneOptions(ContentstackOptions source) + { + if (source == null) return null; + + return new ContentstackOptions + { + ApiKey = source.ApiKey, + AccessToken = source.AccessToken, + DeliveryToken = source.DeliveryToken, + Environment = source.Environment, + Host = source.Host, + Proxy = source.Proxy, + Region = source.Region, + Version = source.Version, + Branch = source.Branch, + Timeout = source.Timeout, + EarlyAccessHeader = source.EarlyAccessHeader, + LivePreview = CloneLivePreviewConfig(source.LivePreview) + }; + } + + /// + /// Clears any in-memory Live Preview context (hash, release, timestamp, content type, entry). + /// Useful when switching back to the Delivery API after using Live Preview / Timeline preview. + /// + public void ResetLivePreview() + { + if (this.LivePreviewConfig == null) return; + + this.LivePreviewConfig.LivePreview = null; + this.LivePreviewConfig.ReleaseId = null; + this.LivePreviewConfig.PreviewTimestamp = null; + this.LivePreviewConfig.ContentTypeUID = null; + this.LivePreviewConfig.EntryUID = null; + this.LivePreviewConfig.PreviewResponse = null; + } + + /// + /// Creates a new client instance with the same configuration but isolated in-memory state. + /// Use this to safely perform Timeline comparisons (left/right) without shared Live Preview context. + /// + public ContentstackClient Fork() + { + var forked = new ContentstackClient(CloneOptions(_options)); + + // Preserve any runtime header mutations (e.g., custom headers added via SetHeader). + if (this._LocalHeaders != null) + { + foreach (var kvp in this._LocalHeaders) + { + forked.SetHeader(kvp.Key, kvp.Value?.ToString()); + } + } + + // Carry over current content type / entry hints (used when live preview query omits them) + forked.currentContenttypeUid = this.currentContenttypeUid; + forked.currentEntryUid = this.currentEntryUid; + + return forked; + } /// /// Initializes a instance of the class. /// diff --git a/Contentstack.Core/Models/Entry.cs b/Contentstack.Core/Models/Entry.cs index d683ecdb..b81f01cb 100644 --- a/Contentstack.Core/Models/Entry.cs +++ b/Contentstack.Core/Models/Entry.cs @@ -1401,13 +1401,17 @@ public async Task Fetch() //Dictionary urlQueries = new Dictionary(); + var livePreviewConfig = this.ContentTypeInstance?.StackInstance?.LivePreviewConfig; if (headers != null && headers.Count() > 0) { foreach (var header in headers) { - if (this.ContentTypeInstance.StackInstance.LivePreviewConfig.Enable == true - && this.ContentTypeInstance.StackInstance.LivePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId - && header.Key == "access_token" && !string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview)) + if (this.ContentTypeInstance != null + && livePreviewConfig != null + && livePreviewConfig.Enable + && livePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId + && header.Key == "access_token" + && !string.IsNullOrEmpty(livePreviewConfig.LivePreview)) { continue; } @@ -1415,25 +1419,36 @@ public async Task Fetch() } } bool isLivePreview = false; - if (this.ContentTypeInstance.StackInstance.LivePreviewConfig.Enable == true && this.ContentTypeInstance.StackInstance.LivePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId) + var hasLivePreviewContext = + this.ContentTypeInstance != null + && livePreviewConfig != null + && livePreviewConfig.Enable + && livePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId + && ( + !string.IsNullOrEmpty(livePreviewConfig.LivePreview) + || !string.IsNullOrEmpty(livePreviewConfig.ReleaseId) + || !string.IsNullOrEmpty(livePreviewConfig.PreviewTimestamp) + ); + + if (hasLivePreviewContext) { - mainJson.Add("live_preview", string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview)? "init" : this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview); + mainJson.Add("live_preview", string.IsNullOrEmpty(livePreviewConfig.LivePreview)? "init" : livePreviewConfig.LivePreview); - if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken)) { - headerAll["authorization"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken; - } else if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewToken)) { - headerAll["preview_token"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewToken; + if (!string.IsNullOrEmpty(livePreviewConfig.ManagementToken)) { + headerAll["authorization"] = livePreviewConfig.ManagementToken; + } else if (!string.IsNullOrEmpty(livePreviewConfig.PreviewToken)) { + headerAll["preview_token"] = livePreviewConfig.PreviewToken; } else { throw new LivePreviewException(); } - if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.ReleaseId)) + if (!string.IsNullOrEmpty(livePreviewConfig.ReleaseId)) { - headerAll["release_id"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.ReleaseId; + headerAll["release_id"] = livePreviewConfig.ReleaseId; } - if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewTimestamp)) + if (!string.IsNullOrEmpty(livePreviewConfig.PreviewTimestamp)) { - headerAll["preview_timestamp"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewTimestamp; + headerAll["preview_timestamp"] = livePreviewConfig.PreviewTimestamp; } isLivePreview = true; diff --git a/Contentstack.Core/Models/Query.cs b/Contentstack.Core/Models/Query.cs index c0d6ead1..09dcc9c8 100644 --- a/Contentstack.Core/Models/Query.cs +++ b/Contentstack.Core/Models/Query.cs @@ -1874,26 +1874,37 @@ private async Task Exec() Dictionary mainJson = new Dictionary(); bool isLivePreview = false; - if (this.ContentTypeInstance!=null && this.ContentTypeInstance.StackInstance.LivePreviewConfig.Enable == true - && this.ContentTypeInstance.StackInstance?.LivePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId) - { - mainJson.Add("live_preview", string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview) ? "init" : this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview); - - if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken)) { - headerAll["authorization"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.ManagementToken; - } else if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewToken)) { - headerAll["preview_token"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewToken; + var livePreviewConfig = this.ContentTypeInstance?.StackInstance?.LivePreviewConfig; + var hasLivePreviewContext = + this.ContentTypeInstance != null + && livePreviewConfig != null + && livePreviewConfig.Enable + && livePreviewConfig.ContentTypeUID == this.ContentTypeInstance.ContentTypeId + && ( + !string.IsNullOrEmpty(livePreviewConfig.LivePreview) + || !string.IsNullOrEmpty(livePreviewConfig.ReleaseId) + || !string.IsNullOrEmpty(livePreviewConfig.PreviewTimestamp) + ); + + if (hasLivePreviewContext) + { + mainJson.Add("live_preview", string.IsNullOrEmpty(livePreviewConfig.LivePreview) ? "init" : livePreviewConfig.LivePreview); + + if (!string.IsNullOrEmpty(livePreviewConfig.ManagementToken)) { + headerAll["authorization"] = livePreviewConfig.ManagementToken; + } else if (!string.IsNullOrEmpty(livePreviewConfig.PreviewToken)) { + headerAll["preview_token"] = livePreviewConfig.PreviewToken; } else { throw new LivePreviewException(); } - if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.ReleaseId)) + if (!string.IsNullOrEmpty(livePreviewConfig.ReleaseId)) { - headerAll["release_id"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.ReleaseId; + headerAll["release_id"] = livePreviewConfig.ReleaseId; } - if (!string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewTimestamp)) + if (!string.IsNullOrEmpty(livePreviewConfig.PreviewTimestamp)) { - headerAll["preview_timestamp"] = this.ContentTypeInstance.StackInstance.LivePreviewConfig.PreviewTimestamp; + headerAll["preview_timestamp"] = livePreviewConfig.PreviewTimestamp; } isLivePreview = true; @@ -1903,10 +1914,11 @@ private async Task Exec() { foreach (var header in headers) { - if (this.ContentTypeInstance!=null && this.ContentTypeInstance?.StackInstance.LivePreviewConfig.Enable == true - && this.ContentTypeInstance?.StackInstance.LivePreviewConfig.ContentTypeUID == this.ContentTypeInstance?.ContentTypeId + if (this.ContentTypeInstance!=null && livePreviewConfig != null + && livePreviewConfig.Enable + && livePreviewConfig.ContentTypeUID == this.ContentTypeInstance?.ContentTypeId && header.Key == "access_token" - && !string.IsNullOrEmpty(this.ContentTypeInstance.StackInstance.LivePreviewConfig.LivePreview)) + && !string.IsNullOrEmpty(livePreviewConfig.LivePreview)) { continue; }