Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/agents/expert-dotnet-software-engineer.agent.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
description: "Provide expert .NET software engineering guidance using modern software design patterns."
name: "Expert .NET software engineer mode instructions"
tools: ["agent", "edit", "execute", "read", "search", "todo", "vscode", "web", "microsoft-docs/*"]
tools: ['vscode', 'execute', 'read', 'edit', 'search', 'web', 'microsoft-docs/*', 'agent', 'todo']
---

# Agent Overview
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ jobs:
shell: pwsh
run: |
dotnet pack ./src/ScissorHands.Plugin.GoogleAnalytics -c Release -o published --include-symbols -p:Version=${{ steps.version.outputs.value }}
dotnet pack ./src/ScissorHands.Plugin.OpenGraph -c Release -o published --include-symbols -p:Version=${{ steps.version.outputs.value }}

- name: Upload Artifact
if: ${{ startsWith(github.ref, 'refs/tags/v') && matrix.os == 'ubuntu-latest' }}
Expand Down
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ Collection of the official plugins for ScissorHands.NET

## List of Plugins

| Name | Description |
|-------------------------------------------------------------------------|--------------------------------|
| [Google Analytics](./src/ScissorHands.Plugin.GoogleAnalytics/README.md) | Google Analytics script plugin |
| Name | Description |
|-------------------------------------------------------------------------|-------------------------|
| [Google Analytics](./src/ScissorHands.Plugin.GoogleAnalytics/README.md) | Google Analytics plugin |
| [Open Graph](./src/ScissorHands.Plugin.OpenGraph/README.md) | Open Graph plugin |

## Build Your Plugin

