Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added reading indicator #208

Merged
merged 8 commits into from
Apr 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,29 @@ root = true
[*]
indent_style = tab
indent_size = tab
tab_size = 4
tab_width = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.cs]
tab_size = 4
indent_style = space

[*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}]
tab_size = 2
tab_width = 2

[*.{htm,html,js,jsm,ts,tsx,css,sass,scss,less,svg,vue}]
tab_size = 2
tab_width = 2

[*.json]
tab_size = 2

[*.{ps1,psm1}]
tab_size = 4
tab_width = 2

[*.sh]
tab_size = 4
end_of_line = lf

[*.{yml,yaml}]
indent_style = space
tab_size = 2
tab_width = 2

[*.md]
trim_trailing_whitespace = false
Expand Down
54 changes: 28 additions & 26 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,39 +58,41 @@ The appsettings.json file has a lot of options to customize the content of the b
"Shortname": "blog"
},
"KofiToken": "ABC123",
"GithubSponsorName": "your-tag-here"
"GithubSponsorName": "your-tag-here",
"ShowReadingIndicator": true
}

```

| Property | Type | Description |
| ------------------------- | -------------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| BlogName | string | Name of your blog. Is used in the navbar and is used as the title of the page. Will not be shown when `BlogBrandUrl` is set |
| BlogBrandUrl | string | The url to an image which is used as a brand image in the navigation bar. If not set or `null` the `BlogName` will be shown |
| Social | node | Represents all possible linked social accounts |
| GithubAccountUrl | string | Url to your github account. If not set it is not shown in the introduction card |
| LinkedInAccountUrl | string | Url to your LinkedIn account. If not set it is not shown in the introduction card |
| TwitterAccountUrl | string | Url to your Twitter account. If not set it is not shown in the introduction card |
| Introduction | | Is used for the introduction part of the blog |
| Description | MarkdownString | Small introduction text for yourself. This is also used for `<meta name="description">` tag. For this the markup will be converted to plain text |
| BackgroundUrl | string | Url or path to the background image. (Optional) |
| ProfilePictureUrl | string | Url or path to your profile picture |
| Property | Type | Description |
| ------------------------- | -------------- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| BlogName | string | Name of your blog. Is used in the navbar and is used as the title of the page. Will not be shown when `BlogBrandUrl` is set |
| BlogBrandUrl | string | The url to an image which is used as a brand image in the navigation bar. If not set or `null` the `BlogName` will be shown |
| Social | node | Represents all possible linked social accounts |
| GithubAccountUrl | string | Url to your github account. If not set it is not shown in the introduction card |
| LinkedInAccountUrl | string | Url to your LinkedIn account. If not set it is not shown in the introduction card |
| TwitterAccountUrl | string | Url to your Twitter account. If not set it is not shown in the introduction card |
| Introduction | | Is used for the introduction part of the blog |
| Description | MarkdownString | Small introduction text for yourself. This is also used for `<meta name="description">` tag. For this the markup will be converted to plain text |
| BackgroundUrl | string | Url or path to the background image. (Optional) |
| ProfilePictureUrl | string | Url or path to your profile picture |
| PersistenceProvider | string | Declares the type of the storage provider (one of the following: `SqlServer`, `Sqlite`, `RavenDb`, `InMemory`, `MySql`). More in-depth explanation down below |
| ConnectionString | string | Is used for connection to a database. Not used when `InMemoryStorageProvider` is used |
| DatabaseName | string | Name of the database. Only used with `RavenDbStorageProvider` |
| Auth0 | | Configuration for setting up Auth0 |
| Domain | string | See more details here: https://manage.auth0.com/dashboard/ |
| ClientId | string | See more details here: https://manage.auth0.com/dashboard/ |
| ClientSecret | string | See more details here: https://manage.auth0.com/dashboard/ |
| BlogPostsPerPage | int | Gives the amount of blog posts loaded and display per page. For more the user has to use the navigation |
| AboutMeProfileInformation | node | Sets information for the About Me Page. If omitted the page is disabled completely |
| Name | string | Name, which is displayed on top of the profile card |
| Heading | string | Displayed under the name. For example job title |
| ProfilePictureUrl | string | Displayed profile picture |
| ConnectionString | string | Is used for connection to a database. Not used when `InMemoryStorageProvider` is used |
| DatabaseName | string | Name of the database. Only used with `RavenDbStorageProvider` |
| Auth0 | | Configuration for setting up Auth0 |
| Domain | string | See more details here: https://manage.auth0.com/dashboard/ |
| ClientId | string | See more details here: https://manage.auth0.com/dashboard/ |
| ClientSecret | string | See more details here: https://manage.auth0.com/dashboard/ |
| BlogPostsPerPage | int | Gives the amount of blog posts loaded and display per page. For more the user has to use the navigation |
| AboutMeProfileInformation | node | Sets information for the About Me Page. If omitted the page is disabled completely |
| Name | string | Name, which is displayed on top of the profile card |
| Heading | string | Displayed under the name. For example job title |
| ProfilePictureUrl | string | Displayed profile picture |
| Giscus | node | Enables the comment section via giscus. If left empty the comment secion will not be shown. For more information checkout the section about comments down below |
| Disqus | node | Enables the comment section via disqus. If left empty the comment secion will not be shown. For more information checkout the section about comments down below |
| KofiToken | string | Enables the "Buy me a Coffee" button of Kofi. To aquire the token head down to the "Kofi" section |
| GithubSponsorName | string | Enables the "Github Sponsor" button which redirects to GitHub. Only pass in the user name instead of the url. |
| KofiToken | string | Enables the "Buy me a Coffee" button of Kofi. To aquire the token head down to the "Kofi" section |
| GithubSponsorName | string | Enables the "Github Sponsor" button which redirects to GitHub. Only pass in the user name instead of the url. |
| ShowReadingIndicator | boolean | If set to `true` (default) a circle indicates the progress when a user reads a blog post (without comments). |

## Storage Provider
Currently, there are 5 Storage-Provider:
Expand Down
2 changes: 2 additions & 0 deletions src/LinkDotNet.Blog.Web/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ public record AppConfiguration
public string GithubSponsorName { get; init; }

public bool IsGithubSponsorAvailable => !string.IsNullOrEmpty(GithubSponsorName);

public bool ShowReadingIndicator { get; init; }
}
1 change: 1 addition & 0 deletions src/LinkDotNet.Blog.Web/AppConfigurationFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public static AppConfiguration Create(IConfiguration config)
DisqusConfiguration = disqus,
KofiToken = config[nameof(AppConfiguration.KofiToken)],
GithubSponsorName = config[nameof(AppConfiguration.GithubSponsorName)],
ShowReadingIndicator = config.GetValue<bool>(nameof(AppConfiguration.ShowReadingIndicator)),
};

return configuration;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@

@code {
private bool MultipleCommentPlugins => AppConfiguration.IsDisqusEnabled && AppConfiguration.IsGiscusEnabled;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@inject IJSRuntime JSRuntime

<div class="progress-container" @ref="progressContainer">
<svg class="progress-circle" viewBox="0 0 36 36">
<circle class="progress-bg" cx="18" cy="18" r="15.91549431" />
<circle class="progress-bar" id="progressBar" cx="18" cy="18" r="15.91549431" />
</svg>
</div>

@code {
[Parameter]
public string ContainerCssSelector { get; set; }

private ElementReference progressContainer;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await using var _ = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./Features/ShowBlogPost/Components/ReadingIndicator.razor.js");
await JSRuntime.InvokeVoidAsync("initCircularReadingProgress", ContainerCssSelector, progressContainer);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
.progress-container {
position: fixed;
bottom: 20px;
right: 20px;
z-index: 1000;
opacity: 0;
transition: opacity 1.5s;
}

.progress-container.visible {
opacity: 1;
}

@keyframes fadeOut {
to {
opacity: 0;
}
}

.progress-circle {
width: 50px;
height: 50px;
}

.progress-bg {
fill: none;
stroke: #f3f3f3;
stroke-width: 4;
}

.progress-bar {
fill: none;
stroke: #4caf50;
stroke-width: 4;
stroke-linecap: round;
transform-origin: center;
transform: rotate(-90deg);
stroke-dasharray: 100, 100;
stroke-dashoffset: 100;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
let progressTimeout;
let rafId;

function getContentHeight(className) {
const content = document.querySelector(className);
if (!content) {
return 0;
}
const contentRect = content.getBoundingClientRect();
return contentRect.height;
}

function showProgressIndicator(progressContainer) {
progressContainer.classList.add("visible");
progressContainer.style.animation = 'none';
}

function hideProgressIndicator(progressContainer) {
progressContainer.style.animation = 'fadeOut 0.5s forwards';
setTimeout(() => {
progressContainer.classList.remove('visible');
}, 500);
}

window.initCircularReadingProgress = (parentContainer, progressContainer) => {
const progressBar = document.getElementById('progressBar');

const onScroll = () => {
clearTimeout(progressTimeout);

const contentHeight = getContentHeight(parentContainer);
const windowHeight = document.documentElement.clientHeight;
const scrollAmount = document.documentElement.scrollTop;
const maxScrollAmount = contentHeight - windowHeight;
const progress = Math.max(0, Math.min(100, (scrollAmount / maxScrollAmount) * 100));
progressBar.style.strokeDashoffset = 100 - progress;

showProgressIndicator(progressContainer);

progressTimeout = setTimeout(() => {
hideProgressIndicator(progressContainer);
}, 2000);

rafId = null;
};

window.addEventListener('scroll', () => {
if (!rafId) {
rafId = requestAnimationFrame(onScroll);
}
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@inject IRepository<BlogPost> BlogPostRepository
@inject IJSRuntime JsRuntime
@inject IUserRecordService UserRecordService
@inject AppConfiguration AppConfiguration

@if (BlogPost == null)
{
Expand All @@ -19,40 +20,45 @@ else
AbsolutePreviewImageUrl="@OgDataImage"
Description="@(Markdown.ToPlainText(BlogPost.ShortDescription))"
Keywords="@Tags"></OgData>
<div class="d-flex justify-content-center pt-2 blog-outer-box">
<div class="content blog-container">
<div>
<header class="text-center">
<h1>@BlogPost.Title</h1></header>
<div class="blogpost-metainformation d-flex flex-row flex-wrap gap-2">
<div><span class="date"></span><span class="ms-1">@BlogPost.UpdatedDate.ToString("dd/MM/yyyy")</span></div>
@if (BlogPost.Tags != null && BlogPost.Tags.Any())
{
<span class="blogpost-tag d-inline-block">
@foreach (var tag in BlogPost.Tags.Select(t => t.Content))
{
<a class="goto-tag ms-1" href="/searchByTag/@(Uri.EscapeDataString(tag))">@tag</a>
}
</span>
}
</div>
<div class="d-flex justify-content-center pt-2 blog-outer-box">
<div class="content blog-container">
<div class="blog-inner-content">
<header class="text-center">
<h1>@BlogPost.Title</h1></header>
<div class="blogpost-metainformation d-flex flex-row flex-wrap gap-2">
<div><span class="date"></span><span class="ms-1">@BlogPost.UpdatedDate.ToString("dd/MM/yyyy")</span></div>
@if (BlogPost.Tags != null && BlogPost.Tags.Any())
{
<span class="blogpost-tag d-inline-block">
@foreach (var tag in BlogPost.Tags.Select(t => t.Content))
{
<a class="goto-tag ms-1" href="/searchByTag/@(Uri.EscapeDataString(tag))">@tag</a>
}
</span>
}
</div>

<div class="admin-action">
<BlogPostAdminActions BlogPostId="@BlogPostId"></BlogPostAdminActions>
</div>
<div class="admin-action">
<BlogPostAdminActions BlogPostId="@BlogPostId"></BlogPostAdminActions>
</div>

<div class="blogpost-content">
@(MarkdownConverter.ToMarkupString(BlogPost.Content))
</div>
</div>
<div class="d-flex justify-content-between py-2">
<Like BlogPost="@BlogPost" OnBlogPostLiked="@UpdateLikes"></Like>
<ShareBlogPost></ShareBlogPost>
</div>
<DonationSection></DonationSection>
<CommentSection></CommentSection>
</div>
</div>
<div class="blogpost-content">
@(MarkdownConverter.ToMarkupString(BlogPost.Content))
</div>
</div>
<div class="d-flex justify-content-between py-2">
<Like BlogPost="@BlogPost" OnBlogPostLiked="@UpdateLikes"></Like>
<ShareBlogPost></ShareBlogPost>
</div>
<DonationSection></DonationSection>
<CommentSection></CommentSection>
</div>
</div>

@if (AppConfiguration.ShowReadingIndicator)
{
<ReadingIndicator ContainerCssSelector=".blog-inner-content"/>
}
}

@code {
Expand Down
3 changes: 2 additions & 1 deletion src/LinkDotNet.Blog.Web/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@
"Name": "Steven Giesel",
"Heading": "Software Engineer",
"ProfilePictureUrl": "assets/profile-picture.webp",
}
},
"ShowReadingIndicator": true
}
Loading