From 862cb72f7386b0e115d067d214a0b4e4a6cd45bd Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Apr 2024 13:36:23 -0500 Subject: [PATCH 1/7] chore: add terms and privacy pages --- src/Blog/Components/Pages/Privacy.razor | 115 +++++++++ src/Blog/Components/Pages/Privacy.razor.css | 16 ++ src/Blog/Components/Pages/Terms.razor | 269 ++++++++++++++++++++ src/Blog/Components/Pages/Terms.razor.css | 16 ++ 4 files changed, 416 insertions(+) create mode 100644 src/Blog/Components/Pages/Privacy.razor create mode 100644 src/Blog/Components/Pages/Privacy.razor.css create mode 100644 src/Blog/Components/Pages/Terms.razor create mode 100644 src/Blog/Components/Pages/Terms.razor.css diff --git a/src/Blog/Components/Pages/Privacy.razor b/src/Blog/Components/Pages/Privacy.razor new file mode 100644 index 0000000..2eaf4b3 --- /dev/null +++ b/src/Blog/Components/Pages/Privacy.razor @@ -0,0 +1,115 @@ +@page "/privacy" + +
+

Privacy Policy for journal

+ +
+

At journal, accessible from https://blog.stevanfreeborn.com, one of + our main priorities is the privacy of our visitors. This Privacy Policy document contains types of information + that + is collected and recorded by journal and how we use it.

+ +

+ If you have additional questions or require more information about our Privacy Policy, do not hesitate to contact + us. +

+
+ +
+

Log Files

+ +

+ journal follows a standard procedure of using log files. These files log visitors when they visit websites. All + hosting companies do this and a part of hosting services' analytics. The information collected by log files + include + internet protocol (IP) addresses, browser type, Internet Service Provider (ISP), date and time stamp, + referring/exit + pages, and possibly the number of clicks. These are not linked to any information that is personally identifiable. + The purpose of the information is for analyzing trends, administering the site, tracking users' movement on the + website, and gathering demographic information. Our Privacy Policy was created with the help of the Privacy Policy Generator. +

+
+ +
+

Cookies and Web Beacons

+ +

+ Like any other website, journal uses "cookies". These cookies are used to store information including visitors' + preferences, and the pages on the website that the visitor accessed or visited. The information is used to + optimize + the users' experience by customizing our web page content based on visitors' browser type and/or other + information. +

+ +

+ For more general information on cookies, please read the "Cookies" article from the Privacy Policy + Generator. +

+
+ +
+

Privacy Policies

+ +

You may consult this list to find the Privacy Policy for each of the advertising partners of journal.

+ +

+ Third-party ad servers or ad networks uses technologies like cookies, JavaScript, or Web Beacons that are used in + their respective advertisements and links that appear on journal, which are sent directly to users' browser. They + automatically receive your IP address when this occurs. These technologies are used to measure the effectiveness + of + their advertising campaigns and/or to personalize the advertising content that you see on websites that you visit. +

+ +

Note that journal has no access to or control over these cookies that are used by third-party advertisers.

+ +
+ +
+

Third Party Privacy Policies

+ +

+ journal's Privacy Policy does not apply to other advertisers or websites. Thus, we are advising you to consult the + respective Privacy Policies of these third-party ad servers for more detailed information. It may include their + practices and instructions about how to opt-out of certain options. +

+ +

+ You can choose to disable cookies through your individual browser options. To know more detailed information about + cookie management with specific web browsers, it can be found at the browsers' respective websites. What Are + Cookies? +

+
+ +
+

Children's Information

+ +

+ Another part of our priority is adding protection for children while using the internet. We encourage parents and + guardians to observe, participate in, and/or monitor and guide their online activity. +

+ +

+ journal does not knowingly collect any Personal Identifiable Information from children under the age of 13. If you + think that your child provided this kind of information on our website, we strongly encourage you to contact us + immediately and we will do our best efforts to promptly remove such information from our records. +

+
+ +
+

Online Privacy Policy Only

+ +

