diff --git a/.editorconfig b/.editorconfig index 6eecb086e..56f423934 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,51 @@ -# editorconfig.org +# editorconfig.org root = true [*] charset = utf-8 end_of_line = lf indent_style = space -indent_size = 4 +indent_size = 2 trim_trailing_whitespace = true insert_final_newline = true +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +tab_width = 2 +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_predefined_type_for_locals_parameters_members = true:silent + +[*.{cshtml}] +indent_size = 2 + +[*.{razor}] +indent_size = 2 [*.{css,scss,js,json,yml}] indent_size = 2 @@ -20,3 +58,145 @@ trim_trailing_whitespace = false [*.sln] indent_style = tab + +[*.cs] +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# 命名样式 + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +csharp_using_directive_placement = outside_namespace:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_conditional_delegate_call = true:suggestion +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = false:silent +csharp_style_var_elsewhere = false:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_prefer_static_local_function = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_space_around_binary_operators = before_and_after +csharp_indent_labels = one_less_than_current +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent + +[*.vb] +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_以_i_开始.severity = suggestion +dotnet_naming_rule.interface_should_be_以_i_开始.symbols = interface +dotnet_naming_rule.interface_should_be_以_i_开始.style = 以_i_开始 + +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.severity = suggestion +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.symbols = 类型 +dotnet_naming_rule.类型_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 + +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.severity = suggestion +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.symbols = 非字段成员 +dotnet_naming_rule.非字段成员_should_be_帕斯卡拼写法.style = 帕斯卡拼写法 + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.类型.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.类型.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.类型.required_modifiers = + +dotnet_naming_symbols.非字段成员.applicable_kinds = property, event, method +dotnet_naming_symbols.非字段成员.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.非字段成员.required_modifiers = + +# 命名样式 + +dotnet_naming_style.以_i_开始.required_prefix = I +dotnet_naming_style.以_i_开始.required_suffix = +dotnet_naming_style.以_i_开始.word_separator = +dotnet_naming_style.以_i_开始.capitalization = pascal_case + +dotnet_naming_style.帕斯卡拼写法.required_prefix = +dotnet_naming_style.帕斯卡拼写法.required_suffix = +dotnet_naming_style.帕斯卡拼写法.word_separator = +dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case + +dotnet_naming_style.帕斯卡拼写法.required_prefix = +dotnet_naming_style.帕斯卡拼写法.required_suffix = +dotnet_naming_style.帕斯卡拼写法.word_separator = +dotnet_naming_style.帕斯卡拼写法.capitalization = pascal_case diff --git a/.gitignore b/.gitignore index 9b70681f6..2c454277b 100644 --- a/.gitignore +++ b/.gitignore @@ -350,3 +350,9 @@ MigrationBackup/ .ionide/ .DS_Store +Blog.db-shm +Blog.db-wal + +App_Data/ + +appsettings.Development.json diff --git a/.vscode/launch.json b/.vscode/launch.json index e47c0c29c..000b34039 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,31 +1,35 @@ { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md - "version": "0.2.0", - "configurations": [ - { - "name": "Launch and Debug Blazor WebAssembly App", - "type": "coreclr", - "request": "launch", - "program": "dotnet", - "args":["run"], - "cwd": "${workspaceFolder}/src/Blogifier", - "env": { "ASPNETCORE_ENVIRONMENT": "Development" }, - "launchBrowser": { - "enabled": true, - "args": "${auto-detect-url}", - "windows": { - "command": "cmd.exe", - "args": "/C start ${auto-detect-url}", - }, - "osx": { - "command": "open" - }, - "linux": { - "command": "xdg-open" - } - } + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": "Launch and Debug Blazor WebAssembly App", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "watch" + ], + "cwd": "${workspaceFolder}/src/Blogifier", + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "launchBrowser": { + "enabled": true, + "args": "${auto-detect-url}", + "windows": { + "command": "cmd.exe", + "args": "/C start ${auto-detect-url}", + }, + "osx": { + "command": "open" + }, + "linux": { + "command": "xdg-open" } - ] -} \ No newline at end of file + } + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 58642d74a..0bf8b754e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,42 +1,42 @@ { - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/src/Blogifier/Blogifier.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/src/Blogifier/Blogifier.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/src/Blogifier/Blogifier.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/Blogifier/Blogifier.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/Blogifier/Blogifier.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/src/Blogifier/Blogifier.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} diff --git a/Blogifier.sln b/Blogifier.sln index e10a69835..fa587eb3c 100644 --- a/Blogifier.sln +++ b/Blogifier.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.0.0 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33516.290 MinimumVisualStudioVersion = 16.0.0.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier", "src\Blogifier\Blogifier.csproj", "{30113938-1040-459A-A0B6-3663E9534C8B}" EndProject @@ -8,8 +8,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Admin", "src\Blog EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Shared", "src\Blogifier.Shared\Blogifier.Shared.csproj", "{A0173B1A-FDC9-4E48-94E1-21D6C0B19257}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Core", "src\Blogifier.Core\Blogifier.Core.csproj", "{99296F5E-F267-4483-97BF-647080D21C35}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{512E41FF-25A7-49D6-B126-EC1450E17B72}" ProjectSection(SolutionItems) = preProject docs\01-Authentication.md = docs\01-Authentication.md @@ -18,19 +16,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{512E41FF-2 docs\04-Localization.md = docs\04-Localization.md docs\05-Emails.md = docs\05-Emails.md docs\06-Newsletters.md = docs\06-Newsletters.md + README.md = README.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{8DDBCDEC-BFD8-4737-93BD-5259C3AE9CAE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blogifier.Tests", "tests\Blogifier.Tests\Blogifier.Tests.csproj", "{27EB38D0-E275-4033-93B6-3ED6E6B7B442}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "data", "data", "{84FCDE15-5F12-484E-A8C2-DA9A1D3122BB}" - ProjectSection(SolutionItems) = preProject - tests\data\test3.xml = tests\data\test3.xml - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "items", "items", "{D2E9C8BA-AC43-4A35-88D9-8D63D26884B8}" ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore Dockerfile = Dockerfile EndProjectSection EndProject @@ -80,18 +76,6 @@ Global {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|x64.Build.0 = Release|Any CPU {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|x86.ActiveCfg = Release|Any CPU {A0173B1A-FDC9-4E48-94E1-21D6C0B19257}.Release|x86.Build.0 = Release|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Debug|x64.ActiveCfg = Debug|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Debug|x64.Build.0 = Debug|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Debug|x86.ActiveCfg = Debug|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Debug|x86.Build.0 = Debug|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Release|Any CPU.Build.0 = Release|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Release|x64.ActiveCfg = Release|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Release|x64.Build.0 = Release|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Release|x86.ActiveCfg = Release|Any CPU - {99296F5E-F267-4483-97BF-647080D21C35}.Release|x86.Build.0 = Release|Any CPU {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|Any CPU.Build.0 = Debug|Any CPU {27EB38D0-E275-4033-93B6-3ED6E6B7B442}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -110,7 +94,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {27EB38D0-E275-4033-93B6-3ED6E6B7B442} = {8DDBCDEC-BFD8-4737-93BD-5259C3AE9CAE} - {84FCDE15-5F12-484E-A8C2-DA9A1D3122BB} = {8DDBCDEC-BFD8-4737-93BD-5259C3AE9CAE} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8AA706E7-D820-4892-9F60-FCEEDC51FF5E} diff --git a/docs/02-Database.md b/docs/02-Database.md index 34f25f34f..87dbf68d2 100644 --- a/docs/02-Database.md +++ b/docs/02-Database.md @@ -11,8 +11,8 @@ ``` Valid providers: `SQLite`, `SqlServer`, `Postgres`, `MySql` (you'll need to supply valid connection string) -2. Remove `Blogifier.Core/Data/Migrations` folder with existing migrations -3. In the Visual Studio, open `Package Manager Console`, set `Blogifier.Core` +2. Remove `Blogifier/Data/Migrations` folder with existing migrations +3. In the Visual Studio, open `Package Manager Console`, set `Blogifier` as Default project and run these commands: ``` @@ -21,4 +21,4 @@ Update-Database ``` First command should re-generate provider specific code migrations and second will -execute them and create database specified in the connection string. \ No newline at end of file +execute them and create database specified in the connection string. diff --git a/src/Blogifier.Admin/App.razor b/src/Blogifier.Admin/App.razor index 3772d469f..c6d65f3b3 100644 --- a/src/Blogifier.Admin/App.razor +++ b/src/Blogifier.Admin/App.razor @@ -1,12 +1,13 @@ - - - - - - - -