Expand Down Expand Up @@ -43,17 +44,17 @@ Collection of the official plugins for ScissorHands.NET
{
public override string Name => "My Awesome ScissorHands Plugin";

public override async Task<ContentDocument> PreMarkdownAsync(ContentDocument document, PluginManifest manifest, CancellationToken cancellationToken = default)
public override async Task<ContentDocument> PreMarkdownAsync(ContentDocument document, PluginManifest plugin, SiteManifest site, CancellationToken cancellationToken = default)
{
// ADD LOGIC HERE
}

public override async Task<ContentDocument> PostMarkdownAsync(ContentDocument document, PluginManifest manifest, CancellationToken cancellationToken = default)
public override async Task<ContentDocument> PostMarkdownAsync(ContentDocument document, PluginManifest plugin, SiteManifest site, CancellationToken cancellationToken = default)
{
// ADD LOGIC HERE
}

public override async Task<string> PostHtmlAsync(string html, ContentDocument document, PluginManifest manifest, CancellationToken cancellationToken = default)
public override async Task<string> PostHtmlAsync(string html, ContentDocument document, PluginManifest plugin, SiteManifest site, CancellationToken cancellationToken = default)
{
// ADD LOGIC HERE
}
Expand Down
30 changes: 30 additions & 0 deletions ScissorHandsPlugins.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScissorHands.Plugin.GoogleAnalytics", "src\ScissorHands.Plugin.GoogleAnalytics\ScissorHands.Plugin.GoogleAnalytics.csproj", "{CB30DD74-0080-424B-A248-E9B2E7746CE2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScissorHands.Plugin.OpenGraph", "src\ScissorHands.Plugin.OpenGraph\ScissorHands.Plugin.OpenGraph.csproj", "{287434CA-3059-457C-BCB5-F3C4E1166942}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C88DD14-F956-CE84-757C-A364CCF449FC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScissorHands.Plugin.GoogleAnalytics.Tests", "test\ScissorHands.Plugin.GoogleAnalytics.Tests\ScissorHands.Plugin.GoogleAnalytics.Tests.csproj", "{8EDFD210-29DC-433A-970A-FB8BD53CCAFB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ScissorHands.Plugin.OpenGraph.Tests", "test\ScissorHands.Plugin.OpenGraph.Tests\ScissorHands.Plugin.OpenGraph.Tests.csproj", "{D6941307-2596-4D5E-B80D-05AB3E7DD55B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -33,6 +37,18 @@ Global
{CB30DD74-0080-424B-A248-E9B2E7746CE2}.Release|x64.Build.0 = Release|Any CPU
{CB30DD74-0080-424B-A248-E9B2E7746CE2}.Release|x86.ActiveCfg = Release|Any CPU
{CB30DD74-0080-424B-A248-E9B2E7746CE2}.Release|x86.Build.0 = Release|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Debug|Any CPU.Build.0 = Debug|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Debug|x64.ActiveCfg = Debug|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Debug|x64.Build.0 = Debug|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Debug|x86.ActiveCfg = Debug|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Debug|x86.Build.0 = Debug|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Release|Any CPU.ActiveCfg = Release|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Release|Any CPU.Build.0 = Release|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Release|x64.ActiveCfg = Release|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Release|x64.Build.0 = Release|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Release|x86.ActiveCfg = Release|Any CPU
{287434CA-3059-457C-BCB5-F3C4E1166942}.Release|x86.Build.0 = Release|Any CPU
{8EDFD210-29DC-433A-970A-FB8BD53CCAFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8EDFD210-29DC-433A-970A-FB8BD53CCAFB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8EDFD210-29DC-433A-970A-FB8BD53CCAFB}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand All @@ -45,12 +61,26 @@ Global
{8EDFD210-29DC-433A-970A-FB8BD53CCAFB}.Release|x64.Build.0 = Release|Any CPU
{8EDFD210-29DC-433A-970A-FB8BD53CCAFB}.Release|x86.ActiveCfg = Release|Any CPU
{8EDFD210-29DC-433A-970A-FB8BD53CCAFB}.Release|x86.Build.0 = Release|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Debug|x64.ActiveCfg = Debug|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Debug|x64.Build.0 = Debug|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Debug|x86.ActiveCfg = Debug|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Debug|x86.Build.0 = Debug|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Release|Any CPU.Build.0 = Release|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Release|x64.ActiveCfg = Release|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Release|x64.Build.0 = Release|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Release|x86.ActiveCfg = Release|Any CPU
{D6941307-2596-4D5E-B80D-05AB3E7DD55B}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{CB30DD74-0080-424B-A248-E9B2E7746CE2} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{287434CA-3059-457C-BCB5-F3C4E1166942} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B}
{8EDFD210-29DC-433A-970A-FB8BD53CCAFB} = {0C88DD14-F956-CE84-757C-A364CCF449FC}
{D6941307-2596-4D5E-B80D-05AB3E7DD55B} = {0C88DD14-F956-CE84-757C-A364CCF449FC}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
@inherits ScissorHands.Plugin.PluginComponentBase

<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=@MeasurementId"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '@MeasurementId');
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace ScissorHands.Plugin.GoogleAnalytics;

/// <summary>
/// This represents the UI component entity for Google Analytics.
/// </summary>
public partial class GoogleAnalyticsComponent : PluginComponentBase
{
/// <summary>
/// Gets or sets the measurement ID.
/// </summary>
protected string? MeasurementId { get; set; }

/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

if (Plugin is null)
{
return;
}

MeasurementId = Plugin.Options!.TryGetValue("MeasurementId", out var measurementIdValue)
? measurementIdValue as string
: default;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ScissorHands.Core.Manifests;
using ScissorHands.Core.Manifests;
using ScissorHands.Core.Models;

namespace ScissorHands.Plugin.GoogleAnalytics;
Expand All @@ -25,19 +25,19 @@ public class GoogleAnalyticsPlugin : ContentPlugin
public override string Name => "Google Analytics";

/// <inheritdoc />
public override async Task<string> PostHtmlAsync(string html, ContentDocument document, PluginManifest manifest, CancellationToken cancellationToken = default)
public override async Task<string> PostHtmlAsync(string html, ContentDocument document, PluginManifest plugin, SiteManifest site, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();

var measurementId = manifest.Options!.TryGetValue("MeasurementId", out var id)
var measurementId = plugin.Options!.TryGetValue("MeasurementId", out var id)
? id as string
: default;
if (measurementId is null)
{
return html;
}

var script = GOOGLE_ANALYTICS_SCRIPT.Replace("{{MEASUREMENT_ID}}", measurementId);
var script = GOOGLE_ANALYTICS_SCRIPT.Replace("{{MEASUREMENT_ID}}", measurementId, StringComparison.OrdinalIgnoreCase);

html = html.Replace(PLACEHOLDER, $"\n{script}\n", StringComparison.OrdinalIgnoreCase);

Expand Down
27 changes: 23 additions & 4 deletions src/ScissorHands.Plugin.GoogleAnalytics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,30 @@ This plugin renders [Google Analytics](https://analytics.google.com) script.
dotnet add package ScissorHands.Plugin.GoogleAnalytics --prerelease
```

1. Add the placeholder, `<plugin:google-analytics />`, to `MainLayout.razor`.
1. Add a UI component, `<GoogleAnalyticsComponent />` with parameters, to `MainLayout.razor`. **It's strongly advised to place right after the opening `<head>` tag.**

```razor
<GoogleAnalyticsComponent Documents="@Documents" Document="@Document" Plugin="@GoogleAnalyticsPlugin" Theme="@Theme" Site="@Site" />

@code {
protected PluginManifest? GoogleAnalyticsPlugin { get; set; }

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

GoogleAnalyticsPlugin = Plugins?.SingleOrDefault(p => p.Name!.Equals("Google Analytics", StringComparison.OrdinalIgnoreCase));
}
}
```

> **NOTE**: Those `@Documents`, `@Document`, `@Theme` and `@Site` values are inherited, and the `@GoogleAnalyticsPlugin` value is calculated from the `OnInitializedAsync()` method.

1. Alternatively, instead of the `<GoogleAnalyticsComponent />` component, add the placeholder, `<plugin:google-analytics />`, to `MainLayout.razor`. **It's strongly advised to place right after the opening `<head>` tag**.

```html
<!-- Add placeholder below -->
<html>
<head>
<plugin:google-analytics />
</body>
</html>
...
```
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk.Razor">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
Expand Down
4 changes: 4 additions & 0 deletions src/ScissorHands.Plugin.GoogleAnalytics/_Imports.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@using Microsoft.AspNetCore.Components.Web
@using ScissorHands.Plugin

@namespace ScissorHands.Plugin.GoogleAnalytics
22 changes: 22 additions & 0 deletions src/ScissorHands.Plugin.OpenGraph/OpenGraphComponent.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
@inherits ScissorHands.Plugin.PluginComponentBase

<meta property="og:title" content="@ContentTitle" />
<meta property="og:description" content="@ContentDescription" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="@ContentLocale" />
<meta property="og:url" content="@ContentUrl" />
<meta property="og:image" content="@HeroImageUrl" />
<meta property="og:site_name" content="@SiteName" />

<meta name="twitter:card" content="summary_large_image">
@if (string.IsNullOrWhiteSpace(TwitterSiteId) == false)
{
<meta name="twitter:site" content="@TwitterSiteId">
}
@if (string.IsNullOrWhiteSpace(TwitterCreatorId) == false)
{
<meta name="twitter:creator" content="@TwitterCreatorId">
}
<meta name="twitter:title" content="@ContentTitle">
<meta name="twitter:description" content="@ContentDescription">
<meta name="twitter:image" content="@HeroImageUrl">
80 changes: 80 additions & 0 deletions src/ScissorHands.Plugin.OpenGraph/OpenGraphComponent.razor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using ScissorHands.Core.Models;

namespace ScissorHands.Plugin.OpenGraph;

/// <summary>
/// This represents the UI component entity for Open Graph.
/// </summary>
public partial class OpenGraphComponent : PluginComponentBase
{
/// <summary>
/// Gets or sets the content title.
/// </summary>
protected string? ContentTitle { get; set; }

/// <summary>
/// Gets or sets the content description.
/// </summary>
protected string? ContentDescription { get; set; }

/// <summary>
/// Gets or sets the content locale.
/// </summary>
protected string? ContentLocale { get; set; }

/// <summary>
/// Gets or sets the content URL.
/// </summary>
protected string? ContentUrl { get; set; }

/// <summary>
/// Gets or sets the hero image URL.
/// </summary>
protected string? HeroImageUrl { get; set; }

/// <summary>
/// Gets or sets the site name.
/// </summary>
protected string? SiteName { get; set; }

/// <summary>
/// Gets or sets the Twitter site ID.
/// </summary>
protected string? TwitterSiteId { get; set; }

/// <summary>
/// Gets or sets the Twitter creator ID.
/// </summary>
protected string? TwitterCreatorId { get; set; }

/// <inheritdoc />
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

if (Plugin is null)
{
return;
}

TwitterSiteId = Plugin.Options!.TryGetValue("TwitterSiteId", out var siteIdValue)
? siteIdValue as string
: default;
TwitterCreatorId = Plugin.Options!.TryGetValue("TwitterCreatorId", out var creatorIdValue)
? creatorIdValue as string
: default;
TwitterCreatorId = Document?.Metadata.TwitterHandle is null ? TwitterCreatorId : Document.Metadata.TwitterHandle;
if (Documents?.Any() == true || Document?.Kind == ContentKind.Page)
{
TwitterCreatorId = default;
}

ContentTitle = Documents?.Any() == true ? Site!.Title : Document?.Metadata.Title;
ContentDescription = Documents?.Any() == true ? Site!.Description : Document?.Metadata.Description;
ContentLocale = Site!.Locale;

ContentUrl = $"{OpenGraphPluginHelper.GetContentUrl(Document, Site)}";
HeroImageUrl = $"{OpenGraphPluginHelper.GetHeroImageUrl(Document, Site)}";
SiteName = Site!.Title;
}
}
Loading
Loading