+ This Privacy Policy applies only to our online activities and is valid for visitors to our website with regards to + the information that they shared and/or collect in journal. This policy is not applicable to any information + collected offline or via channels other than this website. +

+
+ +
+

Consent

+ +

By using our website, you hereby consent to our Privacy Policy and agree to its Terms and Conditions.

+
+
\ No newline at end of file diff --git a/src/Blog/Components/Pages/Privacy.razor.css b/src/Blog/Components/Pages/Privacy.razor.css new file mode 100644 index 0000000..cc6a5d8 --- /dev/null +++ b/src/Blog/Components/Pages/Privacy.razor.css @@ -0,0 +1,16 @@ +.container { + display: flex; + flex-direction: column; + gap: 2rem; + + section { + display: flex; + flex-direction: column; + gap: 1rem; + + ul { + list-style-type: disc; + padding-left: 1rem; + } + } +} \ No newline at end of file diff --git a/src/Blog/Components/Pages/Terms.razor b/src/Blog/Components/Pages/Terms.razor new file mode 100644 index 0000000..42dc04d --- /dev/null +++ b/src/Blog/Components/Pages/Terms.razor @@ -0,0 +1,269 @@ +@page "/terms" + +
+

Terms and Conditions

+ +
+

Welcome to journal!

+ +

These terms and conditions outline the rules and regulations for the use of journal's Website, located at + https://blog.stevanfreeborn.com. +

+ +

+ By accessing this website we assume you accept these terms and conditions. Do not continue to use journal if you + do + not agree to take all of the terms and conditions stated on this page. +

+ +

+ The following terminology applies to these Terms and Conditions, Privacy Statement and Disclaimer Notice and all + Agreements: "Client", "You" and "Your" refers to you, the person log on this website and compliant to the + Company's + terms and conditions. "The Company", "Ourselves", "We", "Our" and "Us", refers to our Company. "Party", "Parties", + or "Us", refers to both the Client and ourselves. All terms refer to the offer, acceptance and consideration of + payment + necessary to undertake the process of our assistance to the Client in the most appropriate manner for the express + purpose of meeting the Client's needs in respect of provision of the Company's stated services, in accordance with + and subject to, prevailing law of Netherlands. Any use of the above terminology or other words in the singular, + plural, + capitalization and/or he/she or they, are taken as interchangeable and therefore as referring to same. Our Terms + and + Conditions were created with the help of the Terms & Conditions Generator. +

+
+ +
+

Cookies

+ +

+ We employ the use of cookies. By accessing journal, you agreed to use cookies in agreement with the journal's + Privacy Policy. +

+ +

+ Most interactive websites use cookies to let us retrieve the user's details for each visit. Cookies are used by + our + website to enable the functionality of certain areas to make it easier for people visiting our website. Some of + our + affiliate/advertising partners may also use cookies. +

+
+ +
+

License

+ +

+ Unless otherwise stated, journal and/or its licensors own the intellectual property rights for all material on + journal. All intellectual property rights are reserved. You may access this from journal for your own personal use + subjected to restrictions set in these terms and conditions. +

+ +

You must not:

+ + +

This Agreement shall begin on the date hereof.

+ +

+ Parts of this website offer an opportunity for users to post and exchange opinions and information in certain + areas + of the website. journal does not filter, edit, publish or review Comments prior to their presence on the website. + Comments do not reflect the views and opinions of journal,its agents and/or affiliates. Comments reflect the views + and opinions of the person who post their views and opinions. To the extent permitted by applicable laws, journal + shall + not be liable for the Comments or for any liability, damages or expenses caused and/or suffered as a result of any + use of and/or posting of and/or appearance of the Comments on this website. +

+ +

+ journal reserves the right to monitor all Comments and to remove any Comments which can be considered + inappropriate, offensive or causes breach of these Terms and Conditions. +

+ +

You warrant and represent that:

+ + + +

+ You hereby grant journal a non-exclusive license to use, reproduce, edit and authorize others to use, reproduce + and + edit any of your Comments in any and all forms, formats or media. +

+
+ +
+

Hyperlinking to our Content

+ +

+ The following organizations may link to our Website without prior written approval: +