Sorry, there's nothing at this address.

-
-
-
-
\ No newline at end of file + + + + + + + +

Sorry, there's nothing at this address.

+
+
+
+
+ diff --git a/src/Blogifier.Admin/BlogAuthStateProvider.cs b/src/Blogifier.Admin/BlogAuthStateProvider.cs new file mode 100644 index 000000000..a13538361 --- /dev/null +++ b/src/Blogifier.Admin/BlogAuthStateProvider.cs @@ -0,0 +1,34 @@ +using Blogifier.Shared; +using Microsoft.AspNetCore.Components.Authorization; +using System.Net.Http; +using System.Net.Http.Json; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace Blogifier.Admin; + +public class BlogAuthStateProvider : AuthenticationStateProvider +{ + private readonly HttpClient _httpClient; + + public BlogAuthStateProvider(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public override async Task GetAuthenticationStateAsync() + { + var author = await _httpClient.GetFromJsonAsync("api/author/getcurrent"); + if (author != null && author.Email != null) + { + var claim = new Claim(ClaimTypes.Name, author.Email); + var claimsIdentity = new ClaimsIdentity(new[] { claim }, "serverAuth"); + var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); + return new AuthenticationState(claimsPrincipal); + } + else + { + return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); + } + } +} diff --git a/src/Blogifier.Admin/BlogAuthenticationStateProvider.cs b/src/Blogifier.Admin/BlogAuthenticationStateProvider.cs deleted file mode 100644 index 5073e23a5..000000000 --- a/src/Blogifier.Admin/BlogAuthenticationStateProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Blogifier.Shared; -using Microsoft.AspNetCore.Components.Authorization; -using System.Net.Http; -using System.Net.Http.Json; -using System.Security.Claims; -using System.Threading.Tasks; - -namespace Blogifier.Admin -{ - public class BlogAuthenticationStateProvider : AuthenticationStateProvider - { - private readonly HttpClient _httpClient; - - public BlogAuthenticationStateProvider(HttpClient httpClient) - { - _httpClient = httpClient; - } - - public override async Task GetAuthenticationStateAsync() - { - Author author = await _httpClient.GetFromJsonAsync("api/author/getcurrent"); - - if (author != null && author.Email != null) - { - var claim = new Claim(ClaimTypes.Name, author.Email); - var claimsIdentity = new ClaimsIdentity(new[] { claim }, "serverAuth"); - var claimsPrincipal = new ClaimsPrincipal(claimsIdentity); - - return new AuthenticationState(claimsPrincipal); - } - else - return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); - } - } -} diff --git a/src/Blogifier.Admin/BlogStateProvider.cs b/src/Blogifier.Admin/BlogStateProvider.cs index 27e8bce7e..1004ee9b7 100644 --- a/src/Blogifier.Admin/BlogStateProvider.cs +++ b/src/Blogifier.Admin/BlogStateProvider.cs @@ -1,20 +1,19 @@ using Blogifier.Shared; using System; -namespace Blogifier.Admin +namespace Blogifier.Admin; + +public class BlogStateProvider { - public class BlogStateProvider - { - public PostType PostType { get; set; } = PostType.Post; + public PostType PostType { get; set; } = PostType.Post; - public event Action OnChange; + public event Action OnChange = default!; - public void SetPostType(PostType postType) - { - PostType = postType; - NotifyStateChanged(); - } + public void SetPostType(PostType postType) + { + PostType = postType; + NotifyStateChanged(); + } - private void NotifyStateChanged() => OnChange?.Invoke(); - } + private void NotifyStateChanged() => OnChange?.Invoke(); } diff --git a/src/Blogifier.Admin/Blogifier.Admin.csproj b/src/Blogifier.Admin/Blogifier.Admin.csproj index a6a78d806..4a6c6eba4 100644 --- a/src/Blogifier.Admin/Blogifier.Admin.csproj +++ b/src/Blogifier.Admin/Blogifier.Admin.csproj @@ -1,19 +1,19 @@ - net6.0 + net7.0 + enable - + - - - + + + - - + - \ No newline at end of file + diff --git a/src/Blogifier.Admin/Components/Blog/CategoriesComponent.razor b/src/Blogifier.Admin/Components/Blog/CategoriesComponent.razor index 425918bed..34bc65cb1 100644 --- a/src/Blogifier.Admin/Components/Blog/CategoriesComponent.razor +++ b/src/Blogifier.Admin/Components/Blog/CategoriesComponent.razor @@ -12,50 +12,53 @@ }
- @if (PostCategories != null) + @if (PostCategories != null) + { + foreach (var item in PostCategories) { - foreach (var item in PostCategories) - { -
- - - - -
- } +
+ + + + +
} - + } +
@code { - [Parameter] public Post Post { get; set; } + [Parameter] public Post Post { get; set; } - protected string Tag { get; set; } - public List PostCategories { get; set; } + protected string Tag { get; set; } + public List PostCategories { get; set; } - protected override async Task OnInitializedAsync() - { - await Load(); - } + protected override async Task OnInitializedAsync() + { + await Load(); + } - protected async Task Load() - { - Tag = ""; - PostCategories = (await _http.GetFromJsonAsync>($"api/category/{Post.Id}")).ToList(); - } + protected async Task Load() + { + Tag = ""; + PostCategories = (await _http.GetFromJsonAsync>($"api/category/{Post.Id}")).ToList(); + } - protected async Task KeyPressed(KeyboardEventArgs eventArgs) - { - if (eventArgs.Code == "Enter") - { - PostCategories.Add(new Category { Content = Tag, DateUpdated = DateTime.UtcNow }); - Tag = await Task.FromResult(""); - } - } + protected async Task KeyPressed(KeyboardEventArgs eventArgs) + { + if (eventArgs.Code == "Enter") + { + PostCategories.Add(new Category { Content = Tag }); + Tag = await Task.FromResult(""); + } + } - protected async Task Remove(string tag) - { - var tagToRemove = await Task.FromResult(PostCategories.Where(c => c.Content == tag).FirstOrDefault()); - PostCategories.Remove(tagToRemove); - } + protected async Task Remove(string tag) + { + var tagToRemove = await Task.FromResult(PostCategories.Where(c => c.Content == tag).FirstOrDefault()); + PostCategories.Remove(tagToRemove); + } } diff --git a/src/Blogifier.Admin/Components/Customize/MenusComponent.razor b/src/Blogifier.Admin/Components/Customize/MenusComponent.razor index e351d2305..53e198642 100644 --- a/src/Blogifier.Admin/Components/Customize/MenusComponent.razor +++ b/src/Blogifier.Admin/Components/Customize/MenusComponent.razor @@ -1,5 +1,6 @@ @inject IStringLocalizer _localizer
- @_localizer["under-development"]. - @_localizer["more-info"] + @_localizer["under-development"]. + @_localizer["more-info"]
diff --git a/src/Blogifier.Admin/Components/Customize/SettingsComponent.razor b/src/Blogifier.Admin/Components/Customize/SettingsComponent.razor index 19970b08c..0b9d47782 100644 --- a/src/Blogifier.Admin/Components/Customize/SettingsComponent.razor +++ b/src/Blogifier.Admin/Components/Customize/SettingsComponent.razor @@ -4,95 +4,97 @@ @if (Settings != null) { -
- @foreach (var item in Settings.Sections) - { - var sec = item.Label.GetHashCode(); +
+ @foreach (var item in Settings.Sections) + { + var sec = item.Label.GetHashCode(); -
-

- -

-
- -
- @foreach (var field in item.Fields) +
+

+ +

+
+ +
+ @foreach (var field in item.Fields) + { +
+ @if (field.Type == "select") + { + + - @foreach (var opt in field.Options) - { - if (field.Value == opt) - { - - } - else - { - - } - } - - } - else if (field.Type == "checkbox") - { -
- - -
- } - else if (field.Type == "textarea") - { - - - } - else - { - - - } -
+ if (field.Value == opt) + { + + } + else + { + + } } -
-
-
- } -
+ + } + else if (field.Type == "checkbox") + { +
+ + +
+ } + else if (field.Type == "textarea") + { + + + } + else + { + + + } +
+ } +
+
+
+ } + } @code { - protected Blog Blog { get; set; } - protected ThemeItem CurrentTheme { get; set; } - protected ToasterComponent Toaster; - protected ThemeSettings Settings { get; set; } + protected Blog Blog { get; set; } + protected ThemeItem CurrentTheme { get; set; } + protected ToasterComponent Toaster; + protected ThemeSettings Settings { get; set; } - protected override async Task OnInitializedAsync() - { - await Load(); - } + protected override async Task OnInitializedAsync() + { + await Load(); + } - public async Task Load() - { - Blog = await _http.GetFromJsonAsync("api/blog"); - var allThemes = await _http.GetFromJsonAsync>($"api/storage/themes"); + public async Task Load() + { + Blog = await _http.GetFromJsonAsync("api/blog"); + var allThemes = await _http.GetFromJsonAsync>($"api/storage/themes"); - Settings = await _http.GetFromJsonAsync($"api/theme/{Blog.Theme}"); - } + Settings = await _http.GetFromJsonAsync($"api/theme/{Blog.Theme}"); + } - public async Task Save() - { - foreach (var section in Settings.Sections) + public async Task Save() + { + foreach (var section in Settings.Sections) + { + foreach (var field in section.Fields) { - foreach (var field in section.Fields) - { - var val = await JSRuntime.InvokeAsync("commonJsFunctions.getFieldValue", field); - field.Value = val.ToString(); - } + var val = await JSRuntime.InvokeAsync("commonJsFunctions.getFieldValue", field); + field.Value = val.ToString(); } - return await _http.PostAsJsonAsync($"api/theme/{Blog.Theme}", Settings); - } + } + return await _http.PostAsJsonAsync($"api/theme/{Blog.Theme}", Settings); + } } diff --git a/src/Blogifier.Admin/Components/Customize/WidgetsComponent.razor b/src/Blogifier.Admin/Components/Customize/WidgetsComponent.razor index e351d2305..53e198642 100644 --- a/src/Blogifier.Admin/Components/Customize/WidgetsComponent.razor +++ b/src/Blogifier.Admin/Components/Customize/WidgetsComponent.razor @@ -1,5 +1,6 @@ @inject IStringLocalizer _localizer
- @_localizer["under-development"]. - @_localizer["more-info"] + @_localizer["under-development"]. + @_localizer["more-info"]
diff --git a/src/Blogifier.Admin/Components/Dashboard/AnalyticsComponent.razor b/src/Blogifier.Admin/Components/Dashboard/AnalyticsComponent.razor index 3cc9a926a..66eed7ab0 100644 --- a/src/Blogifier.Admin/Components/Dashboard/AnalyticsComponent.razor +++ b/src/Blogifier.Admin/Components/Dashboard/AnalyticsComponent.razor @@ -9,209 +9,218 @@ @inject IStringLocalizer _localizer
-
-
@_localizer["analytics"]
- @code { - protected ToasterComponent Toaster; - private BarConfig _config; - protected List _visits; - - protected bool _hideGraph = false; - protected bool _hideList = true; - - protected List _dateOptions; - protected AnalyticsModel _model; - - protected override async Task OnInitializedAsync() - { - _config = GetConfig(); - - _dateOptions = new List(); - _dateOptions.Add(new OptionItem { Id = 1, Title = _localizer["today"] }); - _dateOptions.Add(new OptionItem { Id = 2, Title = _localizer["yesterday"] }); - _dateOptions.Add(new OptionItem { Id = 3, Title = _localizer["7-days"] }); - _dateOptions.Add(new OptionItem { Id = 4, Title = _localizer["30-days"] }); - _dateOptions.Add(new OptionItem { Id = 5, Title = _localizer["90-days"] }); - - await Load(); - } - - protected async Task Load() - { - IDataset dataset = new BarDataset() - { - Label = "Latest Post Views", - BackgroundColor = ColorUtil.FromDrawingColor(Color.FromArgb(98, 42, 255)), - BorderWidth = 0 - }; - - _model = await _http.GetFromJsonAsync("api/analytics"); - - if (_model == null || _model.LatestPostViews == null) - { - LoadData(dataset, TestData()); - } - else - { - _hideList = _model.DisplayType == AnalyticsListType.Graph; - _hideGraph = _model.DisplayType == AnalyticsListType.List; - - LoadData(dataset, _model.LatestPostViews); - } - - if (_config.Data.Datasets.Count > 0) - { - _config.Data.Datasets.Clear(); - } - - _config.Data.Datasets.Add(dataset); - } - - protected BarConfig GetConfig() - { - return new BarConfig - { - Options = new BarOptions - { - Responsive = true, - Legend = new Legend - { - Position = Position.Top - } - } - }; - } - - protected void LoadData(IDataset dataset, BarChartModel model) - { - _visits = new List(); - var labels = model.Labels.ToList(); - var values = model.Data.ToList(); - - _config.Data.Labels.Clear(); - - for (int i = 0; i < labels.Count; i++) - { - _config.Data.Labels.Add(labels[i]); - dataset.Add(values[i]); - - _visits.Add(new PostVisit { Name = labels[i], Value = values[i] }); - } - _visits = _visits.OrderByDescending(v => v.Value).ToList(); - } - - protected async Task ToggleAnalyticsView(bool isGraph) - { - _hideGraph = await Task.FromResult(!isGraph); - _hideList = isGraph; - - int typeId = isGraph ? 2 : 1; - - Toaster.Toast(await _http.PutAsJsonAsync($"api/analytics/displayType/{typeId}", typeId)); - } - - protected async Task DateOptionSelect(int id) - { - Blog blog = await _http.GetFromJsonAsync("api/blog"); - blog.AnalyticsPeriod = id; - _model.DisplayPeriod = (AnalyticsPeriod)id; - Toaster.Toast(await _http.PutAsJsonAsync("api/blog", blog)); - await Load(); - } - - protected BarChartModel TestData() - { - return new BarChartModel() - { - Labels = new List() { "Post One", "Post Two", "Post Three", "Post Four", "Post Five", "Post Six" }, - Data = new List() { 12036, 15350, 9457, 11104, 7938, 8136 } - }; - } - - protected string PeriodLabel() - { - if (_model == null) - return ""; - return _model.DisplayPeriod.ToString(); - } + protected ToasterComponent Toaster; + private BarConfig _config; + protected List _visits; + + protected bool _hideGraph = false; + protected bool _hideList = true; + + protected List _dateOptions; + protected AnalyticsModel _model; + + protected override async Task OnInitializedAsync() + { + _config = GetConfig(); + + _dateOptions = new List(); + _dateOptions.Add(new OptionItem { Id = 1, Title = _localizer["today"] }); + _dateOptions.Add(new OptionItem { Id = 2, Title = _localizer["yesterday"] }); + _dateOptions.Add(new OptionItem { Id = 3, Title = _localizer["7-days"] }); + _dateOptions.Add(new OptionItem { Id = 4, Title = _localizer["30-days"] }); + _dateOptions.Add(new OptionItem { Id = 5, Title = _localizer["90-days"] }); + + await Load(); + } + + protected async Task Load() + { + IDataset dataset = new BarDataset() + { + Label = "Latest Post Views", + BackgroundColor = ColorUtil.FromDrawingColor(Color.FromArgb(98, 42, 255)), + BorderWidth = 0 + }; + + _model = await _http.GetFromJsonAsync("api/analytics"); + + if (_model == null || _model.LatestPostViews == null) + { + LoadData(dataset, TestData()); + } + else + { + _hideList = _model.DisplayType == AnalyticsListType.Graph; + _hideGraph = _model.DisplayType == AnalyticsListType.List; + + LoadData(dataset, _model.LatestPostViews); + } + + if (_config.Data.Datasets.Count > 0) + { + _config.Data.Datasets.Clear(); + } + + _config.Data.Datasets.Add(dataset); + } + + protected BarConfig GetConfig() + { + return new BarConfig + { + Options = new BarOptions + { + Responsive = true, + Legend = new Legend + { + Position = Position.Top + } + } + }; + } + + protected void LoadData(IDataset dataset, BarChartModel model) + { + _visits = new List(); + var labels = model.Labels.ToList(); + var values = model.Data.ToList(); + + _config.Data.Labels.Clear(); + + for (int i = 0; i < labels.Count; i++) + { + _config.Data.Labels.Add(labels[i]); + dataset.Add(values[i]); + + _visits.Add(new PostVisit { Name = labels[i], Value = values[i] }); + } + _visits = _visits.OrderByDescending(v => v.Value).ToList(); + } + + protected async Task ToggleAnalyticsView(bool isGraph) + { + _hideGraph = await Task.FromResult(!isGraph); + _hideList = isGraph; + + int typeId = isGraph ? 2 : 1; + + Toaster.Toast(await _http.PutAsJsonAsync($"api/analytics/displayType/{typeId}", typeId)); + } + + protected async Task DateOptionSelect(int id) + { + Blog blog = await _http.GetFromJsonAsync("api/blog"); + blog.AnalyticsPeriod = id; + _model.DisplayPeriod = (AnalyticsPeriod)id; + Toaster.Toast(await _http.PutAsJsonAsync("api/blog", blog)); + await Load(); + } + + protected BarChartModel TestData() + { + return new BarChartModel() + { + Labels = new List() { "Post One", "Post Two", "Post Three", "Post Four", "Post Five", "Post Six" }, + Data = new List() { 12036, 15350, 9457, 11104, 7938, 8136 } + }; + } + + protected string PeriodLabel() + { + if (_model == null) + return ""; + return _model.DisplayPeriod.ToString(); + } } diff --git a/src/Blogifier.Admin/Components/Dashboard/TotalsComponent.razor b/src/Blogifier.Admin/Components/Dashboard/TotalsComponent.razor index 60357d067..95192830c 100644 --- a/src/Blogifier.Admin/Components/Dashboard/TotalsComponent.razor +++ b/src/Blogifier.Admin/Components/Dashboard/TotalsComponent.razor @@ -3,33 +3,33 @@ @if (_model != null) { -
- - - + } @code { - protected AnalyticsModel _model; + protected AnalyticsModel _model; - protected override async Task OnInitializedAsync() - { - _model = await _http.GetFromJsonAsync("api/analytics"); - } + protected override async Task OnInitializedAsync() + { + _model = await _http.GetFromJsonAsync("api/analytics"); + } } diff --git a/src/Blogifier.Admin/Components/EditorComponent.razor b/src/Blogifier.Admin/Components/EditorComponent.razor index 6d584a00f..6d347736b 100644 --- a/src/Blogifier.Admin/Components/EditorComponent.razor +++ b/src/Blogifier.Admin/Components/EditorComponent.razor @@ -1,20 +1,20 @@ -@inject IJSRuntime JSRuntime +@inject IJSRuntime JSRuntime @inject IStringLocalizer _localizer
- +
@code { - [Parameter] public string Content { get; set; } - [Parameter] public string Toolbar { get; set; } + [Parameter] public string Content { get; set; } + [Parameter] public string Toolbar { get; set; } - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) { - if (firstRender) - { - await JSRuntime.InvokeAsync("commonJsFunctions.loadEditor", Toolbar); - } - - await JSRuntime.InvokeAsync("commonJsFunctions.setEditorValue", Content ); + await JSRuntime.InvokeAsync("commonJsFunctions.loadEditor", Toolbar); } + + await JSRuntime.InvokeAsync("commonJsFunctions.setEditorValue", Content); + } } diff --git a/src/Blogifier.Admin/Components/ToasterComponent.razor b/src/Blogifier.Admin/Components/ToasterComponent.razor index 38531443c..cd59056b3 100644 --- a/src/Blogifier.Admin/Components/ToasterComponent.razor +++ b/src/Blogifier.Admin/Components/ToasterComponent.razor @@ -2,11 +2,11 @@ @inject IStringLocalizer _localizer @code { - public void Toast(HttpResponseMessage msg) - { - if (msg.IsSuccessStatusCode) - _toaster.Success(_localizer["completed"]); - else - _toaster.Error(_localizer["generic-error"]); - } -} \ No newline at end of file + public void Toast(HttpResponseMessage msg) + { + if (msg.IsSuccessStatusCode) + _toaster.Success(_localizer["completed"]); + else + _toaster.Error(_localizer["generic-error"]); + } +} diff --git a/src/Blogifier.Admin/Pages/Account/Login.razor b/src/Blogifier.Admin/Pages/Account/Login.razor index 8dbeaea02..f8fe78611 100644 --- a/src/Blogifier.Admin/Pages/Account/Login.razor +++ b/src/Blogifier.Admin/Pages/Account/Login.razor @@ -8,23 +8,25 @@ @if (showError) { - + } @if (model != null) { - - -
- - - -
-
- - - -
- -
+ + +
+ + + +
+
+ + + +
+ +
} diff --git a/src/Blogifier.Admin/Pages/Account/Login.razor.cs b/src/Blogifier.Admin/Pages/Account/Login.razor.cs index 4d9ae4c2b..32ba0b3db 100644 --- a/src/Blogifier.Admin/Pages/Account/Login.razor.cs +++ b/src/Blogifier.Admin/Pages/Account/Login.razor.cs @@ -7,43 +7,43 @@ namespace Blogifier.Admin.Pages.Account { - public partial class Login - { - public bool showError = false; - public LoginModel model = new LoginModel { Email = "", Password = "" }; - - public async Task LoginUser() - { - var returnUrl = "admin/"; - var uri = _navigationManager.ToAbsoluteUri(_navigationManager.Uri); - - if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("returnUrl", out var param)) - returnUrl = param.First(); - - if(!IsLocalUrl(returnUrl)) - returnUrl = "admin/"; - - var result = await Http.PostAsJsonAsync("api/author/login", model); - - if (result.IsSuccessStatusCode) - { - showError = false; - _navigationManager.NavigateTo(returnUrl, true); - } - else - { - showError = true; - StateHasChanged(); - } - } - - static bool IsLocalUrl(string url) - { - if(url.Contains("//")) - return false; - - Uri result; - return Uri.TryCreate(url, UriKind.Relative, out result); - } - } + public partial class Login + { + public bool showError = false; + public LoginModel model = new LoginModel { Email = "", Password = "" }; + + public async Task LoginUser() + { + var returnUrl = "admin/"; + var uri = _navigationManager.ToAbsoluteUri(_navigationManager.Uri); + + if (QueryHelpers.ParseQuery(uri.Query).TryGetValue("returnUrl", out var param)) + returnUrl = param.First(); + + if (!IsLocalUrl(returnUrl)) + returnUrl = "admin/"; + + var result = await Http.PostAsJsonAsync("api/author/login", model); + + if (result.IsSuccessStatusCode) + { + showError = false; + _navigationManager.NavigateTo(returnUrl, true); + } + else + { + showError = true; + StateHasChanged(); + } + } + + static bool IsLocalUrl(string url) + { + if (url.Contains("//")) + return false; + + Uri result; + return Uri.TryCreate(url, UriKind.Relative, out result); + } + } } diff --git a/src/Blogifier.Admin/Pages/Account/LogoutView.razor b/src/Blogifier.Admin/Pages/Account/LogoutView.razor index dac9fbd41..c6cdb0ea1 100644 --- a/src/Blogifier.Admin/Pages/Account/LogoutView.razor +++ b/src/Blogifier.Admin/Pages/Account/LogoutView.razor @@ -5,10 +5,10 @@ -@code{ - protected override async Task OnInitializedAsync() - { - await _http.GetFromJsonAsync("api/author/logout"); - _navigationManager.NavigateTo(_navigationManager.BaseUri, true); - } +@code { + protected override async Task OnInitializedAsync() + { + await _http.GetFromJsonAsync("api/author/logout"); + _navigationManager.NavigateTo(_navigationManager.BaseUri, true); + } } diff --git a/src/Blogifier.Admin/Pages/Account/PasswordView.razor b/src/Blogifier.Admin/Pages/Account/PasswordView.razor index dc54a2f2c..dafae0d27 100644 --- a/src/Blogifier.Admin/Pages/Account/PasswordView.razor +++ b/src/Blogifier.Admin/Pages/Account/PasswordView.razor @@ -8,51 +8,51 @@

@_localizer["change-password"]

- @if (Author != null) - { - - -
- - - -
-
- - - -
-
- -
-
- } + @if (Author != null) + { + + +
+ + + +
+
+ + + +
+
+ +
+
+ }
@code { - protected Author Author { get; set; } - protected RegisterModel Model { get; set; } - protected ToasterComponent Toaster; + protected Author Author { get; set; } + protected RegisterModel Model { get; set; } + protected ToasterComponent Toaster; - protected override async Task OnInitializedAsync() - { - await Load(); - } + protected override async Task OnInitializedAsync() + { + await Load(); + } - protected async Task Load() - { - Author = await _http.GetFromJsonAsync("api/author/getcurrent"); - Model = new RegisterModel - { - Name = Author.DisplayName, - Email = Author.Email - }; - } + protected async Task Load() + { + Author = await _http.GetFromJsonAsync("api/author/getcurrent"); + Model = new RegisterModel + { + Name = Author.DisplayName, + Email = Author.Email + }; + } - protected async Task SavePassword() - { - Toaster.Toast(await _http.PutAsJsonAsync("api/author/changepassword", Model)); - await Load(); - } + protected async Task SavePassword() + { + Toaster.Toast(await _http.PutAsJsonAsync("api/author/changepassword", Model)); + await Load(); + } } diff --git a/src/Blogifier.Admin/Pages/Account/ProfileView.razor b/src/Blogifier.Admin/Pages/Account/ProfileView.razor index 5e8ee2454..176c6367e 100644 --- a/src/Blogifier.Admin/Pages/Account/ProfileView.razor +++ b/src/Blogifier.Admin/Pages/Account/ProfileView.razor @@ -8,80 +8,87 @@

@_localizer["edit-profile"]

- @if (Author != null) - { - - -
- -
- @Author.DisplayName - - -
-
-
- - - -
-
- - - -
-
- - - -
- +
- -
} @code { - protected Author Author { get; set; } - protected Post Post { get; set; } - protected ToasterComponent Toaster; - protected CategoriesComponent Categories; - protected string Confirm { get; set; } - protected string PostTile - { - get - { - return _stateprovider.PostType == PostType.Post ? _localizer["post-title"] : _localizer["page-title"]; - } - } - - [Parameter] + protected Author Author { get; set; } + protected Post Post { get; set; } + protected ToasterComponent Toaster; + protected CategoriesComponent Categories; + protected string Confirm { get; set; } + protected string PostTile + { + get + { + return _stateprovider.PostType == PostType.Post ? _localizer["post-title"] : _localizer["page-title"]; + } + } + + [Parameter] #nullable enable - public string? Slug { get; set; } + public string? Slug { get; set; } #nullable disable - protected override async Task OnInitializedAsync() - { - Author = await _http.GetFromJsonAsync("api/author/getcurrent"); - Confirm = _localizer["confirm-delete"]; - _stateprovider.OnChange += StateHasChanged; - await Load(); - } - - protected async Task Load() - { - Post = NewPost(); - - if (!string.IsNullOrEmpty(Slug)) - { - Post = await _http.GetFromJsonAsync($"api/post/byslug/{Slug}"); - var headTitle = _localizer["edit"] + " - " + Post.Title; - await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTitle", headTitle); - } - } - - protected async Task SavePost(PostAction postAction) - { - Post.Content = await _jsruntime.InvokeAsync("commonJsFunctions.getEditorValue", ""); - Post.PostType = _stateprovider.PostType; - - Post.Cover = await _jsruntime.InvokeAsync("commonJsFunctions.getSrcValue", "postCover"); - Post.Cover = Post.Cover.Replace(_navigation.BaseUri, ""); - - if(string.IsNullOrEmpty(Post.Cover)) - Post.Cover = Constants.DefaultCover; - - if (string.IsNullOrEmpty(Post.Description)) - Post.Description = Post.Title; - - if (string.IsNullOrEmpty(Post.Title) || string.IsNullOrEmpty(Post.Content)) - { - _toaster.Error(_localizer["title-content-required"]); - return; - } - - if (postAction == PostAction.Publish) - Post.Published = DateTime.UtcNow; - - if (postAction == PostAction.Unpublish) - Post.Published = DateTime.MinValue; - - if (Post.Id == 0) - { - Post.Slug = await _http.GetStringAsync($"api/post/getslug/{Post.Title}"); - HttpResponseMessage result = await _http.PostAsJsonAsync($"api/post/add", Post); - - if(result.IsSuccessStatusCode) - { - Post = await _http.GetFromJsonAsync($"api/post/byslug/{Post.Slug}"); - await _http.PutAsJsonAsync>($"api/category/{Post.Id}", Categories.PostCategories); - - if (postAction == PostAction.Publish) - { - await _http.GetFromJsonAsync($"api/newsletter/send/{Post.Id}"); - } - - _navigation.NavigateTo($"/admin/editor/{Post.Slug}"); - await Load(); - } - - Toaster.Toast(result); - } - else - { - Toaster.Toast(await _http.PutAsJsonAsync($"api/post/update", Post)); - - await _http.PutAsJsonAsync>($"api/category/{Post.Id}", Categories.PostCategories); - - await _http.GetFromJsonAsync($"api/newsletter/send/{Post.Id}"); - await Load(); - } - } - - protected async Task Save() - { - await SavePost(PostAction.Save); - } - - protected async Task Publish() - { - await SavePost(PostAction.Publish); - } - - protected async Task Unpublish() - { - await SavePost(PostAction.Unpublish); - } - - protected async Task Remove(int id) - { - if (await _jsruntime.InvokeAsync("confirm", Confirm)) - { - Toaster.Toast(await _http.DeleteAsync($"api/post/{id}")); - _navigation.NavigateTo($"admin"); - } - } - - protected async Task ResetCover() - { - Post.Cover = Constants.DefaultCover; - await Save(); - } - - protected async Task RemoveCover() - { - Post.Cover = null; - await Save(); - } - - protected Post NewPost() - { - return new Post - { - Id = 0, - Title = "", - Description = "", - Content = "", - AuthorId = Author.Id, - PostType = PostType.Post, - Cover = Constants.DefaultCover - }; - } - - public void Dispose() - { - _stateprovider.OnChange -= StateHasChanged; - } + protected override async Task OnInitializedAsync() + { + Author = await _http.GetFromJsonAsync("api/author/getcurrent"); + Confirm = _localizer["confirm-delete"]; + _stateprovider.OnChange += StateHasChanged; + await Load(); + } + + protected async Task Load() + { + Post = NewPost(); + + if (!string.IsNullOrEmpty(Slug)) + { + Post = await _http.GetFromJsonAsync($"api/post/byslug/{Slug}"); + var headTitle = _localizer["edit"] + " - " + Post.Title; + await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTitle", headTitle); + } + } + + protected async Task SavePost(PostAction postAction) + { + Post.Content = await _jsruntime.InvokeAsync("commonJsFunctions.getEditorValue", ""); + Post.PostType = _stateprovider.PostType; + + Post.Cover = await _jsruntime.InvokeAsync("commonJsFunctions.getSrcValue", "postCover"); + Post.Cover = Post.Cover.Replace(_navigation.BaseUri, ""); + + if (string.IsNullOrEmpty(Post.Cover)) + Post.Cover = Constants.DefaultCover; + + if (string.IsNullOrEmpty(Post.Description)) + Post.Description = Post.Title; + + if (string.IsNullOrEmpty(Post.Title) || string.IsNullOrEmpty(Post.Content)) + { + _toaster.Error(_localizer["title-content-required"]); + return; + } + + if (postAction == PostAction.Publish) + Post.PublishedAt = DateTime.UtcNow; + + if (postAction == PostAction.Unpublish) + Post.PublishedAt = DateTime.MinValue; + + if (Post.Id == 0) + { + Post.Slug = await _http.GetStringAsync($"api/post/getslug/{Post.Title}"); + HttpResponseMessage result = await _http.PostAsJsonAsync($"api/post/add", Post); + + if (result.IsSuccessStatusCode) + { + Post = await _http.GetFromJsonAsync($"api/post/byslug/{Post.Slug}"); + await _http.PutAsJsonAsync>($"api/category/{Post.Id}", Categories.PostCategories); + + if (postAction == PostAction.Publish) + { + await _http.GetFromJsonAsync($"api/newsletter/send/{Post.Id}"); + } + + _navigation.NavigateTo($"/admin/editor/{Post.Slug}"); + await Load(); + } + + Toaster.Toast(result); + } + else + { + Toaster.Toast(await _http.PutAsJsonAsync($"api/post/update", Post)); + + await _http.PutAsJsonAsync>($"api/category/{Post.Id}", Categories.PostCategories); + + await _http.GetFromJsonAsync($"api/newsletter/send/{Post.Id}"); + await Load(); + } + } + + protected async Task Save() + { + await SavePost(PostAction.Save); + } + + protected async Task Publish() + { + await SavePost(PostAction.Publish); + } + + protected async Task Unpublish() + { + await SavePost(PostAction.Unpublish); + } + + protected async Task Remove(int id) + { + if (await _jsruntime.InvokeAsync("confirm", Confirm)) + { + Toaster.Toast(await _http.DeleteAsync($"api/post/{id}")); + _navigation.NavigateTo($"admin"); + } + } + + protected async Task ResetCover() + { + Post.Cover = Constants.DefaultCover; + await Save(); + } + + protected async Task RemoveCover() + { + Post.Cover = null; + await Save(); + } + + protected Post NewPost() + { + return new Post + { + Id = 0, + Title = "", + Description = "", + Content = "", + AuthorId = Author.Id, + PostType = PostType.Post, + Cover = Constants.DefaultCover + }; + } + + public void Dispose() + { + _stateprovider.OnChange -= StateHasChanged; + } } diff --git a/src/Blogifier.Admin/Pages/Blog/ImportView.razor b/src/Blogifier.Admin/Pages/Blog/ImportView.razor index 27622d2bb..d6a271d07 100644 --- a/src/Blogifier.Admin/Pages/Blog/ImportView.razor +++ b/src/Blogifier.Admin/Pages/Blog/ImportView.razor @@ -3,132 +3,129 @@ @inject HttpClient _http @inject IStringLocalizer _localizer @inject IToaster _toaster -@inject IJSRuntime JSRuntime +@inject IJSRuntime _jsRuntime -@if (Posts == null) +@if (Import == null) { -

@_localizer["import"]

-
- - -
- - - -
-
- - - -
-
- -
-
-
+

@_localizer["import"]

+
+ + +
+ + + +
+
+ +
+
+
} -else { -
-
@Posts.Count @_localizer["import-message-found"].
-
- -
-
    - @foreach (var post in Posts) - { -
  • - - - -
    @post.Title
    -
    @post.Published.ToFriendlyShortDateString()
    -
  • - } -
-
@StatusMsg
-
- - -
+else +{ +
+
@Import.Posts.Count @_localizer["import-message-found"].
+
+
+
    + @foreach (var post in Import.Posts) + { +
  • + + + +
    @post.Title
    +
    @post.Published.ToFriendlyShortDateString()
    +
  • + } +
+
@StatusMsg
+
+ + +
+
} @code { - protected ImportModel ImportModel { get; set; } - protected List Posts { get; set; } - protected string StatusMsg { get; set; } - protected string StatusMsgCss { get; set; } + protected ImportRssRequest Model { get; set; } = default!; + protected ImportDto? Import { get; set; } + protected string StatusMsg { get; set; } = default!; + protected string StatusMsgCss { get; set; } = default!; - protected override void OnInitialized() - { - Load(); - } + protected override void OnInitialized() + { + OnLoad(); + } - protected void Load() - { - ImportModel = new ImportModel { FeedUrl = "" }; - Posts = null; - StatusMsg = ""; - StatusMsgCss = "d-none"; - } + protected void OnLoad() + { + Model = new ImportRssRequest { FeedUrl = "" }; + Import = null; + StatusMsg = ""; + } - protected async Task GetEntries() - { - Posts = await _http.GetFromJsonAsync>($"api/syndication/getitems?feedUrl={ImportModel.FeedUrl}&baseUrl={ImportModel.BaseUrl}"); - } - - protected async Task Import() - { - int successCnt = 0; - int failedCnt = 0; + protected async Task OnAnalysis() + { + Import = await _http.GetFromJsonAsync($"api/import/rss?feedUrl={Model.FeedUrl}"); + } - foreach (var post in Posts) - { - if (!post.Selected) - continue; + protected async Task OnImport() + { + int successCnt = 0; + int failedCnt = 0; - var result = await _http.PostAsJsonAsync("api/syndication/import", post); - if (result.IsSuccessStatusCode) - { - await JSRuntime.InvokeAsync("commonJsFunctions.replaceElement", post.Slug, true); - successCnt++; - } - else - { - await JSRuntime.InvokeAsync("commonJsFunctions.replaceElement", post.Slug, false); - failedCnt++; - } - } + foreach (var post in Import!.Posts!) + { + if (!post.Selected) + continue; - if (failedCnt == 0 && successCnt > 0) { - StatusMsg = $"Imported {successCnt} posts."; - StatusMsgCss = $"alert-success"; - } - else { - StatusMsg = $"Imported {successCnt} posts out of {successCnt + failedCnt}. Please check logs for errors."; - StatusMsgCss = $"alert-warning"; - } + var result = await _http.PostAsJsonAsync("api/import/write", post); + if (result.IsSuccessStatusCode) + { + await _jsRuntime.InvokeAsync("commonJsFunctions.replaceElement", post.Slug, true); + successCnt++; + } + else + { + await _jsRuntime.InvokeAsync("commonJsFunctions.replaceElement", post.Slug, false); + failedCnt++; + } } - public void CheckAll(object checkValue) + if (failedCnt == 0 && successCnt > 0) { - bool isChecked = (bool)checkValue; - Posts.ForEach(p => p.Selected = isChecked); - StateHasChanged(); + StatusMsg = $"Imported {successCnt} posts."; + StatusMsgCss = $"alert-success"; } + else + { + StatusMsg = $"Imported {successCnt} posts out of {successCnt + failedCnt}. Please check logs for errors."; + StatusMsgCss = $"alert-warning"; + } + } + + public void CheckAll(object? checkValue) + { + bool isChecked = checkValue != null ? (bool)checkValue : false; + Import!.Posts.ForEach(p => p.Selected = isChecked); + StateHasChanged(); + } } diff --git a/src/Blogifier.Admin/Pages/Blog/PostsView.razor b/src/Blogifier.Admin/Pages/Blog/PostsView.razor index ea7167d36..a2044d4e6 100644 --- a/src/Blogifier.Admin/Pages/Blog/PostsView.razor +++ b/src/Blogifier.Admin/Pages/Blog/PostsView.razor @@ -86,8 +86,8 @@ @{ - string pubDate = post.Published > DateTime.MinValue ? post.Published.ToFriendlyShortDateString() : @_localizer["draft"]; - string pubStatus = post.Published > DateTime.MinValue ? "published" : ""; + string pubDate = post.PublishedAt > DateTime.MinValue ? post.PublishedAt.ToFriendlyShortDateString() : @_localizer["draft"]; + string pubStatus = post.PublishedAt > DateTime.MinValue ? "published" : ""; string featured = post.IsFeatured ? "featured" : ""; } @@ -285,7 +285,7 @@ else public async Task Publish(Post post) { - Toast(await _http.PutAsJsonAsync($"api/post/publish/{post.Id}", (post.Published == DateTime.MinValue))); + Toast(await _http.PutAsJsonAsync($"api/post/publish/{post.Id}", (post.PublishedAt == DateTime.MinValue))); await Load(); } @@ -295,14 +295,6 @@ else await Load(); } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTooltip"); - } - } - protected void Toast(HttpResponseMessage msg) { if (msg.IsSuccessStatusCode) diff --git a/src/Blogifier.Admin/Pages/Drive/DriveView.razor b/src/Blogifier.Admin/Pages/Drive/DriveView.razor index ba6418438..19c3199e8 100644 --- a/src/Blogifier.Admin/Pages/Drive/DriveView.razor +++ b/src/Blogifier.Admin/Pages/Drive/DriveView.razor @@ -1,14 +1,18 @@ @page "/admin/drive/" @inject IStringLocalizer _localizer + + +
-

@_localizer["Drive"]

-
-

Drive is a file storage that you are able to upload and manage your files and use throughout the website.

-
- @_localizer["under-development"]. - - @_localizer["more-info"] - -
+

@_localizer["drive"]

+
+

@_localizer["drive-describe"]

+
+ @_localizer["under-development"]. + + @_localizer["more-info"] +
+
diff --git a/src/Blogifier.Admin/Pages/Newsletter/NewsletterView.razor b/src/Blogifier.Admin/Pages/Newsletter/NewsletterView.razor index 6ba85d109..00f676460 100644 --- a/src/Blogifier.Admin/Pages/Newsletter/NewsletterView.razor +++ b/src/Blogifier.Admin/Pages/Newsletter/NewsletterView.razor @@ -8,76 +8,84 @@

@_localizer["newsletters"]

- @if (Newsletters == null || Newsletters.Count == 0) - { -

@_localizer["not-found"]

- } - else - { -
    - @foreach (var newsletter in Newsletters) + @if (Newsletters == null || Newsletters.Count == 0) + { +

    @_localizer["not-found"]

    + } + else + { +
      + @foreach (var newsletter in Newsletters) + { +
    • + @{ + string title = $"{newsletter.Post.Title}"; + string pubDate = newsletter.CreatedAt.ToLocalTime().ToFriendlyDateTimeString(); + string pubStatus = newsletter.Success ? "published" : ""; + } + @title + - @pubDate - -
    • + + + } -
    - } + + @pubDate + + + } +
+ }
@code { - protected ToasterComponent Toaster; - protected List Newsletters; + protected ToasterComponent Toaster; + protected List Newsletters; - protected override async Task OnInitializedAsync() - { - await Load(); - } + protected override async Task OnInitializedAsync() + { + await Load(); + } - protected async Task Load() - { - Newsletters = await _http.GetFromJsonAsync>($"api/newsletter/newsletters"); - } + protected async Task Load() + { + Newsletters = await _http.GetFromJsonAsync>($"api/newsletter/newsletters"); + } - protected async Task RemoveNewsletter(int id) - { - Toaster.Toast(await _http.DeleteAsync($"api/newsletter/remove/{id}")); - await Load(); - } + protected async Task RemoveNewsletter(int id) + { + Toaster.Toast(await _http.DeleteAsync($"api/newsletter/remove/{id}")); + await Load(); + } - protected async Task Resend(int postId) - { - bool success = await _http.GetFromJsonAsync($"api/newsletter/send/{postId}"); - if (success) - _toaster.Success(_localizer["completed"]); - else - _toaster.Error(_localizer["generic-error"]); - await Load(); - } + protected async Task Resend(int postId) + { + bool success = await _http.GetFromJsonAsync($"api/newsletter/send/{postId}"); + if (success) + _toaster.Success(_localizer["completed"]); + else + _toaster.Error(_localizer["generic-error"]); + await Load(); + } } diff --git a/src/Blogifier.Admin/Pages/Newsletter/SubscribersView.razor b/src/Blogifier.Admin/Pages/Newsletter/SubscribersView.razor index 8d099f9b4..1317cd2d3 100644 --- a/src/Blogifier.Admin/Pages/Newsletter/SubscribersView.razor +++ b/src/Blogifier.Admin/Pages/Newsletter/SubscribersView.razor @@ -20,7 +20,7 @@
  • @{ string title = $"{subscriber.Email} / {subscriber.Country} / {subscriber.Region} / {subscriber.Ip}"; - string pubDate = subscriber.DateCreated.ToFriendlyDateTimeString(); + string pubDate = subscriber.CreatedAt.ToFriendlyDateTimeString(); } @title @pubDate diff --git a/src/Blogifier.Admin/Pages/Pages/Editor.razor b/src/Blogifier.Admin/Pages/Pages/Editor.razor index d39932483..d187af92f 100644 --- a/src/Blogifier.Admin/Pages/Pages/Editor.razor +++ b/src/Blogifier.Admin/Pages/Pages/Editor.razor @@ -22,7 +22,7 @@ } else { - if (Post.Published > DateTime.MinValue) + if (Post.PublishedAt > DateTime.MinValue) { @@ -136,10 +136,10 @@ } if (postAction == PostAction.Publish) - Post.Published = DateTime.UtcNow; + Post.PublishedAt = DateTime.UtcNow; if (postAction == PostAction.Unpublish) - Post.Published = DateTime.MinValue; + Post.PublishedAt = DateTime.MinValue; if (Post.Id == 0) { diff --git a/src/Blogifier.Admin/Pages/Pages/PagesView.razor b/src/Blogifier.Admin/Pages/Pages/PagesView.razor index 56017d03b..06837ab1e 100644 --- a/src/Blogifier.Admin/Pages/Pages/PagesView.razor +++ b/src/Blogifier.Admin/Pages/Pages/PagesView.razor @@ -80,8 +80,8 @@ @{ - string pubDate = post.Published > DateTime.MinValue ? post.Published.ToFriendlyShortDateString() : _localizer["draft"]; - string pubStatus = post.Published > DateTime.MinValue ? "published" : ""; + string pubDate = post.PublishedAt > DateTime.MinValue ? post.PublishedAt.ToFriendlyShortDateString() : _localizer["draft"]; + string pubStatus = post.PublishedAt > DateTime.MinValue ? "published" : ""; }
  • @@ -245,18 +245,10 @@ public async Task Publish(Post post) { - Toast(await _http.PutAsJsonAsync($"api/post/publish/{post.Id}", (post.Published == DateTime.MinValue))); + Toast(await _http.PutAsJsonAsync($"api/post/publish/{post.Id}", (post.PublishedAt == DateTime.MinValue))); await Load(); } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await _jsruntime.InvokeVoidAsync("commonJsFunctions.setTooltip"); - } - } - protected void Toast(HttpResponseMessage msg) { if (msg.IsSuccessStatusCode) diff --git a/src/Blogifier.Admin/Program.cs b/src/Blogifier.Admin/Program.cs index b9475dc20..44da9dd2d 100644 --- a/src/Blogifier.Admin/Program.cs +++ b/src/Blogifier.Admin/Program.cs @@ -1,39 +1,30 @@ +using Blogifier.Admin; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.DependencyInjection; using Sotsera.Blazor.Toaster.Core.Models; using System; using System.Net.Http; -using System.Threading.Tasks; -namespace Blogifier.Admin -{ - public class Program - { - public static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("#app"); +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); - builder.Services.AddLocalization(); +builder.Services.AddLocalization(); - builder.Services.AddOptions(); - builder.Services.AddAuthorizationCore(); +builder.Services.AddOptions(); +builder.Services.AddAuthorizationCore(); - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - builder.Services.AddScoped(); +builder.Services.AddScoped(); - builder.Services.AddToaster(config => - { - config.PositionClass = Defaults.Classes.Position.BottomRight; - config.PreventDuplicates = true; - config.NewestOnTop = false; - }); +builder.Services.AddToaster(config => +{ + config.PositionClass = Defaults.Classes.Position.BottomRight; + config.PreventDuplicates = true; + config.NewestOnTop = false; +}); - builder.Services.AddSingleton(); +builder.Services.AddSingleton(); - await builder.Build().RunAsync(); - } - } -} +await builder.Build().RunAsync(); diff --git a/src/Blogifier.Admin/Shared/BlogLayout.razor b/src/Blogifier.Admin/Shared/BlogLayout.razor index 49922326a..1307ea2c8 100644 --- a/src/Blogifier.Admin/Shared/BlogLayout.razor +++ b/src/Blogifier.Admin/Shared/BlogLayout.razor @@ -11,7 +11,7 @@ Page Title - +
  • - @if (author.IsAdmin) { - + } -
  • +