[V15] Updated dotnet template for Umbraco Packages with Bellisima (#17108)
* [WIP] Create Umbraco/Bellissima Package
* Removes existing 'UmbracoPackage'
This is because the RCL based one will be the only one going forward
* Rename existing UmbracoPackageRCL to UmbracoPackage
* Drop the mentions of RCL in the identifiers
* CodeQL GitHub Action is complaining due to V15 wanting v9 .NET
* Rename UmbracoPackage template to UmbracoExtension
As this will only scaffold an extension and not other bits for a package such as Nuget, Github Actions & other things needed to be done to ship out a package
* Remove package lock as npm install by the OS should generate this and can differ between Windows, Linux/OSX
* Move JS clientside stuff into a folder called Client
Will allow us to ignore the folder if or when doing a dotnet pack with a rule in CSProj
* Add in .VSCode recommened extensions file to get the useful Lit Extension for completions in VSCode
* For now remove the example dashboard & prop editor
* Add a simple entrypoint
* Fix path for primary output after rename
* Use link suggested from Lotte
* Use backofficeEntryPoint as entryPoint is deprecated
* Update the umbraco-package.json to opt into telemetry as per PR suggestion
* Improve commented code to include a link to docs
* Improve readme from suggestions
* Updates package.json to use latest Vite & TS
Copies the tsconfig from the default scaffolding of vite lit-ts CLI
* Adds the base property suggestion from Jacob & puts in a comment as to what its used for
* Work in progress from hackathon day/afternoon
* Hey-API generating a HTTP Client had changed and was a PITA to figure out what had changed
Things to do for next time:
Include these files if they include --include-samples flag
* constants.ts
* Controllers/
* Composers/
* client/src/api
* client/src/dashboards/
* client/src/entrypoints
Change file contents
* client/src/bundle.manifests.ts
* client/src/package.json (extra dependencies)
* Adds in new property/flag/switch for dotnet new template
* Warren cant spell Whether 🙈
* Update template.json to exclude the sample files if flag is not set/false
* Make SLN happy/build
* Conditional content in files for IncludeExample flag/switch
* Need to include the content otherwise it doesnt get packed by nuget
* Fix the path for the openapi-ts-config.ts file to be included/excluded
* Use the project name from the dotnet new to help name manifests
* Update namespaces so they get updated when dotnet new templatge is run with the --name
* Updated example
* Fix up VS Code recommended extension for Lit VSCode
Should be .json not a .ts
* Fix up build - as we dont use the imported UmbCurrentUserContext
* Remove the relative path to the JSON schema as unable to know path to the web project
* Typo fix as spooted by Rich
* Update templates/UmbracoExtension/.template.config/template.json
Co-authored-by: Lotte Pitcher <LottePitcher@users.noreply.github.com>
* Adds a --site-domain flag/switch to use for setting the domain prefix
Sets a default value of https://localhost:5000
We have no way of knowing what URL/domain the site is running at for the Umbraco website project
* Rename stuff so its not 'example' & only have ping if include-examples is not set
* As agreed with Lotte makes sense we always generate OpenAPI & TS client
* Update umbraco-extension description
* Generic node script to generate the TS client
Checks if it can connect to it first and prompts user to ensure Umbraco site running or perhaps they need to change the URL in package.json for the node script
* Generated API has conditional stuff in now to have just Ping or the more examples based on switch/flag
* Adds symbols safeNamespace and safeName
safeNamespace uses the one built in and then safeName, depends on the cleaned namespace to then use a custom transform (forms) to then use a regex replace on . and _ to ensure we have a nicer name still for namespaces, class names, URL/routes for the swagger etc...
* change to use Umbraco.Extension as sourcename - check \.template.config\readme.md for 'placeholder' guidance
* use '-sd' as shortname for site-domain as otherwise shows up as '-p:d'
* fix typescript build error when not including examples
* use provided name for API description as always being added
* Missing renames of Contrioller stuff with Lotte @ hackathon
* We missed the ctor
* Titlecase the API URLs for Swagger/API Controller
* dashboard tweaks
* Missing [Server] on Whats the Time Modal/UUI-box
---------
Co-authored-by: leekelleher <leekelleher@gmail.com>
Co-authored-by: Lotte Pitcher <LottePitcher@users.noreply.github.com>
Co-authored-by: Lotte Pitcher <github@lottepitcher.co.uk>
(cherry picked from commit 2f4b198ad3)
This commit is contained in:
committed by
Sebastiaan Janssen
parent
6a18aa79e3
commit
60393b7676
@@ -16,10 +16,9 @@
|
||||
<Link>UmbracoProject\Program.cs</Link>
|
||||
<PackagePath>UmbracoProject</PackagePath>
|
||||
</Content>
|
||||
<Content Include="UmbracoPackage\**" Exclude="bin;obj" />
|
||||
<Content Include="UmbracoPackageRcl\**" Exclude="bin;obj" />
|
||||
<Content Include="UmbracoProject\**" Exclude="bin;obj" />
|
||||
<Content Include="UmbracoDockerCompose\**" Exclude="bin;obj"/>
|
||||
<Content Include="UmbracoExtension\**" Exclude="bin;obj" />
|
||||
<Content Include="UmbracoDockerCompose\**" Exclude="bin;obj" />
|
||||
<Content Include="..\src\Umbraco.Web.UI\Views\Partials\blocklist\**">
|
||||
<Link>UmbracoProject\Views\Partials\blocklist\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||
<PackagePath>UmbracoProject\Views\Partials\blocklist</PackagePath>
|
||||
@@ -40,6 +39,13 @@
|
||||
<ItemGroup>
|
||||
<Content Update="**\.template.config\template.json" Pack="false" />
|
||||
</ItemGroup>
|
||||
<!-- Added project references as the sample API in umbraco-extension will fail -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\src\Umbraco.Cms.Api.Common\Umbraco.Cms.Api.Common.csproj" />
|
||||
<ProjectReference Include="..\src\Umbraco.Cms.Api.Management\Umbraco.Cms.Api.Management.csproj" />
|
||||
<ProjectReference Include="..\src\Umbraco.Web.Common\Umbraco.Web.Common.csproj" />
|
||||
<ProjectReference Include="..\src\Umbraco.Web.Website\Umbraco.Web.Website.csproj" />
|
||||
</ItemGroup>
|
||||
<Target Name="GetUpdatedTemplateJsonPackageFiles" BeforeTargets="GenerateNuspec" AfterTargets="GetUmbracoBuildVersion">
|
||||
<ItemGroup>
|
||||
<_TemplateJsonFiles Include="**\.template.config\template.json" Exclude="bin\**;obj\**" />
|
||||
|
||||
@@ -17,6 +17,14 @@
|
||||
"SupportPagesAndViews": {
|
||||
"longName": "support-pages-and-views",
|
||||
"shortName": "s"
|
||||
},
|
||||
"IncludeExample": {
|
||||
"longName": "include-example",
|
||||
"shortName": "ex"
|
||||
},
|
||||
"SiteDomain": {
|
||||
"longName": "site-domain",
|
||||
"shortName": "sd"
|
||||
}
|
||||
}
|
||||
}
|
||||
35
templates/UmbracoExtension/.template.config/ide.host.json
Normal file
35
templates/UmbracoExtension/.template.config/ide.host.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/ide.host.json",
|
||||
"order": 0,
|
||||
"icon": "../../icon.png",
|
||||
"description": {
|
||||
"id": "umbraco-extension",
|
||||
"text": "Umbraco Extension - A Razor Class Library project for building Umbraco extensions."
|
||||
},
|
||||
"symbolInfo": [
|
||||
{
|
||||
"id": "UmbracoVersion",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "SupportPagesAndViews",
|
||||
"isVisible": true,
|
||||
"persistenceScope": "templateGroup"
|
||||
},
|
||||
{
|
||||
"id": "IncludeExample",
|
||||
"isVisible": true,
|
||||
"description": {
|
||||
"text": "Whether to include an example dashboard and supporting code"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "SiteDomain",
|
||||
"isVisible": true,
|
||||
"defaultValue": "https://localhost:5000",
|
||||
"description": {
|
||||
"text": "If using the --include-example then you can supply the domain prefix such as 'https://localhost:5000' to communicate with the Umbraco website for generating the TypeScript OpenAPI client"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
25
templates/UmbracoExtension/.template.config/readme.md
Normal file
25
templates/UmbracoExtension/.template.config/readme.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Customising the umbraco-extension template
|
||||
|
||||
## Source Name
|
||||
|
||||
The source name is set to `Umbraco.Extension`
|
||||
|
||||
The templating engine will rename any folder or file whose name contains `Umbraco.Extension` replacing it with the provided name.
|
||||
|
||||
The templating engine will replace the text in any file as follows:
|
||||
|
||||
- `Umbraco.Extension` with the safe namespace for the provided name
|
||||
- `Umbraco_Extension` with the safe default class name for the provided name
|
||||
- `umbraco.extension` with the safe namespace for the provided name, in lower case
|
||||
- `umbraco_extension` with the safe default class name for the provided name, in lower case
|
||||
|
||||
## Custom Replacements
|
||||
|
||||
The following custom placeholders have been configured in `template.json`:
|
||||
|
||||
- `UmbracoExtension` will be replaced with the safe namespace but without . or _
|
||||
- `umbracoextension` will be replaced with the safe namespace but without . or _ , in lower case
|
||||
- `umbraco-extension` will be replaced with the kebab case transform of the provided name
|
||||
- `Umbraco Extension` will be replaced with a 'friendly' version of the provided name, e.g. MyProject > My Project. NB it will render a trailing space so you don't need to add one.
|
||||
|
||||
The first three custom placeholders have been configured to replace the text in both files and filenames.
|
||||
170
templates/UmbracoExtension/.template.config/template.json
Normal file
170
templates/UmbracoExtension/.template.config/template.json
Normal file
@@ -0,0 +1,170 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/template.json",
|
||||
"author": "Umbraco HQ",
|
||||
"classifications": [
|
||||
"Web",
|
||||
"CMS",
|
||||
"Umbraco",
|
||||
"Extension",
|
||||
"Plugin",
|
||||
"Razor Class Library"
|
||||
],
|
||||
"name": "Umbraco Extension",
|
||||
"description": "A Razor Class Library project for building Umbraco extensions.",
|
||||
"groupIdentity": "Umbraco.Templates.UmbracoExtension",
|
||||
"identity": "Umbraco.Templates.UmbracoExtension",
|
||||
"shortName": "umbraco-extension",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"sourceName": "Umbraco.Extension",
|
||||
"defaultName": "Umbraco.Extension",
|
||||
"preferNameDirectory": true,
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"displayName": "Framework",
|
||||
"description": "The target framework for the project.",
|
||||
"type": "parameter",
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"displayName": ".NET 9.0",
|
||||
"description": "Target net9.0",
|
||||
"choice": "net9.0"
|
||||
}
|
||||
],
|
||||
"defaultValue": "net9.0",
|
||||
"replaces": "net9.0"
|
||||
},
|
||||
"UmbracoVersion": {
|
||||
"displayName": "Umbraco version",
|
||||
"description": "The version of Umbraco.Cms to add as PackageReference. By default it installs the latest non pre-release version",
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"defaultValue": "*",
|
||||
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
|
||||
},
|
||||
"SkipRestore": {
|
||||
"displayName": "Skip restore",
|
||||
"description": "If specified, skips the automatic restore of the project on create.",
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"SupportPagesAndViews": {
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false",
|
||||
"displayName": "Support pages and views",
|
||||
"description": "Whether to support adding traditional Razor pages and Views to this library."
|
||||
},
|
||||
"KebabCasedName": {
|
||||
"type": "derived",
|
||||
"valueSource": "name",
|
||||
"replaces": "umbraco-extension",
|
||||
"fileRename": "umbraco-extension",
|
||||
"valueTransform": "kebabCase"
|
||||
},
|
||||
"SafeName": {
|
||||
"type": "derived",
|
||||
"valueSource": "name",
|
||||
"valueTransform": "safe_namespace"
|
||||
},
|
||||
"SafeCleanName": {
|
||||
"type": "derived",
|
||||
"valueSource": "SafeName",
|
||||
"replaces": "UmbracoExtension",
|
||||
"fileRename": "UmbracoExtension",
|
||||
"valueTransform": "removePunctuation"
|
||||
},
|
||||
"SafeCleanNameLower": {
|
||||
"type": "derived",
|
||||
"valueSource": "SafeCleanName",
|
||||
"replaces": "umbracoextension",
|
||||
"fileRename": "umbracoextension",
|
||||
"valueTransform": "lowerCase"
|
||||
},
|
||||
"SafeCleanNameFriendly": {
|
||||
"type": "derived",
|
||||
"valueSource": "SafeCleanName",
|
||||
"replaces": "Umbraco Extension",
|
||||
"valueTransform": "pascalCaseToSpaces"
|
||||
},
|
||||
"IncludeExample": {
|
||||
"displayName": "Include Example Code",
|
||||
"description": "Whether to include an example dashboard and other code to get started with.",
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"SiteDomain": {
|
||||
"displayName": "Site Domain",
|
||||
"description": "If using the --include-example then you can supply the domain prefix such as 'https://localhost:5000' to communicate with the Umbraco website for generating the TypeScript OpenAPI client",
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"defaultValue": "https://localhost:5000",
|
||||
"replaces": "https://localhost:44339"
|
||||
}
|
||||
},
|
||||
"forms": {
|
||||
"removePunctuation": {
|
||||
"identifier": "replace",
|
||||
"pattern": "[\\._]",
|
||||
"replacement": ""
|
||||
},
|
||||
"pascalCaseToSpaces": {
|
||||
"identifier": "replace",
|
||||
"pattern": "([A-Z][a-z]+)",
|
||||
"replacement": "$1 "
|
||||
}
|
||||
},
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"path": "Umbraco.Extension.csproj"
|
||||
}
|
||||
],
|
||||
"postActions": [
|
||||
{
|
||||
"id": "restore",
|
||||
"condition": "(!SkipRestore)",
|
||||
"description": "Restore NuGet packages required by this project.",
|
||||
"manualInstructions": [
|
||||
{
|
||||
"text": "Run 'dotnet restore'"
|
||||
}
|
||||
],
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
},
|
||||
{
|
||||
"actionId": "3A7C4B45-1F5D-4A30-959A-51B88E82B5D2",
|
||||
"args": {
|
||||
"executable": "powershell",
|
||||
"args": "cd Client;npm install;npm run build;",
|
||||
"redirectStandardError": false,
|
||||
"redirectStandardOutput": false
|
||||
},
|
||||
"manualInstructions": [
|
||||
{
|
||||
"text": "From the 'Client' folder run 'npm install' and then 'npm run build'"
|
||||
}
|
||||
],
|
||||
"continueOnError": true,
|
||||
"description ": "Installs node modules"
|
||||
}
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"modifiers": [
|
||||
{
|
||||
"condition": "(!IncludeExample)",
|
||||
"exclude": [
|
||||
"[Cc]lient/src/dashboards/**",
|
||||
"[Cc]lient/src/api/schemas.gen.ts"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
5
templates/UmbracoExtension/Client/.vscode/extensions.json
vendored
Normal file
5
templates/UmbracoExtension/Client/.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"runem.lit-plugin"
|
||||
]
|
||||
}
|
||||
21
templates/UmbracoExtension/Client/package.json
Normal file
21
templates/UmbracoExtension/Client/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "umbraco-extension",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"watch": "tsc && vite build --watch",
|
||||
"build": "tsc && vite build",
|
||||
"generate-client": "node scripts/generate-openapi.js https://localhost:44339/umbraco/swagger/umbracoextension/swagger.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@hey-api/client-fetch": "^0.4.2",
|
||||
"@hey-api/openapi-ts": "^0.53.11",
|
||||
"@umbraco-cms/backoffice": "^UMBRACO_VERSION_FROM_TEMPLATE",
|
||||
"chalk": "^5.3.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"node-fetch": "^3.3.2",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^5.4.9"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"id": "Umbraco.Extension",
|
||||
"name": "Umbraco.Extension",
|
||||
"version": "0.0.0",
|
||||
"allowPackageTelemetry": true,
|
||||
"extensions": [
|
||||
{
|
||||
"name": "Umbraco ExtensionBundle",
|
||||
"alias": "Umbraco.Extension.Bundle",
|
||||
"type": "bundle",
|
||||
"js": "/App_Plugins/UmbracoExtension/umbraco-extension.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import fetch from 'node-fetch';
|
||||
import chalk from 'chalk';
|
||||
import { createClient } from '@hey-api/openapi-ts';
|
||||
|
||||
// Start notifying user we are generating the TypeScript client
|
||||
console.log(chalk.green("Generating OpenAPI client..."));
|
||||
|
||||
const swaggerUrl = process.argv[2];
|
||||
if (swaggerUrl === undefined) {
|
||||
console.error(chalk.red(`ERROR: Missing URL to OpenAPI spec`));
|
||||
console.error(`Please provide the URL to the OpenAPI spec as the first argument found in ${chalk.yellow('package.json')}`);
|
||||
console.error(`Example: node generate-openapi.js ${chalk.yellow('https://localhost:44331/umbraco/swagger/REPLACE_ME/swagger.json')}`);
|
||||
process.exit();
|
||||
}
|
||||
|
||||
// Needed to ignore self-signed certificates from running Umbraco on https on localhost
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
|
||||
// Start checking to see if we can connect to the OpenAPI spec
|
||||
console.log("Ensure your Umbraco instance is running");
|
||||
console.log(`Fetching OpenAPI definition from ${chalk.yellow(swaggerUrl)}`);
|
||||
|
||||
fetch(swaggerUrl).then(response => {
|
||||
if (!response.ok) {
|
||||
console.error(chalk.red(`ERROR: OpenAPI spec returned with a non OK (200) response: ${response.status} ${response.statusText}`));
|
||||
console.error(`The URL to your Umbraco instance may be wrong or the instance is not running`);
|
||||
console.error(`Please verify or change the URL in the ${chalk.yellow('package.json')} for the script ${chalk.yellow('generate-openapi')}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`OpenAPI spec fetched successfully`);
|
||||
console.log(`Calling ${chalk.yellow('hey-api')} to generate TypeScript client`);
|
||||
|
||||
createClient({
|
||||
client: '@hey-api/client-fetch',
|
||||
input: swaggerUrl,
|
||||
output: 'src/api',
|
||||
services: {
|
||||
asClass: true,
|
||||
}
|
||||
});
|
||||
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(`ERROR: Failed to connect to the OpenAPI spec: ${chalk.red(error.message)}`);
|
||||
console.error(`The URL to your Umbraco instance may be wrong or the instance is not running`);
|
||||
console.error(`Please verify or change the URL in the ${chalk.yellow('package.json')} for the script ${chalk.yellow('generate-openapi')}`);
|
||||
});
|
||||
6
templates/UmbracoExtension/Client/src/api/index.ts
Normal file
6
templates/UmbracoExtension/Client/src/api/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
//#if(IncludeExample)
|
||||
export * from './schemas.gen';
|
||||
//#endif
|
||||
export * from './services.gen';
|
||||
export * from './types.gen';
|
||||
391
templates/UmbracoExtension/Client/src/api/schemas.gen.ts
Normal file
391
templates/UmbracoExtension/Client/src/api/schemas.gen.ts
Normal file
@@ -0,0 +1,391 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export const DocumentGranularPermissionModelSchema = {
|
||||
required: ['context', 'key', 'permission'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
key: {
|
||||
type: 'string',
|
||||
format: 'uuid'
|
||||
},
|
||||
context: {
|
||||
type: 'string',
|
||||
readOnly: true
|
||||
},
|
||||
permission: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
} as const;
|
||||
|
||||
export const ReadOnlyUserGroupModelSchema = {
|
||||
required: ['alias', 'allowedLanguages', 'allowedSections', 'granularPermissions', 'hasAccessToAllLanguages', 'id', 'key', 'name', 'permissions'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
format: 'uuid'
|
||||
},
|
||||
name: {
|
||||
type: 'string'
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
startContentId: {
|
||||
type: 'integer',
|
||||
format: 'int32',
|
||||
nullable: true
|
||||
},
|
||||
startMediaId: {
|
||||
type: 'integer',
|
||||
format: 'int32',
|
||||
nullable: true
|
||||
},
|
||||
alias: {
|
||||
type: 'string'
|
||||
},
|
||||
hasAccessToAllLanguages: {
|
||||
type: 'boolean'
|
||||
},
|
||||
allowedLanguages: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
}
|
||||
},
|
||||
permissions: {
|
||||
uniqueItems: true,
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
granularPermissions: {
|
||||
uniqueItems: true,
|
||||
type: 'array',
|
||||
items: {
|
||||
oneOf: [
|
||||
{
|
||||
'$ref': '#/components/schemas/DocumentGranularPermissionModel'
|
||||
},
|
||||
{
|
||||
'$ref': '#/components/schemas/UnknownTypeGranularPermissionModel'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
allowedSections: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
} as const;
|
||||
|
||||
export const UnknownTypeGranularPermissionModelSchema = {
|
||||
required: ['context', 'permission'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
context: {
|
||||
type: 'string'
|
||||
},
|
||||
permission: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
} as const;
|
||||
|
||||
export const UserGroupModelSchema = {
|
||||
required: ['alias', 'allowedLanguages', 'allowedSections', 'createDate', 'granularPermissions', 'hasAccessToAllLanguages', 'hasIdentity', 'id', 'key', 'permissions', 'updateDate', 'userCount'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
format: 'uuid'
|
||||
},
|
||||
createDate: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
updateDate: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
deleteDate: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
nullable: true
|
||||
},
|
||||
hasIdentity: {
|
||||
type: 'boolean',
|
||||
readOnly: true
|
||||
},
|
||||
startMediaId: {
|
||||
type: 'integer',
|
||||
format: 'int32',
|
||||
nullable: true
|
||||
},
|
||||
startContentId: {
|
||||
type: 'integer',
|
||||
format: 'int32',
|
||||
nullable: true
|
||||
},
|
||||
icon: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
alias: {
|
||||
type: 'string'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
hasAccessToAllLanguages: {
|
||||
type: 'boolean'
|
||||
},
|
||||
permissions: {
|
||||
uniqueItems: true,
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
granularPermissions: {
|
||||
uniqueItems: true,
|
||||
type: 'array',
|
||||
items: {
|
||||
oneOf: [
|
||||
{
|
||||
'$ref': '#/components/schemas/DocumentGranularPermissionModel'
|
||||
},
|
||||
{
|
||||
'$ref': '#/components/schemas/UnknownTypeGranularPermissionModel'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
allowedSections: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
readOnly: true
|
||||
},
|
||||
userCount: {
|
||||
type: 'integer',
|
||||
format: 'int32',
|
||||
readOnly: true
|
||||
},
|
||||
allowedLanguages: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
readOnly: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
} as const;
|
||||
|
||||
export const UserKindModelSchema = {
|
||||
enum: ['Default', 'Api'],
|
||||
type: 'string'
|
||||
} as const;
|
||||
|
||||
export const UserModelSchema = {
|
||||
required: ['allowedSections', 'createDate', 'email', 'failedPasswordAttempts', 'groups', 'hasIdentity', 'id', 'isApproved', 'isLockedOut', 'key', 'kind', 'profileData', 'sessionTimeout', 'updateDate', 'username', 'userState'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
key: {
|
||||
type: 'string',
|
||||
format: 'uuid'
|
||||
},
|
||||
createDate: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
updateDate: {
|
||||
type: 'string',
|
||||
format: 'date-time'
|
||||
},
|
||||
deleteDate: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
nullable: true
|
||||
},
|
||||
hasIdentity: {
|
||||
type: 'boolean',
|
||||
readOnly: true
|
||||
},
|
||||
emailConfirmedDate: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
nullable: true
|
||||
},
|
||||
invitedDate: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
nullable: true
|
||||
},
|
||||
username: {
|
||||
type: 'string'
|
||||
},
|
||||
email: {
|
||||
type: 'string'
|
||||
},
|
||||
rawPasswordValue: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
passwordConfiguration: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
isApproved: {
|
||||
type: 'boolean'
|
||||
},
|
||||
isLockedOut: {
|
||||
type: 'boolean'
|
||||
},
|
||||
lastLoginDate: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
nullable: true
|
||||
},
|
||||
lastPasswordChangeDate: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
nullable: true
|
||||
},
|
||||
lastLockoutDate: {
|
||||
type: 'string',
|
||||
format: 'date-time',
|
||||
nullable: true
|
||||
},
|
||||
failedPasswordAttempts: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
comments: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
userState: {
|
||||
'$ref': '#/components/schemas/UserStateModel'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
allowedSections: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
readOnly: true
|
||||
},
|
||||
profileData: {
|
||||
oneOf: [
|
||||
{
|
||||
'$ref': '#/components/schemas/UserModel'
|
||||
},
|
||||
{
|
||||
'$ref': '#/components/schemas/UserProfileModel'
|
||||
}
|
||||
],
|
||||
readOnly: true
|
||||
},
|
||||
securityStamp: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
avatar: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
sessionTimeout: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
startContentIds: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
nullable: true
|
||||
},
|
||||
startMediaIds: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
nullable: true
|
||||
},
|
||||
language: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
},
|
||||
kind: {
|
||||
'$ref': '#/components/schemas/UserKindModel'
|
||||
},
|
||||
groups: {
|
||||
type: 'array',
|
||||
items: {
|
||||
oneOf: [
|
||||
{
|
||||
'$ref': '#/components/schemas/ReadOnlyUserGroupModel'
|
||||
},
|
||||
{
|
||||
'$ref': '#/components/schemas/UserGroupModel'
|
||||
}
|
||||
]
|
||||
},
|
||||
readOnly: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
} as const;
|
||||
|
||||
export const UserProfileModelSchema = {
|
||||
required: ['id'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'integer',
|
||||
format: 'int32'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
} as const;
|
||||
|
||||
export const UserStateModelSchema = {
|
||||
enum: ['Active', 'Disabled', 'LockedOut', 'Invited', 'Inactive', 'All'],
|
||||
type: 'string'
|
||||
} as const;
|
||||
41
templates/UmbracoExtension/Client/src/api/services.gen.ts
Normal file
41
templates/UmbracoExtension/Client/src/api/services.gen.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
import { createClient, createConfig, type Options } from '@hey-api/client-fetch';
|
||||
//#if(IncludeExample)
|
||||
import type { PingError, PingResponse, WhatsMyNameError, WhatsMyNameResponse, WhatsTheTimeMrWolfError, WhatsTheTimeMrWolfResponse, WhoAmIError, WhoAmIResponse } from './types.gen';
|
||||
//#else
|
||||
import type { PingError, PingResponse } from './types.gen';
|
||||
//#endif
|
||||
|
||||
export const client = createClient(createConfig());
|
||||
|
||||
export class UmbracoExtensionService {
|
||||
public static ping<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<PingResponse, PingError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/ping'
|
||||
});
|
||||
}
|
||||
//#if(IncludeExample)
|
||||
public static whatsMyName<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<WhatsMyNameResponse, WhatsMyNameError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/whatsMyName'
|
||||
});
|
||||
}
|
||||
|
||||
public static whatsTheTimeMrWolf<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<WhatsTheTimeMrWolfResponse, WhatsTheTimeMrWolfError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/whatsTheTimeMrWolf'
|
||||
});
|
||||
}
|
||||
|
||||
public static whoAmI<ThrowOnError extends boolean = false>(options?: Options<unknown, ThrowOnError>) {
|
||||
return (options?.client ?? client).get<WhoAmIResponse, WhoAmIError, ThrowOnError>({
|
||||
...options,
|
||||
url: '/umbraco/umbracoextension/api/v1/whoAmI'
|
||||
});
|
||||
}
|
||||
//#endif
|
||||
}
|
||||
107
templates/UmbracoExtension/Client/src/api/types.gen.ts
Normal file
107
templates/UmbracoExtension/Client/src/api/types.gen.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
//#if(IncludeExample)
|
||||
export type DocumentGranularPermissionModel = {
|
||||
key: string;
|
||||
readonly context: string;
|
||||
permission: string;
|
||||
};
|
||||
|
||||
export type ReadOnlyUserGroupModel = {
|
||||
id: number;
|
||||
key: string;
|
||||
name: string;
|
||||
icon?: (string) | null;
|
||||
startContentId?: (number) | null;
|
||||
startMediaId?: (number) | null;
|
||||
alias: string;
|
||||
hasAccessToAllLanguages: boolean;
|
||||
allowedLanguages: Array<(number)>;
|
||||
permissions: Array<(string)>;
|
||||
granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>;
|
||||
allowedSections: Array<(string)>;
|
||||
};
|
||||
|
||||
export type UnknownTypeGranularPermissionModel = {
|
||||
context: string;
|
||||
permission: string;
|
||||
};
|
||||
|
||||
export type UserGroupModel = {
|
||||
id: number;
|
||||
key: string;
|
||||
createDate: string;
|
||||
updateDate: string;
|
||||
deleteDate?: (string) | null;
|
||||
readonly hasIdentity: boolean;
|
||||
startMediaId?: (number) | null;
|
||||
startContentId?: (number) | null;
|
||||
icon?: (string) | null;
|
||||
alias: string;
|
||||
name?: (string) | null;
|
||||
hasAccessToAllLanguages: boolean;
|
||||
permissions: Array<(string)>;
|
||||
granularPermissions: Array<(DocumentGranularPermissionModel | UnknownTypeGranularPermissionModel)>;
|
||||
readonly allowedSections: Array<(string)>;
|
||||
readonly userCount: number;
|
||||
readonly allowedLanguages: Array<(number)>;
|
||||
};
|
||||
|
||||
export type UserKindModel = 'Default' | 'Api';
|
||||
|
||||
export type UserModel = {
|
||||
id: number;
|
||||
key: string;
|
||||
createDate: string;
|
||||
updateDate: string;
|
||||
deleteDate?: (string) | null;
|
||||
readonly hasIdentity: boolean;
|
||||
emailConfirmedDate?: (string) | null;
|
||||
invitedDate?: (string) | null;
|
||||
username: string;
|
||||
email: string;
|
||||
rawPasswordValue?: (string) | null;
|
||||
passwordConfiguration?: (string) | null;
|
||||
isApproved: boolean;
|
||||
isLockedOut: boolean;
|
||||
lastLoginDate?: (string) | null;
|
||||
lastPasswordChangeDate?: (string) | null;
|
||||
lastLockoutDate?: (string) | null;
|
||||
failedPasswordAttempts: number;
|
||||
comments?: (string) | null;
|
||||
userState: UserStateModel;
|
||||
name?: (string) | null;
|
||||
readonly allowedSections: Array<(string)>;
|
||||
readonly profileData: (UserModel | UserProfileModel);
|
||||
securityStamp?: (string) | null;
|
||||
avatar?: (string) | null;
|
||||
sessionTimeout: number;
|
||||
startContentIds?: Array<(number)> | null;
|
||||
startMediaIds?: Array<(number)> | null;
|
||||
language?: (string) | null;
|
||||
kind: UserKindModel;
|
||||
readonly groups: Array<(ReadOnlyUserGroupModel | UserGroupModel)>;
|
||||
};
|
||||
|
||||
export type UserProfileModel = {
|
||||
id: number;
|
||||
name?: (string) | null;
|
||||
};
|
||||
|
||||
export type UserStateModel = 'Active' | 'Disabled' | 'LockedOut' | 'Invited' | 'Inactive' | 'All';
|
||||
//#endif
|
||||
export type PingResponse = (string);
|
||||
|
||||
export type PingError = (unknown);
|
||||
//#if(IncludeExample)
|
||||
export type WhatsMyNameResponse = (string);
|
||||
|
||||
export type WhatsMyNameError = (unknown);
|
||||
|
||||
export type WhatsTheTimeMrWolfResponse = (string);
|
||||
|
||||
export type WhatsTheTimeMrWolfError = (unknown);
|
||||
|
||||
export type WhoAmIResponse = ((UserModel));
|
||||
|
||||
export type WhoAmIError = (unknown);
|
||||
//#endif
|
||||
13
templates/UmbracoExtension/Client/src/bundle.manifests.ts
Normal file
13
templates/UmbracoExtension/Client/src/bundle.manifests.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { manifests as entrypoints } from './entrypoints/manifest';
|
||||
//#if IncludeExample
|
||||
import { manifests as dashboards } from './dashboards/manifest';
|
||||
//#endif
|
||||
|
||||
// Job of the bundle is to collate all the manifests from different parts of the extension and load other manifests
|
||||
// We load this bundle from umbraco-package.json
|
||||
export const manifests: Array<UmbExtensionManifest> = [
|
||||
...entrypoints,
|
||||
//#if IncludeExample
|
||||
...dashboards,
|
||||
//#endif
|
||||
];
|
||||
@@ -0,0 +1,176 @@
|
||||
import { LitElement, css, html, customElement, state } from "@umbraco-cms/backoffice/external/lit";
|
||||
import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
|
||||
import { UmbracoExtensionService, UserModel } from "../api";
|
||||
import { UUIButtonElement } from "@umbraco-cms/backoffice/external/uui";
|
||||
import { UMB_NOTIFICATION_CONTEXT, UmbNotificationContext } from "@umbraco-cms/backoffice/notification";
|
||||
import { UMB_CURRENT_USER_CONTEXT, UmbCurrentUserModel } from "@umbraco-cms/backoffice/current-user";
|
||||
|
||||
@customElement('example-dashboard')
|
||||
export class ExampleDashboardElement extends UmbElementMixin(LitElement) {
|
||||
|
||||
@state()
|
||||
private _yourName: string | undefined = "Press the button!";
|
||||
|
||||
@state()
|
||||
private _timeFromMrWolf: Date | undefined;
|
||||
|
||||
@state()
|
||||
private _serverUserData: UserModel | undefined = undefined;
|
||||
|
||||
@state()
|
||||
private _contextCurrentUser: UmbCurrentUserModel | undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.consumeContext(UMB_NOTIFICATION_CONTEXT, (notificationContext) => {
|
||||
this.#notificationContext = notificationContext;
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_CURRENT_USER_CONTEXT, (currentUserContext) => {
|
||||
|
||||
// When we have the current user context
|
||||
// We can observe properties from it, such as the current user or perhaps just individual properties
|
||||
// When the currentUser object changes we will get notified and can reset the @state properrty
|
||||
this.observe(currentUserContext.currentUser, (currentUser) => {
|
||||
this._contextCurrentUser = currentUser;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#notificationContext: UmbNotificationContext | undefined = undefined;
|
||||
|
||||
#onClickWhoAmI = async (ev: Event) => {
|
||||
const buttonElement = ev.target as UUIButtonElement;
|
||||
buttonElement.state = "waiting";
|
||||
|
||||
const { data, error } = await UmbracoExtensionService.whoAmI();
|
||||
|
||||
if (error) {
|
||||
buttonElement.state = "failed";
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
this._serverUserData = data;
|
||||
buttonElement.state = "success";
|
||||
}
|
||||
|
||||
if (this.#notificationContext) {
|
||||
this.#notificationContext.peek("warning", {
|
||||
data: {
|
||||
headline: `You are ${this._serverUserData?.name}`,
|
||||
message: `Your email is ${this._serverUserData?.email}`,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#onClickWhatsTheTimeMrWolf = async (ev: Event) => {
|
||||
const buttonElement = ev.target as UUIButtonElement;
|
||||
buttonElement.state = "waiting";
|
||||
|
||||
// Getting a string - should I expect a datetime?!
|
||||
const { data, error } = await UmbracoExtensionService.whatsTheTimeMrWolf();
|
||||
|
||||
if (error) {
|
||||
buttonElement.state = "failed";
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data !== undefined) {
|
||||
this._timeFromMrWolf = new Date(data);
|
||||
buttonElement.state = "success";
|
||||
}
|
||||
}
|
||||
|
||||
#onClickWhatsMyName = async (ev: Event) => {
|
||||
const buttonElement = ev.target as UUIButtonElement;
|
||||
buttonElement.state = "waiting";
|
||||
|
||||
const { data, error } = await UmbracoExtensionService.whatsMyName();
|
||||
|
||||
if (error) {
|
||||
buttonElement.state = "failed";
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this._yourName = data;
|
||||
buttonElement.state = "success";
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<uui-box headline="Who am I?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2><uui-icon name="icon-user"></uui-icon>${this._serverUserData?.email ? this._serverUserData.email : 'Press the button!'}</h2>
|
||||
<ul>
|
||||
${this._serverUserData?.groups.map(group => html`<li>${group.name}</li>`)}
|
||||
</ul>
|
||||
<uui-button color="default" look="primary" @click="${this.#onClickWhoAmI}">
|
||||
Who am I?
|
||||
</uui-button>
|
||||
<p>This endpoint gets your current user from the server and displays your email and list of user groups.
|
||||
It also displays a Notification with your details.</p>
|
||||
</uui-box>
|
||||
|
||||
<uui-box headline="What's my Name?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2><uui-icon name="icon-user"></uui-icon> ${this._yourName }</h2>
|
||||
<uui-button color="default" look="primary" @click="${this.#onClickWhatsMyName}">
|
||||
Whats my name?
|
||||
</uui-button>
|
||||
<p>This endpoint has a forced delay to show the button 'waiting' state for a few seconds before completing the request.</p>
|
||||
</uui-box>
|
||||
|
||||
<uui-box headline="What's the Time?">
|
||||
<div slot="header">[Server]</div>
|
||||
<h2><uui-icon name="icon-alarm-clock"></uui-icon> ${this._timeFromMrWolf ? this._timeFromMrWolf.toLocaleString() : 'Press the button!'}</h2>
|
||||
<uui-button color="default" look="primary" @click="${this.#onClickWhatsTheTimeMrWolf}">
|
||||
Whats the time Mr Wolf?
|
||||
</uui-button>
|
||||
<p>This endpoint gets the current date and time from the server.</p>
|
||||
</uui-box>
|
||||
|
||||
<uui-box headline="Who am I?" class="wide">
|
||||
<div slot="header">[Context]</div>
|
||||
<p>Current user email: <b>${this._contextCurrentUser?.email}</b></p>
|
||||
<p>This is the JSON object available by consuming the 'UMB_CURRENT_USER_CONTEXT' context:</p>
|
||||
<umb-code-block language="json" copy>${JSON.stringify(this._contextCurrentUser, null, 2)}</umb-code-block>
|
||||
</uui-box>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = [
|
||||
css`
|
||||
:host {
|
||||
display: grid;
|
||||
gap: var(--uui-size-layout-1);
|
||||
padding: var(--uui-size-layout-1);
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
uui-box {
|
||||
margin-bottom: var(--uui-size-layout-1);
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin-top:0;
|
||||
}
|
||||
|
||||
.wide {
|
||||
grid-column: span 3;
|
||||
}
|
||||
`];
|
||||
}
|
||||
|
||||
export default ExampleDashboardElement;
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'example-dashboard': ExampleDashboardElement;
|
||||
}
|
||||
}
|
||||
18
templates/UmbracoExtension/Client/src/dashboards/manifest.ts
Normal file
18
templates/UmbracoExtension/Client/src/dashboards/manifest.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const manifests: Array<UmbExtensionManifest> = [
|
||||
{
|
||||
name: "Umbraco ExtensionDashboard",
|
||||
alias: "Umbraco.Extension.Dashboard",
|
||||
type: 'dashboard',
|
||||
js: () => import("./dashboard.element"),
|
||||
meta: {
|
||||
label: "Example Dashboard",
|
||||
pathname: "example-dashboard"
|
||||
},
|
||||
conditions: [
|
||||
{
|
||||
alias: 'Umb.Condition.SectionAlias',
|
||||
match: 'Umb.Section.Content',
|
||||
}
|
||||
],
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
import { UmbEntryPointOnInit, UmbEntryPointOnUnload } from '@umbraco-cms/backoffice/extension-api';
|
||||
//#if IncludeExample
|
||||
import { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
|
||||
import { client } from '../api';
|
||||
//#endif
|
||||
|
||||
// load up the manifests here
|
||||
export const onInit: UmbEntryPointOnInit = (_host, _extensionRegistry) => {
|
||||
|
||||
console.log('Hello from my extension 🎉');
|
||||
//#if IncludeExample
|
||||
// Will use only to add in Open API config with generated TS OpenAPI HTTPS Client
|
||||
// Do the OAuth token handshake stuff
|
||||
_host.consumeContext(UMB_AUTH_CONTEXT, async (authContext) => {
|
||||
|
||||
// Get the token info from Umbraco
|
||||
const config = authContext.getOpenApiConfiguration();
|
||||
|
||||
client.setConfig({
|
||||
baseUrl: config.base,
|
||||
credentials: config.credentials
|
||||
});
|
||||
|
||||
// For every request being made, add the token to the headers
|
||||
// Can't use the setConfig approach above as its set only once and
|
||||
// tokens expire and get refreshed
|
||||
client.interceptors.request.use(async (request, _options) => {
|
||||
const token = await config.token();
|
||||
request.headers.set('Authorization', `Bearer ${token}`);
|
||||
return request;
|
||||
});
|
||||
});
|
||||
//#endif
|
||||
};
|
||||
|
||||
export const onUnload: UmbEntryPointOnUnload = (_host, _extensionRegistry) => {
|
||||
console.log('Goodbye from my extension 👋');
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export const manifests: Array<UmbExtensionManifest> = [
|
||||
{
|
||||
name: "Umbraco ExtensionEntrypoint",
|
||||
alias: "Umbraco.Extension.Entrypoint",
|
||||
type: "backofficeEntryPoint",
|
||||
js: () => import("./entrypoint"),
|
||||
}
|
||||
];
|
||||
26
templates/UmbracoExtension/Client/tsconfig.json
Normal file
26
templates/UmbracoExtension/Client/tsconfig.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"module": "ESNext",
|
||||
"lib": [ "ES2020", "DOM", "DOM.Iterable" ],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
|
||||
"types": [ "@umbraco-cms/backoffice/extension-types" ]
|
||||
},
|
||||
"include": [ "src" ]
|
||||
}
|
||||
17
templates/UmbracoExtension/Client/vite.config.ts
Normal file
17
templates/UmbracoExtension/Client/vite.config.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
lib: {
|
||||
entry: "src/bundle.manifests.ts", // Bundle registers one or more manifests
|
||||
formats: ["es"],
|
||||
fileName: "umbraco-extension",
|
||||
},
|
||||
outDir: "../wwwroot/App_Plugins/UmbracoExtension", // your web component will be saved in this location
|
||||
emptyOutDir: true,
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
external: [/^@umbraco/],
|
||||
},
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Mvc.ApiExplorer;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Umbraco.Cms.Core.Composing;
|
||||
using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Api.Management.OpenApi;
|
||||
using Umbraco.Cms.Api.Common.OpenApi;
|
||||
|
||||
namespace Umbraco.Extension.Composers
|
||||
{
|
||||
public class UmbracoExtensionApiComposer : IComposer
|
||||
{
|
||||
public void Compose(IUmbracoBuilder builder)
|
||||
{
|
||||
|
||||
builder.Services.AddSingleton<IOperationIdHandler, CustomOperationHandler>();
|
||||
|
||||
builder.Services.Configure<SwaggerGenOptions>(opt =>
|
||||
{
|
||||
// Related documentation:
|
||||
// https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api
|
||||
// https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/adding-a-custom-swagger-document
|
||||
// https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/versioning-your-api
|
||||
// https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/access-policies
|
||||
|
||||
// Configure the Swagger generation options
|
||||
// Add in a new Swagger API document solely for our own package that can be browsed via Swagger UI
|
||||
// Along with having a generated swagger JSON file that we can use to auto generate a TypeScript client
|
||||
opt.SwaggerDoc(Constants.ApiName, new OpenApiInfo
|
||||
{
|
||||
Title = "Umbraco ExtensionBackoffice API",
|
||||
Version = "1.0",
|
||||
// Contact = new OpenApiContact
|
||||
// {
|
||||
// Name = "Some Developer",
|
||||
// Email = "you@company.com",
|
||||
// Url = new Uri("https://company.com")
|
||||
// }
|
||||
});
|
||||
|
||||
// Enable Umbraco authentication for the "Example" Swagger document
|
||||
// PR: https://github.com/umbraco/Umbraco-CMS/pull/15699
|
||||
opt.OperationFilter<UmbracoExtensionOperationSecurityFilter>();
|
||||
});
|
||||
}
|
||||
|
||||
public class UmbracoExtensionOperationSecurityFilter : BackOfficeSecurityRequirementsOperationFilterBase
|
||||
{
|
||||
protected override string ApiName => Constants.ApiName;
|
||||
}
|
||||
|
||||
// This is used to generate nice operation IDs in our swagger json file
|
||||
// So that the gnerated TypeScript client has nice method names and not too verbose
|
||||
// https://docs.umbraco.com/umbraco-cms/tutorials/creating-a-backoffice-api/umbraco-schema-and-operation-ids#operation-ids
|
||||
public class CustomOperationHandler : OperationIdHandler
|
||||
{
|
||||
public CustomOperationHandler(IOptions<ApiVersioningOptions> apiVersioningOptions) : base(apiVersioningOptions)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool CanHandle(ApiDescription apiDescription, ControllerActionDescriptor controllerActionDescriptor)
|
||||
{
|
||||
return controllerActionDescriptor.ControllerTypeInfo.Namespace?.StartsWith("Umbraco.Extension.Controllers", comparisonType: StringComparison.InvariantCultureIgnoreCase) is true;
|
||||
}
|
||||
|
||||
public override string Handle(ApiDescription apiDescription) => $"{apiDescription.ActionDescriptor.RouteValues["action"]}";
|
||||
}
|
||||
}
|
||||
}
|
||||
7
templates/UmbracoExtension/Constants.cs
Normal file
7
templates/UmbracoExtension/Constants.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Extension
|
||||
{
|
||||
public class Constants
|
||||
{
|
||||
public const string ApiName = "umbracoextension";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using Asp.Versioning;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
#if IncludeExample
|
||||
using Umbraco.Cms.Core.Models.Membership;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
#endif
|
||||
|
||||
namespace Umbraco.Extension.Controllers
|
||||
{
|
||||
[ApiVersion("1.0")]
|
||||
[ApiExplorerSettings(GroupName = "Umbraco.Extension")]
|
||||
public class UmbracoExtensionApiController : UmbracoExtensionApiControllerBase
|
||||
{
|
||||
#if IncludeExample
|
||||
private readonly IBackOfficeSecurityAccessor _backOfficeSecurityAccessor;
|
||||
|
||||
public UmbracoExtensionApiController(IBackOfficeSecurityAccessor backOfficeSecurityAccessor)
|
||||
{
|
||||
_backOfficeSecurityAccessor = backOfficeSecurityAccessor;
|
||||
}
|
||||
#endif
|
||||
|
||||
[HttpGet("ping")]
|
||||
[ProducesResponseType<string>(StatusCodes.Status200OK)]
|
||||
public string Ping() => "Pong";
|
||||
#if IncludeExample
|
||||
|
||||
[HttpGet("whatsTheTimeMrWolf")]
|
||||
[ProducesResponseType(typeof(DateTime), 200)]
|
||||
public DateTime WhatsTheTimeMrWolf() => DateTime.Now;
|
||||
|
||||
[HttpGet("whatsMyName")]
|
||||
[ProducesResponseType<string>(StatusCodes.Status200OK)]
|
||||
public string WhatsMyName()
|
||||
{
|
||||
// So we can see a long request in the dashboard with a spinning progress wheel
|
||||
Thread.Sleep(2000);
|
||||
|
||||
var currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
||||
return currentUser?.Name ?? "I have no idea who you are";
|
||||
}
|
||||
|
||||
[HttpGet("whoAmI")]
|
||||
[ProducesResponseType<IUser>(StatusCodes.Status200OK)]
|
||||
public IUser? WhoAmI() => _backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Api.Common.Attributes;
|
||||
using Umbraco.Cms.Web.Common.Authorization;
|
||||
using Umbraco.Cms.Web.Common.Routing;
|
||||
|
||||
namespace Umbraco.Extension.Controllers
|
||||
{
|
||||
[ApiController]
|
||||
[BackOfficeRoute("umbracoextension/api/v{version:apiVersion}")]
|
||||
[Authorize(Policy = AuthorizationPolicies.SectionAccessContent)]
|
||||
[MapToApi(Constants.ApiName)]
|
||||
public class UmbracoExtensionApiControllerBase : ControllerBase
|
||||
{
|
||||
}
|
||||
}
|
||||
38
templates/UmbracoExtension/README.txt
Normal file
38
templates/UmbracoExtension/README.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
_ _ _
|
||||
| | | | | |
|
||||
__| | ___ | |_ _ __ ___| |_ _ __ _____ __
|
||||
/ _` |/ _ \| __| '_ \ / _ \ __| | '_ \ / _ \ \ /\ / /
|
||||
| (_| | (_) | |_| | | | __/ |_ | | | | __/\ V V /
|
||||
\__,_|\___/ \__|_| |_|\___|\__| |_| |_|\___| \_/\_/ _ _
|
||||
| | | | (_)
|
||||
_ _ _ __ ___ | |__ _ __ __ _ ___ ___ _____ _| |_ ___ _ __ ___ _ ___ _ __
|
||||
| | | | '_ ` _ \| '_ \| '__/ _` |/ __/ _ \ / _ \ \/ / __/ _ \ '_ \/ __| |/ _ \| '_ \
|
||||
| |_| | | | | | | |_) | | | (_| | (_| (_) | | __/> <| || __/ | | \__ \ | (_) | | | |
|
||||
\__,_|_| |_| |_|_.__/|_| \__,_|\___\___/ \___/_/\_\\__\___|_| |_|___/_|\___/|_| |_|
|
||||
|
||||
|
||||
== Requirements ==
|
||||
* Node LTS Version 20.17.0+
|
||||
* Use a tool such as NVM (Node Version Manager) for your OS to help manage multiple versions of Node
|
||||
|
||||
== Node Version Manager tools ==
|
||||
* https://github.com/coreybutler/nvm-windows
|
||||
* https://github.com/nvm-sh/nvm
|
||||
* https://docs.volta.sh/guide/getting-started
|
||||
|
||||
== Steps ==
|
||||
* Open a terminal inside the `\Client` folder
|
||||
* Run `npm install` to install all the dependencies
|
||||
* Run `npm run build` to build the project
|
||||
* The build output is copied to `wwwroot\App_Plugins\UmbracoExtension\umbraco-extension.js`
|
||||
|
||||
== File Watching ==
|
||||
* Add this Razor Class Library Project as a project reference to an Umbraco Website project
|
||||
* From the `\Client` folder run the command `npm run watch` this will monitor the changes to the *.ts files and rebuild the project
|
||||
* With the Umbraco website project running the Razor Class Library Project will refresh the browser when the build is complete
|
||||
|
||||
== Suggestion ==
|
||||
* Use VSCode as the editor of choice as it has good tooling support for TypeScript and it will recommend a VSCode Extension for good Lit WebComponent completions
|
||||
|
||||
== Other Resources ==
|
||||
* Umbraco Docs - https://docs.umbraco.com/umbraco-cms/customizing/extend-and-customize-editing-experience
|
||||
@@ -4,16 +4,14 @@
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<AddRazorSupportForMvc Condition="'$(SupportPagesAndViews)' == 'True'">true</AddRazorSupportForMvc>
|
||||
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">UmbracoPackage</RootNamespace>
|
||||
<StaticWebAssetBasePath>App_Plugins/UmbracoPackage</StaticWebAssetBasePath>
|
||||
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">Umbraco.Extension</RootNamespace>
|
||||
<StaticWebAssetBasePath>/</StaticWebAssetBasePath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>UmbracoPackage</PackageId>
|
||||
<Product>UmbracoPackage</Product>
|
||||
<Title>UmbracoPackage</Title>
|
||||
<Description>...</Description>
|
||||
<PackageTags>umbraco plugin package</PackageTags>
|
||||
<PackageId>Umbraco.Extension</PackageId>
|
||||
<Product>Umbraco.Extension</Product>
|
||||
<Title>Umbraco.Extension</Title>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Condition="'$(SupportPagesAndViews)' == 'True'">
|
||||
@@ -23,5 +21,16 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Umbraco.Cms.Web.Website" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
<PackageReference Include="Umbraco.Cms.Web.Common" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
<PackageReference Include="Umbraco.Cms.Api.Common" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
<PackageReference Include="Umbraco.Cms.Api.Management" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Dont include the client folder as part of packaging nuget build -->
|
||||
<Content Remove="Client\**" />
|
||||
|
||||
<!-- However make the Umbraco-package.json included for dotnet pack or nuget package and visible to the solution -->
|
||||
<None Include="Client\public\umbraco-package.json" Pack="false" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/dotnetcli.host.json",
|
||||
"symbolInfo": {
|
||||
"Framework": {
|
||||
"longName": "Framework",
|
||||
"shortName": "F",
|
||||
"isHidden": true
|
||||
},
|
||||
"UmbracoVersion": {
|
||||
"longName": "version",
|
||||
"shortName": "v"
|
||||
},
|
||||
"SkipRestore": {
|
||||
"longName": "no-restore",
|
||||
"shortName": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/ide.host.json",
|
||||
"order": 0,
|
||||
"icon": "../../icon.png",
|
||||
"description": {
|
||||
"id": "UmbracoPackage",
|
||||
"text": "Umbraco Package - An empty Umbraco CMS package/plugin."
|
||||
},
|
||||
"symbolInfo": [
|
||||
{
|
||||
"id": "UmbracoVersion",
|
||||
"isVisible": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/template.json",
|
||||
"author": "Umbraco HQ",
|
||||
"classifications": [
|
||||
"Web",
|
||||
"CMS",
|
||||
"Umbraco",
|
||||
"Package",
|
||||
"Plugin"
|
||||
],
|
||||
"name": "Umbraco Package",
|
||||
"description": "An empty Umbraco package/plugin project ready to get started.",
|
||||
"groupIdentity": "Umbraco.Templates.UmbracoPackage",
|
||||
"identity": "Umbraco.Templates.UmbracoPackage.CSharp",
|
||||
"shortName": "umbracopackage",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"sourceName": "UmbracoPackage",
|
||||
"defaultName": "UmbracoPackage1",
|
||||
"preferNameDirectory": true,
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"displayName": "Framework",
|
||||
"description": "The target framework for the project.",
|
||||
"type": "parameter",
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"displayName": ".NET 9.0",
|
||||
"description": "Target net9.0",
|
||||
"choice": "net9.0"
|
||||
}
|
||||
],
|
||||
"defaultValue": "net9.0",
|
||||
"replaces": "net9.0"
|
||||
},
|
||||
"UmbracoVersion": {
|
||||
"displayName": "Umbraco version",
|
||||
"description": "The version of Umbraco.Cms to add as PackageReference.",
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"defaultValue": "*",
|
||||
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
|
||||
},
|
||||
"SkipRestore": {
|
||||
"displayName": "Skip restore",
|
||||
"description": "If specified, skips the automatic restore of the project on create.",
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"Namespace": {
|
||||
"type": "derived",
|
||||
"valueSource": "name",
|
||||
"valueTransform": "safe_namespace",
|
||||
"fileRename": "UmbracoPackage",
|
||||
"replaces": "UmbracoPackage"
|
||||
},
|
||||
"MsBuildName": {
|
||||
"type": "generated",
|
||||
"generator": "regex",
|
||||
"dataType": "string",
|
||||
"parameters": {
|
||||
"source": "name",
|
||||
"steps": [
|
||||
{
|
||||
"regex": "\\s",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"regex": "\\.",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"regex": "-",
|
||||
"replacement": ""
|
||||
},
|
||||
{
|
||||
"regex": "^[^a-zA-Z_]+",
|
||||
"replacement": ""
|
||||
}
|
||||
]
|
||||
},
|
||||
"replaces": "UmbracoPackageMsBuild"
|
||||
}
|
||||
},
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"path": "UmbracoPackage.csproj"
|
||||
}
|
||||
],
|
||||
"postActions": [
|
||||
{
|
||||
"id": "restore",
|
||||
"condition": "(!SkipRestore)",
|
||||
"description": "Restore NuGet packages required by this project.",
|
||||
"manualInstructions": [
|
||||
{
|
||||
"text": "Run 'dotnet restore'"
|
||||
}
|
||||
],
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"id": "UmbracoPackage",
|
||||
"name": "UmbracoPackage",
|
||||
"allowPackageTelemetry": true,
|
||||
"extensions": []
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ContentTargetFolders>.</ContentTargetFolders>
|
||||
<RootNamespace Condition="'$(name)' != '$(name{-VALUE-FORMS-}safe_namespace)'">UmbracoPackage</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<PackageId>UmbracoPackage</PackageId>
|
||||
<Product>UmbracoPackage</Product>
|
||||
<Title>UmbracoPackage</Title>
|
||||
<Description>...</Description>
|
||||
<PackageTags>umbraco plugin package</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Umbraco.Cms.Web.Website" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
<PackageReference Include="Umbraco.Cms.Web.Common" Version="UMBRACO_VERSION_FROM_TEMPLATE" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Content Include="App_Plugins\UmbracoPackage\**" ExcludeFromSingleFile="true" CopyToPublishDirectory="Always" />
|
||||
<None Include="buildTransitive\**" Pack="true" PackagePath="buildTransitive" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,21 +0,0 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<UmbracoPackageMsBuildContentFilesPath>$(MSBuildThisFileDirectory)..\App_Plugins\UmbracoPackage\**\*.*</UmbracoPackageMsBuildContentFilesPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<Target Name="CopyUmbracoPackageMsBuildAssets" BeforeTargets="BeforeBuild">
|
||||
<ItemGroup>
|
||||
<UmbracoPackageMsBuildContentFiles Include="$(UmbracoPackageMsBuildContentFilesPath)" />
|
||||
</ItemGroup>
|
||||
<Message Text="Copying UmbracoPackage files: $(UmbracoPackageMsBuildContentFilesPath) - #@(UmbracoPackageMsBuildContentFiles->Count()) files" Importance="high" />
|
||||
<Copy SourceFiles="@(UmbracoPackageMsBuildContentFiles)" DestinationFiles="@(UmbracoPackageMsBuildContentFiles->'$(MSBuildProjectDirectory)\App_Plugins\UmbracoPackage\%(RecursiveDir)%(Filename)%(Extension)')" SkipUnchangedFiles="true" />
|
||||
</Target>
|
||||
|
||||
<Target Name="ClearUmbracoPackageMsBuildAssets" BeforeTargets="Clean">
|
||||
<ItemGroup>
|
||||
<UmbracoPackageMsBuildDir Include="$(MSBuildProjectDirectory)\App_Plugins\UmbracoPackage\" />
|
||||
</ItemGroup>
|
||||
<Message Text="Clear old UmbracoPackage data" Importance="high" />
|
||||
<RemoveDir Directories="@(UmbracoPackageMsBuildDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/ide.host.json",
|
||||
"order": 0,
|
||||
"icon": "../../icon.png",
|
||||
"description": {
|
||||
"id": "UmbracoPackageRcl",
|
||||
"text": "Umbraco Package RCL - An empty Umbraco package/plugin (Razor Class Library)."
|
||||
},
|
||||
"symbolInfo": [
|
||||
{
|
||||
"id": "UmbracoVersion",
|
||||
"isVisible": true
|
||||
},
|
||||
{
|
||||
"id": "SupportPagesAndViews",
|
||||
"isVisible": true,
|
||||
"persistenceScope": "templateGroup"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/template.json",
|
||||
"author": "Umbraco HQ",
|
||||
"classifications": [
|
||||
"Web",
|
||||
"CMS",
|
||||
"Umbraco",
|
||||
"Package",
|
||||
"Plugin",
|
||||
"Razor Class Library"
|
||||
],
|
||||
"name": "Umbraco Package RCL",
|
||||
"description": "An empty Umbraco package/plugin (Razor Class Library).",
|
||||
"groupIdentity": "Umbraco.Templates.UmbracoPackageRcl",
|
||||
"identity": "Umbraco.Templates.UmbracoPackageRcl.CSharp",
|
||||
"shortName": "umbracopackage-rcl",
|
||||
"tags": {
|
||||
"language": "C#",
|
||||
"type": "project"
|
||||
},
|
||||
"sourceName": "UmbracoPackage",
|
||||
"defaultName": "UmbracoPackage1",
|
||||
"preferNameDirectory": true,
|
||||
"symbols": {
|
||||
"Framework": {
|
||||
"displayName": "Framework",
|
||||
"description": "The target framework for the project.",
|
||||
"type": "parameter",
|
||||
"datatype": "choice",
|
||||
"choices": [
|
||||
{
|
||||
"displayName": ".NET 9.0",
|
||||
"description": "Target net9.0",
|
||||
"choice": "net9.0"
|
||||
}
|
||||
],
|
||||
"defaultValue": "net9.0",
|
||||
"replaces": "net9.0"
|
||||
},
|
||||
"UmbracoVersion": {
|
||||
"displayName": "Umbraco version",
|
||||
"description": "The version of Umbraco.Cms to add as PackageReference.",
|
||||
"type": "parameter",
|
||||
"datatype": "string",
|
||||
"defaultValue": "*",
|
||||
"replaces": "UMBRACO_VERSION_FROM_TEMPLATE"
|
||||
},
|
||||
"SkipRestore": {
|
||||
"displayName": "Skip restore",
|
||||
"description": "If specified, skips the automatic restore of the project on create.",
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false"
|
||||
},
|
||||
"SupportPagesAndViews": {
|
||||
"type": "parameter",
|
||||
"datatype": "bool",
|
||||
"defaultValue": "false",
|
||||
"displayName": "Support pages and views",
|
||||
"description": "Whether to support adding traditional Razor pages and Views to this library."
|
||||
}
|
||||
},
|
||||
"primaryOutputs": [
|
||||
{
|
||||
"path": "UmbracoPackage.csproj"
|
||||
}
|
||||
],
|
||||
"postActions": [
|
||||
{
|
||||
"id": "restore",
|
||||
"condition": "(!SkipRestore)",
|
||||
"description": "Restore NuGet packages required by this project.",
|
||||
"manualInstructions": [
|
||||
{
|
||||
"text": "Run 'dotnet restore'"
|
||||
}
|
||||
],
|
||||
"actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
|
||||
"continueOnError": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"id": "UmbracoPackage",
|
||||
"name": "UmbracoPackage",
|
||||
"allowPackageTelemetry": true,
|
||||
"extensions": []
|
||||
}
|
||||
Reference in New Issue
Block a user