├── .assets ├── movie-001.gif ├── nupkg-icon.docx ├── nupkg-icon.png ├── social-media.png └── youtube-video-thumbnail.png ├── .editorconfig ├── .github └── workflows │ ├── gh-pages.yml │ └── unit-tests.yml ├── .gitignore ├── LICENSE ├── MD2RazorGenerator.MSBuild.Task ├── GenerateRazorClassDeclarationsFromMarkdown.cs └── MD2RazorGenerator.MSBuild.Task.csproj ├── MD2RazorGenerator.Shared ├── Internals │ ├── FrontMatter.cs │ ├── GlobalOptions.cs │ ├── Imports.cs │ ├── ImportsCollectionExtensions.cs │ ├── PathUtils.cs │ └── ValidIdentifier.cs ├── MD2Razor.cs ├── MD2RazorGenerator.Shared.projitems └── MD2RazorGenerator.Shared.shproj ├── MD2RazorGenerator.Test ├── BuildTests.cs ├── Fixtures │ ├── 2025+Jan │ │ └── 001$Sapporo.md │ ├── Attributes │ │ ├── BarAttribute.cs │ │ ├── FizzBuzz │ │ │ └── FizzBuzzAttribute.cs │ │ └── FooAttribute.cs │ ├── ComponentTypes │ │ ├── MainLayoutA.cs │ │ ├── MainLayoutB.cs │ │ ├── MarkdownComponentBaseA.cs │ │ ├── MarkdownComponentBaseB.cs │ │ └── OtherLayouts │ │ │ └── MainLayoutC.cs │ ├── Sample01.NoYamlFrontMatter.md │ ├── Sample02.WithYamlFrontMatter.Scalar.md │ ├── Sample03.WithYamlFrontMatter.Sequence.md │ ├── Sample04.HyperLinks.md │ ├── Sample05.Table.md │ ├── Sample06.DependsImports.md │ ├── SampleProjects │ │ └── Project01 │ │ │ ├── App.razor │ │ │ ├── Components │ │ │ └── CustomComponentBase.cs │ │ │ ├── Pages │ │ │ ├── Error.cshtml │ │ │ ├── Error.cshtml.cs │ │ │ └── _Host.cshtml │ │ │ ├── Program.cs │ │ │ ├── Project01.csproj │ │ │ ├── Project01.sln │ │ │ ├── Properties │ │ │ └── launchSettings.json │ │ │ ├── Welcome.md │ │ │ ├── _Imports.razor │ │ │ ├── appsettings.Development.json │ │ │ ├── appsettings.json │ │ │ └── wwwroot │ │ │ ├── css │ │ │ └── blazor-ui.css │ │ │ └── favicon.ico │ └── _Imports.razor ├── GeneratedPageTests.cs ├── ImportsTests.cs ├── MD2RazorGenerator.Test.csproj ├── MD2RazorTests.cs └── _Imports.razor ├── MD2RazorGenerator.sln ├── MD2RazorGenerator ├── MD2RazorGenerator.cs ├── MD2RazorGenerator.csproj ├── PackageContents │ └── build │ │ ├── MD2RazorGenerateRazorClassDeclarations.targets │ │ ├── MD2RazorGenerator.targets │ │ └── MD2RazorPopulateMarkdownFiles.targets └── Properties │ └── launchSettings.json ├── README.md ├── RELEASE-NOTES.txt ├── SampleSites └── BlazorWasmApp1 │ ├── App.razor │ ├── BlazorWasmApp1.csproj │ ├── BlazorWasmApp1.sln │ ├── Components │ └── WeatherForecastTable.md │ ├── Layout │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ └── NavMenu.razor.css │ ├── Pages │ ├── Code.md │ ├── Code.md.cs │ ├── Home.md │ └── Weather.razor │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── _Imports.razor │ └── wwwroot │ ├── css │ └── app.css │ ├── favicon.png │ ├── icon-192.png │ ├── index.html │ └── lib │ └── bootstrap │ └── dist │ ├── css │ ├── bootstrap-grid.css │ ├── bootstrap-grid.css.map │ ├── bootstrap-grid.min.css │ ├── bootstrap-grid.min.css.map │ ├── bootstrap-grid.rtl.css │ ├── bootstrap-grid.rtl.css.map │ ├── bootstrap-grid.rtl.min.css │ ├── bootstrap-grid.rtl.min.css.map │ ├── bootstrap-reboot.css │ ├── bootstrap-reboot.css.map │ ├── bootstrap-reboot.min.css │ ├── bootstrap-reboot.min.css.map │ ├── bootstrap-reboot.rtl.css │ ├── bootstrap-reboot.rtl.css.map │ ├── bootstrap-reboot.rtl.min.css │ ├── bootstrap-reboot.rtl.min.css.map │ ├── bootstrap-utilities.css │ ├── bootstrap-utilities.css.map │ ├── bootstrap-utilities.min.css │ ├── bootstrap-utilities.min.css.map │ ├── bootstrap-utilities.rtl.css │ ├── bootstrap-utilities.rtl.css.map │ ├── bootstrap-utilities.rtl.min.css │ ├── bootstrap-utilities.rtl.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ ├── bootstrap.rtl.css │ ├── bootstrap.rtl.css.map │ ├── bootstrap.rtl.min.css │ └── bootstrap.rtl.min.css.map │ └── js │ ├── bootstrap.bundle.js │ ├── bootstrap.bundle.js.map │ ├── bootstrap.bundle.min.js │ ├── bootstrap.bundle.min.js.map │ ├── bootstrap.esm.js │ ├── bootstrap.esm.js.map │ ├── bootstrap.esm.min.js │ ├── bootstrap.esm.min.js.map │ ├── bootstrap.js │ ├── bootstrap.js.map │ ├── bootstrap.min.js │ └── bootstrap.min.js.map ├── THIRDPARTYNOTICES.txt ├── VersionInfo.props ├── _dist └── .gitkeep └── nuget.config /.assets/movie-001.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/.assets/movie-001.gif -------------------------------------------------------------------------------- /.assets/nupkg-icon.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/.assets/nupkg-icon.docx -------------------------------------------------------------------------------- /.assets/nupkg-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/.assets/nupkg-icon.png -------------------------------------------------------------------------------- /.assets/social-media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/.assets/social-media.png -------------------------------------------------------------------------------- /.assets/youtube-video-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/.assets/youtube-video-thumbnail.png -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.csproj] 2 | indent_style = space 3 | indent_size = 2 4 | [*.{cs,vb}] 5 | #### Naming styles #### 6 | 7 | # Naming rules 8 | 9 | dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion 10 | dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface 11 | dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i 12 | 13 | dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion 14 | dotnet_naming_rule.types_should_be_pascal_case.symbols = types 15 | dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case 16 | 17 | dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion 18 | dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members 19 | dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case 20 | 21 | # Symbol specifications 22 | 23 | dotnet_naming_symbols.interface.applicable_kinds = interface 24 | dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 25 | dotnet_naming_symbols.interface.required_modifiers = 26 | 27 | dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum 28 | dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 29 | dotnet_naming_symbols.types.required_modifiers = 30 | 31 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method 32 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected 33 | dotnet_naming_symbols.non_field_members.required_modifiers = 34 | 35 | # Naming styles 36 | 37 | dotnet_naming_style.begins_with_i.required_prefix = I 38 | dotnet_naming_style.begins_with_i.required_suffix = 39 | dotnet_naming_style.begins_with_i.word_separator = 40 | dotnet_naming_style.begins_with_i.capitalization = pascal_case 41 | 42 | dotnet_naming_style.pascal_case.required_prefix = 43 | dotnet_naming_style.pascal_case.required_suffix = 44 | dotnet_naming_style.pascal_case.word_separator = 45 | dotnet_naming_style.pascal_case.capitalization = pascal_case 46 | 47 | dotnet_naming_style.pascal_case.required_prefix = 48 | dotnet_naming_style.pascal_case.required_suffix = 49 | dotnet_naming_style.pascal_case.word_separator = 50 | dotnet_naming_style.pascal_case.capitalization = pascal_case 51 | dotnet_style_operator_placement_when_wrapping = beginning_of_line 52 | tab_width = 4 53 | indent_size = 4 54 | end_of_line = crlf 55 | dotnet_style_coalesce_expression = true:suggestion 56 | dotnet_style_null_propagation = true:suggestion 57 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion 58 | dotnet_style_prefer_auto_properties = true:silent 59 | dotnet_style_object_initializer = true:suggestion 60 | dotnet_style_collection_initializer = true:suggestion 61 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion 62 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent 63 | dotnet_style_prefer_conditional_expression_over_return = true:silent 64 | dotnet_style_explicit_tuple_names = true:suggestion 65 | dotnet_style_prefer_inferred_tuple_names = true:suggestion 66 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion 67 | dotnet_style_prefer_compound_assignment = true:suggestion 68 | dotnet_style_prefer_simplified_interpolation = true:suggestion 69 | dotnet_style_namespace_match_folder = true:suggestion 70 | dotnet_style_readonly_field = true:suggestion 71 | dotnet_style_qualification_for_field = true:warning 72 | dotnet_style_qualification_for_property = true:warning 73 | dotnet_style_qualification_for_method = true:warning 74 | dotnet_style_qualification_for_event = true:warning 75 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent 76 | dotnet_style_predefined_type_for_member_access = true:silent 77 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent 78 | dotnet_style_allow_multiple_blank_lines_experimental = true:silent 79 | dotnet_style_allow_statement_immediately_after_block_experimental = true:silent 80 | dotnet_code_quality_unused_parameters = all:suggestion 81 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent 82 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent 83 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent 84 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent 85 | 86 | [*.cs] 87 | csharp_indent_labels = one_less_than_current 88 | csharp_space_around_binary_operators = before_and_after 89 | csharp_using_directive_placement = outside_namespace:silent 90 | csharp_prefer_simple_using_statement = true:suggestion 91 | csharp_prefer_braces = true:silent 92 | csharp_style_namespace_declarations = file_scoped:suggestion 93 | csharp_style_prefer_method_group_conversion = true:silent 94 | csharp_style_expression_bodied_methods = false:silent 95 | csharp_style_expression_bodied_constructors = false:silent 96 | csharp_style_expression_bodied_operators = false:silent 97 | csharp_style_expression_bodied_properties = true:silent 98 | csharp_style_expression_bodied_indexers = true:silent 99 | csharp_style_expression_bodied_accessors = true:silent 100 | csharp_style_expression_bodied_lambdas = true:silent 101 | csharp_style_expression_bodied_local_functions = false:silent 102 | csharp_style_throw_expression = true:suggestion 103 | csharp_style_prefer_null_check_over_type_check = true:suggestion 104 | csharp_prefer_simple_default_expression = true:suggestion 105 | csharp_style_prefer_local_over_anonymous_function = true:suggestion 106 | csharp_style_prefer_index_operator = true:suggestion 107 | csharp_style_prefer_range_operator = true:suggestion 108 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion 109 | csharp_style_prefer_tuple_swap = true:suggestion 110 | csharp_style_inlined_variable_declaration = true:suggestion 111 | csharp_style_deconstructed_variable_declaration = true:suggestion 112 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion 113 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent 114 | csharp_prefer_static_local_function = true:suggestion 115 | csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent 116 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent 117 | csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent 118 | csharp_style_conditional_delegate_call = true:suggestion 119 | csharp_style_prefer_parameter_null_checking = true:suggestion 120 | csharp_style_prefer_switch_expression = true:suggestion 121 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion 122 | csharp_style_prefer_pattern_matching = true:silent 123 | csharp_style_prefer_not_pattern = true:suggestion 124 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion 125 | csharp_style_prefer_extended_property_pattern = true:suggestion 126 | csharp_style_var_for_built_in_types = true:warning 127 | csharp_style_var_when_type_is_apparent = true:warning 128 | csharp_style_var_elsewhere = true:warning -------------------------------------------------------------------------------- /.github/workflows/gh-pages.yml: -------------------------------------------------------------------------------- 1 | name: github pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | deploy: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # Checkout the code 13 | - uses: actions/checkout@v4 14 | 15 | # Install .NET SDK 16 | - name: Setup .NET SDK 17 | uses: actions/setup-dotnet@v4 18 | with: 19 | dotnet-version: 9.0.x 20 | - name: Install .NET WebAssembly Tools 21 | run: dotnet workload install wasm-tools 22 | 23 | # Publish the site 24 | - name: Publish 25 | run: dotnet publish SampleSites/BlazorWasmApp1/BlazorWasmApp1.csproj -c:Release -o:public -p:GHPages=true 26 | 27 | # Deploy the site 28 | - name: Deploy 29 | uses: peaceiris/actions-gh-pages@v4 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | publish_dir: public/wwwroot 33 | force_orphan: true 34 | -------------------------------------------------------------------------------- /.github/workflows/unit-tests.yml: -------------------------------------------------------------------------------- 1 | name: unit tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | perform-unit-tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | # Checkout the code 13 | - uses: actions/checkout@v4 14 | 15 | # Install .NET SDK 16 | - name: Setup .NET SDK 17 | uses: actions/setup-dotnet@v4 18 | with: 19 | dotnet-version: | 20 | 9.0.x 21 | 22 | # Perform unit tests 23 | - name: Perform unit tests 24 | run: dotnet test MD2RazorGenerator.Test -l "console;verbosity=normal" -v:q --nologo -tl:off 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | MD2RazorGenerator/PackageContents/tools/ 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | *.binlog 12 | *.bak 13 | 14 | # User-specific files (MonoDevelop/Xamarin Studio) 15 | *.userprefs 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Dd]ebugPublic/ 20 | [Rr]elease/ 21 | [Rr]eleases/ 22 | x64/ 23 | x86/ 24 | bld/ 25 | [Bb]in/ 26 | [Oo]bj/ 27 | [Ll]og/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | # Uncomment if you have tasks that create the project's static files in wwwroot 32 | #wwwroot/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | # DNX 48 | project.lock.json 49 | project.fragment.lock.json 50 | artifacts/ 51 | 52 | *_i.c 53 | *_p.c 54 | *_i.h 55 | *.ilk 56 | *.meta 57 | *.obj 58 | *.pch 59 | *.pdb 60 | *.pgc 61 | *.pgd 62 | *.rsp 63 | *.sbr 64 | *.tlb 65 | *.tli 66 | *.tlh 67 | *.tmp 68 | *.tmp_proj 69 | *.log 70 | *.vspscc 71 | *.vssscc 72 | .builds 73 | *.pidb 74 | *.svclog 75 | *.scc 76 | 77 | # Chutzpah Test files 78 | _Chutzpah* 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opendb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | *.VC.db 89 | *.VC.VC.opendb 90 | 91 | # Visual Studio profiler 92 | *.psess 93 | *.vsp 94 | *.vspx 95 | *.sap 96 | 97 | # TFS 2012 Local Workspace 98 | $tf/ 99 | 100 | # Guidance Automation Toolkit 101 | *.gpState 102 | 103 | # ReSharper is a .NET coding add-in 104 | _ReSharper*/ 105 | *.[Rr]e[Ss]harper 106 | *.DotSettings.user 107 | 108 | # JustCode is a .NET coding add-in 109 | .JustCode 110 | 111 | # TeamCity is a build add-in 112 | _TeamCity* 113 | 114 | # DotCover is a Code Coverage Tool 115 | *.dotCover 116 | 117 | # NCrunch 118 | _NCrunch_* 119 | .*crunch*.local.xml 120 | nCrunchTemp_* 121 | 122 | # MightyMoose 123 | *.mm.* 124 | AutoTest.Net/ 125 | 126 | # Web workbench (sass) 127 | .sass-cache/ 128 | 129 | # Installshield output folder 130 | [Ee]xpress/ 131 | 132 | # DocProject is a documentation generator add-in 133 | DocProject/buildhelp/ 134 | DocProject/Help/*.HxT 135 | DocProject/Help/*.HxC 136 | DocProject/Help/*.hhc 137 | DocProject/Help/*.hhk 138 | DocProject/Help/*.hhp 139 | DocProject/Help/Html2 140 | DocProject/Help/html 141 | 142 | # Click-Once directory 143 | publish/ 144 | 145 | # Publish Web Output 146 | *.[Pp]ublish.xml 147 | *.azurePubxml 148 | # TODO: Comment the next line if you want to checkin your web deploy settings 149 | # but database connection strings (with potential passwords) will be unencrypted 150 | #*.pubxml 151 | *.publishproj 152 | 153 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 154 | # checkin your Azure Web App publish settings, but sensitive information contained 155 | # in these scripts will be unencrypted 156 | PublishScripts/ 157 | 158 | # NuGet Packages 159 | *.nupkg 160 | # The packages folder can be ignored because of Package Restore 161 | **/packages/* 162 | # except build/, which is used as an MSBuild target. 163 | !**/packages/build/ 164 | # Uncomment if necessary however generally it will be regenerated when needed 165 | #!**/packages/repositories.config 166 | # NuGet v3's project.json files produces more ignoreable files 167 | *.nuget.props 168 | *.nuget.targets 169 | 170 | # Microsoft Azure Build Output 171 | csx/ 172 | *.build.csdef 173 | 174 | # Microsoft Azure Emulator 175 | ecf/ 176 | rcf/ 177 | 178 | # Windows Store app package directories and files 179 | AppPackages/ 180 | BundleArtifacts/ 181 | Package.StoreAssociation.xml 182 | _pkginfo.txt 183 | 184 | # Visual Studio cache files 185 | # files ending in .cache can be ignored 186 | *.[Cc]ache 187 | # but keep track of directories ending in .cache 188 | !*.[Cc]ache/ 189 | 190 | # Others 191 | ClientBin/ 192 | ~$* 193 | *~ 194 | *.dbmdl 195 | *.dbproj.schemaview 196 | *.jfm 197 | *.pfx 198 | *.publishsettings 199 | node_modules/ 200 | orleans.codegen.cs 201 | 202 | # Since there are multiple workflows, uncomment next line to ignore bower_components 203 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 204 | #bower_components/ 205 | 206 | # RIA/Silverlight projects 207 | Generated_Code/ 208 | 209 | # Backup & report files from converting an old project file 210 | # to a newer Visual Studio version. Backup files are not needed, 211 | # because we have git ;-) 212 | _UpgradeReport_Files/ 213 | Backup*/ 214 | UpgradeLog*.XML 215 | UpgradeLog*.htm 216 | 217 | # SQL Server files 218 | *.mdf 219 | *.ldf 220 | 221 | # Business Intelligence projects 222 | *.rdl.data 223 | *.bim.layout 224 | *.bim_*.settings 225 | 226 | # Microsoft Fakes 227 | FakesAssemblies/ 228 | 229 | # GhostDoc plugin setting file 230 | *.GhostDoc.xml 231 | 232 | # Node.js Tools for Visual Studio 233 | .ntvs_analysis.dat 234 | 235 | # Visual Studio 6 build log 236 | *.plg 237 | 238 | # Visual Studio 6 workspace options file 239 | *.opt 240 | 241 | # Visual Studio LightSwitch build output 242 | **/*.HTMLClient/GeneratedArtifacts 243 | **/*.DesktopClient/GeneratedArtifacts 244 | **/*.DesktopClient/ModelManifest.xml 245 | **/*.Server/GeneratedArtifacts 246 | **/*.Server/ModelManifest.xml 247 | _Pvt_Extensions 248 | 249 | # Paket dependency manager 250 | .paket/paket.exe 251 | paket-files/ 252 | 253 | # FAKE - F# Make 254 | .fake/ 255 | 256 | # JetBrains Rider 257 | .idea/ 258 | *.sln.iml 259 | 260 | # CodeRush 261 | .cr/ 262 | 263 | # Python Tools for Visual Studio (PTVS) 264 | __pycache__/ 265 | *.pyc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /MD2RazorGenerator.MSBuild.Task/GenerateRazorClassDeclarationsFromMarkdown.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Microsoft.Build.Framework; 3 | using Microsoft.Build.Utilities; 4 | using Toolbelt.Blazor.MD2Razor.Internals; 5 | 6 | namespace Toolbelt.Blazor.MD2Razor; 7 | 8 | public class GenerateRazorClassDeclarationsFromMarkdown : Microsoft.Build.Utilities.Task 9 | { 10 | [Required] 11 | public ITaskItem[]? MarkdownFiles { get; set; } 12 | 13 | [Required] 14 | public ITaskItem[]? ImportsFiles { get; set; } 15 | 16 | [Required] 17 | public string? OutputDir { get; set; } 18 | 19 | public string? RootNamespace { get; set; } 20 | 21 | public string? ProjectDir { get; set; } 22 | 23 | public string? DefaultBaseClass { get; set; } 24 | 25 | [Output] 26 | public ITaskItem[] Generated { get; set; } = []; 27 | 28 | public override bool Execute() 29 | { 30 | if (!Directory.Exists(this.OutputDir)) Directory.CreateDirectory(this.OutputDir); 31 | 32 | // Load the imports files and read their content 33 | var importsCollection = (this.ImportsFiles ?? []) 34 | .Select(imports => new Imports(path: imports.ItemSpec, text: File.ReadAllText(imports.ItemSpec))) 35 | .ToArray(); 36 | 37 | var globalOptions = new GlobalOptions( 38 | rootNamespace: this.RootNamespace ?? "", 39 | projectDir: this.ProjectDir ?? "", 40 | defaultBaseClass: this.DefaultBaseClass ?? "" 41 | ); 42 | 43 | // Check if the global options have changed since the last generation 44 | // If they have, we need to regenerate all files. 45 | var globalOptionsHasBeenChanged = this.CheckGlobalOptionsHasBeenChanged(globalOptions); 46 | 47 | var md2razor = new MD2Razor(); 48 | var generatedFilesPath = new List(); 49 | Parallel.ForEach(this.MarkdownFiles ?? [], markdownFile => 50 | { 51 | var markdownFilePath = markdownFile.ItemSpec; 52 | 53 | // Determine the output path for the generated file 54 | var hintName = MD2Razor.TransformToDotSeparatedPath(markdownFilePath, this.ProjectDir ?? "") + ".g.cs"; 55 | var outputPath = Path.Combine(this.OutputDir ?? "", hintName); 56 | generatedFilesPath.Add(outputPath); 57 | 58 | // Skip if the generated file is up to date as long as the global options have not changed 59 | if (!globalOptionsHasBeenChanged) 60 | { 61 | var markdownTimestamp = File.GetLastWriteTime(markdownFilePath); 62 | var outputTimestamp = File.Exists(outputPath) ? File.GetLastWriteTime(outputPath) : DateTime.MinValue; 63 | if (markdownTimestamp <= outputTimestamp) return; 64 | } 65 | 66 | // Generate the Razor component class declaration code from the Markdown file 67 | var markdownText = File.ReadAllText(markdownFilePath); 68 | var applicableImports = importsCollection.GetApplicableImports(markdownFilePath); 69 | var generatedCode = md2razor.GenerateCode(markdownFilePath, markdownText, applicableImports, globalOptions, declarationOnly: true); 70 | File.WriteAllText(outputPath, generatedCode); 71 | }); 72 | 73 | // Delete old generated files that are not in the current generation 74 | var existingFilesPath = Directory.GetFiles(this.OutputDir ?? "", "*.g.cs", SearchOption.TopDirectoryOnly); 75 | foreach (var file in existingFilesPath.Where(p1 => !generatedFilesPath.Any(p2 => p1.Equals(p2, StringComparison.InvariantCultureIgnoreCase)))) 76 | { 77 | File.Delete(file); 78 | } 79 | 80 | this.Generated = generatedFilesPath.Select(p => new TaskItem(p)).ToArray(); 81 | return true; 82 | } 83 | 84 | /// 85 | /// Determines whether the global options have changed compared to the previously saved state. 86 | /// 87 | /// This method checks for changes in the global options by comparing the provided options with 88 | /// the options stored in a file located in the output directory. If the file does not exist or the options have 89 | /// changed, the method updates the file with the current options. 90 | /// The current global options to compare against the previously saved options. 91 | /// if the global options have changed since the last saved state; otherwise, . 93 | private bool CheckGlobalOptionsHasBeenChanged(GlobalOptions globalOptions) 94 | { 95 | var globalOptionsHasBeenChanged = true; 96 | var prevGlobalOptionsPath = Path.Combine(this.OutputDir, ".globaloptions"); 97 | var prevGlobalOptionsExists = File.Exists(prevGlobalOptionsPath); 98 | 99 | // If the previous global options file exists, read it and compare with the current global options 100 | if (prevGlobalOptionsExists) 101 | { 102 | var entries = new Dictionary(); 103 | foreach (var globalOptionLine in File.ReadLines(prevGlobalOptionsPath, Encoding.UTF8)) 104 | { 105 | var parts = globalOptionLine.Split(['='], 2); 106 | if (parts.Length != 2) continue; 107 | entries[parts[0].Trim()] = parts[1].Trim(); 108 | } 109 | string getValue(string key) => entries.TryGetValue(key, out var value) ? value : string.Empty; 110 | var prevGlobalOptions = new GlobalOptions( 111 | rootNamespace: getValue(nameof(this.RootNamespace)), 112 | projectDir: getValue(nameof(this.ProjectDir)), 113 | defaultBaseClass: getValue(nameof(this.DefaultBaseClass)) 114 | ); 115 | globalOptionsHasBeenChanged = !globalOptions.Equals(prevGlobalOptions); 116 | } 117 | 118 | // If the previous global options file does not exist or the options have changed, write the new global options to the file 119 | if (!prevGlobalOptionsExists || globalOptionsHasBeenChanged) 120 | { 121 | File.WriteAllLines(prevGlobalOptionsPath, 122 | [ 123 | $"{nameof(this.RootNamespace)}={globalOptions.RootNamespace}", 124 | $"{nameof(this.ProjectDir)}={globalOptions.ProjectDir}", 125 | $"{nameof(this.DefaultBaseClass)}={globalOptions.DefaultBaseClass}" 126 | ], Encoding.UTF8); 127 | } 128 | 129 | return globalOptionsHasBeenChanged; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /MD2RazorGenerator.MSBuild.Task/MD2RazorGenerator.MSBuild.Task.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 12 6 | enable 7 | enable 8 | $(WarningsAsErrors);nullable 9 | $(NoWarn);CS1591 10 | true 11 | Toolbelt.Blazor.MD2Razor 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/Internals/FrontMatter.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace Toolbelt.Blazor.MD2Razor.Internals; 3 | 4 | internal class FrontMatter 5 | { 6 | public IEnumerable Pages { get; } 7 | public IEnumerable Usings { get; } 8 | public string? Namespace { get; } 9 | public IEnumerable Attributes { get; } 10 | public string? Layout { get; } 11 | public string? Inherit { get; } 12 | public string? PageTitle { get; } 13 | 14 | public FrontMatter() : this([], [], [], [], [], [], []) 15 | { 16 | } 17 | 18 | public FrontMatter(IEnumerable pages, IEnumerable usings, IEnumerable namespaces, IEnumerable attributes, IEnumerable layouts, IEnumerable inherits, IEnumerable pageTitles) 19 | { 20 | this.Pages = pages; 21 | this.Usings = usings; 22 | this.Namespace = namespaces.FirstOrDefault(); 23 | this.Attributes = attributes; 24 | this.Layout = layouts.FirstOrDefault(); 25 | this.Inherit = inherits.FirstOrDefault(); 26 | this.PageTitle = pageTitles.FirstOrDefault(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/Internals/GlobalOptions.cs: -------------------------------------------------------------------------------- 1 | namespace Toolbelt.Blazor.MD2Razor.Internals; 2 | 3 | internal class GlobalOptions : IEquatable 4 | { 5 | public string RootNamespace { get; } 6 | 7 | public string ProjectDir { get; } 8 | 9 | public string DefaultBaseClass { get; } 10 | 11 | public GlobalOptions(string rootNamespace, string projectDir, string defaultBaseClass) 12 | { 13 | this.RootNamespace = rootNamespace; 14 | this.ProjectDir = projectDir; 15 | this.DefaultBaseClass = string.IsNullOrWhiteSpace(defaultBaseClass) ? "global::Microsoft.AspNetCore.Components.ComponentBase" : defaultBaseClass; 16 | } 17 | 18 | public override bool Equals(object? obj) => this.Equals(obj as GlobalOptions); 19 | 20 | public bool Equals(GlobalOptions? other) 21 | { 22 | return other is not null && 23 | this.RootNamespace == other.RootNamespace && 24 | this.ProjectDir == other.ProjectDir && 25 | this.DefaultBaseClass == other.DefaultBaseClass; 26 | } 27 | 28 | public override int GetHashCode() 29 | { 30 | var hashCode = -847805451; 31 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.RootNamespace); 32 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.ProjectDir); 33 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.DefaultBaseClass); 34 | return hashCode; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/Internals/Imports.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Toolbelt.Blazor.MD2Razor.Internals; 4 | 5 | /// 6 | /// Represents a collection of using statements from a specific file path. 7 | /// Used to track and extract @using directives from "_Import.razor"" files. 8 | /// 9 | internal class Imports : IEquatable 10 | { 11 | /// 12 | /// Gets the file path of an individual "_Imports.razor" file. 13 | /// 14 | public string Path { get; } 15 | 16 | /// 17 | /// The raw text content of the "_Imports.razor" file. 18 | /// 19 | private string _text; 20 | 21 | /// 22 | /// Cached collection of using namespace statements extracted from the text. 23 | /// 24 | private IEnumerable? _usings = null!; 25 | 26 | /// 27 | /// Initializes a new instance of the class. 28 | /// 29 | /// The file path of the "_Imports.razor" file that contains the "@using" directives. 30 | /// The text content of the "_Imports.razor" file, or null if not available. 31 | public Imports(string path, string? text) 32 | { 33 | this.Path = path; 34 | this._text = text ?? ""; 35 | } 36 | 37 | /// 38 | /// Extracts and returns the namespaces come from @using directives in the text content. 39 | /// 40 | /// A collection of namespace strings from @using directives. 41 | public IEnumerable GetUsings() 42 | { 43 | return this._usings ??= this._text.Split(['\n'], StringSplitOptions.RemoveEmptyEntries) 44 | .Select(line => Regex.Match(line, @"^[ \t]*@using[ \t]+(?(static[ \t]+)?[^ \t;]+).*$")) 45 | .Where(match => match.Success) 46 | .Select(match => match.Groups["namespace"].Value.Trim()) 47 | .ToArray(); 48 | } 49 | 50 | /// 51 | /// Determines whether the specified object is equal to the current object. 52 | /// 53 | /// The object to compare with the current object. 54 | /// true if the specified object is equal to the current object; otherwise, false. 55 | public override bool Equals(object? obj) 56 | { 57 | return this.Equals(obj as Imports); 58 | } 59 | 60 | /// 61 | /// Determines whether the specified Imports instance is equal to the current instance. 62 | /// 63 | /// The Imports instance to compare with the current instance. 64 | /// true if the specified instance is equal to the current instance; otherwise, false. 65 | public bool Equals(Imports? other) 66 | { 67 | return other is not null && 68 | this.Path == other.Path && 69 | this._text == other._text; 70 | } 71 | 72 | /// 73 | /// Serves as the default hash function. 74 | /// 75 | /// A hash code for the current object. 76 | public override int GetHashCode() 77 | { 78 | var hashCode = 833777843; 79 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this.Path); 80 | hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(this._text); 81 | return hashCode; 82 | } 83 | } -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/Internals/ImportsCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace Toolbelt.Blazor.MD2Razor.Internals; 2 | 3 | /// 4 | /// Provides extension methods for collections of objects. 5 | /// 6 | internal static class ImportsCollectionExtensions 7 | { 8 | /// 9 | /// Filters a collection of imports to find those that apply to a given file path based on directory hierarchy. 10 | /// 11 | /// The collection of to filter. 12 | /// The file path to find applicable imports for. 13 | /// A filtered collection of that are applicable to the specified path. 14 | /// 15 | /// An import is considered "applicable" if the directory containing the specified path 16 | /// starts with the directory containing the import's path. This matches Razor's cascading 17 | /// import behavior where _Imports.razor files apply to the directory they're in and all subdirectories. 18 | /// 19 | /// Thrown when the provided path is invalid. 20 | public static IEnumerable GetApplicableImports(this IEnumerable importsCollection, string path) 21 | { 22 | var baseDir = Path.GetDirectoryName(PathUtils.NormalizePath(path)) ?? throw new ArgumentException($"Invalid path: {path}"); 23 | return importsCollection 24 | .Where(i => baseDir.StartsWith(Path.GetDirectoryName(PathUtils.NormalizePath(i.Path)), StringComparison.InvariantCultureIgnoreCase)); 25 | } 26 | } -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/Internals/PathUtils.cs: -------------------------------------------------------------------------------- 1 | namespace Toolbelt.Blazor.MD2Razor.Internals; 2 | 3 | internal static class PathUtils 4 | { 5 | /// 6 | /// Normalizes a file path by replacing backslashes or forward slashes with current platform's directory separator. 7 | /// 8 | /// The file path to normalize. 9 | public static string NormalizePath(string path) => NormalizePath(path, Path.DirectorySeparatorChar); 10 | 11 | /// 12 | /// Normalizes a file path by replacing backslashes or forward slashes with the specified directory separator. 13 | /// 14 | /// The file path to normalize. 15 | /// The character to use as the path separator. 16 | /// The normalized file path. 17 | public static string NormalizePath(string path, char pathSeparator) 18 | { 19 | return string.Join(pathSeparator.ToString(), path.Split('\\', '/', ':')) 20 | .TrimEnd(pathSeparator); 21 | } 22 | } -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/Internals/ValidIdentifier.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Toolbelt.Blazor.MD2Razor.Internals; 4 | 5 | internal static class ValidIdentifier 6 | { 7 | private static readonly HashSet _reservedWords = new([ 8 | // https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/ 9 | "add", "field", "nint", "scoped", 10 | "allows", "file", "not", "select", 11 | "alias", "from", "notnull", "set", 12 | "and", "get", "nuint", "unmanaged", 13 | "ascending", "global", "on", "value", 14 | "args", "group", "or", "var", 15 | "async", "init", "orderby", "when", 16 | "await", "into", "partial", "where", 17 | "by", "join", "record", "with", 18 | "descending", "let", "remove", "yield", 19 | "dynamic", "managed", "required", 20 | "equals", "nameof", 21 | "extension", 22 | ]); 23 | 24 | public static string Create(string? name) 25 | { 26 | if (name is null) throw new ArgumentNullException(nameof(name)); 27 | if (name.Length == 0) return name; 28 | 29 | if (Regex.IsMatch(name[0].ToString(), @"^\p{Nd}")) name = "_" + name; 30 | name = Regex.Replace(name, @"^[^\p{L}\p{Nl}_]", "_"); 31 | name = Regex.Replace(name, @"[^\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Nl}\p{Pc}_]", "_"); 32 | 33 | if (_reservedWords.Contains(name)) name += "_"; 34 | 35 | return name; 36 | } 37 | } -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/MD2Razor.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.RegularExpressions; 3 | using Markdig; 4 | using Markdig.Extensions.Yaml; 5 | using Markdig.Renderers.Html; 6 | using Markdig.Syntax; 7 | using Markdig.Syntax.Inlines; 8 | using Toolbelt.Blazor.MD2Razor.Internals; 9 | using YamlDotNet.Helpers; 10 | using YamlDotNet.RepresentationModel; 11 | 12 | namespace Toolbelt.Blazor.MD2Razor; 13 | 14 | public class MD2Razor 15 | { 16 | /// 17 | /// The Markdown pipeline used for parsing Markdown content. 18 | /// 19 | private readonly MarkdownPipeline _markdownPipeline; 20 | 21 | public MD2Razor() 22 | { 23 | // Initialize the Markdown pipeline with YAML front matter support and other extensions. 24 | this._markdownPipeline = new MarkdownPipelineBuilder() 25 | .UseYamlFrontMatter() 26 | .UseEmojiAndSmiley() 27 | .UseAdvancedExtensions() 28 | .Build(); 29 | } 30 | 31 | /// 32 | /// Generates C# source code from the provided Markdown content. 33 | /// 34 | /// The file path of the Markdown file. 35 | /// The content of the Markdown file. 36 | /// A collection of additional using directives to include in the generated code. 37 | /// Global options such as the root namespace and project directory. 38 | /// If set to true, only the class declaration and its metadata (e.g., namespace and using directives) will be generated without the implementation of the BuildRenderTree method. 39 | /// The generated source code. 40 | internal string GenerateCode(string markdownPath, string markdownText, IEnumerable imports, GlobalOptions globalOptions, bool declarationOnly = false) 41 | { 42 | // Parse the Markdown content into a Markdown document using the configured pipeline. 43 | var markdownDoc = Markdown.Parse(markdownText, this._markdownPipeline); 44 | var frontMatter = ParseFrontMatter(markdownDoc); 45 | 46 | // Begin generating the source code for the Razor component. 47 | var sourceBuilder = new StringBuilder(); 48 | 49 | // Generate the using directives based on the front matter. 50 | var usings = imports 51 | .GetApplicableImports(markdownPath) 52 | .SelectMany(i => i.GetUsings()) 53 | .Concat(frontMatter.Usings) 54 | .OrderBy(ns => ns) 55 | .Distinct(); 56 | foreach (var usingItem in usings) 57 | { 58 | sourceBuilder.AppendLine($"using {usingItem};"); 59 | } 60 | if (frontMatter.Usings.Any()) sourceBuilder.AppendLine(); 61 | 62 | // Generate the namespace for the generated class. 63 | var fullNamespace = frontMatter.Namespace ?? GenerateNamespaceFromPath(markdownPath, globalOptions); 64 | if (!string.IsNullOrEmpty(fullNamespace)) 65 | { 66 | sourceBuilder.AppendLine($"namespace {fullNamespace};"); 67 | sourceBuilder.AppendLine(); 68 | } 69 | 70 | if (!declarationOnly) 71 | { 72 | // Generate the route attributes for the generated class. 73 | foreach (var page in frontMatter.Pages) 74 | { 75 | sourceBuilder.AppendLine($"[global::Microsoft.AspNetCore.Components.RouteAttribute(\"{page}\")]"); 76 | } 77 | 78 | // Generate the layout attributes for the generated class. 79 | if (!string.IsNullOrWhiteSpace(frontMatter.Layout)) 80 | { 81 | sourceBuilder.AppendLine($"[global::Microsoft.AspNetCore.Components.LayoutAttribute(typeof({frontMatter.Layout}))]"); 82 | } 83 | 84 | // Generate other attributes for the generated class. 85 | foreach (var attrib in frontMatter.Attributes) 86 | { 87 | sourceBuilder.AppendLine($"[{attrib}]"); 88 | } 89 | } 90 | 91 | // Generate the class name and base class for the generated Razor component. 92 | var className = ValidIdentifier.Create(Path.GetFileNameWithoutExtension(markdownPath)); 93 | var baseClass = frontMatter.Inherit ?? globalOptions.DefaultBaseClass; 94 | 95 | // Generate the class definition for the Razor component. 96 | var n = 0; 97 | sourceBuilder.AppendLine("#pragma warning disable CS1591"); 98 | sourceBuilder.AppendLine($"public partial class {className} : {baseClass}"); 99 | sourceBuilder.AppendLine("{"); 100 | 101 | if (!declarationOnly) 102 | { 103 | sourceBuilder.AppendLine(" protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)"); 104 | sourceBuilder.AppendLine(" {"); 105 | 106 | if (!string.IsNullOrWhiteSpace(frontMatter.PageTitle)) 107 | { 108 | sourceBuilder.AppendLine($" __builder.OpenComponent({n++});"); 109 | sourceBuilder.AppendLine($" __builder.AddAttribute({n++}, \"ChildContent\", (global::Microsoft.AspNetCore.Components.RenderFragment)((__builder2) => {{"); 110 | sourceBuilder.AppendLine($" __builder2.AddContent({n++}, @\"{frontMatter.PageTitle?.Replace("\"", "\"\"")}\");"); 111 | sourceBuilder.AppendLine($" }}));"); 112 | sourceBuilder.AppendLine($" __builder.CloseComponent();"); 113 | sourceBuilder.AppendLine(); 114 | } 115 | 116 | sourceBuilder.Append($" __builder.AddMarkupContent({n++}, @\""); 117 | 118 | // Update the Markdown document to add target="_blank" to external links. 119 | foreach (var node in markdownDoc.Descendants()) 120 | { 121 | var url = 122 | node is AutolinkInline autoLink ? autoLink.Url : 123 | node is LinkInline link ? link.Url : 124 | default; 125 | if (string.IsNullOrEmpty(url)) continue; 126 | 127 | if (Regex.IsMatch(url, "^[a-z]+://")) 128 | { 129 | var attributes = node.GetAttributes(); 130 | attributes.AddPropertyIfNotExist("target", "_blank"); 131 | } 132 | } 133 | 134 | // Convert the Markdown document to HTML using the configured pipeline. 135 | var html = markdownDoc.ToHtml(this._markdownPipeline).Replace("\"", "\"\""); 136 | sourceBuilder.Append(html); 137 | 138 | sourceBuilder.AppendLine("\");"); 139 | sourceBuilder.AppendLine(" }"); // End of BuildRenderTree method 140 | } 141 | 142 | sourceBuilder.AppendLine("}"); // End of class 143 | sourceBuilder.AppendLine("#pragma warning restore CS1591"); 144 | 145 | return sourceBuilder.ToString(); 146 | } 147 | 148 | /// 149 | /// Parses the YAML front matter from the provided Markdown document. 150 | /// 151 | /// The Markdown document to parse. 152 | /// A object containing the parsed front matter. 153 | private static FrontMatter ParseFrontMatter(MarkdownDocument markdownDoc) 154 | { 155 | var yamlBlock = markdownDoc.OfType().FirstOrDefault(); 156 | if (yamlBlock is null) return new FrontMatter(); 157 | 158 | markdownDoc.Remove(yamlBlock); 159 | 160 | var yamlText = yamlBlock.Lines.ToString().Trim('-', '\r', '\n'); 161 | using var reader = new StringReader(yamlText); 162 | var yamlStream = new YamlStream(); 163 | yamlStream.Load(reader); 164 | 165 | var yamlNodeRoot = yamlStream.Documents.FirstOrDefault()?.RootNode as YamlMappingNode; 166 | var yamlNodeMap = yamlNodeRoot?.Children; 167 | 168 | static IEnumerable getFrontMatterEntry(IOrderedDictionary? nodeMap, string key) 169 | { 170 | if (nodeMap?.TryGetValue(new YamlScalarNode(key), out var node) != true) return []; 171 | if (node is YamlSequenceNode sequence) 172 | { 173 | return sequence.Select(s => s.ToString()).ToArray(); 174 | } 175 | return [node.ToString()]; 176 | } 177 | 178 | return new FrontMatter( 179 | pages: getFrontMatterEntry(yamlNodeMap, "url"), 180 | usings: getFrontMatterEntry(yamlNodeMap, "$using"), 181 | namespaces: getFrontMatterEntry(yamlNodeMap, "$namespace"), 182 | attributes: getFrontMatterEntry(yamlNodeMap, "$attribute"), 183 | layouts: getFrontMatterEntry(yamlNodeMap, "$layout"), 184 | inherits: getFrontMatterEntry(yamlNodeMap, "$inherit"), 185 | pageTitles: getFrontMatterEntry(yamlNodeMap, "title") 186 | ); 187 | } 188 | 189 | /// 190 | /// Generates a namespace for the generated class based on the provided parameters. 191 | /// 192 | /// The file path of the Markdown file. 193 | /// Global options such as the root namespace and project directory. 194 | /// The generated namespace as a string. 195 | internal static string GenerateNamespaceFromPath(string markdownPath, GlobalOptions globalOptions) 196 | { 197 | // Get the relative path of the file from the project directory. 198 | var relativePath = 199 | !string.IsNullOrWhiteSpace(globalOptions.ProjectDir) && markdownPath.StartsWith(globalOptions.ProjectDir) ? 200 | markdownPath.Substring(globalOptions.ProjectDir.Length) : 201 | Path.GetFileName(markdownPath); 202 | 203 | // Convert the relative path to a namespace format. 204 | var namespacePath = Path.GetDirectoryName(relativePath)?.Replace("/", ".").Replace("\\", ".") ?? ""; 205 | 206 | // Sanitize and combine the root namespace and the relative path. 207 | return string.Join(".", globalOptions.RootNamespace.Split('.').Concat(namespacePath.Split('.')) 208 | .Where(s => !string.IsNullOrWhiteSpace(s)) 209 | .Select(ValidIdentifier.Create)); 210 | } 211 | 212 | /// 213 | /// Transforms a file path to a dot-separated path based on the provided base path. 214 | /// 215 | /// When the file path is "/foo/bar/baz.txt" and the base path is "/foo", the result will be "bar.baz.txt". 216 | /// A file path to transform. This can be a relative or absolute path. 217 | /// A base path to use for the transformation. This should be a directory path. 218 | /// returns a dot-separated path that represents the file's location relative to the base path. 219 | internal static string TransformToDotSeparatedPath(string path, string basePath) 220 | { 221 | if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(basePath)) return path; 222 | 223 | basePath = PathUtils.NormalizePath(basePath, '.'); 224 | path = PathUtils.NormalizePath(path, '.'); 225 | 226 | return string.Join(".", 227 | (path.StartsWith(basePath, StringComparison.InvariantCultureIgnoreCase) ? 228 | path.Substring(basePath.Length) : 229 | path).TrimStart('.').Split('.').Select(ValidIdentifier.Create)); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/MD2RazorGenerator.Shared.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | fa804ae9-bb9d-4b4b-a3db-215e8b517ffb 7 | 8 | 9 | MD2RazorGenerator.Shared 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Shared/MD2RazorGenerator.Shared.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | fa804ae9-bb9d-4b4b-a3db-215e8b517ffb 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/BuildTests.cs: -------------------------------------------------------------------------------- 1 | using Mono.Cecil; 2 | using Toolbelt; 3 | using Toolbelt.Diagnostics; 4 | 5 | namespace MD2RazorGenerator.Test; 6 | 7 | public class BuildTests 8 | { 9 | public class TestContext : IDisposable 10 | { 11 | public string TestProjectDir { get; } 12 | public WorkDirectory WorkDir { get; } 13 | public string AssemblyPath { get; } 14 | public TestContext() 15 | { 16 | this.TestProjectDir = FileIO.FindContainerDirToAncestor("MD2RazorGenerator.Test.csproj"); 17 | this.WorkDir = new WorkDirectory(baseDir: Path.Combine([this.TestProjectDir, "bin", "Debug"])); 18 | this.AssemblyPath = Path.Combine(this.WorkDir, "bin", "Debug", "net8.0", "Project01.dll"); 19 | FileIO.XcopyDir( 20 | srcDir: Path.Combine([this.TestProjectDir, "Fixtures", "SampleProjects", "Project01"]), 21 | dstDir: this.WorkDir, 22 | predicate: arg => arg.Name is not "bin" and not "obj"); 23 | } 24 | public void Dispose() => this.WorkDir.Dispose(); 25 | } 26 | 27 | [Test] 28 | public async Task Specify_BaseClass_Test() 29 | { 30 | using var testContext = new TestContext(); 31 | 32 | using var build = await XProcess.Start("dotnet", "build -p MD2RazorDefaultBaseClass=CustomComponentBase", testContext.WorkDir).WaitForExitAsync(); 33 | build.ExitCode.Is(0, build.Output); 34 | 35 | using var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath); 36 | using var module = assembly.MainModule; 37 | 38 | module.GetType("Project01.Welcome") 39 | .IsNotNull("The type 'Project01.Welcome' was not found in the assembly.") 40 | .BaseType.IsNotNull("The type 'Project01.Welcome' does not have a base type.") 41 | .FullName.Is("Project01.Components.CustomComponentBase"); 42 | } 43 | 44 | [Test] 45 | public async Task Change_BaseClass_from_Default_to_Custom_Test() 46 | { 47 | using var testContext = new TestContext(); 48 | 49 | using (var build = await XProcess.Start("dotnet", "build", testContext.WorkDir).WaitForExitAsync()) 50 | build.ExitCode.Is(0, build.Output); 51 | 52 | using (var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath)) 53 | { 54 | using var module = assembly.MainModule; 55 | module.GetType("Project01.Welcome") 56 | .IsNotNull("The type 'Project01.Welcome' was not found in the assembly.") 57 | .BaseType.IsNotNull("The type 'Project01.Welcome' does not have a base type.") 58 | .FullName.Is("Microsoft.AspNetCore.Components.ComponentBase"); 59 | } 60 | 61 | using (var build = await XProcess.Start("dotnet", "build -p MD2RazorDefaultBaseClass=CustomComponentBase", testContext.WorkDir).WaitForExitAsync()) 62 | build.ExitCode.Is(0, build.Output); 63 | 64 | using (var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath)) 65 | { 66 | using var module = assembly.MainModule; 67 | module.GetType("Project01.Welcome") 68 | .IsNotNull("The type 'Project01.Welcome' was not found in the assembly.") 69 | .BaseType.IsNotNull("The type 'Project01.Welcome' does not have a base type.") 70 | .FullName.Is("Project01.Components.CustomComponentBase"); 71 | } 72 | } 73 | 74 | [Test] 75 | public async Task Change_BaseClass_from_Custom_to_Default_Test() 76 | { 77 | using var testContext = new TestContext(); 78 | 79 | using (var build = await XProcess.Start("dotnet", "build -p MD2RazorDefaultBaseClass=CustomComponentBase", testContext.WorkDir).WaitForExitAsync()) 80 | build.ExitCode.Is(0, build.Output); 81 | 82 | using (var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath)) 83 | { 84 | using var module = assembly.MainModule; 85 | module.GetType("Project01.Welcome") 86 | .IsNotNull("The type 'Project01.Welcome' was not found in the assembly.") 87 | .BaseType.IsNotNull("The type 'Project01.Welcome' does not have a base type.") 88 | .FullName.Is("Project01.Components.CustomComponentBase"); 89 | } 90 | 91 | using (var build = await XProcess.Start("dotnet", "build", testContext.WorkDir).WaitForExitAsync()) 92 | build.ExitCode.Is(0, build.Output); 93 | 94 | using (var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath)) 95 | { 96 | using var module = assembly.MainModule; 97 | module.GetType("Project01.Welcome") 98 | .IsNotNull("The type 'Project01.Welcome' was not found in the assembly.") 99 | .BaseType.IsNotNull("The type 'Project01.Welcome' does not have a base type.") 100 | .FullName.Is("Microsoft.AspNetCore.Components.ComponentBase"); 101 | } 102 | } 103 | 104 | [Test] 105 | public async Task Clean_Test() 106 | { 107 | // Given 108 | using var testContext = new TestContext(); 109 | var generatedFilePath = Path.Combine(testContext.WorkDir, "obj", "Debug", "net8.0", "md2razor", "Welcome.md.g.cs"); 110 | 111 | using (var build = await XProcess.Start("dotnet", "build", testContext.WorkDir).WaitForExitAsync()) 112 | build.ExitCode.Is(0, build.Output); 113 | 114 | File.Exists(generatedFilePath).IsTrue("The generated file should be generated after build, but it was not."); 115 | 116 | // When 117 | using (var clean = await XProcess.Start("dotnet", "clean", testContext.WorkDir).WaitForExitAsync()) 118 | clean.ExitCode.Is(0, clean.Output); 119 | 120 | // Then 121 | File.Exists(generatedFilePath).IsFalse("The generated file should be deleted after clean, but it was not."); 122 | } 123 | 124 | [Test] 125 | public async Task Move_FileLocation_Test() 126 | { 127 | // Given 128 | using var testContext = new TestContext(); 129 | using (var build = await XProcess.Start("dotnet", "build", testContext.WorkDir).WaitForExitAsync()) 130 | build.ExitCode.Is(0, build.Output); 131 | 132 | using (var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath)) 133 | { 134 | using var module = assembly.MainModule; 135 | module.GetType("Project01.Welcome").IsNotNull("The type 'Project01.Welcome' was not found in the assembly."); 136 | module.GetType("Project01.Pages.Welcome").IsNull("The type 'Project01.Pages.Welcome' should not be found in the assembly, but it was."); 137 | } 138 | 139 | // When 140 | var oldPathOfWelcomeMd = Path.Combine(testContext.WorkDir, "Welcome.md"); 141 | var newPathOfWelcomeMd = Path.Combine(testContext.WorkDir, "Pages", "Welcome.md"); 142 | File.Move(oldPathOfWelcomeMd, newPathOfWelcomeMd); 143 | 144 | using (var build = await XProcess.Start("dotnet", "build", testContext.WorkDir).WaitForExitAsync()) 145 | build.ExitCode.Is(0, build.Output); 146 | 147 | // Then 148 | using (var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath)) 149 | { 150 | using var module = assembly.MainModule; 151 | module.GetType("Project01.Pages.Welcome").IsNotNull("The type 'Project01.Pages.Welcome' was not found in the assembly."); 152 | module.GetType("Project01.Welcome").IsNull("The type 'Project01.Welcome' should not be found in the assembly, but it was."); 153 | } 154 | } 155 | 156 | [Test] 157 | public async Task Has_Markdowns_with_SameName_in_DifferentDir_Test() 158 | { 159 | // Given 160 | using var testContext = new TestContext(); 161 | Directory.CreateDirectory(Path.Combine(testContext.WorkDir, "AnotherDir")); 162 | File.Copy( 163 | Path.Combine(testContext.WorkDir, "Welcome.md"), 164 | Path.Combine(testContext.WorkDir, "AnotherDir", "Welcome.md")); 165 | File.ReadAllText(Path.Combine(testContext.WorkDir, "AnotherDir", "Welcome.md")).Trim().Is("# Hello, World!"); 166 | 167 | // When 168 | using var build = await XProcess.Start("dotnet", "build", testContext.WorkDir).WaitForExitAsync(); 169 | build.ExitCode.Is(0, build.Output); 170 | 171 | // Then: There should be two types with the same name in different namespaces 172 | using var assembly = AssemblyDefinition.ReadAssembly(testContext.AssemblyPath); 173 | using var module = assembly.MainModule; 174 | module.GetType("Project01.Welcome").IsNotNull("The type 'Project01.Welcome' should exist in the assembly, but it was not found."); 175 | module.GetType("Project01.AnotherDir.Welcome").IsNotNull("The type 'Project01.AnotherDir.Welcome' should exist in the assembly, but it was not found."); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/2025+Jan/001$Sapporo.md: -------------------------------------------------------------------------------- 1 | # 001 2 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Attributes/BarAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MD2RazorGenerator.Test.Fixtures.Attributes; 2 | 3 | [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 4 | sealed class BarAttribute : Attribute 5 | { 6 | public string Text { get; private set; } 7 | 8 | public BarAttribute(string text) => this.Text = text; 9 | } 10 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Attributes/FizzBuzz/FizzBuzzAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MD2RazorGenerator.Test.Fixtures.Attributes.FizzBuzz; 2 | 3 | [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 4 | sealed class FizzBuzzAttribute : Attribute 5 | { 6 | public string Text { get; private set; } 7 | 8 | public FizzBuzzAttribute(string text) => this.Text = text; 9 | } 10 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Attributes/FooAttribute.cs: -------------------------------------------------------------------------------- 1 | namespace MD2RazorGenerator.Test.Fixtures.Attributes; 2 | 3 | [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] 4 | sealed class FooAttribute : Attribute 5 | { 6 | public string Text { get; private set; } 7 | 8 | public FooAttribute(string text) => this.Text = text; 9 | } 10 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/ComponentTypes/MainLayoutA.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace MD2RazorGenerator.Test.Fixtures.ComponentTypes; 5 | 6 | public class MainLayoutA : LayoutComponentBase 7 | { 8 | protected override void BuildRenderTree(RenderTreeBuilder __builder) 9 | { 10 | __builder.OpenElement(0, "main"); 11 | __builder.AddContent(1, this.Body); 12 | __builder.CloseElement(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/ComponentTypes/MainLayoutB.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace MD2RazorGenerator.Test.Fixtures.ComponentTypes; 5 | 6 | public class MainLayoutB : LayoutComponentBase 7 | { 8 | protected override void BuildRenderTree(RenderTreeBuilder __builder) 9 | { 10 | __builder.OpenElement(0, "main"); 11 | __builder.AddContent(1, this.Body); 12 | __builder.CloseElement(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/ComponentTypes/MarkdownComponentBaseA.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace MD2RazorGenerator.Test.Fixtures.ComponentTypes; 4 | 5 | public class MarkdownComponentBaseA : ComponentBase 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/ComponentTypes/MarkdownComponentBaseB.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace MD2RazorGenerator.Test.Fixtures.ComponentTypes; 4 | 5 | public class MarkdownComponentBaseB : ComponentBase 6 | { 7 | } 8 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/ComponentTypes/OtherLayouts/MainLayoutC.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | using Microsoft.AspNetCore.Components.Rendering; 3 | 4 | namespace MD2RazorGenerator.Test.Fixtures.ComponentTypes.OtherLayouts; 5 | 6 | public class MainLayoutC : LayoutComponentBase 7 | { 8 | protected override void BuildRenderTree(RenderTreeBuilder __builder) 9 | { 10 | __builder.OpenElement(0, "main"); 11 | __builder.AddContent(1, this.Body); 12 | __builder.CloseElement(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Sample01.NoYamlFrontMatter.md: -------------------------------------------------------------------------------- 1 | # Sample 01 2 | 3 | ## Section A 4 | 5 | Lorem ipsum dolor sit amet sit. 6 | 7 | ## Section B 8 | 9 | Faite aux cette demain et abondent ce que ses bonheur tes et. 10 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Sample02.WithYamlFrontMatter.Scalar.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Sample 02 3 | url: /sample02 4 | $using: MD2RazorGenerator.Test.Fixtures.Attributes 5 | $namespace: Lorem.Ipsum 6 | $attribute: Foo("Hello") 7 | $inherit: MD2RazorGenerator.Test.Fixtures.ComponentTypes.MarkdownComponentBaseA 8 | $layout: MD2RazorGenerator.Test.Fixtures.ComponentTypes.MainLayoutA 9 | --- 10 | 11 | # Sample 02 -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Sample03.WithYamlFrontMatter.Sequence.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: [Sample 03, Sample 03 A, Sample 03 B] 3 | url: [/sample03, /sample03/A, /sample03/B] 4 | $using: 5 | - MD2RazorGenerator.Test.Fixtures.Attributes 6 | - MD2RazorGenerator.Test.Fixtures.ComponentTypes 7 | - MD2RazorGenerator.Test.Fixtures.ComponentTypes.OtherLayouts 8 | $namespace: [Fizz.Buzz.FizzBuzz, Lorem.Ipsum] 9 | $attribute: [Foo("Hello"), Bar("World")] 10 | $inherit: [MarkdownComponentBaseB, MarkdownComponentBaseA] 11 | $layout: 12 | - MD2RazorGenerator.Test.Fixtures.ComponentTypes.MainLayoutB 13 | - MD2RazorGenerator.Test.Fixtures.ComponentTypes.MainLayoutA 14 | --- 15 | 16 | # Sample 03 -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Sample04.HyperLinks.md: -------------------------------------------------------------------------------- 1 | - [About Blazor](https://blazor.net/) 2 | - https://blazor.net/ 3 | - [Home](/home) 4 | - [Counter](./counter) 5 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Sample05.Table.md: -------------------------------------------------------------------------------- 1 | | Column A | Column B | Column C | 2 | |----------|----------|----------| 3 | | Lorem | Ipsum | Dolor | 4 | | Sit | Amet | Consecte | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/Sample06.DependsImports.md: -------------------------------------------------------------------------------- 1 | --- 2 | $attribute: FizzBuzz("Hello, World!") 3 | $layout: MainLayoutC 4 | --- 5 | 6 | # Sample 06 -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/App.razor: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Components/CustomComponentBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | 3 | namespace Project01.Components; 4 | 5 | /// 6 | /// A custom component base class that inherits from ComponentBase. 7 | /// 8 | public class CustomComponentBase : ComponentBase 9 | { 10 | } -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Project01.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 |