+ + + +

+ These organizations may link to our home page, to publications or to other Website information so long as the + link: + (a) is not in any way deceptive; (b) does not falsely imply sponsorship, endorsement or approval of the linking + party and its products and/or services; and (c) fits within the context of the linking party's site. +

+ +

We may consider and approve other link requests from the following types of organizations:

+ + + +

+ We will approve link requests from these organizations if we decide that: (a) the link would not make us look + unfavorably to ourselves or to our accredited businesses; (b) the organization does not have any negative records + with us; (c) the benefit to us from the visibility of the hyperlink compensates the absence of journal; and (d) + the + link + is in the context of general resource information. +

+ +

+ These organizations may link to our home page so long as the link: (a) is not in any way deceptive; (b) does not + falsely imply sponsorship, endorsement or approval of the linking party and its products or services; and (c) fits + within the context of the linking party's site. +

+ +

+ If you are one of the organizations listed in paragraph 2 above and are interested in linking to our website, you + must inform us by sending an e-mail to journal. Please include your name, your organization name, contact + information as well as the URL of your site, a list of any URLs from which you intend to link to our Website, and + a + list of the + URLs on our site to which you would like to link. Wait 2-3 weeks for a response. +

+ +

Approved organizations may hyperlink to our Website as follows:

+ + + +

No use of journal's logo or other artwork will be allowed for linking absent a trademark license agreement.

+
+ +
+

iFrames

+ +

+ Without prior approval and written permission, you may not create frames around our Webpages that alter in any way + the visual presentation or appearance of our Website. +

+
+ +
+

Content Liability

+ +

+ We shall not be hold responsible for any content that appears on your Website. You agree to protect and defend us + against all claims that is rising on your Website. No link(s) should appear on any Website that may be interpreted + as libelous, obscene or criminal, or which infringes, otherwise violates, or advocates the infringement or other + violation of, any third party rights. +

+
+ +
+

Reservation of Rights

+ +

+ We reserve the right to request that you remove all links or any particular link to our Website. You approve to + immediately remove all links to our Website upon request. We also reserve the right to amen these terms and + conditions and it's linking policy at any time. By continuously linking to our Website, you agree to be bound to + and + follow + these linking terms and conditions. +

+
+ +
+

Removal of links from our website

+ +

+ If you find any link on our Website that is offensive for any reason, you are free to contact and inform us any + moment. We will consider requests to remove links but we are not obligated to or so or to respond to you directly. +

+ +

+ We do not ensure that the information on this website is correct, we do not warrant its completeness or accuracy; + nor do we promise to ensure that the website remains available or that the material on the website is kept up to + date. +

+
+ +
+

Disclaimer

+ +

+ To the maximum extent permitted by applicable law, we exclude all representations, warranties and conditions + relating to our website and the use of this website. Nothing in this disclaimer will: +

+ + + +

+ The limitations and prohibitions of liability set in this Section and elsewhere in this disclaimer: (a) are + subject + to the preceding paragraph; and (b) govern all liabilities arising under the disclaimer, including liabilities + arising in contract, in tort and for breach of statutory duty. +

+ +

+ As long as the website and the information and services on the website are provided free of charge, we will not be + liable for any loss or damage of any nature. +

