diff --git a/src/Blog/Components/App.razor b/src/Blog/Components/App.razor index 9e10592..9de1b94 100644 --- a/src/Blog/Components/App.razor +++ b/src/Blog/Components/App.razor @@ -8,13 +8,14 @@ - + - + + diff --git a/src/Blog/Components/Layout/Header.razor b/src/Blog/Components/Layout/Header.razor index 6d311d8..d7dff6f 100644 --- a/src/Blog/Components/Layout/Header.razor +++ b/src/Blog/Components/Layout/Header.razor @@ -2,9 +2,9 @@
by diff --git a/src/Blog/Components/Layout/MainLayout.razor b/src/Blog/Components/Layout/MainLayout.razor index 99391fc..a5b445a 100644 --- a/src/Blog/Components/Layout/MainLayout.razor +++ b/src/Blog/Components/Layout/MainLayout.razor @@ -1,5 +1,4 @@ @inherits LayoutComponentBase -@rendermode InteractiveServer
@@ -13,3 +12,10 @@ Reload 🗙
+ +
+
+

Connection lost

+

Attempting to reconnect...

+
+
\ No newline at end of file diff --git a/src/Blog/Components/Layout/MainLayout.razor.css b/src/Blog/Components/Layout/MainLayout.razor.css index 6ddc2c9..1d53736 100644 --- a/src/Blog/Components/Layout/MainLayout.razor.css +++ b/src/Blog/Components/Layout/MainLayout.razor.css @@ -32,3 +32,11 @@ main { right: 0.75rem; top: 0.5rem; } + +#components-reconnect-modal { + display: none; + position: fixed; + bottom: 0; + right: 0; + padding: 0.5rem; +} \ No newline at end of file diff --git a/src/Blog/Components/Pages/Error.razor b/src/Blog/Components/Pages/Error.razor index c04dc20..f89d2ae 100644 --- a/src/Blog/Components/Pages/Error.razor +++ b/src/Blog/Components/Pages/Error.razor @@ -15,7 +15,8 @@ }
-@code{ +@code +{ [CascadingParameter] private HttpContext? HttpContext { get; set; } diff --git a/src/Blog/Components/Pages/Post.razor b/src/Blog/Components/Pages/Post.razor index d158c90..1628c12 100644 --- a/src/Blog/Components/Pages/Post.razor +++ b/src/Blog/Components/Pages/Post.razor @@ -1,5 +1,4 @@ @page "/{slug:nonfile}" -@implements IAsyncDisposable @inject IPostService PostService @inject NavigationManager NavigationManager @inject IJSRuntime JSRuntime @@ -13,11 +12,12 @@ else { @BlogPost.Title - journal + + -
@@ -31,11 +31,11 @@ else
} -@code { +@code +{ [Parameter] public string Slug { get; set; } = string.Empty; private PostWithContent? BlogPost; - private IJSObjectReference? module; protected override async Task OnInitializedAsync() { @@ -49,32 +49,4 @@ else BlogPost = post; } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - module = await JSRuntime.InvokeAsync("import", "./Components/Pages/Post.razor.js"); - await module.InvokeVoidAsync("addAnchors"); - await module.InvokeVoidAsync("addClipboard"); - } - } - - async ValueTask IAsyncDisposable.DisposeAsync() - { - try - { - if (module is not null) - { - await module.DisposeAsync(); - } - } - catch (Exception ex) when (ex is JSDisconnectedException) - { - } - catch (Exception ex) - { - Logger.LogError(ex, "Error disposing of the module"); - } - } } \ No newline at end of file diff --git a/src/Blog/Components/Pages/Post.razor.js b/src/Blog/Components/Pages/Post.razor.js index 04db9aa..f11ab7a 100644 --- a/src/Blog/Components/Pages/Post.razor.js +++ b/src/Blog/Components/Pages/Post.razor.js @@ -1,9 +1,37 @@ +let initialized = false; + +export function onLoad() { + if (initialized) { + return; + } + + init(); +} + +export function onUpdate() { + if (initialized) { + return; + } + + init(); +} + +export function onDispose() { + initialized = false; +} + +function init(){ + addAnchors(); + addClipboard(); + Prism.highlightAll(); + initialized = true; +} /** * @summary Adds anchors to each heading in the post * so that users can link directly to them. */ -export function addAnchors() { +function addAnchors() { const selectors = [ '.markdown-body h2', '.markdown-body h3', @@ -21,7 +49,7 @@ export function addAnchors() { * @summary Adds a button to each code block that * allows users to copy the code to their clipboard. */ -export function addClipboard() { +function addClipboard() { const codeBlocks = document.querySelectorAll('pre'); codeBlocks.forEach((block) => { diff --git a/src/Blog/Components/Posts/Feed.razor b/src/Blog/Components/Posts/Feed.razor index ae9ed17..acfbfa3 100644 --- a/src/Blog/Components/Posts/Feed.razor +++ b/src/Blog/Components/Posts/Feed.razor @@ -18,19 +18,22 @@
@foreach (var post in Posts) { - +

@post.Title

@post.PublishedAt.ToString("MMMM d, yyyy")

@post.Lead

-
+ }
} -@code { +@code +{ private List? Posts; + + private string GetLink(Post post) => $"/{post.Slug}"; protected override async Task OnInitializedAsync() { diff --git a/src/Blog/Components/Utils/PageScript.razor b/src/Blog/Components/Utils/PageScript.razor new file mode 100644 index 0000000..06c80fc --- /dev/null +++ b/src/Blog/Components/Utils/PageScript.razor @@ -0,0 +1,19 @@ +@namespace Blog.Components.Utils + +@* + custom web-component defined in: + Blog.lib.module.js + + Manages loading colocated scripts + for pages and invoking expected + lifecycle methods to support + enhanced page navigation. +*@ + + +@code +{ + [Parameter] + [EditorRequired] + public string Src { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Blog/Components/_Imports.razor b/src/Blog/Components/_Imports.razor index cc46e1b..090e66b 100644 --- a/src/Blog/Components/_Imports.razor +++ b/src/Blog/Components/_Imports.razor @@ -8,3 +8,4 @@ @using Microsoft.JSInterop @using Blog @using Blog.Components +@using Blog.Components.Utils diff --git a/src/Blog/Program.cs b/src/Blog/Program.cs index 05f5b87..74e4259 100644 --- a/src/Blog/Program.cs +++ b/src/Blog/Program.cs @@ -4,9 +4,7 @@ builder.Services.ConfigureOptions(); builder.Services.AddSingleton(); builder.Services.AddScoped(); -builder.Services - .AddRazorComponents() - .AddInteractiveServerComponents(); +builder.Services.AddRazorComponents(); var app = builder.Build(); @@ -88,10 +86,7 @@ app .WithDisplayName("RSS Feed") .WithDescription("RSS feed for the blog"); -app - .MapRazorComponents() - .AddInteractiveServerRenderMode(); - +app.MapRazorComponents(); app.Run(); diff --git a/src/Blog/wwwroot/Blog.lib.module.js b/src/Blog/wwwroot/Blog.lib.module.js new file mode 100644 index 0000000..7415504 --- /dev/null +++ b/src/Blog/wwwroot/Blog.lib.module.js @@ -0,0 +1,95 @@ +const pageScriptInfoBySrc = new Map(); + +function registerPageScriptElement(src) { + if (!src) { + throw new Error('Must provide a non-empty value for the "src" attribute.'); + } + + let pageScriptInfo = pageScriptInfoBySrc.get(src); + + if (pageScriptInfo) { + pageScriptInfo.referenceCount++; + return; + } + + pageScriptInfo = { referenceCount: 1, module: null }; + pageScriptInfoBySrc.set(src, pageScriptInfo); + initializePageScriptModule(src, pageScriptInfo); +} + +function unregisterPageScriptElement(src) { + if (!src) { + return; + } + + const pageScriptInfo = pageScriptInfoBySrc.get(src); + + if (!pageScriptInfo) { + return; + } + + pageScriptInfo.referenceCount--; +} + +async function initializePageScriptModule(src, pageScriptInfo) { + // If the path is relative, normalize it by by making it an absolute URL + // with document's the base HREF. + if (src.startsWith("./")) { + src = new URL(src.substr(2), document.baseURI).toString(); + } + + const module = await import(src); + + if (pageScriptInfo.referenceCount <= 0) { + // All page-script elements with the same 'src' were + // unregistered while we were loading the module. + return; + } + + pageScriptInfo.module = module; + module.onLoad?.(); + module.onUpdate?.(); +} + +function onEnhancedLoad() { + // Start by invoking 'onDispose' on any modules that are no longer referenced. + for (const [src, { module, referenceCount }] of pageScriptInfoBySrc) { + if (referenceCount <= 0) { + module?.onDispose?.(); + pageScriptInfoBySrc.delete(src); + } + } + + // Then invoke 'onUpdate' on the remaining modules. + for (const { module } of pageScriptInfoBySrc.values()) { + module?.onUpdate?.(); + } +} + +export function afterWebStarted(blazor) { + customElements.define( + "page-script", + class extends HTMLElement { + static observedAttributes = ["src"]; + + // We use attributeChangedCallback instead of connectedCallback + // because a page-script element might get reused between enhanced + // navigations. + attributeChangedCallback(name, oldValue, newValue) { + if (name !== "src") { + return; + } + + this.src = newValue; + unregisterPageScriptElement(oldValue); + registerPageScriptElement(newValue); + } + + disconnectedCallback() { + unregisterPageScriptElement(this.src); + } + } + ); + + blazor.addEventListener("enhancedload", onEnhancedLoad); +}