Error.

15 |

An error occurred while processing your request.

16 | 17 | @if (Model.ShowRequestId) 18 | { 19 |

20 | Request ID: @Model.RequestId 21 |

22 | } 23 | 24 |

Development Mode

25 |

26 | Swapping to the Development environment displays detailed information about the error that occurred. 27 |

28 |

29 | The Development environment shouldn't be enabled for deployed applications. 30 | It can result in displaying sensitive information from exceptions to end users. 31 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 32 | and restarting the app. 33 |

34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | 5 | namespace Project01.Pages; 6 | 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | #pragma warning disable CS1591 10 | public class ErrorModel : PageModel 11 | { 12 | public string? RequestId { get; set; } 13 | 14 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 15 | 16 | private readonly ILogger _logger; 17 | 18 | public ErrorModel(ILogger logger) 19 | { 20 | _logger = logger; 21 | } 22 | 23 | public void OnGet() 24 | { 25 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 26 | } 27 | } 28 | #pragma warning restore CS1591 29 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using Microsoft.AspNetCore.Components.Web 3 | @namespace Project01.Pages 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | An error has occurred. This application may no longer respond until reloaded. 21 | 22 | 23 | An unhandled exception has occurred. See browser dev tools for details. 24 | 25 | Reload 26 | 🗙 27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Program.cs: -------------------------------------------------------------------------------- 1 | var builder = WebApplication.CreateBuilder(args); 2 | 3 | // Add services to the container. 4 | builder.Services.AddRazorPages(); 5 | builder.Services.AddServerSideBlazor(); 6 | 7 | var app = builder.Build(); 8 | 9 | // Configure the HTTP request pipeline. 10 | if (!app.Environment.IsDevelopment()) 11 | { 12 | app.UseExceptionHandler("/Error"); 13 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 14 | app.UseHsts(); 15 | } 16 | 17 | app.UseHttpsRedirection(); 18 | 19 | app.UseStaticFiles(); 20 | 21 | app.UseRouting(); 22 | 23 | app.MapBlazorHub(); 24 | app.MapFallbackToPage("/_Host"); 25 | 26 | app.Run(); 27 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Project01.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | True 8 | $(WarningsAsErrors);RZ10012;CS8785;CS0105;CS1591 9 | 10 | 11 | 12 | 13 | Analyzer 14 | false 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Project01.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.5.2.0 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Project01", "Project01.csproj", "{EF5347D0-E409-6CC1-9D91-13E5E71B497D}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {EF5347D0-E409-6CC1-9D91-13E5E71B497D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {EF5347D0-E409-6CC1-9D91-13E5E71B497D}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {EF5347D0-E409-6CC1-9D91-13E5E71B497D}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {EF5347D0-E409-6CC1-9D91-13E5E71B497D}.Release|Any CPU.Build.0 = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(SolutionProperties) = preSolution 19 | HideSolutionNode = FALSE 20 | EndGlobalSection 21 | GlobalSection(ExtensibilityGlobals) = postSolution 22 | SolutionGuid = {1169CA4A-BB05-4EA5-8FFE-96F2A3C31E5D} 23 | EndGlobalSection 24 | EndGlobal 25 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:38988", 7 | "sslPort": 44307 8 | } 9 | }, 10 | "profiles": { 11 | "http": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "http://localhost:5002", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "https": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": true, 23 | "launchBrowser": true, 24 | "applicationUrl": "https://localhost:7277;http://localhost:5002", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | }, 29 | "IIS Express": { 30 | "commandName": "IISExpress", 31 | "launchBrowser": true, 32 | "environmentVariables": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/Welcome.md: -------------------------------------------------------------------------------- 1 | # Hello, World! -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using Project01 10 | @using Project01.Pages 11 | @using Project01.Components 12 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/wwwroot/css/blazor-ui.css: -------------------------------------------------------------------------------- 1 | #blazor-error-ui { 2 | background: #ffffe0; 3 | bottom: 0; 4 | box-shadow: 0 -1px 2px rgba(0, 0, 0, .2); 5 | display: none; 6 | left: 0; 7 | padding: .6rem 1.25rem .7rem 1.25rem; 8 | position: fixed; 9 | right: 0; 10 | z-index: 1000 11 | } 12 | 13 | #blazor-error-ui .dismiss { 14 | cursor: pointer; 15 | position: absolute; 16 | right: .75rem; 17 | top: .5rem 18 | } 19 | 20 | .blazor-error-boundary { 21 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 22 | padding: 1rem 1rem 1rem 3.7rem; 23 | color: white; 24 | } 25 | 26 | .blazor-error-boundary::after { 27 | content: "An error has occurred." 28 | } -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/MD2RazorGenerator.Test/Fixtures/SampleProjects/Project01/wwwroot/favicon.ico -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/Fixtures/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using MD2RazorGenerator.Test.Fixtures.Attributes.FizzBuzz -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/GeneratedPageTests.cs: -------------------------------------------------------------------------------- 1 | using Bunit; 2 | using Fizz.Buzz.FizzBuzz; 3 | using Lorem.Ipsum; 4 | using MD2RazorGenerator.Test.Fixtures; 5 | using MD2RazorGenerator.Test.Fixtures.Attributes; 6 | using MD2RazorGenerator.Test.Fixtures.Attributes.FizzBuzz; 7 | using MD2RazorGenerator.Test.Fixtures.ComponentTypes; 8 | using MD2RazorGenerator.Test.Fixtures.ComponentTypes.OtherLayouts; 9 | using Microsoft.AspNetCore.Components; 10 | using Microsoft.AspNetCore.Components.Web; 11 | 12 | namespace MD2RazorGenerator.Test; 13 | 14 | public class GeneratedPageTests 15 | { 16 | [Test] 17 | public void NoYamlFrontMatter_Test() 18 | { 19 | // No Attributes 20 | var attributes = typeof(Sample01_NoYamlFrontMatter).GetCustomAttributes(inherit: true); 21 | attributes.Length.Is(0, "Expected No attribute, but found {0}"); 22 | 23 | // Default namespace based on file path 24 | typeof(Sample01_NoYamlFrontMatter).Namespace.Is("MD2RazorGenerator.Test.Fixtures"); 25 | 26 | // Default base class 27 | typeof(Sample01_NoYamlFrontMatter).BaseType.Is(typeof(ComponentBase)); 28 | } 29 | 30 | [Test] 31 | public void WithYamlFrontMatter_Scalar_Test() 32 | { 33 | // A [Foo], a [Route], and a [Layout] attribute in the front matter 34 | var attributes = typeof(Sample02_WithYamlFrontMatter_Scalar).GetCustomAttributes(inherit: true); 35 | attributes.Length.Is(3, "Expected 3 attribute, but found {0}"); 36 | attributes.OfType().Single().Text.Is("Hello", "Expected FooAttribute with 'Hello', but not found"); 37 | attributes.OfType().Single().Template.Is("/sample02", "Expected RouteAttribute with '/sample02', but not found"); 38 | attributes.OfType().Single().LayoutType.Is(typeof(MainLayoutA)); 39 | 40 | // The namespace specified in the front matter 41 | typeof(Sample02_WithYamlFrontMatter_Scalar).Namespace.Is("Lorem.Ipsum"); 42 | 43 | // The base class specified in the front matter 44 | typeof(Sample02_WithYamlFrontMatter_Scalar).BaseType.Is(typeof(MarkdownComponentBaseA)); 45 | } 46 | 47 | [Test] 48 | public void WithYamlFrontMatter_Sequence_Test() 49 | { 50 | // A [Foo], a [Bar], three [Route], and a [Layout] attributes in the front matter 51 | var attributes = typeof(Sample03_WithYamlFrontMatter_Sequence).GetCustomAttributes(inherit: true); 52 | attributes.Length.Is(6, "Expected 6 attribute, but found {0}"); 53 | attributes.OfType().Single().Text.Is("Hello", "Expected FooAttribute with 'Hello', but not found"); 54 | attributes.OfType().Single().Text.Is("World", "Expected FooAttribute with 'World', but not found"); 55 | attributes.OfType().Select(a => a.Template).Order().Is( 56 | "/sample03", 57 | "/sample03/A", 58 | "/sample03/B"); 59 | attributes.OfType().Single().LayoutType.Is(typeof(MainLayoutB)); 60 | 61 | // The namespace specified the first in the sequence 62 | typeof(Sample03_WithYamlFrontMatter_Sequence).Namespace.Is("Fizz.Buzz.FizzBuzz"); 63 | 64 | // The base class specified the first in the sequence 65 | typeof(Sample03_WithYamlFrontMatter_Sequence).BaseType.Is(typeof(MarkdownComponentBaseB)); 66 | } 67 | 68 | [Test] 69 | public void HyperLinks_Test() 70 | { 71 | using var context = new Bunit.TestContext(); 72 | using var cut = context.RenderComponent(); 73 | 74 | // If the URL is absolute, it should be opened in a new tab 75 | cut.MarkupMatches(""" 76 | 82 | """); 83 | } 84 | 85 | [Test] 86 | public void Title_in_Scaler_Test() 87 | { 88 | using var context = new Bunit.TestContext(); 89 | using var cut = context.RenderComponent(); 90 | 91 | using var pageTitleComponent = cut.FindComponent(); 92 | pageTitleComponent.IsNotNull("Expected PageTitle component, but not found"); 93 | pageTitleComponent.Instance.ChildContent.IsNotNull("Page title content should be specified, but did'nt."); 94 | using var pageTitleText = context.Render(pageTitleComponent.Instance.ChildContent); 95 | pageTitleText.Markup.Is("Sample 02"); 96 | } 97 | 98 | [Test] 99 | public void Title_in_Sequence_Test() 100 | { 101 | using var context = new Bunit.TestContext(); 102 | using var cut = context.RenderComponent(); 103 | 104 | using var pageTitleComponent = cut.FindComponent(); 105 | pageTitleComponent.IsNotNull("Expected PageTitle component, but not found"); 106 | pageTitleComponent.Instance.ChildContent.IsNotNull("Page title content should be specified, but did'nt."); 107 | using var pageTitleText = context.Render(pageTitleComponent.Instance.ChildContent); 108 | pageTitleText.Markup.Is("Sample 03"); 109 | } 110 | 111 | [Test] 112 | public void DependsOn_ImportsRazor_Test() 113 | { 114 | // A [FizzBuzz] and a [Layout] attribute in the front matter 115 | var attributes = typeof(Sample06_DependsImports).GetCustomAttributes(inherit: true); 116 | attributes.Length.Is(2, "Expected 2 attribute, but found {0}"); 117 | attributes.OfType().Single().Text.Is("Hello, World!", "Expected FizzBuzzAttribute with 'Hello, World!', but not found"); 118 | attributes.OfType().Single().LayoutType.Is(typeof(MainLayoutC)); 119 | } 120 | 121 | [Test] 122 | public void Table_Test() 123 | { 124 | using var context = new Bunit.TestContext(); 125 | using var cut = context.RenderComponent(); 126 | cut.MarkupMatches(""" 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
Column AColumn BColumn C
LoremIpsumDolor
SitAmetConsecte
148 | """); 149 | } 150 | 151 | [Test] 152 | public void Directory_and_Filename_Includes_InvalidChars_Test() 153 | { 154 | using var context = new Bunit.TestContext(); 155 | using var cut = context.RenderComponent(); 156 | cut.MarkupMatches("

001

"); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/ImportsTests.cs: -------------------------------------------------------------------------------- 1 | using Toolbelt.Blazor.MD2Razor.Internals; 2 | 3 | namespace MD2RazorGenerator.Test; 4 | 5 | public class ImportsTests 6 | { 7 | [Test] 8 | public void GetUsings_Test() 9 | { 10 | var imports = new Imports(path: "_Imports.razor", """ 11 | @using System 12 | 13 | @using System.Collections.Generic @* Comment *@ 14 | @using System.Linq 15 | @using static BlazingStory.Types.RenderMode 16 | """); 17 | 18 | imports.GetUsings().Is( 19 | "System", 20 | "System.Collections.Generic", 21 | "System.Linq", 22 | "static BlazingStory.Types.RenderMode"); 23 | } 24 | 25 | [Test] 26 | public void Applicable_Test() 27 | { 28 | var importsCollection = new Imports[] { 29 | new(path:"C:\\Project\\_Imports.razor", text:""), 30 | new(path:"C:\\Project\\Foo\\_Imports.razor", text:""), 31 | new(path:"C:\\Project\\Foo\\Bar\\_Imports.razor", text:""), 32 | new(path:"C:\\Project\\Foo\\Fizz\\_Imports.razor", text:""), 33 | new(path:"C:\\Project\\Buzz\\_Imports.razor", text:""), 34 | }; 35 | 36 | var applicableImports = importsCollection.GetApplicableImports("C:\\Project\\Foo\\Bar\\Welcome.md"); 37 | applicableImports.Select(i => i.Path).Is( 38 | "C:\\Project\\_Imports.razor", 39 | "C:\\Project\\Foo\\_Imports.razor", 40 | "C:\\Project\\Foo\\Bar\\_Imports.razor" 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/MD2RazorGenerator.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | latest 6 | enable 7 | enable 8 | false 9 | $(NoWarn);MSB3243 10 | $(WarningsAsErrors);nullable;RZ10012;CS8785;CS0105 11 | 12 | true 13 | 14 | 15 | 16 | 17 | 18 | 19 | all 20 | runtime; build; native; contentfiles; analyzers; buildtransitive 21 | 22 | 23 | 24 | 25 | all 26 | runtime; build; native; contentfiles; analyzers; buildtransitive 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Analyzer 38 | false 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | <_MD2Razor_MarkdownFile_Pattern>Fixtures\*.md;Fixtures\2025+Jan\*.md 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/MD2RazorTests.cs: -------------------------------------------------------------------------------- 1 | using Toolbelt.Blazor.MD2Razor; 2 | using Toolbelt.Blazor.MD2Razor.Internals; 3 | 4 | namespace MD2RazorGenerator.Test; 5 | 6 | public class MD2RazorTests 7 | { 8 | private static string NormalizePath(string path) => string.Join(Path.DirectorySeparatorChar, path.Split('/')); 9 | 10 | private static readonly GlobalOptions _globalOptions = new( 11 | rootNamespace: "Toolbelt.Samples", 12 | projectDir: NormalizePath("/home/user/projects/sample001/"), 13 | defaultBaseClass: "global::Microsoft.AspNetCore.Components.ComponentBase" 14 | ); 15 | 16 | [Test] 17 | public void GenerateNamespaceFromPath_RootPath_Test() 18 | { 19 | // Given 20 | var markdownPath = NormalizePath("/home/user/projects/sample001/001.md"); 21 | 22 | // When 23 | var generatedNamespace = MD2Razor.GenerateNamespaceFromPath(markdownPath, _globalOptions); 24 | 25 | // Then 26 | generatedNamespace.Is("Toolbelt.Samples"); 27 | } 28 | 29 | [Test] 30 | public void GenerateNamespaceFromPath_SubPath_Test() 31 | { 32 | // Given 33 | var markdownPath = NormalizePath("/home/user/projects/sample001/Pages/001.md"); 34 | 35 | // When 36 | var generatedNamespace = MD2Razor.GenerateNamespaceFromPath(markdownPath, _globalOptions); 37 | 38 | // Then 39 | generatedNamespace.Is("Toolbelt.Samples.Pages"); 40 | } 41 | 42 | [Test] 43 | public void GenerateNamespaceFromPath_with_InvalidChars_Test() 44 | { 45 | // Given 46 | var markdownPath = NormalizePath("/home/user/projects/sample001/Posts/2025+01/Jan@Sapporo/001.md"); 47 | 48 | // When 49 | var generatedNamespace = MD2Razor.GenerateNamespaceFromPath(markdownPath, _globalOptions); 50 | 51 | // Then 52 | generatedNamespace.Is("Toolbelt.Samples.Posts._2025_01.Jan_Sapporo"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /MD2RazorGenerator.Test/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using MD2RazorGenerator.Test.Fixtures.ComponentTypes.OtherLayouts -------------------------------------------------------------------------------- /MD2RazorGenerator.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31903.59 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MD2RazorGenerator", "MD2RazorGenerator\MD2RazorGenerator.csproj", "{0DC55229-090A-4057-9CDF-86439C271FAD}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MD2RazorGenerator.Test", "MD2RazorGenerator.Test\MD2RazorGenerator.Test.csproj", "{EAA3A80E-9561-4A6D-BC20-E889D57495D5}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "README", "README", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" 11 | ProjectSection(SolutionItems) = preProject 12 | LICENSE = LICENSE 13 | README.md = README.md 14 | RELEASE-NOTES.txt = RELEASE-NOTES.txt 15 | THIRDPARTYNOTICES.txt = THIRDPARTYNOTICES.txt 16 | EndProjectSection 17 | EndProject 18 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "MD2RazorGenerator.Shared", "MD2RazorGenerator.Shared\MD2RazorGenerator.Shared.shproj", "{FA804AE9-BB9D-4B4B-A3DB-215E8B517FFB}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MD2RazorGenerator.MSBuild.Task", "MD2RazorGenerator.MSBuild.Task\MD2RazorGenerator.MSBuild.Task.csproj", "{DDC56D24-37CE-4AC8-A509-5DAC48BC6449}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}" 23 | ProjectSection(SolutionItems) = preProject 24 | VersionInfo.props = VersionInfo.props 25 | EndProjectSection 26 | EndProject 27 | Global 28 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 29 | Debug|Any CPU = Debug|Any CPU 30 | Debug|x64 = Debug|x64 31 | Debug|x86 = Debug|x86 32 | Release|Any CPU = Release|Any CPU 33 | Release|x64 = Release|x64 34 | Release|x86 = Release|x86 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Debug|x64.ActiveCfg = Debug|Any CPU 40 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Debug|x64.Build.0 = Debug|Any CPU 41 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Debug|x86.ActiveCfg = Debug|Any CPU 42 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Debug|x86.Build.0 = Debug|Any CPU 43 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Release|x64.ActiveCfg = Release|Any CPU 46 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Release|x64.Build.0 = Release|Any CPU 47 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Release|x86.ActiveCfg = Release|Any CPU 48 | {0DC55229-090A-4057-9CDF-86439C271FAD}.Release|x86.Build.0 = Release|Any CPU 49 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Debug|x64.ActiveCfg = Debug|Any CPU 52 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Debug|x64.Build.0 = Debug|Any CPU 53 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Debug|x86.ActiveCfg = Debug|Any CPU 54 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Debug|x86.Build.0 = Debug|Any CPU 55 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Release|x64.ActiveCfg = Release|Any CPU 58 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Release|x64.Build.0 = Release|Any CPU 59 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Release|x86.ActiveCfg = Release|Any CPU 60 | {EAA3A80E-9561-4A6D-BC20-E889D57495D5}.Release|x86.Build.0 = Release|Any CPU 61 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 62 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Debug|Any CPU.Build.0 = Debug|Any CPU 63 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Debug|x64.ActiveCfg = Debug|Any CPU 64 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Debug|x64.Build.0 = Debug|Any CPU 65 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Debug|x86.ActiveCfg = Debug|Any CPU 66 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Debug|x86.Build.0 = Debug|Any CPU 67 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Release|Any CPU.ActiveCfg = Release|Any CPU 68 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Release|Any CPU.Build.0 = Release|Any CPU 69 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Release|x64.ActiveCfg = Release|Any CPU 70 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Release|x64.Build.0 = Release|Any CPU 71 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Release|x86.ActiveCfg = Release|Any CPU 72 | {DDC56D24-37CE-4AC8-A509-5DAC48BC6449}.Release|x86.Build.0 = Release|Any CPU 73 | EndGlobalSection 74 | GlobalSection(SolutionProperties) = preSolution 75 | HideSolutionNode = FALSE 76 | EndGlobalSection 77 | GlobalSection(ExtensibilityGlobals) = postSolution 78 | SolutionGuid = {A083EF62-136C-4132-82BE-C5AC83305515} 79 | EndGlobalSection 80 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 81 | MD2RazorGenerator.Shared\MD2RazorGenerator.Shared.projitems*{0dc55229-090a-4057-9cdf-86439c271fad}*SharedItemsImports = 5 82 | MD2RazorGenerator.Shared\MD2RazorGenerator.Shared.projitems*{ddc56d24-37ce-4ac8-a509-5dac48bc6449}*SharedItemsImports = 5 83 | MD2RazorGenerator.Shared\MD2RazorGenerator.Shared.projitems*{fa804ae9-bb9d-4b4b-a3db-215e8b517ffb}*SharedItemsImports = 13 84 | EndGlobalSection 85 | EndGlobal 86 | -------------------------------------------------------------------------------- /MD2RazorGenerator/MD2RazorGenerator.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Toolbelt.Blazor.MD2Razor.Internals; 3 | 4 | namespace Toolbelt.Blazor.MD2Razor; 5 | 6 | /// 7 | /// A source generator that converts Markdown (.md) files into Razor components. 8 | /// 9 | [Generator(LanguageNames.CSharp)] 10 | public partial class MD2RazorGenerator : IIncrementalGenerator 11 | { 12 | /// 13 | /// Initializes the source generator with the provided context. 14 | /// 15 | /// The initialization context for the incremental generator. 16 | public void Initialize(IncrementalGeneratorInitializationContext context) 17 | { 18 | // Filter additional text files to include only Markdown (.md) files. 19 | var markdownFiles = context.AdditionalTextsProvider.Where(t => t.Path.EndsWith(".md")); 20 | 21 | var importsProvider = context.AdditionalTextsProvider 22 | .Where(t => Path.GetFileName(t.Path).Equals("_Imports.razor", StringComparison.InvariantCultureIgnoreCase)) 23 | .Select((t, token) => new Imports(t.Path, t.GetText(token)?.ToString())) 24 | .Collect(); 25 | 26 | // Create a provider to retrieve global options such as the root namespace and project directory. 27 | var globalOptionsProvider = GetGlobalOptionsProvider(context); 28 | 29 | // Register the source generation logic. 30 | var md2razor = new MD2Razor(); 31 | context.RegisterSourceOutput(markdownFiles.Combine(globalOptionsProvider).Combine(importsProvider), (context, pair) => 32 | { 33 | var ((markdownFile, globalOptions), imports) = pair; 34 | var markdownText = markdownFile.GetText(context.CancellationToken)?.ToString(); 35 | if (markdownText is null) return; 36 | 37 | var generatedCode = md2razor.GenerateCode(markdownFile.Path, markdownText, imports, globalOptions); 38 | var hintName = MD2Razor.TransformToDotSeparatedPath(markdownFile.Path, globalOptions.ProjectDir) + ".g.cs"; 39 | context.AddSource(hintName, generatedCode); 40 | }); 41 | } 42 | 43 | /// 44 | /// Creates an incremental value provider for retrieving global options. 45 | /// 46 | /// The initialization context for the incremental generator. 47 | /// An incremental value provider for global options. 48 | private static IncrementalValueProvider GetGlobalOptionsProvider(IncrementalGeneratorInitializationContext context) 49 | { 50 | return context 51 | .AnalyzerConfigOptionsProvider 52 | .Select((options, _) => new GlobalOptions( 53 | rootNamespace: options.GlobalOptions.TryGetValue("build_property.RootNamespace", out var rootNamespace) ? rootNamespace : "", 54 | projectDir: options.GlobalOptions.TryGetValue("build_property.ProjectDir", out var projectDir) ? projectDir : "", 55 | defaultBaseClass: options.GlobalOptions.TryGetValue("build_property.MD2RazorDefaultBaseClass", out var defaultBaseClass) ? defaultBaseClass : "" 56 | )); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MD2RazorGenerator/MD2RazorGenerator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 12 6 | enable 7 | cs 8 | enable 9 | $(WarningsAsErrors);nullable 10 | $(NoWarn);RS1036;CS1591 11 | true 12 | $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs 13 | $(GetTargetPathDependsOn);GetDependencyTargetPaths 14 | false 15 | true 16 | false 17 | true 18 | true 19 | Toolbelt.Blazor.MD2Razor 20 | 21 | 22 | 23 | 24 | git 25 | https://github.com/jsakamoto/MD2RazorGenerator/ 26 | MPL-2.0 27 | $(RepositoryUrl) 28 | blazor,markdown,razor 29 | nupkg-icon.png 30 | README.md 31 | ../_dist 32 | (Please write the package release notes in "../RELEASE-NOTES.txt") 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | all 48 | runtime; build; native; contentfiles; analyzers; buildtransitive 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | @(ReleaseNoteLines, '%0a');%0av.0.0 81 | $(PackageReleaseNotes.Replace('<','&lt;')) 82 | $(PackageReleaseNotes.Replace('>','&gt;')) 83 | $([System.Text.RegularExpressions.Regex]::Match($(PackageReleaseNotes), "^(v\.[\d\.]+.+?)v\.[\d\.]+", System.Text.RegularExpressions.RegexOptions.Singleline).Groups[1].Value) 84 | 85 | $(PackageReleaseNotes)%0aTo see all the change logs, please visit the following URL.%0a- https://github.com/jsakamoto/MD2RazorGenerator/blob/main/RELEASE-NOTES.txt 86 | 87 | 88 | 89 | 90 | 91 | ..\MD2RazorGenerator.MSBuild.Task\ 92 | 93 | 94 | 95 | <_MD2Razor_MSBuildTaskOutput Include="$(MD2RazorMSBuildTaskProjectDir)bin\Release\netstandard2.0\*.dll" /> 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /MD2RazorGenerator/PackageContents/build/MD2RazorGenerateRazorClassDeclarations.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | $(CoreCompileDependsOn);_MD2Razor_GenerateRazorClassDeclarations 7 | 8 | 9 | 10 | 11 | $(IntermediateOutputPath)md2razor 12 | 13 | 14 | 15 | 16 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /MD2RazorGenerator/PackageContents/build/MD2RazorGenerator.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /MD2RazorGenerator/PackageContents/build/MD2RazorPopulateMarkdownFiles.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | $(CoreCompileDependsOn);_MD2Razor_PopulateMarkdownFiles 12 | 13 | 14 | 15 | 16 | 17 | 18 | <_MD2Razor_MarkdownFile_Pattern Condition=" '$(_MD2Razor_MarkdownFile_Pattern)' == '' ">**\*.md 19 | 20 | 21 | 22 | <_MD2Razor_ImportsFile Include="**\_Imports.razor" /> 23 | <_MD2Razor_MarkdownFile Include="$(_MD2Razor_MarkdownFile_Pattern)" /> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /MD2RazorGenerator/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "MD2RazorGenerator": { 4 | "commandName": "DebugRoslynComponent", 5 | "targetProject": "..\\MD2RazorGenerator.Test\\MD2RazorGenerator.Test.csproj" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # "MD2RazorGenerator" - Markdown to Razor Component Generator for Blazor 2 | 3 | [![NuGet Package](https://img.shields.io/nuget/v/MD2RazorGenerator.svg)](https://www.nuget.org/packages/MD2RazorGenerator/) [![unit tests](https://github.com/jsakamoto/MD2RazorGenerator/actions/workflows/unit-tests.yml/badge.svg)](https://github.com/jsakamoto/MD2RazorGenerator/actions/workflows/unit-tests.yml) [![Discord](https://img.shields.io/discord/798312431893348414?style=flat&logo=discord&logoColor=white&label=Blazor%20Community&labelColor=5865f2&color=gray)](https://discord.com/channels/798312431893348414/1202165955900473375) 4 | 5 | ## Summary 6 | 7 | MD2RazorGenerator is a C# source generator NuGet package for Blazor that converts Markdown files (`.md`) into Razor components. Once you install this NuGet package, any Markdown files (`.md`) in your Blazor project will automatically be compiled into Razor components. 8 | 9 | ![](https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/refs/heads/main/.assets/social-media.png) 10 | 11 | Live Demo Site: [https://jsakamoto.github.io/MD2RazorGenerator/](https://jsakamoto.github.io/MD2RazorGenerator/) 12 | 13 | Generated components work seamlessly with all Blazor rendering modes, including Server, WebAssembly, and Server-Side Rendering (SSR). It also fully supports hot reload, ensuring a smooth and productive development experience regardless of your chosen hosting model. 14 | 15 | For a quick visual overview, watch Jimmy Engström’s introduction video: 16 | 17 | [![](https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/refs/heads/main/.assets/youtube-video-thumbnail.png)](https://youtu.be/3ABOgEScyRc) 18 | 19 | ## Quick Start 20 | 21 | **1.** Install the NuGet package via the .NET CLI: 22 | 23 | ```shell 24 | dotnet add package MD2RazorGenerator 25 | ``` 26 | 27 | Or use the NuGet Package Manager in Visual Studio. 28 | 29 | **2.** Add a Markdown file to your project. For example, create a file named `Greeting.md` in the `Pages` folder: 30 | 31 | ```markdown 32 | # Hello, World! 33 | ``` 34 | 35 | **3.** The `Greeting` Razor component will be generated automatically. You can use it anywhere in your Blazor application: 36 | 37 | ```html 38 | 39 | @page "/" 40 | 41 | 42 | ``` 43 | 44 | ## Creating Routable Page Components 45 | 46 | To make a generated component routable, specify a `url` in the YAML front matter at the top of your Markdown file. This works the same as the `@page` directive in `.razor` files. For example, the following in `Home.md`: 47 | 48 | ```markdown 49 | --- 50 | url: /home 51 | --- 52 | # Hello, World! 53 | ``` 54 | 55 | This will generate a `Home` Razor component, allowing you to navigate to `/home` in your Blazor application. You can also specify multiple URLs using YAML sequence syntax: 56 | 57 | ```markdown 58 | --- 59 | url: [/, /home] 60 | --- 61 | ``` 62 | 63 | ## Available YAML Front Matter Keys 64 | 65 | The following keys are available in the YAML front matter of Markdown files: 66 | 67 | | Key | Description | Sequence Supported | 68 | |------------|-------------|--------------------| 69 | | url | The URL(s) for the component. Functions like the `@page` directive. | Yes | 70 | | title | The page title. If specified, a `` component will be included in the generated Razor component. | No | 71 | | $using | Using directives for the generated Razor component. Functions like the `@using` directive. | Yes | 72 | | $namespace | The namespace for the generated Razor component. | No | 73 | | $attribute | Attributes for the generated Razor component. Functions like the `@attribute` directive. | Yes | 74 | | $layout | The layout class for the generated Razor component. Functions like the `@layout` directive. | No | 75 | | $inherit | The base class for the generated Razor component. Functions like the `@inherits` directive. | No | 76 | 77 | ## MSBuild Properties 78 | 79 | You can control the behavior of MD2RazorGenerator with the following MSBuild property: 80 | 81 | | Property | Description | Default Value | 82 | |----------|-------------|---------------| 83 | | MD2RazorDefaultBaseClass | The default base class for generated Razor components. | `Microsoft.AspNetCore.Components.ComponentBase` | 84 | 85 | ## Known Limitations 86 | 87 | ### _Imports.razor Parsing 88 | 89 | MD2RazorGenerator now supports reading `@using` directives from `_Imports.razor` files to apply them to generated components. However, the parser uses a simple regex-based approach that may not correctly handle complex scenarios. In particular: 90 | 91 | - Comments within the same line as a `@using` directive might cause parsing issues 92 | - Multi-line directives might not be properly recognized 93 | 94 | For best results, keep your `@using` directives in `_Imports.razor` files simple and on dedicated lines. 95 | 96 | ## Appendix: Code Syntax Highlighting 97 | 98 | This NuGet package does not provide code syntax highlighting itself. To enable code syntax highlighting, you can use [Prism.js](https://prismjs.com/). Follow these steps: 99 | 100 | **1.** Add the Prism.js CSS and JS files to your project. One of the easiest ways is to use a CDN. Add the following to your fallback page, such as `wwwroot/index.html`: 101 | 102 | ```html 103 | 104 | 105 | 106 | ... 107 | 108 | 109 | 110 | 111 | ... 112 | 113 | 114 | 115 | 116 | ... 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | ``` 125 | 126 | **2.** Add a code-behind file for your Markdown file. For example, create `Greeting.md.cs` in the same folder as `Greeting.md`: 127 | 128 | ```csharp 129 | using Microsoft.AspNetCore.Components; 130 | using Microsoft.JSInterop; 131 | namespace YourNamespace.Pages; 132 | 133 | public partial class Greeting 134 | { 135 | [Inject] 136 | private IJSRuntime JsRuntime { get; set; } = default!; 137 | 138 | protected override async Task OnAfterRenderAsync(bool firstRender) 139 | { 140 | // Call Prism to highlight code blocks after rendering 141 | await JsRuntime.InvokeVoidAsync("Prism.highlightAll"); 142 | } 143 | } 144 | ``` 145 | 146 | Alternatively, you can create a base class that handles code syntax highlighting and specify this base class in your Markdown file using the `$inherit` key in the YAML front matter. This allows you to centralize the highlighting logic and reuse it across multiple generated components. 147 | Instead of specifying the base class in each Markdown file, you can also set the base class for all Markdown files at once by configuring the `MD2RazorDefaultBaseClass` MSBuild property. This makes it easy to apply the same base class to every generated component in your project. 148 | 149 | ## Appendix: Comparison with Other Markdown Libraries for Blazor 150 | 151 | Several libraries are available for rendering Markdown as HTML in Blazor applications, such as [Radzen Blazor Markdown](https://blazor.radzen.com/markdown#text), [Blazorise Markdown](https://blazorise.com/docs/extensions/markdown), and [MudBlazor.Markdown](https://github.com/MyNihongo/MudBlazor.Markdown). These libraries typically load Markdown text, convert it to HTML, and render it at runtime. 152 | 153 | **Key advantages of MD2RazorGenerator:** 154 | 155 | - **Native Markdown Editing:** Since you work directly with plain Markdown (`.md`) files, you can take full advantage of Markdown-compatible editors and their rich ecosystem of Markdown-specific features, such as live preview, syntax highlighting, and linting. 156 | - **Build-Time Conversion:** Unlike runtime libraries, MD2RazorGenerator uses a C# source generator to convert Markdown files into Razor components at build time. No Markdown-to-HTML conversion occurs during application execution. 157 | - **No Runtime Overhead:** Since conversion happens at build time, there is no additional memory usage or processing cost at runtime for Markdown parsing or rendering. This results in faster page loads and lower memory consumption. 158 | - **No Increase in App Size:** Runtime Markdown libraries often require bundling additional parsing libraries, which can increase the size of the published Blazor application. MD2RazorGenerator does not add such dependencies, keeping your app lightweight. 159 | - **Routable Page Components:** Each Markdown file becomes a fully routable Razor component, allowing you to define pages directly from Markdown with simple YAML front matter configuration. 160 | - **Seamless Integration:** The generated components behave like standard Razor components, making it easy to use them throughout your Blazor application without special wrappers or controls. 161 | 162 | In summary, MD2RazorGenerator provides an efficient and seamless way to use Markdown in Blazor, with significant benefits in performance, simplicity, and application size compared to traditional runtime Markdown libraries. 163 | 164 | ## Release Notes 165 | 166 | See [Release Notes](https://github.com/jsakamoto/MD2RazorGenerator/blob/main/RELEASE-NOTES.txt). 167 | 168 | ## License & Third Party Notices 169 | 170 | - [Mozilla Public License Version 2.0](https://github.com/jsakamoto/MD2RazorGenerator/blob/main/LICENSE) 171 | - [Third party notices](https://github.com/jsakamoto/MD2RazorGenerator/blob/main/THIRDPARTYNOTICES.txt) 172 | -------------------------------------------------------------------------------- /RELEASE-NOTES.txt: -------------------------------------------------------------------------------- 1 | v.1.2.2 2 | - Fix: Files with invalid characters in their paths could not be compiled if those characters were not permitted in C# class or namespace names. Now such Markdown files are handled correctly. 3 | 4 | v.1.2.1 5 | - Fix: The generator did not recognize the "static using" syntax in "_Imports.razor" files. 6 | - Fix: Suppressed XML comment warning CS1591: "Missing XML comment for publicly visible type or member." 7 | 8 | v.1.2.0 9 | - Feature: Generated components now honor @using directives from _Imports.razor. 10 | 11 | v.1.1.4 12 | - Fix: Resolved CS8785 error that occurred when markdown files with the same name existed in different folders. 13 | 14 | v.1.1.3 15 | - Fix: Moving the location of .md files sometimes corrupted the build. 16 | 17 | v.1.1.2 18 | - Fix: Warning RZ10012 was triggered when the MD2RazorDefaultBaseClass MSBuild property was changed. 19 | 20 | v.1.1.1 21 | - Fix: "dotnet clean" was not functioning as expected. 22 | 23 | v.1.1.0 24 | - Added support for specifying a custom layout class using the "$layout" key in YAML front matter. 25 | 26 | v.1.0.0 27 | - Initial release. 28 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/BlazorWasmApp1.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | true 8 | false 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/BlazorWasmApp1.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.14.36017.23 d17.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorWasmApp1", "BlazorWasmApp1.csproj", "{5D16102A-BB87-4FD6-892B-6A08BFB8E19D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5D16102A-BB87-4FD6-892B-6A08BFB8E19D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5D16102A-BB87-4FD6-892B-6A08BFB8E19D}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5D16102A-BB87-4FD6-892B-6A08BFB8E19D}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5D16102A-BB87-4FD6-892B-6A08BFB8E19D}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {BE7EC1A8-3FB6-446D-94AA-B1CF7CEE2574} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Components/WeatherForecastTable.md: -------------------------------------------------------------------------------- 1 | | Date | Temp. (C) | Temp. (F) | Summary | 2 | |----------|-----------|-----------|----------| 3 | | 1/6/2022 | 1 | 33 | Freezing | 4 | | 1/7/2022 | 14 | 57 | Bracing | 5 | | 1/8/2022 | -13 | 9 | Freezing | 6 | | 1/9/2022 | -16 | 4 | Balmy | 7 | | 1/10/2022| -2 | 29 | Chilly | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Layout/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 |
3 | 6 | 7 |
8 | 11 | 12 |
13 | @Body 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Layout/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row { 41 | justify-content: space-between; 42 | } 43 | 44 | .top-row ::deep a, .top-row ::deep .btn-link { 45 | margin-left: 0; 46 | } 47 | } 48 | 49 | @media (min-width: 641px) { 50 | .page { 51 | flex-direction: row; 52 | } 53 | 54 | .sidebar { 55 | width: 250px; 56 | height: 100vh; 57 | position: sticky; 58 | top: 0; 59 | } 60 | 61 | .top-row { 62 | position: sticky; 63 | top: 0; 64 | z-index: 1; 65 | } 66 | 67 | .top-row.auth ::deep a:first-child { 68 | flex: 1; 69 | text-align: right; 70 | width: 0; 71 | } 72 | 73 | .top-row, article { 74 | padding-left: 2rem !important; 75 | padding-right: 1.5rem !important; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Layout/NavMenu.razor: -------------------------------------------------------------------------------- 1 |  9 | 10 | 33 | 34 | @code { 35 | private bool collapseNavMenu = true; 36 | 37 | private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null; 38 | 39 | private void ToggleNavMenu() 40 | { 41 | collapseNavMenu = !collapseNavMenu; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Layout/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | min-height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1rem; 12 | } 13 | 14 | .bi { 15 | display: inline-block; 16 | position: relative; 17 | width: 1.25rem; 18 | height: 1.25rem; 19 | margin-right: 0.75rem; 20 | top: -1px; 21 | background-size: cover; 22 | } 23 | 24 | .bi-house-door-fill-nav-menu { 25 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); 26 | } 27 | 28 | .bi-list-nested-nav-menu { 29 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); 30 | } 31 | 32 | .bi-markup-nav-menu { 33 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' viewBox='0 -960 960 960' %3E%3Cpath d='M336-240 96-480l240-240 51 51-189 189 189 189-51 51Zm288 0-51-51 189-189-189-189 51-51 240 240-240 240Z'/%3E%3C/svg%3E"); 34 | } 35 | 36 | .nav-item { 37 | font-size: 0.9rem; 38 | padding-bottom: 0.5rem; 39 | } 40 | 41 | .nav-item:first-of-type { 42 | padding-top: 1rem; 43 | } 44 | 45 | .nav-item:last-of-type { 46 | padding-bottom: 1rem; 47 | } 48 | 49 | .nav-item ::deep a { 50 | color: #d7d7d7; 51 | border-radius: 4px; 52 | height: 3rem; 53 | display: flex; 54 | align-items: center; 55 | line-height: 3rem; 56 | } 57 | 58 | .nav-item ::deep a.active { 59 | background-color: rgba(255,255,255,0.37); 60 | color: white; 61 | } 62 | 63 | .nav-item ::deep a:hover { 64 | background-color: rgba(255,255,255,0.1); 65 | color: white; 66 | } 67 | 68 | @media (min-width: 641px) { 69 | .navbar-toggler { 70 | display: none; 71 | } 72 | 73 | .collapse { 74 | /* Never collapse the sidebar for wide screens */ 75 | display: block; 76 | } 77 | 78 | .nav-scrollable { 79 | /* Allow sidebar to scroll for tall menus */ 80 | height: calc(100vh - 3.5rem); 81 | overflow-y: auto; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Pages/Code.md: -------------------------------------------------------------------------------- 1 | --- 2 | url: /code 3 | title: Code 4 | --- 5 | 6 | # Code 7 | 8 | This component demonstrates how to syntax-highlight code blocks in Razor components coming from markdown files using ["Prism.js."](https://prismjs.com/) 9 | 10 | See the source code [here](https://github.com/jsakamoto/MD2RazorGenerator/blob/main/SampleSites/BlazorWasmApp1/Pages/Code.md.cs) as well. 11 | 12 | HTML 13 | 14 | ```html 15 |
16 | 17 |

Code

18 |
19 | ``` 20 | 21 | C# 22 | 23 | ```csharp 24 | using System; 25 | 26 | var builder = WebApplication.CreateBuilder(args); 27 | var app = builder.Build(); 28 | app.MapGet("/", () => "Hello World!"); 29 | app.Run(); 30 | ``` -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Pages/Code.md.cs: -------------------------------------------------------------------------------- 1 |  2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.JSInterop; 4 | 5 | namespace BlazorWasmApp1.Pages; 6 | 7 | public partial class Code 8 | { 9 | [Inject] 10 | private IJSRuntime JsRuntime { get; set; } = default!; 11 | 12 | protected override async Task OnAfterRenderAsync(bool firstRender) 13 | { 14 | // Call a Prism to colorize code blocks after the component has rendered 15 | await this.JsRuntime.InvokeVoidAsync("Prism.highlightAll"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Pages/Home.md: -------------------------------------------------------------------------------- 1 | --- 2 | url: / 3 | title: Home 4 | --- 5 | 6 | # Hello, world! 7 | 8 | Welcome to your new app. 9 | 10 | This page comes from **the markdown file, `Home.md`,** in the `Pages` folder. 11 | 12 | See the source code [here](https://github.com/jsakamoto/MD2RazorGenerator/blob/main/SampleSites/BlazorWasmApp1/Pages/Home.md?plain=1) as well. -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Pages/Weather.razor: -------------------------------------------------------------------------------- 1 | @page "/weather" 2 | 3 | Weather 4 | 5 |

Weather

6 | 7 |

This component demonstrates embedding a Blazor component built from a markdown file (.md.)

8 |

See the source code here as well.

9 | 10 | 11 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Program.cs: -------------------------------------------------------------------------------- 1 | using BlazorWasmApp1; 2 | using Microsoft.AspNetCore.Components.Web; 3 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 4 | 5 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 6 | builder.RootComponents.Add("#app"); 7 | builder.RootComponents.Add("head::after"); 8 | 9 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 10 | 11 | await builder.Build().RunAsync(); 12 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "profiles": { 4 | "http": { 5 | "commandName": "Project", 6 | "dotnetRunMessages": true, 7 | "launchBrowser": true, 8 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 9 | "applicationUrl": "http://localhost:5276", 10 | "environmentVariables": { 11 | "ASPNETCORE_ENVIRONMENT": "Development" 12 | } 13 | }, 14 | "https": { 15 | "commandName": "Project", 16 | "dotnetRunMessages": true, 17 | "launchBrowser": true, 18 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 19 | "applicationUrl": "https://localhost:7080;http://localhost:5276", 20 | "environmentVariables": { 21 | "ASPNETCORE_ENVIRONMENT": "Development" 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using BlazorWasmApp1 10 | @using BlazorWasmApp1.Layout 11 | @using BlazorWasmApp1.Components 12 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/css/app.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | h1:focus { 6 | outline: none; 7 | } 8 | 9 | a, .btn-link { 10 | color: #0071c1; 11 | } 12 | 13 | .btn-primary { 14 | color: #fff; 15 | background-color: #1b6ec2; 16 | border-color: #1861ac; 17 | } 18 | 19 | .btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { 20 | box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; 21 | } 22 | 23 | .content { 24 | padding-top: 1.1rem; 25 | } 26 | 27 | .valid.modified:not([type=checkbox]) { 28 | outline: 1px solid #26b050; 29 | } 30 | 31 | .invalid { 32 | outline: 1px solid red; 33 | } 34 | 35 | .validation-message { 36 | color: red; 37 | } 38 | 39 | #blazor-error-ui { 40 | color-scheme: light only; 41 | background: lightyellow; 42 | bottom: 0; 43 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 44 | box-sizing: border-box; 45 | display: none; 46 | left: 0; 47 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 48 | position: fixed; 49 | width: 100%; 50 | z-index: 1000; 51 | } 52 | 53 | #blazor-error-ui .dismiss { 54 | cursor: pointer; 55 | position: absolute; 56 | right: 0.75rem; 57 | top: 0.5rem; 58 | } 59 | 60 | .blazor-error-boundary { 61 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 62 | padding: 1rem 1rem 1rem 3.7rem; 63 | color: white; 64 | } 65 | 66 | .blazor-error-boundary::after { 67 | content: "An error has occurred." 68 | } 69 | 70 | .loading-progress { 71 | position: relative; 72 | display: block; 73 | width: 8rem; 74 | height: 8rem; 75 | margin: 20vh auto 1rem auto; 76 | } 77 | 78 | .loading-progress circle { 79 | fill: none; 80 | stroke: #e0e0e0; 81 | stroke-width: 0.6rem; 82 | transform-origin: 50% 50%; 83 | transform: rotate(-90deg); 84 | } 85 | 86 | .loading-progress circle:last-child { 87 | stroke: #1b6ec2; 88 | stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; 89 | transition: stroke-dasharray 0.05s ease-in-out; 90 | } 91 | 92 | .loading-progress-text { 93 | position: absolute; 94 | text-align: center; 95 | font-weight: bold; 96 | inset: calc(20vh + 3.25rem) 0 auto 0.2rem; 97 | } 98 | 99 | .loading-progress-text:after { 100 | content: var(--blazor-load-percentage-text, "Loading"); 101 | } 102 | 103 | code { 104 | color: #c02d76; 105 | } 106 | 107 | .form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder { 108 | color: var(--bs-secondary-color); 109 | text-align: end; 110 | } 111 | 112 | .form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder { 113 | text-align: start; 114 | } -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/SampleSites/BlazorWasmApp1/wwwroot/favicon.png -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/SampleSites/BlazorWasmApp1/wwwroot/icon-192.png -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | BlazorWasmApp1 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 |
23 |
24 | 25 |
26 | An unhandled error has occurred. 27 | Reload 28 | 🗙 29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.3.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2024 The Bootstrap Authors 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | :root, 7 | [data-bs-theme=light] { 8 | --bs-blue: #0d6efd; 9 | --bs-indigo: #6610f2; 10 | --bs-purple: #6f42c1; 11 | --bs-pink: #d63384; 12 | --bs-red: #dc3545; 13 | --bs-orange: #fd7e14; 14 | --bs-yellow: #ffc107; 15 | --bs-green: #198754; 16 | --bs-teal: #20c997; 17 | --bs-cyan: #0dcaf0; 18 | --bs-black: #000; 19 | --bs-white: #fff; 20 | --bs-gray: #6c757d; 21 | --bs-gray-dark: #343a40; 22 | --bs-gray-100: #f8f9fa; 23 | --bs-gray-200: #e9ecef; 24 | --bs-gray-300: #dee2e6; 25 | --bs-gray-400: #ced4da; 26 | --bs-gray-500: #adb5bd; 27 | --bs-gray-600: #6c757d; 28 | --bs-gray-700: #495057; 29 | --bs-gray-800: #343a40; 30 | --bs-gray-900: #212529; 31 | --bs-primary: #0d6efd; 32 | --bs-secondary: #6c757d; 33 | --bs-success: #198754; 34 | --bs-info: #0dcaf0; 35 | --bs-warning: #ffc107; 36 | --bs-danger: #dc3545; 37 | --bs-light: #f8f9fa; 38 | --bs-dark: #212529; 39 | --bs-primary-rgb: 13, 110, 253; 40 | --bs-secondary-rgb: 108, 117, 125; 41 | --bs-success-rgb: 25, 135, 84; 42 | --bs-info-rgb: 13, 202, 240; 43 | --bs-warning-rgb: 255, 193, 7; 44 | --bs-danger-rgb: 220, 53, 69; 45 | --bs-light-rgb: 248, 249, 250; 46 | --bs-dark-rgb: 33, 37, 41; 47 | --bs-primary-text-emphasis: #052c65; 48 | --bs-secondary-text-emphasis: #2b2f32; 49 | --bs-success-text-emphasis: #0a3622; 50 | --bs-info-text-emphasis: #055160; 51 | --bs-warning-text-emphasis: #664d03; 52 | --bs-danger-text-emphasis: #58151c; 53 | --bs-light-text-emphasis: #495057; 54 | --bs-dark-text-emphasis: #495057; 55 | --bs-primary-bg-subtle: #cfe2ff; 56 | --bs-secondary-bg-subtle: #e2e3e5; 57 | --bs-success-bg-subtle: #d1e7dd; 58 | --bs-info-bg-subtle: #cff4fc; 59 | --bs-warning-bg-subtle: #fff3cd; 60 | --bs-danger-bg-subtle: #f8d7da; 61 | --bs-light-bg-subtle: #fcfcfd; 62 | --bs-dark-bg-subtle: #ced4da; 63 | --bs-primary-border-subtle: #9ec5fe; 64 | --bs-secondary-border-subtle: #c4c8cb; 65 | --bs-success-border-subtle: #a3cfbb; 66 | --bs-info-border-subtle: #9eeaf9; 67 | --bs-warning-border-subtle: #ffe69c; 68 | --bs-danger-border-subtle: #f1aeb5; 69 | --bs-light-border-subtle: #e9ecef; 70 | --bs-dark-border-subtle: #adb5bd; 71 | --bs-white-rgb: 255, 255, 255; 72 | --bs-black-rgb: 0, 0, 0; 73 | --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 74 | --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 75 | --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); 76 | --bs-body-font-family: var(--bs-font-sans-serif); 77 | --bs-body-font-size: 1rem; 78 | --bs-body-font-weight: 400; 79 | --bs-body-line-height: 1.5; 80 | --bs-body-color: #212529; 81 | --bs-body-color-rgb: 33, 37, 41; 82 | --bs-body-bg: #fff; 83 | --bs-body-bg-rgb: 255, 255, 255; 84 | --bs-emphasis-color: #000; 85 | --bs-emphasis-color-rgb: 0, 0, 0; 86 | --bs-secondary-color: rgba(33, 37, 41, 0.75); 87 | --bs-secondary-color-rgb: 33, 37, 41; 88 | --bs-secondary-bg: #e9ecef; 89 | --bs-secondary-bg-rgb: 233, 236, 239; 90 | --bs-tertiary-color: rgba(33, 37, 41, 0.5); 91 | --bs-tertiary-color-rgb: 33, 37, 41; 92 | --bs-tertiary-bg: #f8f9fa; 93 | --bs-tertiary-bg-rgb: 248, 249, 250; 94 | --bs-heading-color: inherit; 95 | --bs-link-color: #0d6efd; 96 | --bs-link-color-rgb: 13, 110, 253; 97 | --bs-link-decoration: underline; 98 | --bs-link-hover-color: #0a58ca; 99 | --bs-link-hover-color-rgb: 10, 88, 202; 100 | --bs-code-color: #d63384; 101 | --bs-highlight-color: #212529; 102 | --bs-highlight-bg: #fff3cd; 103 | --bs-border-width: 1px; 104 | --bs-border-style: solid; 105 | --bs-border-color: #dee2e6; 106 | --bs-border-color-translucent: rgba(0, 0, 0, 0.175); 107 | --bs-border-radius: 0.375rem; 108 | --bs-border-radius-sm: 0.25rem; 109 | --bs-border-radius-lg: 0.5rem; 110 | --bs-border-radius-xl: 1rem; 111 | --bs-border-radius-xxl: 2rem; 112 | --bs-border-radius-2xl: var(--bs-border-radius-xxl); 113 | --bs-border-radius-pill: 50rem; 114 | --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); 115 | --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); 116 | --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); 117 | --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); 118 | --bs-focus-ring-width: 0.25rem; 119 | --bs-focus-ring-opacity: 0.25; 120 | --bs-focus-ring-color: rgba(13, 110, 253, 0.25); 121 | --bs-form-valid-color: #198754; 122 | --bs-form-valid-border-color: #198754; 123 | --bs-form-invalid-color: #dc3545; 124 | --bs-form-invalid-border-color: #dc3545; 125 | } 126 | 127 | [data-bs-theme=dark] { 128 | color-scheme: dark; 129 | --bs-body-color: #dee2e6; 130 | --bs-body-color-rgb: 222, 226, 230; 131 | --bs-body-bg: #212529; 132 | --bs-body-bg-rgb: 33, 37, 41; 133 | --bs-emphasis-color: #fff; 134 | --bs-emphasis-color-rgb: 255, 255, 255; 135 | --bs-secondary-color: rgba(222, 226, 230, 0.75); 136 | --bs-secondary-color-rgb: 222, 226, 230; 137 | --bs-secondary-bg: #343a40; 138 | --bs-secondary-bg-rgb: 52, 58, 64; 139 | --bs-tertiary-color: rgba(222, 226, 230, 0.5); 140 | --bs-tertiary-color-rgb: 222, 226, 230; 141 | --bs-tertiary-bg: #2b3035; 142 | --bs-tertiary-bg-rgb: 43, 48, 53; 143 | --bs-primary-text-emphasis: #6ea8fe; 144 | --bs-secondary-text-emphasis: #a7acb1; 145 | --bs-success-text-emphasis: #75b798; 146 | --bs-info-text-emphasis: #6edff6; 147 | --bs-warning-text-emphasis: #ffda6a; 148 | --bs-danger-text-emphasis: #ea868f; 149 | --bs-light-text-emphasis: #f8f9fa; 150 | --bs-dark-text-emphasis: #dee2e6; 151 | --bs-primary-bg-subtle: #031633; 152 | --bs-secondary-bg-subtle: #161719; 153 | --bs-success-bg-subtle: #051b11; 154 | --bs-info-bg-subtle: #032830; 155 | --bs-warning-bg-subtle: #332701; 156 | --bs-danger-bg-subtle: #2c0b0e; 157 | --bs-light-bg-subtle: #343a40; 158 | --bs-dark-bg-subtle: #1a1d20; 159 | --bs-primary-border-subtle: #084298; 160 | --bs-secondary-border-subtle: #41464b; 161 | --bs-success-border-subtle: #0f5132; 162 | --bs-info-border-subtle: #087990; 163 | --bs-warning-border-subtle: #997404; 164 | --bs-danger-border-subtle: #842029; 165 | --bs-light-border-subtle: #495057; 166 | --bs-dark-border-subtle: #343a40; 167 | --bs-heading-color: inherit; 168 | --bs-link-color: #6ea8fe; 169 | --bs-link-hover-color: #8bb9fe; 170 | --bs-link-color-rgb: 110, 168, 254; 171 | --bs-link-hover-color-rgb: 139, 185, 254; 172 | --bs-code-color: #e685b5; 173 | --bs-highlight-color: #dee2e6; 174 | --bs-highlight-bg: #664d03; 175 | --bs-border-color: #495057; 176 | --bs-border-color-translucent: rgba(255, 255, 255, 0.15); 177 | --bs-form-valid-color: #75b798; 178 | --bs-form-valid-border-color: #75b798; 179 | --bs-form-invalid-color: #ea868f; 180 | --bs-form-invalid-border-color: #ea868f; 181 | } 182 | 183 | *, 184 | *::before, 185 | *::after { 186 | box-sizing: border-box; 187 | } 188 | 189 | @media (prefers-reduced-motion: no-preference) { 190 | :root { 191 | scroll-behavior: smooth; 192 | } 193 | } 194 | 195 | body { 196 | margin: 0; 197 | font-family: var(--bs-body-font-family); 198 | font-size: var(--bs-body-font-size); 199 | font-weight: var(--bs-body-font-weight); 200 | line-height: var(--bs-body-line-height); 201 | color: var(--bs-body-color); 202 | text-align: var(--bs-body-text-align); 203 | background-color: var(--bs-body-bg); 204 | -webkit-text-size-adjust: 100%; 205 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 206 | } 207 | 208 | hr { 209 | margin: 1rem 0; 210 | color: inherit; 211 | border: 0; 212 | border-top: var(--bs-border-width) solid; 213 | opacity: 0.25; 214 | } 215 | 216 | h6, h5, h4, h3, h2, h1 { 217 | margin-top: 0; 218 | margin-bottom: 0.5rem; 219 | font-weight: 500; 220 | line-height: 1.2; 221 | color: var(--bs-heading-color); 222 | } 223 | 224 | h1 { 225 | font-size: calc(1.375rem + 1.5vw); 226 | } 227 | @media (min-width: 1200px) { 228 | h1 { 229 | font-size: 2.5rem; 230 | } 231 | } 232 | 233 | h2 { 234 | font-size: calc(1.325rem + 0.9vw); 235 | } 236 | @media (min-width: 1200px) { 237 | h2 { 238 | font-size: 2rem; 239 | } 240 | } 241 | 242 | h3 { 243 | font-size: calc(1.3rem + 0.6vw); 244 | } 245 | @media (min-width: 1200px) { 246 | h3 { 247 | font-size: 1.75rem; 248 | } 249 | } 250 | 251 | h4 { 252 | font-size: calc(1.275rem + 0.3vw); 253 | } 254 | @media (min-width: 1200px) { 255 | h4 { 256 | font-size: 1.5rem; 257 | } 258 | } 259 | 260 | h5 { 261 | font-size: 1.25rem; 262 | } 263 | 264 | h6 { 265 | font-size: 1rem; 266 | } 267 | 268 | p { 269 | margin-top: 0; 270 | margin-bottom: 1rem; 271 | } 272 | 273 | abbr[title] { 274 | -webkit-text-decoration: underline dotted; 275 | text-decoration: underline dotted; 276 | cursor: help; 277 | -webkit-text-decoration-skip-ink: none; 278 | text-decoration-skip-ink: none; 279 | } 280 | 281 | address { 282 | margin-bottom: 1rem; 283 | font-style: normal; 284 | line-height: inherit; 285 | } 286 | 287 | ol, 288 | ul { 289 | padding-left: 2rem; 290 | } 291 | 292 | ol, 293 | ul, 294 | dl { 295 | margin-top: 0; 296 | margin-bottom: 1rem; 297 | } 298 | 299 | ol ol, 300 | ul ul, 301 | ol ul, 302 | ul ol { 303 | margin-bottom: 0; 304 | } 305 | 306 | dt { 307 | font-weight: 700; 308 | } 309 | 310 | dd { 311 | margin-bottom: 0.5rem; 312 | margin-left: 0; 313 | } 314 | 315 | blockquote { 316 | margin: 0 0 1rem; 317 | } 318 | 319 | b, 320 | strong { 321 | font-weight: bolder; 322 | } 323 | 324 | small { 325 | font-size: 0.875em; 326 | } 327 | 328 | mark { 329 | padding: 0.1875em; 330 | color: var(--bs-highlight-color); 331 | background-color: var(--bs-highlight-bg); 332 | } 333 | 334 | sub, 335 | sup { 336 | position: relative; 337 | font-size: 0.75em; 338 | line-height: 0; 339 | vertical-align: baseline; 340 | } 341 | 342 | sub { 343 | bottom: -0.25em; 344 | } 345 | 346 | sup { 347 | top: -0.5em; 348 | } 349 | 350 | a { 351 | color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); 352 | text-decoration: underline; 353 | } 354 | a:hover { 355 | --bs-link-color-rgb: var(--bs-link-hover-color-rgb); 356 | } 357 | 358 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 359 | color: inherit; 360 | text-decoration: none; 361 | } 362 | 363 | pre, 364 | code, 365 | kbd, 366 | samp { 367 | font-family: var(--bs-font-monospace); 368 | font-size: 1em; 369 | } 370 | 371 | pre { 372 | display: block; 373 | margin-top: 0; 374 | margin-bottom: 1rem; 375 | overflow: auto; 376 | font-size: 0.875em; 377 | } 378 | pre code { 379 | font-size: inherit; 380 | color: inherit; 381 | word-break: normal; 382 | } 383 | 384 | code { 385 | font-size: 0.875em; 386 | color: var(--bs-code-color); 387 | word-wrap: break-word; 388 | } 389 | a > code { 390 | color: inherit; 391 | } 392 | 393 | kbd { 394 | padding: 0.1875rem 0.375rem; 395 | font-size: 0.875em; 396 | color: var(--bs-body-bg); 397 | background-color: var(--bs-body-color); 398 | border-radius: 0.25rem; 399 | } 400 | kbd kbd { 401 | padding: 0; 402 | font-size: 1em; 403 | } 404 | 405 | figure { 406 | margin: 0 0 1rem; 407 | } 408 | 409 | img, 410 | svg { 411 | vertical-align: middle; 412 | } 413 | 414 | table { 415 | caption-side: bottom; 416 | border-collapse: collapse; 417 | } 418 | 419 | caption { 420 | padding-top: 0.5rem; 421 | padding-bottom: 0.5rem; 422 | color: var(--bs-secondary-color); 423 | text-align: left; 424 | } 425 | 426 | th { 427 | text-align: inherit; 428 | text-align: -webkit-match-parent; 429 | } 430 | 431 | thead, 432 | tbody, 433 | tfoot, 434 | tr, 435 | td, 436 | th { 437 | border-color: inherit; 438 | border-style: solid; 439 | border-width: 0; 440 | } 441 | 442 | label { 443 | display: inline-block; 444 | } 445 | 446 | button { 447 | border-radius: 0; 448 | } 449 | 450 | button:focus:not(:focus-visible) { 451 | outline: 0; 452 | } 453 | 454 | input, 455 | button, 456 | select, 457 | optgroup, 458 | textarea { 459 | margin: 0; 460 | font-family: inherit; 461 | font-size: inherit; 462 | line-height: inherit; 463 | } 464 | 465 | button, 466 | select { 467 | text-transform: none; 468 | } 469 | 470 | [role=button] { 471 | cursor: pointer; 472 | } 473 | 474 | select { 475 | word-wrap: normal; 476 | } 477 | select:disabled { 478 | opacity: 1; 479 | } 480 | 481 | [list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { 482 | display: none !important; 483 | } 484 | 485 | button, 486 | [type=button], 487 | [type=reset], 488 | [type=submit] { 489 | -webkit-appearance: button; 490 | } 491 | button:not(:disabled), 492 | [type=button]:not(:disabled), 493 | [type=reset]:not(:disabled), 494 | [type=submit]:not(:disabled) { 495 | cursor: pointer; 496 | } 497 | 498 | ::-moz-focus-inner { 499 | padding: 0; 500 | border-style: none; 501 | } 502 | 503 | textarea { 504 | resize: vertical; 505 | } 506 | 507 | fieldset { 508 | min-width: 0; 509 | padding: 0; 510 | margin: 0; 511 | border: 0; 512 | } 513 | 514 | legend { 515 | float: left; 516 | width: 100%; 517 | padding: 0; 518 | margin-bottom: 0.5rem; 519 | font-size: calc(1.275rem + 0.3vw); 520 | line-height: inherit; 521 | } 522 | @media (min-width: 1200px) { 523 | legend { 524 | font-size: 1.5rem; 525 | } 526 | } 527 | legend + * { 528 | clear: left; 529 | } 530 | 531 | ::-webkit-datetime-edit-fields-wrapper, 532 | ::-webkit-datetime-edit-text, 533 | ::-webkit-datetime-edit-minute, 534 | ::-webkit-datetime-edit-hour-field, 535 | ::-webkit-datetime-edit-day-field, 536 | ::-webkit-datetime-edit-month-field, 537 | ::-webkit-datetime-edit-year-field { 538 | padding: 0; 539 | } 540 | 541 | ::-webkit-inner-spin-button { 542 | height: auto; 543 | } 544 | 545 | [type=search] { 546 | -webkit-appearance: textfield; 547 | outline-offset: -2px; 548 | } 549 | 550 | /* rtl:raw: 551 | [type="tel"], 552 | [type="url"], 553 | [type="email"], 554 | [type="number"] { 555 | direction: ltr; 556 | } 557 | */ 558 | ::-webkit-search-decoration { 559 | -webkit-appearance: none; 560 | } 561 | 562 | ::-webkit-color-swatch-wrapper { 563 | padding: 0; 564 | } 565 | 566 | ::-webkit-file-upload-button { 567 | font: inherit; 568 | -webkit-appearance: button; 569 | } 570 | 571 | ::file-selector-button { 572 | font: inherit; 573 | -webkit-appearance: button; 574 | } 575 | 576 | output { 577 | display: inline-block; 578 | } 579 | 580 | iframe { 581 | border: 0; 582 | } 583 | 584 | summary { 585 | display: list-item; 586 | cursor: pointer; 587 | } 588 | 589 | progress { 590 | vertical-align: baseline; 591 | } 592 | 593 | [hidden] { 594 | display: none !important; 595 | } 596 | 597 | /*# sourceMappingURL=bootstrap-reboot.css.map */ -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.3.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2024 The Bootstrap Authors 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 6 | /*# sourceMappingURL=bootstrap-reboot.min.css.map */ -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.3.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2024 The Bootstrap Authors 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */ 6 | :root, 7 | [data-bs-theme=light] { 8 | --bs-blue: #0d6efd; 9 | --bs-indigo: #6610f2; 10 | --bs-purple: #6f42c1; 11 | --bs-pink: #d63384; 12 | --bs-red: #dc3545; 13 | --bs-orange: #fd7e14; 14 | --bs-yellow: #ffc107; 15 | --bs-green: #198754; 16 | --bs-teal: #20c997; 17 | --bs-cyan: #0dcaf0; 18 | --bs-black: #000; 19 | --bs-white: #fff; 20 | --bs-gray: #6c757d; 21 | --bs-gray-dark: #343a40; 22 | --bs-gray-100: #f8f9fa; 23 | --bs-gray-200: #e9ecef; 24 | --bs-gray-300: #dee2e6; 25 | --bs-gray-400: #ced4da; 26 | --bs-gray-500: #adb5bd; 27 | --bs-gray-600: #6c757d; 28 | --bs-gray-700: #495057; 29 | --bs-gray-800: #343a40; 30 | --bs-gray-900: #212529; 31 | --bs-primary: #0d6efd; 32 | --bs-secondary: #6c757d; 33 | --bs-success: #198754; 34 | --bs-info: #0dcaf0; 35 | --bs-warning: #ffc107; 36 | --bs-danger: #dc3545; 37 | --bs-light: #f8f9fa; 38 | --bs-dark: #212529; 39 | --bs-primary-rgb: 13, 110, 253; 40 | --bs-secondary-rgb: 108, 117, 125; 41 | --bs-success-rgb: 25, 135, 84; 42 | --bs-info-rgb: 13, 202, 240; 43 | --bs-warning-rgb: 255, 193, 7; 44 | --bs-danger-rgb: 220, 53, 69; 45 | --bs-light-rgb: 248, 249, 250; 46 | --bs-dark-rgb: 33, 37, 41; 47 | --bs-primary-text-emphasis: #052c65; 48 | --bs-secondary-text-emphasis: #2b2f32; 49 | --bs-success-text-emphasis: #0a3622; 50 | --bs-info-text-emphasis: #055160; 51 | --bs-warning-text-emphasis: #664d03; 52 | --bs-danger-text-emphasis: #58151c; 53 | --bs-light-text-emphasis: #495057; 54 | --bs-dark-text-emphasis: #495057; 55 | --bs-primary-bg-subtle: #cfe2ff; 56 | --bs-secondary-bg-subtle: #e2e3e5; 57 | --bs-success-bg-subtle: #d1e7dd; 58 | --bs-info-bg-subtle: #cff4fc; 59 | --bs-warning-bg-subtle: #fff3cd; 60 | --bs-danger-bg-subtle: #f8d7da; 61 | --bs-light-bg-subtle: #fcfcfd; 62 | --bs-dark-bg-subtle: #ced4da; 63 | --bs-primary-border-subtle: #9ec5fe; 64 | --bs-secondary-border-subtle: #c4c8cb; 65 | --bs-success-border-subtle: #a3cfbb; 66 | --bs-info-border-subtle: #9eeaf9; 67 | --bs-warning-border-subtle: #ffe69c; 68 | --bs-danger-border-subtle: #f1aeb5; 69 | --bs-light-border-subtle: #e9ecef; 70 | --bs-dark-border-subtle: #adb5bd; 71 | --bs-white-rgb: 255, 255, 255; 72 | --bs-black-rgb: 0, 0, 0; 73 | --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 74 | --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 75 | --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); 76 | --bs-body-font-family: var(--bs-font-sans-serif); 77 | --bs-body-font-size: 1rem; 78 | --bs-body-font-weight: 400; 79 | --bs-body-line-height: 1.5; 80 | --bs-body-color: #212529; 81 | --bs-body-color-rgb: 33, 37, 41; 82 | --bs-body-bg: #fff; 83 | --bs-body-bg-rgb: 255, 255, 255; 84 | --bs-emphasis-color: #000; 85 | --bs-emphasis-color-rgb: 0, 0, 0; 86 | --bs-secondary-color: rgba(33, 37, 41, 0.75); 87 | --bs-secondary-color-rgb: 33, 37, 41; 88 | --bs-secondary-bg: #e9ecef; 89 | --bs-secondary-bg-rgb: 233, 236, 239; 90 | --bs-tertiary-color: rgba(33, 37, 41, 0.5); 91 | --bs-tertiary-color-rgb: 33, 37, 41; 92 | --bs-tertiary-bg: #f8f9fa; 93 | --bs-tertiary-bg-rgb: 248, 249, 250; 94 | --bs-heading-color: inherit; 95 | --bs-link-color: #0d6efd; 96 | --bs-link-color-rgb: 13, 110, 253; 97 | --bs-link-decoration: underline; 98 | --bs-link-hover-color: #0a58ca; 99 | --bs-link-hover-color-rgb: 10, 88, 202; 100 | --bs-code-color: #d63384; 101 | --bs-highlight-color: #212529; 102 | --bs-highlight-bg: #fff3cd; 103 | --bs-border-width: 1px; 104 | --bs-border-style: solid; 105 | --bs-border-color: #dee2e6; 106 | --bs-border-color-translucent: rgba(0, 0, 0, 0.175); 107 | --bs-border-radius: 0.375rem; 108 | --bs-border-radius-sm: 0.25rem; 109 | --bs-border-radius-lg: 0.5rem; 110 | --bs-border-radius-xl: 1rem; 111 | --bs-border-radius-xxl: 2rem; 112 | --bs-border-radius-2xl: var(--bs-border-radius-xxl); 113 | --bs-border-radius-pill: 50rem; 114 | --bs-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15); 115 | --bs-box-shadow-sm: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075); 116 | --bs-box-shadow-lg: 0 1rem 3rem rgba(0, 0, 0, 0.175); 117 | --bs-box-shadow-inset: inset 0 1px 2px rgba(0, 0, 0, 0.075); 118 | --bs-focus-ring-width: 0.25rem; 119 | --bs-focus-ring-opacity: 0.25; 120 | --bs-focus-ring-color: rgba(13, 110, 253, 0.25); 121 | --bs-form-valid-color: #198754; 122 | --bs-form-valid-border-color: #198754; 123 | --bs-form-invalid-color: #dc3545; 124 | --bs-form-invalid-border-color: #dc3545; 125 | } 126 | 127 | [data-bs-theme=dark] { 128 | color-scheme: dark; 129 | --bs-body-color: #dee2e6; 130 | --bs-body-color-rgb: 222, 226, 230; 131 | --bs-body-bg: #212529; 132 | --bs-body-bg-rgb: 33, 37, 41; 133 | --bs-emphasis-color: #fff; 134 | --bs-emphasis-color-rgb: 255, 255, 255; 135 | --bs-secondary-color: rgba(222, 226, 230, 0.75); 136 | --bs-secondary-color-rgb: 222, 226, 230; 137 | --bs-secondary-bg: #343a40; 138 | --bs-secondary-bg-rgb: 52, 58, 64; 139 | --bs-tertiary-color: rgba(222, 226, 230, 0.5); 140 | --bs-tertiary-color-rgb: 222, 226, 230; 141 | --bs-tertiary-bg: #2b3035; 142 | --bs-tertiary-bg-rgb: 43, 48, 53; 143 | --bs-primary-text-emphasis: #6ea8fe; 144 | --bs-secondary-text-emphasis: #a7acb1; 145 | --bs-success-text-emphasis: #75b798; 146 | --bs-info-text-emphasis: #6edff6; 147 | --bs-warning-text-emphasis: #ffda6a; 148 | --bs-danger-text-emphasis: #ea868f; 149 | --bs-light-text-emphasis: #f8f9fa; 150 | --bs-dark-text-emphasis: #dee2e6; 151 | --bs-primary-bg-subtle: #031633; 152 | --bs-secondary-bg-subtle: #161719; 153 | --bs-success-bg-subtle: #051b11; 154 | --bs-info-bg-subtle: #032830; 155 | --bs-warning-bg-subtle: #332701; 156 | --bs-danger-bg-subtle: #2c0b0e; 157 | --bs-light-bg-subtle: #343a40; 158 | --bs-dark-bg-subtle: #1a1d20; 159 | --bs-primary-border-subtle: #084298; 160 | --bs-secondary-border-subtle: #41464b; 161 | --bs-success-border-subtle: #0f5132; 162 | --bs-info-border-subtle: #087990; 163 | --bs-warning-border-subtle: #997404; 164 | --bs-danger-border-subtle: #842029; 165 | --bs-light-border-subtle: #495057; 166 | --bs-dark-border-subtle: #343a40; 167 | --bs-heading-color: inherit; 168 | --bs-link-color: #6ea8fe; 169 | --bs-link-hover-color: #8bb9fe; 170 | --bs-link-color-rgb: 110, 168, 254; 171 | --bs-link-hover-color-rgb: 139, 185, 254; 172 | --bs-code-color: #e685b5; 173 | --bs-highlight-color: #dee2e6; 174 | --bs-highlight-bg: #664d03; 175 | --bs-border-color: #495057; 176 | --bs-border-color-translucent: rgba(255, 255, 255, 0.15); 177 | --bs-form-valid-color: #75b798; 178 | --bs-form-valid-border-color: #75b798; 179 | --bs-form-invalid-color: #ea868f; 180 | --bs-form-invalid-border-color: #ea868f; 181 | } 182 | 183 | *, 184 | *::before, 185 | *::after { 186 | box-sizing: border-box; 187 | } 188 | 189 | @media (prefers-reduced-motion: no-preference) { 190 | :root { 191 | scroll-behavior: smooth; 192 | } 193 | } 194 | 195 | body { 196 | margin: 0; 197 | font-family: var(--bs-body-font-family); 198 | font-size: var(--bs-body-font-size); 199 | font-weight: var(--bs-body-font-weight); 200 | line-height: var(--bs-body-line-height); 201 | color: var(--bs-body-color); 202 | text-align: var(--bs-body-text-align); 203 | background-color: var(--bs-body-bg); 204 | -webkit-text-size-adjust: 100%; 205 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 206 | } 207 | 208 | hr { 209 | margin: 1rem 0; 210 | color: inherit; 211 | border: 0; 212 | border-top: var(--bs-border-width) solid; 213 | opacity: 0.25; 214 | } 215 | 216 | h6, h5, h4, h3, h2, h1 { 217 | margin-top: 0; 218 | margin-bottom: 0.5rem; 219 | font-weight: 500; 220 | line-height: 1.2; 221 | color: var(--bs-heading-color); 222 | } 223 | 224 | h1 { 225 | font-size: calc(1.375rem + 1.5vw); 226 | } 227 | @media (min-width: 1200px) { 228 | h1 { 229 | font-size: 2.5rem; 230 | } 231 | } 232 | 233 | h2 { 234 | font-size: calc(1.325rem + 0.9vw); 235 | } 236 | @media (min-width: 1200px) { 237 | h2 { 238 | font-size: 2rem; 239 | } 240 | } 241 | 242 | h3 { 243 | font-size: calc(1.3rem + 0.6vw); 244 | } 245 | @media (min-width: 1200px) { 246 | h3 { 247 | font-size: 1.75rem; 248 | } 249 | } 250 | 251 | h4 { 252 | font-size: calc(1.275rem + 0.3vw); 253 | } 254 | @media (min-width: 1200px) { 255 | h4 { 256 | font-size: 1.5rem; 257 | } 258 | } 259 | 260 | h5 { 261 | font-size: 1.25rem; 262 | } 263 | 264 | h6 { 265 | font-size: 1rem; 266 | } 267 | 268 | p { 269 | margin-top: 0; 270 | margin-bottom: 1rem; 271 | } 272 | 273 | abbr[title] { 274 | -webkit-text-decoration: underline dotted; 275 | text-decoration: underline dotted; 276 | cursor: help; 277 | -webkit-text-decoration-skip-ink: none; 278 | text-decoration-skip-ink: none; 279 | } 280 | 281 | address { 282 | margin-bottom: 1rem; 283 | font-style: normal; 284 | line-height: inherit; 285 | } 286 | 287 | ol, 288 | ul { 289 | padding-right: 2rem; 290 | } 291 | 292 | ol, 293 | ul, 294 | dl { 295 | margin-top: 0; 296 | margin-bottom: 1rem; 297 | } 298 | 299 | ol ol, 300 | ul ul, 301 | ol ul, 302 | ul ol { 303 | margin-bottom: 0; 304 | } 305 | 306 | dt { 307 | font-weight: 700; 308 | } 309 | 310 | dd { 311 | margin-bottom: 0.5rem; 312 | margin-right: 0; 313 | } 314 | 315 | blockquote { 316 | margin: 0 0 1rem; 317 | } 318 | 319 | b, 320 | strong { 321 | font-weight: bolder; 322 | } 323 | 324 | small { 325 | font-size: 0.875em; 326 | } 327 | 328 | mark { 329 | padding: 0.1875em; 330 | color: var(--bs-highlight-color); 331 | background-color: var(--bs-highlight-bg); 332 | } 333 | 334 | sub, 335 | sup { 336 | position: relative; 337 | font-size: 0.75em; 338 | line-height: 0; 339 | vertical-align: baseline; 340 | } 341 | 342 | sub { 343 | bottom: -0.25em; 344 | } 345 | 346 | sup { 347 | top: -0.5em; 348 | } 349 | 350 | a { 351 | color: rgba(var(--bs-link-color-rgb), var(--bs-link-opacity, 1)); 352 | text-decoration: underline; 353 | } 354 | a:hover { 355 | --bs-link-color-rgb: var(--bs-link-hover-color-rgb); 356 | } 357 | 358 | a:not([href]):not([class]), a:not([href]):not([class]):hover { 359 | color: inherit; 360 | text-decoration: none; 361 | } 362 | 363 | pre, 364 | code, 365 | kbd, 366 | samp { 367 | font-family: var(--bs-font-monospace); 368 | font-size: 1em; 369 | } 370 | 371 | pre { 372 | display: block; 373 | margin-top: 0; 374 | margin-bottom: 1rem; 375 | overflow: auto; 376 | font-size: 0.875em; 377 | } 378 | pre code { 379 | font-size: inherit; 380 | color: inherit; 381 | word-break: normal; 382 | } 383 | 384 | code { 385 | font-size: 0.875em; 386 | color: var(--bs-code-color); 387 | word-wrap: break-word; 388 | } 389 | a > code { 390 | color: inherit; 391 | } 392 | 393 | kbd { 394 | padding: 0.1875rem 0.375rem; 395 | font-size: 0.875em; 396 | color: var(--bs-body-bg); 397 | background-color: var(--bs-body-color); 398 | border-radius: 0.25rem; 399 | } 400 | kbd kbd { 401 | padding: 0; 402 | font-size: 1em; 403 | } 404 | 405 | figure { 406 | margin: 0 0 1rem; 407 | } 408 | 409 | img, 410 | svg { 411 | vertical-align: middle; 412 | } 413 | 414 | table { 415 | caption-side: bottom; 416 | border-collapse: collapse; 417 | } 418 | 419 | caption { 420 | padding-top: 0.5rem; 421 | padding-bottom: 0.5rem; 422 | color: var(--bs-secondary-color); 423 | text-align: right; 424 | } 425 | 426 | th { 427 | text-align: inherit; 428 | text-align: -webkit-match-parent; 429 | } 430 | 431 | thead, 432 | tbody, 433 | tfoot, 434 | tr, 435 | td, 436 | th { 437 | border-color: inherit; 438 | border-style: solid; 439 | border-width: 0; 440 | } 441 | 442 | label { 443 | display: inline-block; 444 | } 445 | 446 | button { 447 | border-radius: 0; 448 | } 449 | 450 | button:focus:not(:focus-visible) { 451 | outline: 0; 452 | } 453 | 454 | input, 455 | button, 456 | select, 457 | optgroup, 458 | textarea { 459 | margin: 0; 460 | font-family: inherit; 461 | font-size: inherit; 462 | line-height: inherit; 463 | } 464 | 465 | button, 466 | select { 467 | text-transform: none; 468 | } 469 | 470 | [role=button] { 471 | cursor: pointer; 472 | } 473 | 474 | select { 475 | word-wrap: normal; 476 | } 477 | select:disabled { 478 | opacity: 1; 479 | } 480 | 481 | [list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator { 482 | display: none !important; 483 | } 484 | 485 | button, 486 | [type=button], 487 | [type=reset], 488 | [type=submit] { 489 | -webkit-appearance: button; 490 | } 491 | button:not(:disabled), 492 | [type=button]:not(:disabled), 493 | [type=reset]:not(:disabled), 494 | [type=submit]:not(:disabled) { 495 | cursor: pointer; 496 | } 497 | 498 | ::-moz-focus-inner { 499 | padding: 0; 500 | border-style: none; 501 | } 502 | 503 | textarea { 504 | resize: vertical; 505 | } 506 | 507 | fieldset { 508 | min-width: 0; 509 | padding: 0; 510 | margin: 0; 511 | border: 0; 512 | } 513 | 514 | legend { 515 | float: right; 516 | width: 100%; 517 | padding: 0; 518 | margin-bottom: 0.5rem; 519 | font-size: calc(1.275rem + 0.3vw); 520 | line-height: inherit; 521 | } 522 | @media (min-width: 1200px) { 523 | legend { 524 | font-size: 1.5rem; 525 | } 526 | } 527 | legend + * { 528 | clear: right; 529 | } 530 | 531 | ::-webkit-datetime-edit-fields-wrapper, 532 | ::-webkit-datetime-edit-text, 533 | ::-webkit-datetime-edit-minute, 534 | ::-webkit-datetime-edit-hour-field, 535 | ::-webkit-datetime-edit-day-field, 536 | ::-webkit-datetime-edit-month-field, 537 | ::-webkit-datetime-edit-year-field { 538 | padding: 0; 539 | } 540 | 541 | ::-webkit-inner-spin-button { 542 | height: auto; 543 | } 544 | 545 | [type=search] { 546 | -webkit-appearance: textfield; 547 | outline-offset: -2px; 548 | } 549 | 550 | [type="tel"], 551 | [type="url"], 552 | [type="email"], 553 | [type="number"] { 554 | direction: ltr; 555 | } 556 | ::-webkit-search-decoration { 557 | -webkit-appearance: none; 558 | } 559 | 560 | ::-webkit-color-swatch-wrapper { 561 | padding: 0; 562 | } 563 | 564 | ::-webkit-file-upload-button { 565 | font: inherit; 566 | -webkit-appearance: button; 567 | } 568 | 569 | ::file-selector-button { 570 | font: inherit; 571 | -webkit-appearance: button; 572 | } 573 | 574 | output { 575 | display: inline-block; 576 | } 577 | 578 | iframe { 579 | border: 0; 580 | } 581 | 582 | summary { 583 | display: list-item; 584 | cursor: pointer; 585 | } 586 | 587 | progress { 588 | vertical-align: baseline; 589 | } 590 | 591 | [hidden] { 592 | display: none !important; 593 | } 594 | /*# sourceMappingURL=bootstrap-reboot.rtl.css.map */ -------------------------------------------------------------------------------- /SampleSites/BlazorWasmApp1/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Reboot v5.3.3 (https://getbootstrap.com/) 3 | * Copyright 2011-2024 The Bootstrap Authors 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) 5 | */:root,[data-bs-theme=light]{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-black:#000;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-primary-text-emphasis:#052c65;--bs-secondary-text-emphasis:#2b2f32;--bs-success-text-emphasis:#0a3622;--bs-info-text-emphasis:#055160;--bs-warning-text-emphasis:#664d03;--bs-danger-text-emphasis:#58151c;--bs-light-text-emphasis:#495057;--bs-dark-text-emphasis:#495057;--bs-primary-bg-subtle:#cfe2ff;--bs-secondary-bg-subtle:#e2e3e5;--bs-success-bg-subtle:#d1e7dd;--bs-info-bg-subtle:#cff4fc;--bs-warning-bg-subtle:#fff3cd;--bs-danger-bg-subtle:#f8d7da;--bs-light-bg-subtle:#fcfcfd;--bs-dark-bg-subtle:#ced4da;--bs-primary-border-subtle:#9ec5fe;--bs-secondary-border-subtle:#c4c8cb;--bs-success-border-subtle:#a3cfbb;--bs-info-border-subtle:#9eeaf9;--bs-warning-border-subtle:#ffe69c;--bs-danger-border-subtle:#f1aeb5;--bs-light-border-subtle:#e9ecef;--bs-dark-border-subtle:#adb5bd;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans","Liberation Sans",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-color-rgb:33,37,41;--bs-body-bg:#fff;--bs-body-bg-rgb:255,255,255;--bs-emphasis-color:#000;--bs-emphasis-color-rgb:0,0,0;--bs-secondary-color:rgba(33, 37, 41, 0.75);--bs-secondary-color-rgb:33,37,41;--bs-secondary-bg:#e9ecef;--bs-secondary-bg-rgb:233,236,239;--bs-tertiary-color:rgba(33, 37, 41, 0.5);--bs-tertiary-color-rgb:33,37,41;--bs-tertiary-bg:#f8f9fa;--bs-tertiary-bg-rgb:248,249,250;--bs-heading-color:inherit;--bs-link-color:#0d6efd;--bs-link-color-rgb:13,110,253;--bs-link-decoration:underline;--bs-link-hover-color:#0a58ca;--bs-link-hover-color-rgb:10,88,202;--bs-code-color:#d63384;--bs-highlight-color:#212529;--bs-highlight-bg:#fff3cd;--bs-border-width:1px;--bs-border-style:solid;--bs-border-color:#dee2e6;--bs-border-color-translucent:rgba(0, 0, 0, 0.175);--bs-border-radius:0.375rem;--bs-border-radius-sm:0.25rem;--bs-border-radius-lg:0.5rem;--bs-border-radius-xl:1rem;--bs-border-radius-xxl:2rem;--bs-border-radius-2xl:var(--bs-border-radius-xxl);--bs-border-radius-pill:50rem;--bs-box-shadow:0 0.5rem 1rem rgba(0, 0, 0, 0.15);--bs-box-shadow-sm:0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);--bs-box-shadow-lg:0 1rem 3rem rgba(0, 0, 0, 0.175);--bs-box-shadow-inset:inset 0 1px 2px rgba(0, 0, 0, 0.075);--bs-focus-ring-width:0.25rem;--bs-focus-ring-opacity:0.25;--bs-focus-ring-color:rgba(13, 110, 253, 0.25);--bs-form-valid-color:#198754;--bs-form-valid-border-color:#198754;--bs-form-invalid-color:#dc3545;--bs-form-invalid-border-color:#dc3545}[data-bs-theme=dark]{color-scheme:dark;--bs-body-color:#dee2e6;--bs-body-color-rgb:222,226,230;--bs-body-bg:#212529;--bs-body-bg-rgb:33,37,41;--bs-emphasis-color:#fff;--bs-emphasis-color-rgb:255,255,255;--bs-secondary-color:rgba(222, 226, 230, 0.75);--bs-secondary-color-rgb:222,226,230;--bs-secondary-bg:#343a40;--bs-secondary-bg-rgb:52,58,64;--bs-tertiary-color:rgba(222, 226, 230, 0.5);--bs-tertiary-color-rgb:222,226,230;--bs-tertiary-bg:#2b3035;--bs-tertiary-bg-rgb:43,48,53;--bs-primary-text-emphasis:#6ea8fe;--bs-secondary-text-emphasis:#a7acb1;--bs-success-text-emphasis:#75b798;--bs-info-text-emphasis:#6edff6;--bs-warning-text-emphasis:#ffda6a;--bs-danger-text-emphasis:#ea868f;--bs-light-text-emphasis:#f8f9fa;--bs-dark-text-emphasis:#dee2e6;--bs-primary-bg-subtle:#031633;--bs-secondary-bg-subtle:#161719;--bs-success-bg-subtle:#051b11;--bs-info-bg-subtle:#032830;--bs-warning-bg-subtle:#332701;--bs-danger-bg-subtle:#2c0b0e;--bs-light-bg-subtle:#343a40;--bs-dark-bg-subtle:#1a1d20;--bs-primary-border-subtle:#084298;--bs-secondary-border-subtle:#41464b;--bs-success-border-subtle:#0f5132;--bs-info-border-subtle:#087990;--bs-warning-border-subtle:#997404;--bs-danger-border-subtle:#842029;--bs-light-border-subtle:#495057;--bs-dark-border-subtle:#343a40;--bs-heading-color:inherit;--bs-link-color:#6ea8fe;--bs-link-hover-color:#8bb9fe;--bs-link-color-rgb:110,168,254;--bs-link-hover-color-rgb:139,185,254;--bs-code-color:#e685b5;--bs-highlight-color:#dee2e6;--bs-highlight-bg:#664d03;--bs-border-color:#495057;--bs-border-color-translucent:rgba(255, 255, 255, 0.15);--bs-form-valid-color:#75b798;--bs-form-valid-border-color:#75b798;--bs-form-invalid-color:#ea868f;--bs-form-invalid-border-color:#ea868f}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;border:0;border-top:var(--bs-border-width) solid;opacity:.25}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2;color:var(--bs-heading-color)}h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){h1{font-size:2.5rem}}h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){h2{font-size:2rem}}h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){h3{font-size:1.75rem}}h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){h4{font-size:1.5rem}}h5{font-size:1.25rem}h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-right:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-right:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:.875em}mark{padding:.1875em;color:var(--bs-highlight-color);background-color:var(--bs-highlight-bg)}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:rgba(var(--bs-link-color-rgb),var(--bs-link-opacity,1));text-decoration:underline}a:hover{--bs-link-color-rgb:var(--bs-link-hover-color-rgb)}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:var(--bs-code-color);word-wrap:break-word}a>code{color:inherit}kbd{padding:.1875rem .375rem;font-size:.875em;color:var(--bs-body-bg);background-color:var(--bs-body-color);border-radius:.25rem}kbd kbd{padding:0;font-size:1em}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:var(--bs-secondary-color);text-align:right}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]:not([type=date]):not([type=datetime-local]):not([type=month]):not([type=week]):not([type=time])::-webkit-calendar-picker-indicator{display:none!important}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:right;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:right}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=email],[type=number],[type=tel],[type=url]{direction:ltr}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}::file-selector-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important} 6 | /*# sourceMappingURL=bootstrap-reboot.rtl.min.css.map */ -------------------------------------------------------------------------------- /THIRDPARTYNOTICES.txt: -------------------------------------------------------------------------------- 1 | MD2RazorGenerator uses third-party material as listed below. 2 | The attached notices are provided for informational purposes only. 3 | 4 | Notice for Markdig 5 | ------------------------------- 6 | 7 | The BSD 2-Clause "Simplified" License 8 | 9 | Copyright (c) 2018-2019, Alexandre Mutel 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without modification 13 | , are permitted provided that the following conditions are met: 14 | 15 | 1. Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | 2. Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | ------------------------------- 34 | 35 | Notice for YamlDotNet 36 | ------------------------------- 37 | 38 | The MIT License (MIT) 39 | 40 | Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Antoine Aubry and contributors 41 | 42 | Permission is hereby granted, free of charge, to any person obtaining a copy of 43 | this software and associated documentation files (the "Software"), to deal in 44 | the Software without restriction, including without limitation the rights to 45 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 46 | of the Software, and to permit persons to whom the Software is furnished to do 47 | so, subject to the following conditions: 48 | 49 | The above copyright notice and this permission notice shall be included in all 50 | copies or substantial portions of the Software. 51 | 52 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 53 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 54 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 55 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 56 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 57 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 58 | SOFTWARE. 59 | -------------------------------------------------------------------------------- /VersionInfo.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1.2.2 4 | Copyright © 2025 J.Sakamoto, Mozilla Public License 2.0 5 | J.Sakamoto 6 | A C# source generator for Blazor that converts Markdown files (.md) into Razor components. 7 | 8 | -------------------------------------------------------------------------------- /_dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsakamoto/MD2RazorGenerator/16e48a9fa32beaa7c7bb23d4e266849adb6e917e/_dist/.gitkeep -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------