--- lang: ja-jp breaks: true --- # Fluent UI Blazor デモにある `CodeSnippet` を`net8.0` のプロジェクトテンプレートでも動作するようにする 2024-03-19 ## `CodeSnippet` > microsoft/fluentui-blazor > https://github.com/microsoft/fluentui-blazor ![image](https://hackmd.io/_uploads/SyCl128Ra.png) > https://github.com/microsoft/fluentui-blazor/blob/main/examples/Demo/Shared/Components/CodeSnippet.razor > https://github.com/microsoft/fluentui-blazor/blob/main/examples/Demo/Shared/Components/CodeSnippet.razor.css > https://github.com/microsoft/fluentui-blazor/blob/main/examples/Demo/Shared/Components/CodeSnippet.razor.js > ## テンプレートよりプロジェクトを作成する。 ![image](https://hackmd.io/_uploads/SJiI0oLRa.png) ![image](https://hackmd.io/_uploads/rJzrJ2ICp.png) ## プロジェクト作成直後 ![image](https://hackmd.io/_uploads/rJj9128C6.png) ## `CodeSnippet` 関連のモジュールをコピーして貼り付ける。 ![image](https://hackmd.io/_uploads/H1REI280T.png) :::info * ~~`CodeSnippet.razor.js` のプロパティから、「新しい場合はコピーする」に変更する。~~ * 2024-03-21 「コピーしない」でも動作するっぽい。 ![image](https://hackmd.io/_uploads/BkpLJfuC6.png) ::: ![image](https://hackmd.io/_uploads/Hy14lnUC6.png) ## `App.razor` に 以下を追加する。 ```javascript= <!-- highlight --> <script type="text/javascript" src="/js/highlight-extensions.js"></script> ``` ```razor= ・・・ <body> <Routes /> <script src="_framework/blazor.web.js"></script> <!-- highlight --> <script type="text/javascript" src="/js/highlight-extensions.js"></script> </body> ・・・ ``` ## `CodeSnippet.razor.cs` の `namespace` を書き換える ```csharp= namespace HighlightJsApp.Client.Components; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; ``` ## `CodeSnippet.razor.cs` 内、`CodeSnippet.razor.js` のパスを書き換える ```csharp= protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JSRuntime.InvokeVoidAsync("hljs.highlightElement", codeElement); _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "/Components/CodeSnippet.razor.js"); await _jsModule.InvokeVoidAsync("addCopyButton"); } } ``` ## `CodeSnippet` を使用するサンプルを作成する * 以下のコードを、`Home.razor`、`Weather.razor`、`Counter.razor` に貼り付ける。 * `Home.razor`、`Weather.razor`には、`@rendermode InteractiveServer` を貼り付ける。 * `@using HighlightJsApp.Client.Components` を先頭付近に追加する。 ```razer= <CodeSnippet Language="xml">@(@"<FluentDesignTheme StorageName=""theme"" />")</CodeSnippet> <CodeSnippet Language="xml"> @(@"<!-- Set the default theme --> <script src=""_content/Microsoft.FluentUI.AspNetCore.Components/js/loading-theme.js"" type=""text/javascript""></script> <loading-theme storage-name=""theme""></loading-theme>") </CodeSnippet> <CodeSnippet Language="css"> ::deep fluent-tabs::part(activeIndicator) { width: 85%; } </CodeSnippet> <CodeSnippet Language="language-xml">@("<FluentIcon Value=\"@(new Icons.Regular.Size24.Bookmark())\" />")</CodeSnippet> <CodeSnippet> @@using Microsoft.FluentUI.AspNetCore.Components. &lt;FluentCard Width="400px" Height="250px"&gt; &lt;h2&gt;Hello World!&lt;/h2&gt; &lt;FluentButton Appearance=&quot;@@Appearance.Accent&quot;&gt;Click Me&lt;/FluentButton&gt; &lt;/FluentCard&gt; </CodeSnippet> ``` ## 動作確認 ### 一見、正常に動作ししているように見える ![image](https://hackmd.io/_uploads/r1eTLh806.png) ![image](https://hackmd.io/_uploads/SylIPhICp.png) ### しかし、別のページに移動すると * コードが強調表示されていない ![image](https://hackmd.io/_uploads/SyJkP3ICT.png) ![image](https://hackmd.io/_uploads/S1iPP2ICp.png) ## ページ遷移すると、スタイルシートとJavaScriptが `Head`と`Body`から消えてしまう > DOM の操作 > https://learn.microsoft.com/ja-jp/aspnet/core/blazor/javascript-interoperability/?view=aspnetcore-8.0#interaction-with-the-dom > ![image](https://hackmd.io/_uploads/HkfJ_2I0T.png) > コンポーネントのレンダリング後 (OnAfterRender{Async}) > https://learn.microsoft.com/ja-jp/aspnet/core/blazor/components/lifecycle?view=aspnetcore-8.0#after-component-render-onafterrenderasync > ![image](https://hackmd.io/_uploads/HkE_d2URp.png) ## 正常に動作するように修正する。 ### `highlight-extensions.js` のグローバル部分をメソッド内に移動する。 * `hljs_OnAfterRender` メソッドを作成して、グローバル部分を切り取って貼り付ける。 ```javascript= export function hljs_OnAfterRender() { // Add Stylesheets hljs_addStylesheet('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/styles/vs.min.css', 'highlight-light', null); hljs_addStylesheet('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/styles/vs2015.min.css', 'highlight-dark', 'disabled'); hljs_addInlineStylesheet(`pre[class~="snippet"] { --font-monospace: "courier"; --type-ramp-base-font-variations: unset; font-weight: bold; }`); // Add Scripts const highlight = hljs_addJavaScript('https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11.6.0/highlight.min.js'); // Add custom code highlight.onload = () => { const hljsRazor = hljs_addJavaScript('https://cdn.jsdelivr.net/npm/highlightjs-cshtml-razor@2.1.1/dist/cshtml-razor.min.js'); // Switch highlight Dark/Light theme const theme = document.querySelector('loading-theme > fluent-design-theme'); if (theme != null) { theme.addEventListener('onchange', (e) => { if (e.detail.name == 'mode') { if (e.detail.newValue === 'undefined') return; const isDark = e.detail.newValue.includes('dark'); hljs_ColorSwitcher(isDark); } }); } // Detect system theme changing window.matchMedia('(prefers-color-scheme: dark)') .addEventListener('change', (e) => { hljs_ColorSystem(); }); // First/default theme hljs_ColorSystem(); } } ``` ### `highlight-extensions.js` の `body`タグに追加している部分を `head` に変更する。 ```javascript= //document.body.appendChild(script); document.head.appendChild(script); ``` ```javascript= // Add a <script> to the <body> element function hljs_addJavaScript(src) { const script = document.createElement('script'); script.type = 'text/javascript'; script.src = src; script.async = true; script.onerror = () => { // Error occurred while loading script console.error('Error occurred while loading script', src); }; //document.body.appendChild(script); document.head.appendChild(script); return script; } ``` ## `HighlightExtensions.razor` と その `.cs` ファイルを作成する。 ` ![image](https://hackmd.io/_uploads/rypuhhL0a.png) * `HighlightExtensions.razor` は空のファイル。 * `HighlightExtensions.razor.cs` ```razor= using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; namespace HighlightJsApp.Components; public partial class HighlightExtensions { private IJSObjectReference _jsModule = default!; [Inject] protected IJSRuntime JSRuntime { get; set; } = default!; protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>( "import", "/js/highlight-extensions.js" ); await _jsModule.InvokeVoidAsync("hljs_OnAfterRender"); } } } ``` ### `CodeSnippet.razor.cs` の `OnAfterRenderAsync` メソッドを改造する。 * エラーになったら3回までリトライするように修正する。 ```csharp= protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { int count = 3; // 3回位までにしておく。 while (true) { try { await JSRuntime.InvokeVoidAsync("hljs.highlightElement", codeElement); break; } catch (Microsoft.JSInterop.JSException) { if (count > 0) { // 「Microsoft.JSInterop.JSException: 'Could not find 'hljs.highlightElement' ('hljs' was undefined).」 // `HighlightExtensions.razor`の`OnAfterRenderAsync`が完了する前に動作すると、必ずエラーが発生するので。準備ができるまで待機する。 await Task.Delay(100); } else { throw; } } count--; } _jsModule = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "/Components/CodeSnippet.razor.js"); await _jsModule.InvokeVoidAsync("addCopyButton"); } } ``` ### `App.razor` を変更する。 * `head`タグの最後で `<HighlightExtensions @rendermode="InteractiveServer" />` を呼び出す。 * `/js/highlight-extensions.js` の呼び出しを削除する。 ```htmlembedded= <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <base href="/" /> <link rel="stylesheet" href="app.css" /> <link rel="stylesheet" href="HighlightJsApp.styles.css" /> <link rel="icon" type="image/png" href="favicon.ico" /> <HeadOutlet /> <HighlightExtensions @rendermode="InteractiveServer" /> </head> <body> <Routes /> <script src="_framework/blazor.web.js"></script> <!-- highlight --> <!-- <script type="text/javascript" src="/js/highlight-extensions.js"></script> --> </body> </html> ``` ## 以上で、ページ遷移しても正常に動作する。 ![image](https://hackmd.io/_uploads/ryvzCnIRT.png) ![image](https://hackmd.io/_uploads/ByhmR3UAa.png) ###### tags: `Fluent UI Blazor` `CodeSnippet` `highlight.js`