+
+
\ No newline at end of file diff --git a/src/Blog/Components/Pages/Terms.razor.css b/src/Blog/Components/Pages/Terms.razor.css new file mode 100644 index 0000000..cc6a5d8 --- /dev/null +++ b/src/Blog/Components/Pages/Terms.razor.css @@ -0,0 +1,16 @@ +.container { + display: flex; + flex-direction: column; + gap: 2rem; + + section { + display: flex; + flex-direction: column; + gap: 1rem; + + ul { + list-style-type: disc; + padding-left: 1rem; + } + } +} \ No newline at end of file From 279f80fc975ccedcd3d279620b9d8fa8a3860e23 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Apr 2024 13:41:11 -0500 Subject: [PATCH 2/7] chore: stub out footer --- src/Blog/Components/Layout/Footer.razor | 3 +++ src/Blog/Components/Layout/Footer.razor.css | 1 + 2 files changed, 4 insertions(+) create mode 100644 src/Blog/Components/Layout/Footer.razor create mode 100644 src/Blog/Components/Layout/Footer.razor.css diff --git a/src/Blog/Components/Layout/Footer.razor b/src/Blog/Components/Layout/Footer.razor new file mode 100644 index 0000000..398f51f --- /dev/null +++ b/src/Blog/Components/Layout/Footer.razor @@ -0,0 +1,3 @@ +@namespace Blog.Components.Layout + + \ No newline at end of file diff --git a/src/Blog/Components/Layout/Footer.razor.css b/src/Blog/Components/Layout/Footer.razor.css new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Blog/Components/Layout/Footer.razor.css @@ -0,0 +1 @@ + From 66bef092f33df12a64570b7070a54823c23c53dd Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Apr 2024 19:09:03 -0500 Subject: [PATCH 3/7] fix: switch to SSR only - not actually making use of interactive server functionality - don't need to deal with websocket disconnect when server is restarted - can get necessary functionality using static rendering plus executing js on client when loaded - added page script component to handle loading collocated scripts for pages that also work with enhanced navigation --- src/Blog/Components/App.razor | 5 +- src/Blog/Components/Layout/Header.razor | 4 +- src/Blog/Components/Layout/MainLayout.razor | 8 +- .../Components/Layout/MainLayout.razor.css | 8 ++ src/Blog/Components/Pages/Error.razor | 3 +- src/Blog/Components/Pages/Post.razor | 36 +------ src/Blog/Components/Pages/Post.razor.js | 32 ++++++- src/Blog/Components/Posts/Feed.razor | 9 +- src/Blog/Components/Utils/PageScript.razor | 19 ++++ src/Blog/Components/_Imports.razor | 1 + src/Blog/Program.cs | 9 +- src/Blog/wwwroot/Blog.lib.module.js | 95 +++++++++++++++++++ 12 files changed, 179 insertions(+), 50 deletions(-) create mode 100644 src/Blog/Components/Utils/PageScript.razor create mode 100644 src/Blog/wwwroot/Blog.lib.module.js 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); +} From 7fb9c2806492c55c5772dae973536e65de64e7ae Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sat, 13 Apr 2024 19:25:38 -0500 Subject: [PATCH 4/7] feat: add footer with nav --- src/Blog/Components/Layout/Footer.razor | 6 +- src/Blog/Components/Layout/Footer.razor.css | 7 +- src/Blog/Components/Layout/MainLayout.razor | 1 + .../Components/Layout/MainLayout.razor.css | 2 + src/Blog/Components/Posts/Feed.razor | 8 +-- src/Blog/Components/Posts/Feed.razor.css | 64 ++++++++++--------- 6 files changed, 51 insertions(+), 37 deletions(-) diff --git a/src/Blog/Components/Layout/Footer.razor b/src/Blog/Components/Layout/Footer.razor index 398f51f..87239ba 100644 --- a/src/Blog/Components/Layout/Footer.razor +++ b/src/Blog/Components/Layout/Footer.razor @@ -1,3 +1,7 @@ @namespace Blog.Components.Layout -
\ No newline at end of file +
+ Code + Terms + Privacy +
\ No newline at end of file diff --git a/src/Blog/Components/Layout/Footer.razor.css b/src/Blog/Components/Layout/Footer.razor.css index 8b13789..8fdbfc5 100644 --- a/src/Blog/Components/Layout/Footer.razor.css +++ b/src/Blog/Components/Layout/Footer.razor.css @@ -1 +1,6 @@ - +footer { + display: flex; + justify-content: flex-end; + width: 100%; + gap: 1rem; +} \ No newline at end of file diff --git a/src/Blog/Components/Layout/MainLayout.razor b/src/Blog/Components/Layout/MainLayout.razor index a5b445a..ac58ee7 100644 --- a/src/Blog/Components/Layout/MainLayout.razor +++ b/src/Blog/Components/Layout/MainLayout.razor @@ -5,6 +5,7 @@
@Body
+