diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..a2f866c --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +{ + "name": "nkdAgility Website Codespace", + "image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu", // or your preferred Docker image + "customizations": { + "vscode": { + "extensions": [ + "eliostruyf.vscode-front-matter", + "budparr.language-hugo-vscode", + "esbenp.prettier-vscode", + "kaellarkin.hugo-shortcode-syntax", + "eliostruyf.vscode-front-matter-beta", + "ms-azuretools.vscode-azurestaticwebapps", + "ms-azuretools.vscode-azurefunctions" + ] + } + } + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {"ghcr.io/devcontainers/features/hugo:1": {"extended":true}}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "uname -a", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" + } + diff --git a/.frontmatter/database/mediaDb.json b/.frontmatter/database/mediaDb.json new file mode 100644 index 0000000..fd63b5d --- /dev/null +++ b/.frontmatter/database/mediaDb.json @@ -0,0 +1 @@ +{"users":{"martinhinshelwoodnkd":{"source":{"repos":{"nkdagility.com":{"site":{"content":{"capabilities":{"training-courses":{"courses":{"scrumorg-professional-scrum-master":{"images":{}},"scrumorg-professional-scrum-product-owner":{},"scrumorg-applying-professional-scrum":{"images":{}}}}}}}}}}}}} \ No newline at end of file diff --git a/.frontmatter/database/pinnedItemsDb.json b/.frontmatter/database/pinnedItemsDb.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.frontmatter/database/pinnedItemsDb.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/.frontmatter/database/taxonomyDb.json b/.frontmatter/database/taxonomyDb.json new file mode 100644 index 0000000..0250804 --- /dev/null +++ b/.frontmatter/database/taxonomyDb.json @@ -0,0 +1 @@ +{"taxonomy":{"tags":["2012-2","Agile","Collaboration","Continuous Improvement","Productivity","Scrum Masters","Team","_atomic_fetch_sub_4","access-levels","active-directory","adfs","advfirewall","agent","agent-scope","aggreko","agile","agile-bs","agile-estimation","agile-in-africa","agile-planning-tools","agile-portfolio-management","agile-testing","agile-transformation","agileleadership","agility","analysis-services","analytics-management","android","annoucement","answers","anti-pattern","api","application-insights","area-hierarchy","area-path","argumentnullexception","asynchronous-development","atdd","automated-build","automated-builds","automated-testing","automation","awards","azure","azure-active-directory","backlog","backlog-management","backups","bdd","betacodex","big-scrum","blob","branching","budget","bug","bugs","build","build-agent","build-management","build-vnext","business-agility","business-mastery","caveat-utilitor","certification","change","change-for-the-better","change-management","charity","cloud","cloud-service","cmake","code","code-analysis","code-clone-analysis","code-complexity","code-coverage","code-management","code-metrics","code-quality","code-sense","codedui","codeplex","codeproject","collocation","company-culture","configuration","consulting","continious","continious-delivery","continious-integration","continious-value-delivery","continuous-improvement","continuous-quality","core","cross-functional-teams","cross-platform","css","culture","custom-activity","cycle","cycle-time","daily-scrum","daretochange","database","define","definition-of-done","dell-venue-8","dependency-injection","deploy","design-sprint","detach","develop","developers","development","development-team","devops","digital-transformation","dms","dns","dod","domain","done","dps","dvcs","dyslexia","ebm","ebmgt","eclipse","elevated-privileges","email-alert-settings","emergent-architecture","engineering-excellence","engineering-practices","enterprise","entrepreneurs","europe","events-and-presentations","evidence","evidence-based-management","excel","facilitation","fail","featured","feedback","feedback-client","flow","forcast","games","ghana","git","git-tfs","github","globallist","golden-circle","google","google-mail","hardware","harris-beach","hidden","homepage","hootoo-tripmate","hyper-v","hypothesis-driven-development","iaas","ie8","imap","import","improve","increment","indyref","infrastructure","inrelease","install","intel","intermediate-goal","intermediate-strategic-goal","intermediate-tactical-goal","introduction-to-scrum","invest","iscotland","iteration","iteration-hierarchy","jake","java","jenkins","jobs","kanban","kanban-theory","kb","kerberos","kpi","lab","lab-management","label","language","leadership","leadership-track","lean","lean-agile","linkedin","live","live-id","live-webcast","macro","make-requests-on-behalf-of-others","manage-process-template","management","manual-test","maven","measure","mergeconflict","merics","microsoft-deployment-agent","microsoft-id","microsoft-visual-studio-scrum","migration","ml","mobile","modern-alm","modern-application-lifecycle","moss","moss2007","mount-spcontentdatabase","move-spuser","msbuild","msdn","msf","msf-for-agile-software-development","msf-for-cmmi-process-improvement","mssqlserver","mtm","multisite","mvc","mvvm","my-work","ndc","netsh","network","nexus-framework","northwest-cadence","nwcadence","octopus","off-topic","office","office-2013","office-365","office14","one-team-project-seriese","onedrive","operate","operational","opshub","organisation","organisational-change","organisational-change-team","organisational-physics","organisational-transformational-mastery","outlook","outlook-2010","pagelines","patterns","pendingfilerenameoperations","people","perforce","permissions","philosophy","planning-tools","police","portfolio-backlog","portfolio-management","portfolio-management-tools","powerpoint","powershell","practices","predictable-quality","preview","process","process-improvement","process-template","process-template-editor","process-template-manager","processconfiguration","product","product-backlog","product-backlog-item","product-backlog-items","product-backlog-management","product-backog","product-discovery","product-goal","product-owner","product-planning","professioal-scrum","professional-kanban","professional-scrum","professional-scrum-developer","professional-scrum-foundations","professional-scrum-master","professional-scrum-with-kanban","professionalism","programmatically","project","project-administrator","project-management","project-manager","project-server","projectmanagement","proscrumdev","ps2013","psd","psf","psm","pst","puzzles","qa","qc","quality-enablement","quality-management","refinement","release","release-management","release-management-client","release-management-server","release-pipline","release-planning","remote-execute","reporting","reporting-services","requirements","requirements-management","resource-group","retrospective","review","roadmap","router","rtm","s30d","scaled-agile","scaled-professional-scrum","scandinavia","scheduled-backup","scotland","scrat","scrum","scrum-at-scale","scrum-day","scrum-definition","scrum-master","scrum-masters","scrum-org","scrum-rules","scrum-tapas","scrum-team","scrum-theory","scrum-values","scrumble","security","self-organisation","seo","series-modern-application-lifecycle","server-2012-r2","service-oriented-architecture","sfts","sharepoint","sharepoint-2013","sharepoint-2013-sp1","silverlight","snapshot","snapshotidentities","software-engineering","solid","source-control","sp2007","sp2013","spf2010","sprint","sprint-backlog","sprint-goal","sprint-planning","sprint-review","sps","sql","sql-enterprise","sql-express","sql-server","sql-server-2012","sql-server-error","ssas","ssl","ssrs","ssw","standard-environments","start-azurestorageblobcopy","starteam","storage","storyboarding","strategic","strong-name","subscription","surface","surface-2","surface-2-pro","surface-3-pro","svcs","svn","sync","tactical","tag","task","tasktop","taylorism","tdd","team","team-build","team-field","team-project","team-project-collection","team-room","team-rooms","teams","teamviewer","technical-mastery","technical-track","test-controller","test-driven-development","test-first","test-hub","test-management","test-plan","testing","tf-build","tf-service","tf10141","tf14009","tf14080","tf14092","tf14098","tf200035","tf205022","tf215097","tf22022","tf237165","tf246017","tf250052","tf254027","tf254078","tf255193","tf255356","tf255375","tf255430","tf255435","tf255466","tf255484","tf255507","tf259641","tf26204","tf294003","tf294012","tf294026","tf30046","tf30063","tf30065","tf31001","tf400080","tf400264","tf400324","tf400432","tf400508","tf400512","tf400654","tf400670","tf400711","tf400860","tf400898","tf400998","tf50309","tf50620","tf51011","tf53010","tf54000","tf60014","tf60087","tfget","tfignore","tfplugable","tfs","tfs-11","tfs-2010-sp1","tfs-2012-3","tfs-2012-4","tfs-2013","tfs-2013-2","tfs-2013-3","tfs-2013-4","tfs-2015","tfs-api","tfs-build","tfs-event-handler","tfs-integration-platform","tfs-sticky-buddy","tfs2005","tfs2008","tfs2010","tfs2012","tfs2012-1","tfs2012-2","tfs2013","tfsap","tfslab","tfvc","the-new-normal","the-sprint","theme","timely-migration","tools","traditional-project-management","training","traveling","ttp","uk","unit-testing","upgrade","user-groups","user-stories","value","value-track","vba","version-control","versioncontrol","vhd","videos","virtual-labs","virtual-machines","virtual-network","vista","visual-basic","visual-basic-9","visual-sourcesafe","visual-studio","visual-studio-2013","visual-studio-2013-3","visual-studio-2013-4","visual-studio-2015","visual-studio-alm","vm","vnext","vs-2012-1","vs-2013","vs2005","vs2008","vs2010","vs2012","vsalmrangers","vsbuild","vsip","vsteamservices","vstest","vsts","waterfall","wcf","web","web-access","webcast","webcast-2","wf","whats-new","why","win8","win8-1","windows","windows-10","windows-8","windows-8-1","windows-mobile-6","windows-phone","windows-phone-8","windows-phone-8-1","windows-server","windows-server-2012","windows-server-2012-r2","wiql","wit","wit-tagging","witadmin","wordpress","work-item-type","workflow","working-software","workitemstore","workitemtracking","workshop","wp7","wpf","xaml","xbox","xcode"],"categories":["Azure DevOps","agility","alm","code-and-complexity","collaboration","devops","discovery-ideation","events-and-presentations","install-and-configuration","kanban","me","measure-and-learn","news-and-reviews","organisational-change","people-and-process","problems-and-puzzles","products-and-books","test-and-validation","tools-and-techniques","transparency-commitment","updated2019","upgrade-and-maintenance"]}} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..2125666 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.github/GitVersion.yml b/.github/GitVersion.yml new file mode 100644 index 0000000..e407845 --- /dev/null +++ b/.github/GitVersion.yml @@ -0,0 +1,27 @@ +assembly-versioning-scheme: MajorMinorPatch +mode: ContinuousDelivery +continuous-delivery-fallback-tag: 'Canary' +next-version: 0.0.1 +branches: + main: + mode: ContinuousDelivery + tag: 'Preview' + increment: Patch + is-mainline: true + prevent-increment-of-merged-branch-version: false + tracks-release-branches: true + regex: ^master$|^main$ + release: + mode: ContinuousDelivery + tag: "" + increment: Patch + track-merge-target: false + regex: ^release(s)?[\/-] + source-branches: + - master + - main + is-release-branch: true + is-mainline: false +ignore: + sha: [] +merge-message-formats: {} \ No newline at end of file diff --git a/.github/workflows/close-pr.yaml b/.github/workflows/close-pr.yaml new file mode 100644 index 0000000..9df0ced --- /dev/null +++ b/.github/workflows/close-pr.yaml @@ -0,0 +1,25 @@ +name: Azure Static Web App PR Cleanup + +on: + pull_request: + types: [closed] + +jobs: + pr_cleanup: + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} + - name: Clean up pull request environment + uses: Azure/static-web-apps-deploy@v1 + with: + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} + action: "close" + deployment_environment: canary-${{ github.event.pull_request.number }} + app_location: "" + skip_app_build: true + skip_api_build: true + repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..2b878c7 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,480 @@ +name: Build & Release (NKDAgility) + +permissions: + contents: read + pull-requests: write + +on: + push: + branches: ["main"] + tags-ignore: ["v*-*"] + pull_request: + branches: ["main"] + workflow_dispatch: + inputs: + ForceRelease: + description: "Force a release! Use when changes hapen out of sync and `src` and `docs` folder changes are not detected." + required: false + default: false + type: boolean + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: pwsh + +jobs: + # Setup & Configuration + Setup: + name: "Setup & Configuration " + runs-on: ubuntu-latest + outputs: + GitVersion_BranchName: ${{ steps.gitversion.outputs.GitVersion_BranchName }} + GitVersion_SemVer: ${{ steps.gitversion.outputs.GitVersion_SemVer }} + GitVersion_PreReleaseLabel: ${{ steps.gitversion.outputs.GitVersion_PreReleaseLabel }} + GitVersion_AssemblySemVer: ${{ steps.gitversion.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ steps.gitversion.outputs.GitVersion_InformationalVersion }} + GitVersion_NuGetVersion: ${{ steps.gitversion.outputs.GitVersion_NuGetVersion }} + GitVersion_PreReleaseNumber: ${{ steps.gitversion.outputs.GitVersion_PreReleaseNumber }} + GitVersion_MajorMinorPatch: ${{ steps.gitversion.outputs.GitVersion_MajorMinorPatch }} + nkdAgility_Ring: ${{ steps.nkdagility.outputs.Ring }} + nkdAgility_AzureSitesEnvironment: ${{ steps.nkdagility.outputs.AzureSitesEnvironment }} + nkdAgility_AzureSitesConfig: ${{ steps.nkdagility.outputs.AzureSitesConfig }} + nkdAgility_BLOB_URL_BIT: ${{ steps.nkdagility.outputs.BLOB_URL_BIT }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: List folders + shell: pwsh + run: | + Get-ChildItem -Path "${{ github.workspace }}" -Directory -Force | Select-Object -ExpandProperty FullName + + - uses: actions/upload-artifact@v4 + with: + name: Scripts + path: "./.powershell/**" + if-no-files-found: error + include-hidden-files: true + - name: Install GitVersion + uses: gittools/actions/gitversion/setup@v3.0.0 + with: + versionSpec: "5.x" + includePrerelease: true + - name: Execute GitVersion + id: gitversion + uses: gittools/actions/gitversion/execute@v3.0.0 + with: + useConfigFile: true + configFilePath: .github/GitVersion.yml + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + content: + - 'content/**' + - name: "Build NKDAgility Outputs" + shell: pwsh + id: nkdagility + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + # Get Branch Name + Write-Output "::group::Get Branch Name" + Write-Output "-------------------------------------------" + $branchName = "${{ github.head_ref || github.ref_name }}" + Write-Output "We are running on: $branchName!" + $branchSafeName = $branchName.Replace("/", "-") + Write-Output "branchSafeName: $branchSafeName!" + Write-Output "-------------------------------------------" + Write-Output "::endgroup::" + # Ring Setup + Write-Output "::group::Ring Control Setup" + Write-Output "-------------------------------------------" + Write-Output "Ring Control Setup" + Write-Output "-------------------------------------------" + $Ring = "Canary" + $docs_deploy_folder = "./azure-devops-migration-tools/"; + $docs_baseURL = "/learn/azure-devops-migration-tools" + $BLOB_URL_BIT = "/blob" + switch ($Env:GitVersion_PreReleaseLabel) { + "" { + $Ring = "Production"; + $AzureSitesEnvironment = "" + $AzureSitesConfig = "production" + $docs_baseURL = "/" + } + "Preview" { + $Ring = "Preview"; + $AzureSitesEnvironment = "preview"; + $AzureSitesConfig = "preview" + $docs_baseURL = "/preview" + } + default { + $Ring = "Canary"; + $AzureSitesEnvironment = "canary-${{ github.event.pull_request.number }}" + $AzureSitesConfig = "canary" + $BLOB_URL_BIT = 'https://nkdagilityblobs.blob.core.windows.net/$web' + $docs_baseURL = "/canary/$branchSafeName" + } + } + Write-Output "We are running for the $Ring Ring!" + echo "Ring=$Ring" >> $env:GITHUB_OUTPUT + + Write-Output "We are running for the $AzureSitesEnvironment AzureSitesEnvironment!" + echo "AzureSitesEnvironment=$AzureSitesEnvironment" >> $env:GITHUB_OUTPUT + + Write-Output "We are running for the $AzureSitesConfig AzureSitesConfig!" + echo "AzureSitesConfig=$AzureSitesConfig" >> $env:GITHUB_OUTPUT + + Write-Output "We are running for the $BLOB_URL_BIT BLOB_URL_BIT!" + echo "BLOB_URL_BIT=$BLOB_URL_BIT" >> $env:GITHUB_OUTPUT + + Write-Output "docs_baseURL=$docs_baseURL" + echo "docs_baseURL=$docs_baseURL" >> $env:GITHUB_OUTPUT + Write-Output "-------------------------------------------" + Write-Output "::endgroup::" + + # Setup Validator + SetupSummeryStage: + name: "Build Run Data" + runs-on: ubuntu-latest + needs: Setup + steps: + - name: "Show Summery" + if: always() + shell: pwsh + id: nkdagility-summery + run: | + $markdown = @" + ## ${{needs.Setup.outputs.GitVersion_SemVer}} (${{needs.Setup.outputs.nkdAgility_Ring}}) + ### NKDAgility + - nkdAgility_Ring: ${{needs.Setup.outputs.nkdAgility_Ring}} + - nkdAgility_AzureSitesEnvironment: ${{needs.Setup.outputs.nkdAgility_AzureSitesEnvironment}} + - nkdAgility_AzureSitesConfig: ${{needs.Setup.outputs.nkdAgility_AzureSitesConfig}} + - nkdAgility_BLOB_URL_BIT: ${{needs.Setup.outputs.nkdAgility_BLOB_URL_BIT}} + + ### GitVersion + - GitVersion_BranchName: ${{needs.Setup.outputs.GitVersion_BranchName}} + - GitVersion_SemVer: ${{needs.Setup.outputs.GitVersion_SemVer}} + - GitVersion_PreReleaseLabel: ${{needs.Setup.outputs.GitVersion_PreReleaseLabel}} + - GitVersion_AssemblySemVer: ${{needs.Setup.outputs.GitVersion_AssemblySemVer}} + - GitVersion_InformationalVersion: ${{needs.Setup.outputs.GitVersion_InformationalVersion}} + - GitVersion_NuGetVersion: ${{needs.Setup.outputs.GitVersion_NuGetVersion}} + "@ + echo $markdown >> $Env:GITHUB_STEP_SUMMARY + + # Spellcheck + Spellcheck: + name: "Spellcheck Site" + runs-on: ubuntu-latest + if: ${{ success() }} + needs: [Setup] + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true # Fetch Hugo themes (true OR recursive) + - uses: igsekor/pyspelling-any@v1.0.4 + id: spellcheck + name: Spellcheck + with: + args: --report report.json + + - uses: actions/upload-artifact@v4 + with: + name: spellcheck + path: ./report.json + + # Build Docs + BuildSite: + name: "Build Site" + runs-on: ubuntu-latest + if: ${{ success() }} + needs: [Setup] + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true # Fetch Hugo themes (true OR recursive) + - uses: cschleiden/replace-tokens@v1 + with: + files: '["**/*.html", "**/*.yaml"]' + tokenPrefix: "#{" + tokenSuffix: "}#" + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} + GitVersion.SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + nkdAgility_AzureSitesConfig: ${{ needs.Setup.outputs.nkdAgility_AzureSitesConfig }} + PR_Number: ${{ github.event.number }} + + - name: Setup Hugo + uses: peaceiris/actions-hugo@v3 + with: + hugo-version: "latest" + extended: true + + - name: Build + run: | + Write-Host "Building site for ${{ (needs.Setup.outputs.nkdAgility_AzureSitesConfig) }}"; + $env:HUGO_ENV = "${{ (needs.Setup.outputs.nkdAgility_AzureSitesConfig) }}"; + hugo --source site --config hugo.yaml,hugo.${{ (needs.Setup.outputs.nkdAgility_AzureSitesConfig) }}.yaml --logLevel info; # --minify + + - name: Copy Files + shell: pwsh + run: | + Get-ChildItem -Path "${{ github.workspace }}" -Directory | Select-Object -ExpandProperty FullName + Copy-Item -Path "./staticwebapp.config.json" -Destination ./public/ + Get-ChildItem -Path "./" -Filter "staticwebapp.config.*.json" | ForEach-Object { + Copy-Item -Path $_.FullName -Destination "./public/" + } + + - name: Check if folder size exceeds 500MB + shell: pwsh + run: | + $folder = "./public/" + $size = (Get-ChildItem -Recurse -File -Path $folder | Measure-Object -Property Length -Sum).Sum + + # Example: Perform an action if the folder is larger than 500MB (524288000 bytes) + if ($size -gt 524288000 ) { + Write-Host "Folder is larger than 500MB" + # Add additional actions or logic here + } else { + Write-Host "Folder is under 500MB" + } + + - uses: actions/upload-artifact@v4 + with: + name: Site + path: ./public/**/* + + # Build Api + BuildApi: + name: "Build Api" + runs-on: ubuntu-latest + if: ${{ false }} + needs: [Setup] + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true # Fetch Hugo themes (true OR recursive) + + - name: Setup DotNet Environment + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 8.x + - name: Check if folder size exceeds 500MB + shell: pwsh + run: | + dotnet build functions/functions.csproj --configuration Release --output ./api/ + + - uses: actions/upload-artifact@v4 + with: + name: API + path: ./api/**/* + + # Offload Images + OffloadImages: + name: "Ofload Site Images to Blob Storage" + runs-on: ubuntu-latest + if: ${{ success() }} + needs: [BuildSite, Setup] + env: + GitVersion_SemVer: ${{ needs.Setup.outputs.GitVersion_SemVer }} + GitVersion_AssemblySemVer: ${{ needs.Setup.outputs.GitVersion_AssemblySemVer }} + GitVersion_InformationalVersion: ${{ needs.Setup.outputs.GitVersion_InformationalVersion }} + steps: + - name: Download a single artifact + uses: actions/download-artifact@v4 + with: + name: Scripts + path: ./.powershell/ + - name: Download a single artifact + uses: actions/download-artifact@v4 + with: + name: Site + path: ./_site + - name: List folders + shell: pwsh + run: | + Get-ChildItem -Path "${{ github.workspace }}" -Directory -Force | Select-Object -ExpandProperty FullName + - name: "Sync Images to Blob Storage" + shell: pwsh + run: | + . ./.powershell/_includes/ImagesToBlobStorage.ps1 + Upload-ImageFiles -LocalPath $env:LOCAL_IMAGE_PATH -BlobUrlBase $env:BLOB_STORAGE_URL -AzureSASToken $env:AZURE_BLOB_STORAGE_SAS_TOKEN + env: + LOCAL_IMAGE_PATH: "./_site/" + BLOB_STORAGE_URL: "https://nkdagilityblobs.blob.core.windows.net/$web" + AZURE_BLOB_STORAGE_SAS_TOKEN: ${{ secrets.AZURE_BLOB_STORAGE_SAS_TOKEN }} + AZCOPY_FAIL_ON_ERROR: "true" + - name: "Rewrite Image Links" + shell: pwsh + run: | + . ./.powershell/_includes/ImagesToBlobStorage.ps1 + Rewrite-ImageLinks -LocalPath $env:LOCAL_IMAGE_PATH -BlobUrl $env:BLOB_URL_BIT + env: + LOCAL_IMAGE_PATH: "./_site/" + BLOB_URL_BIT: ${{needs.Setup.outputs.nkdAgility_BLOB_URL_BIT}} + - name: "Delete Local Images" + shell: pwsh + run: | + . ./.powershell/_includes/ImagesToBlobStorage.ps1 + Delete-LocalImageFiles -LocalPath $env:LOCAL_IMAGE_PATH + env: + LOCAL_IMAGE_PATH: "./_site/" + # - name: "Build NKDAgility Outputs" + # shell: pwsh + # run: ./.powershell/build/Sync-BlobStorageImages.ps1 + # env: + # LOCAL_IMAGE_PATH: "./_site/" + # BLOB_URL_BIT: "/blob" + # BLOB_STORAGE_URL: "https://nkdagilityblobs.blob.core.windows.net/`$web" + # AZURE_BLOB_STORAGE_SAS_TOKEN: ${{ secrets.AZURE_BLOB_STORAGE_SAS_TOKEN }} + # AZCOPY_FAIL_ON_ERROR: "true" + - uses: actions/upload-artifact@v4 + with: + name: Site-Blobbed + path: ./_site/**/* + + # Release to Docs + Publsh: + name: "Publish Site" + runs-on: ubuntu-latest + needs: [Setup, BuildSite, Spellcheck, OffloadImages] + if: ${{ success() }} + outputs: + static_web_app_url: ${{ steps.azureDeploy.outputs.static_web_app_url }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true # Fetch Hugo themes (true OR recursive) + - name: Download a single artifact + uses: actions/download-artifact@v4 + with: + name: Site-Blobbed + path: ./_site + - name: Download a single artifact + if: ${{ false }} + uses: actions/download-artifact@v4 + with: + name: API + path: ./_api + + - name: Merge Configuration Files + run: | + # Find all files matching the pattern in a safe way + $files = Get-ChildItem -Path "." -Recurse -Filter "staticwebapp.*.json" -ErrorAction Stop + + if ($files.Count -eq 0) { + Write-Host "No files matching the pattern 'staticwebapp.*.json' were found." + exit 1 + } + + # Output each file's full name (for verification/debugging purposes) + $files | ForEach-Object { + Write-Host "Found file: $($_.FullName)" + } + + # Paths to main and environment-specific config files + $rootConfig = "./_site/staticwebapp.config.json" + $environmentConfig = "./_site/staticwebapp.config.${{ (needs.Setup.outputs.nkdAgility_AzureSitesConfig) }}.json" + $routesConfig = "./_site/staticwebapp.config.routes.json" + + # Check if both target files exist + if ((Test-Path -Path $rootConfig -ErrorAction Stop) -and (Test-Path -Path $environmentConfig -ErrorAction Stop)) { + try { + # Run jq to merge files and capture the output + $mergedContent = & jq -s 'reduce .[] as $item ({}; . * $item)' $rootConfig $environmentConfig $routesConfig + + if ($mergedContent -ne "") { + # Write the merged content to the output file + $mergedContent | Set-Content -Path "./staticwebapp.config.json" + Write-Host "Merged JSON files successfully." + } + else { + Write-Host "jq command produced empty output. Check JSON structures in input files." + exit 1 + } + } + catch { + Write-Host "Error merging JSON files with jq: $_" + exit 1 + } + } + else { + Write-Host "One or both of the specified config files were not found:" + if (!(Test-Path -Path $rootConfig)) { Write-Host " - $rootConfig not found" } + if (!(Test-Path -Path $environmentConfig)) { Write-Host " - $environmentConfig not found" } + exit 1 + } + + # Verify and read the merged file content + try { + $content = Get-Content -Path "./staticwebapp.config.json" -ErrorAction Stop + Write-Host "Content of merged config file:" + Write-Output $content + } + catch { + Write-Host "Error reading the merged config file: $_" + exit 1 + } + + - name: "Find files" + shell: pwsh + run: | + Get-ChildItem -Path ".\" -File + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} + + - name: Build and Deploy + uses: Azure/static-web-apps-deploy@v1 + id: azureDeploy + env: + VERBOSE: true + with: + repo_token: ${{ steps.app-token.outputs.token }} + action: "upload" + app_location: ./_site + api_location: ./functions + skip_app_build: true + skip_api_build: false + output_location: "" + deployment_environment: ${{ (needs.Setup.outputs.nkdAgility_AzureSitesEnvironment) }} + azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN }} + + - name: "Show Summery" + if: always() + shell: pwsh + id: nkdagility-summery + run: | + $markdown = @" + ## ${{needs.Setup.outputs.GitVersion_SemVer}} (${{needs.Setup.outputs.nkdAgility_Ring}}) + Deployed to [${{steps.azureDeploy.outputs.static_web_app_url}}](${{steps.azureDeploy.outputs.static_web_app_url}}) + "@ + echo $markdown >> $Env:GITHUB_STEP_SUMMARY diff --git a/.github/workflows/shedule.yml b/.github/workflows/shedule.yml new file mode 100644 index 0000000..82a4510 --- /dev/null +++ b/.github/workflows/shedule.yml @@ -0,0 +1,62 @@ +name: Schedule Re-Deploy Latest + +permissions: + contents: write + actions: write + +on: + workflow_dispatch: + schedule: + - cron: 0 */3 * * * # Adjust the schedule as needed + +jobs: + find-latest-tag: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} + + - name: Authenticate GitHub CLI + run: gh auth status + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Find latest semantic version tag (PowerShell) + id: find_tag + shell: pwsh + run: | + # GitHub API URL for the latest release + $apiUrl = "https://api.github.com/repos/${{ github.repository }}/releases/latest" + + # Send the API request + $response = Invoke-RestMethod -Uri $apiUrl -Headers @{ "Accept" = "application/vnd.github+json" } + + # Extract release information + $latestRelease = $response | Select-Object -Property tag_name, name, published_at, body + + # Output the details + Write-Host "Latest Release for ${{ github.repository }}:" + Write-Host "Tag: $($latestRelease.tag_name)" + Write-Host "Name: $($latestRelease.name)" + Write-Host "Published: $($latestRelease.published_at)" + + # GitHub CLI api + # https://cli.github.com/manual/gh_api + + # GitHub CLI API call + gh api ` + --method POST ` + -H "Accept: application/vnd.github+json" ` + -H "X-GitHub-Api-Version: 2022-11-28" ` + "/repos/${{ github.repository }}/actions/workflows/main.yaml/dispatches" ` + -f ref=$($latestRelease.tag_name) + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/update-youtube.yml b/.github/workflows/update-youtube.yml new file mode 100644 index 0000000..f505225 --- /dev/null +++ b/.github/workflows/update-youtube.yml @@ -0,0 +1,53 @@ +name: Youtube Daily Pull + +permissions: + contents: write + pull-requests: write + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" # Runs daily at midnight UTC + +jobs: + update-data: + runs-on: ubuntu-latest + + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ secrets.NKDAGILITY_BOT_APP_ID }} + private-key: ${{ secrets.NKDAGILITY_BOT_CLIENTSECRET }} + + # Step 1: Checkout the repository + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Update-YoutubeChannelData + shell: pwsh + run: ./.powershell/build/Update-YoutubeChannelData.ps1 + env: + YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }} + GOOGLE_ACCESS_TOKEN: ${{ secrets.GOOGLE_ACCESS_TOKEN }} + GOOGLE_REFRESH_TOKEN: ${{ secrets.GOOGLE_REFRESH_TOKEN }} + GOOGLE_CLINET_ID: ${{ secrets.GOOGLE_CLINET_ID }} + GOOGLE_CLINET_SECRET: ${{ secrets.GOOGLE_CLINET_SECRET }} + + - name: Update-YoutubeMarkdownFiles + shell: pwsh + run: ./.powershell/build/Update-YoutubeMarkdownFiles.ps1 + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + + # Step 6: Create or update the pull request + - name: Create or update Pull Request + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ steps.app-token.outputs.token }} + branch: "youtube-daily-update" + base: main + committer: NKDARobot[bot] + title: "Youtube Daily Pull" + body: "This pull request contains daily updates from the automated workflow." + reviewers: mrhinsh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..740b451 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# ignore directories +/public/ +/site/public/ +**/.obsidian/ +**/_gen/ +/resources/ +/.processing/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..79e6575 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,12 @@ +{ + "recommendations": [ + "ms-azuretools.vscode-azurefunctions", + "ms-dotnettools.csharp", + "eliostruyf.vscode-front-matter", + "budparr.language-hugo-vscode", + "esbenp.prettier-vscode", + "kaellarkin.hugo-shortcode-syntax", + "eliostruyf.vscode-front-matter-beta", + "ms-azuretools.vscode-azurestaticwebapps" + ] +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..189c044 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "PowerShell Launch Current File", + "type": "PowerShell", + "request": "launch", + "script": "${file}", + "cwd": "${cwd}" + }, + { + "name": "Attach to .NET Functions", + "type": "coreclr", + "request": "attach", + "processId": "${command:azureFunctions.pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..09e0a70 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,84 @@ +{ + // GitHub Copilot configuration + "github.copilot.enable": { + "*": true, // Enable Copilot for all languages by default + "plaintext": false, // Disable Copilot for plain text files + "markdown": true, // Enable Copilot for Markdown files + "scminput": false, // Disable Copilot for Source Control input + "yaml": true // Enable Copilot for YAML files + }, + + // Automatically fetch updates from remote Git repositories + "git.autofetch": true, + + // Default formatter configuration for specific languages + "[html]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" // Prettier as the default HTML formatter + }, + "[json]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" // Prettier as the default JSON formatter + }, + "[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" // Prettier as the default JavaScript formatter + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" // Prettier as the default Markdown formatter + }, + + // Disable word wrap in diff editor + "diffEditor.wordWrap": "off", + + // Associate .ps1 files with PowerShell language + "files.associations": { + "*.ps1": "powershell" + }, + + // Set the spelling language to British English + "cSpell.language": "en-GB", + + // Prettier configuration + "prettier.bracketSameLine": false, // Ensure the `>` in HTML elements is not on the next line (only applies to non-self-closing elements) + "prettier.bracketSpacing": true, // Print spaces inside object literals + "prettier.enable": true, // Enable Prettier globally + "prettier.printWidth": 255, // Set the maximum line length to 255 characters + "prettier.proseWrap": "never", // Never wrap prose (Markdown/text inside HTML elements) + "prettier.configPath": ".prettierrc", // Path to the Prettier configuration file + + // Format files on save + "editor.formatOnSave": true, + + // Exclude the 'public' folder from file watching (useful for avoiding unnecessary performance impact) + "files.watcherExclude": { + "**/public/**": true + }, + + // Recommended extensions for the project + "recommendations": [ + "esbenp.prettier-vscode", // Prettier extension for code formatting + "eliostruyf.vscode-front-matter", // Front Matter CMS extension + "budparr.language-hugo-vscode", // Hugo language support for VSCode + "kaellarkin.hugo-shortcode-syntax", // Syntax highlighting for Hugo shortcodes + "ms-vscode.powershell" // PowerShell extension for VSCode + ], + + // Grammarly configuration for spell checking + "grammarly.config.documentDialect": "british", // Set Grammarly to British English dialect + "grammarly.files.include": [ // Apply Grammarly to specific file types + "**/readme.md", + "**/README.md", + "**/*.txt", + "**/*.md" + ], + "azureFunctions.deploySubpath": "functions/bin/Release/net8.0/publish", + "azureFunctions.projectLanguage": "C#", + "azureFunctions.projectRuntime": "~4", + "debug.internalConsoleOptions": "neverOpen", + "azureFunctions.preDeployTask": "publish (functions)", + "cSpell.words": [ + "Accentient", + "Hinshelwood", + "Kanban", + "nkdagility", + "youtube" + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..80eef91 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,81 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "clean (functions)", + "command": "dotnet", + "args": [ + "clean", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/functions" + } + }, + { + "label": "build (functions)", + "command": "dotnet", + "args": [ + "build", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean (functions)", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/functions" + } + }, + { + "label": "clean release (functions)", + "command": "dotnet", + "args": [ + "clean", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/functions" + } + }, + { + "label": "publish (functions)", + "command": "dotnet", + "args": [ + "publish", + "--configuration", + "Release", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "type": "process", + "dependsOn": "clean release (functions)", + "problemMatcher": "$msCompile", + "options": { + "cwd": "${workspaceFolder}/functions" + } + }, + { + "type": "func", + "dependsOn": "build (functions)", + "options": { + "cwd": "${workspaceFolder}/functions/bin/Debug/net8.0" + }, + "command": "host start", + "isBackground": true, + "problemMatcher": "$func-dotnet-watch" + } + ] +} \ No newline at end of file diff --git a/LICENCE b/LICENCE new file mode 100644 index 0000000..30dd8f2 --- /dev/null +++ b/LICENCE @@ -0,0 +1,45 @@ +# Closed Source License for Naked Agility Limited + +This website and all associated content (the "Content"), including but not limited to text, images, code, and media, are the exclusive property of Naked Agility Limited ("the Owner") and are protected by copyright law. All rights are reserved unless explicitly stated otherwise. + +## Copyright and Ownership + +1. **Ownership**: The Content is owned by Naked Agility Limited, unless otherwise specified. +2. **Copyright**: All Content, unless specifically exempted or attributed to third parties, is protected by copyright law. Any unauthorized use, copying, modification, distribution, or reproduction of the Content is strictly prohibited. +3. **Third-Party Content**: Any third-party content, where explicitly mentioned, is used with permission and remains the property of its respective owners. + +## Permissions + +- **Viewing and Access**: You are granted permission to view and browse the website and its Content for personal or informational purposes only. +- **No Redistribution**: You are not permitted to copy, distribute, or publicly display any Content from this website without prior written consent from Naked Agility Limited. +- **No Commercial Use**: The Content may not be used for commercial purposes, including but not limited to reselling, without explicit written permission from Naked Agility Limited. +- **No Modification**: You may not modify, translate, adapt, or create derivative works from the Content without explicit authorization from Naked Agility Limited. + +## Exemptions + +- Specific sections of the Content may be exempt from these restrictions, as explicitly noted on the website. In such cases, the exempted Content may have different terms of use, which will be clearly stated. + +## Restrictions + +You shall not, and shall not permit others to: + +1. **Distribute or Sell**: Distribute, sublicense, rent, or sell any portion of the Content without written permission from Naked Agility Limited. +2. **Reverse Engineering**: Reverse engineer, decompile, or disassemble any code or software components of the website. +3. **Misuse**: Use the Content for any illegal, fraudulent, or harmful activities, or in a manner that violates any applicable law. +4. **False Attribution**: Claim ownership or authorship of any part of the Content. + +## Liability and Warranty Disclaimer + +THE CONTENT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL NAKED AGILITY LIMITED BE LIABLE FOR ANY DAMAGES, CLAIMS, OR LOSSES ARISING FROM THE USE OF THE CONTENT OR THE WEBSITE. + +## Termination + +This license and the rights granted hereunder will terminate automatically upon any unauthorized use of the Content or breach of these terms. + +## Intellectual Property + +All trademarks, service marks, logos, and other intellectual property displayed on this website are the property of Naked Agility Limited or third parties. No rights are granted to use any trademarks without the prior written consent of the respective owner. + +--- + +© 2013–2025 Naked Agility Limited. All rights reserved. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e0a2f19 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# NKDAgility Website + +- [Live](https://nkdagility.com) +- [Prototype](https://prototype.nkdagility.com) +- [Preview](https://yellow-pond-042d21b03-preview.westeurope.5.azurestaticapps.net/) + +Pull request sites are yellow-pond-042d21b03-{PRNumber}.westeurope.5.azurestaticapps.net/ + +For each PR there will be a custom environemnt created that will be listed on the PR. + +## Testing Locally + +## prerequisits: + +1. `winget install hugo` +2. `npm install -g @azure/static-web-apps-cli` +3. `npm i -g azure-functions-core-tools@4 --unsafe-perm true` +4. Install https://learn.microsoft.com/en-us/azure/azure-functions/functions-run-local + +## Running + +4. Start Hugo Server `hugo server --source site --config hugo.yaml,hugo.debug.yaml --debug` +5. Start Swa Server `swa start http://localhost:1313 --api-location ./functions` diff --git a/frontmatter.json b/frontmatter.json new file mode 100644 index 0000000..6ee8bd5 --- /dev/null +++ b/frontmatter.json @@ -0,0 +1,625 @@ +--- +{ + "delivery": { + "topics": "1. The Scrum Framework\n2. Scrum in Action\n3. Adopting Scrum\n4. The Team Project\n5. The Product Backlog\n6. Planning and Tracking a Sprint\n7. Collaborating as a Team\n8. Agile Software Testing\n9. Agile Software Development\n10. Reporting\n" + } +} +--- +{ + "$schema": "https://frontmatter.codes/frontmatter.schema.json", + "frontMatter.taxonomy.contentTypes": [ + { + "name": "default", + "pageBundle": true, + "previewPath": null, + "fields": [ + { + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Description", + "name": "description", + "type": "string" + }, + { + "title": "Publishing date", + "name": "date", + "type": "datetime", + "default": "{{now}}", + "isPublishDate": true + }, + { + "title": "Content preview", + "name": "preview", + "type": "image" + }, + { + "title": "Is in draft", + "name": "draft", + "type": "draft" + }, + { + "title": "Tags", + "name": "tags", + "type": "tags" + }, + { + "title": "Categories", + "name": "categories", + "type": "categories" + }, + { + "title": "aliases", + "name": "aliases", + "type": "list" + }, + { + "title": "type", + "name": "type", + "type": "string" + }, + { + "title": "layout", + "name": "layout", + "type": "string" + }, + { + "title": "card", + "type": "fields", + "name": "card", + "fields": [ + { + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "button", + "type": "fields", + "name": "button", + "fields": [ + { + "title": "Content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + } + ] + } + ] + }, + { + "title": "author", + "name": "author", + "type": "string" + }, + { + "title": "headline", + "name": "headline", + "type": "fields", + "fields": [ + { + "title": "title", + "name": "title", + "type": "string" + }, + { + "title": "content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "cards", + "name": "cards", + "type": "block", + "fieldGroup": [ + "cards_group" + ] + } + ] + }, + { + "title": "id", + "name": "id", + "type": "number" + } + ] + }, + { + "name": "course", + "pageBundle": true, + "previewPath": null, + "fields": [ + { + "title": "categories", + "name": "categories", + "type": "categories" + }, + { + "title": "author", + "name": "author", + "type": "string" + }, + { + "title": "title", + "name": "title", + "type": "string" + }, + { + "title": "aliases", + "name": "aliases", + "type": "choice", + "choices": [ + "practicing-kanban-using-azure-boards", + "/training-courses/azure-devops-training-courses/practicing-kanban-using-azure-boards-training/" + ] + }, + { + "title": "date", + "name": "date", + "type": "datetime" + }, + { + "title": "delivery", + "name": "delivery", + "type": "fields", + "fields": [ + { + "title": "audience", + "name": "audience", + "type": "string" + }, + { + "title": "skilllevel", + "name": "skilllevel", + "type": "string" + }, + { + "title": "format", + "name": "format", + "type": "string" + }, + { + "title": "courseAssessmentIcon", + "name": "courseAssessmentIcon", + "type": "string" + }, + { + "title": "objectives", + "name": "objectives", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "certification", + "name": "certification", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "prerequisites", + "name": "prerequisites", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "duration", + "name": "duration", + "type": "number" + }, + { + "title": "code", + "name": "code", + "type": "string" + }, + { + "title": "topics", + "name": "topics", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "details", + "name": "details", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "type", + "name": "type", + "type": "string" + }, + { + "title": "brand", + "name": "brand", + "type": "fields", + "fields": [ + { + "title": "colour", + "name": "colour", + "type": "string" + }, + { + "title": "vendor", + "name": "vendor", + "type": "string" + } + ] + }, + { + "title": "lead", + "name": "lead", + "type": "string" + }, + { + "title": "courseIcon", + "name": "courseIcon", + "type": "string" + } + ] + }, + { + "title": "id", + "name": "id", + "type": "datetime" + }, + { + "title": "tags", + "name": "tags", + "type": "tags" + }, + { + "title": "card", + "name": "card", + "type": "fields", + "fields": [ + { + "title": "content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "title", + "name": "title", + "type": "string" + }, + { + "title": "button", + "name": "button", + "type": "fields", + "fields": [ + { + "title": "content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + } + ] + } + ] + }, + { + "title": "type", + "name": "type", + "type": "string" + } + ] + }, + { + "name": "marketingPages", + "pageBundle": true, + "previewPath": null, + "fields": [ + { + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Description", + "name": "description", + "type": "string" + }, + { + "title": "Publishing date", + "name": "date", + "type": "datetime", + "default": "{{now}}", + "isPublishDate": true + }, + { + "title": "Content preview", + "name": "preview", + "type": "image" + }, + { + "title": "Is in draft", + "name": "draft", + "type": "draft" + }, + { + "title": "Tags", + "name": "tags", + "type": "tags" + }, + { + "title": "Categories", + "name": "categories", + "type": "categories" + }, + { + "title": "card", + "type": "fields", + "name": "card", + "fields": [ + { + "title": "Title", + "name": "title", + "type": "string" + }, + { + "title": "Content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "button", + "type": "fields", + "name": "button", + "fields": [ + { + "title": "Content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + } + ] + } + ] + }, + { + "title": "id", + "name": "id", + "type": "datetime" + }, + { + "title": "author", + "name": "author", + "type": "string" + }, + { + "title": "aliases", + "name": "aliases", + "type": "choice", + "choices": [ + "/capabilities/azure-devops-migration-services/" + ] + }, + { + "title": "type", + "name": "type", + "type": "string" + }, + { + "title": "layout", + "name": "layout", + "type": "string" + }, + { + "title": "headline", + "name": "headline", + "type": "fields", + "fields": [ + { + "title": "title", + "name": "title", + "type": "string" + }, + { + "title": "content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "cards", + "name": "cards", + "type": "block", + "fieldGroup": [ + "cards_group" + ] + } + ] + }, + { + "title": "sections", + "name": "sections", + "type": "block", + "fieldGroup": [ + "sections_group" + ] + } + ] + } + ], + "frontMatter.framework.id": "hugo", + "frontMatter.content.publicFolder": "static", + "frontMatter.preview.host": "http://localhost:1313", + "frontMatter.content.pageFolders": [ + { + "title": "principles", + "path": "[[workspace]]/site/content/principles", + "previewPath": "/principles", + "contentTypes": [ + "marketingPages" + ] + }, + { + "title": "capabilities", + "path": "[[workspace]]/site/content/capabilities", + "previewPath": "/capabilities", + "contentTypes": [ + "marketingPages" + ] + }, + { + "title": "outcomes", + "path": "[[workspace]]/site/content/outcomes", + "previewPath": "/outcomes", + "contentTypes": [ + "marketingPages" + ] + }, + { + "title": "methods", + "path": "[[workspace]]/site/content/methods", + "previewPath": "/methods", + "contentTypes": [ + "marketingPages" + ] + }, + { + "title": "workshops", + "path": "[[workspace]]/site/content/resources/workshops", + "previewPath": "/resources/workshops" + }, + { + "title": "videos", + "path": "[[workspace]]/site/content/resources/videos", + "previewPath": "/resources/videos" + }, + { + "title": "recipes", + "path": "[[workspace]]/site/content/resources/recipes", + "previewPath": "/resources/recipes" + }, + { + "title": "practices", + "path": "[[workspace]]/site/content/resources/practices", + "previewPath": "/resources/practices" + }, + { + "title": "podcast", + "path": "[[workspace]]/site/content/resources/podcast", + "previewPath": "/resources/podcast" + }, + { + "title": "newsletters", + "path": "[[workspace]]/site/content/resources/newsletters", + "previewPath": "/resources/newsletters" + }, + { + "title": "learning-series", + "path": "[[workspace]]/site/content/resources/learning-series", + "previewPath": "/resources/learning-series" + }, + { + "title": "blog", + "path": "[[workspace]]/site/content/resources/blog", + "previewPath": "/resources/blog" + }, + { + "title": "guides", + "path": "[[workspace]]/site/content/resources/guides", + "previewPath": "/resources/guides" + } + ], + "frontMatter.git.enabled": false, + "frontMatter.taxonomy.fieldGroups": [ + { + "id": "cards_group", + "fields": [ + { + "title": "title", + "name": "title", + "type": "string" + }, + { + "title": "content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "image", + "name": "image", + "type": "image" + } + ] + }, + { + "id": "features_group", + "fields": [ + { + "title": "title", + "name": "title", + "type": "string" + }, + { + "title": "content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "media", + "name": "media", + "type": "string" + } + ] + }, + { + "id": "sections_group", + "fields": [ + { + "title": "title", + "name": "title", + "type": "string" + }, + { + "title": "content", + "name": "content", + "type": "string", + "wysiwyg": "markdown" + }, + { + "title": "source", + "name": "source", + "type": "string" + }, + { + "title": "type", + "name": "type", + "type": "string" + }, + { + "title": "features", + "name": "features", + "type": "block", + "fieldGroup": [ + "features_group" + ] + }, + { + "title": "related", + "name": "related", + "type": "choice", + "choices": [ + "/capabilities/training-courses/managing-projects-using-visual-studio-and-scrum-training" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/theme/.hugo_build.lock b/theme/.hugo_build.lock new file mode 100644 index 0000000..e69de29 diff --git a/theme/archetypes/default.md b/theme/archetypes/default.md new file mode 100644 index 0000000..9742825 --- /dev/null +++ b/theme/archetypes/default.md @@ -0,0 +1,5 @@ ++++ +title = '{{- replace .File.ContentBaseName "-" " " | title }}' +date = {{- .Date }} +draft = true ++++ diff --git a/theme/hugo.canary.yaml b/theme/hugo.canary.yaml new file mode 100644 index 0000000..a6cbfca --- /dev/null +++ b/theme/hugo.canary.yaml @@ -0,0 +1,6 @@ +baseURL: "https://yellow-pond-042d21b03-#{PR_Number}#.westeurope.5.azurestaticapps.net/" +environment: "canary" + +buildDrafts: true +buildFuture: true +buildExpired: true diff --git a/theme/hugo.local.yaml b/theme/hugo.local.yaml new file mode 100644 index 0000000..3d3a4b1 --- /dev/null +++ b/theme/hugo.local.yaml @@ -0,0 +1,7 @@ +Environment: "development" + +buildDrafts: true +buildFuture: true +buildExpired: true + +disableAliases: true diff --git a/theme/hugo.preview.yaml b/theme/hugo.preview.yaml new file mode 100644 index 0000000..40d7e90 --- /dev/null +++ b/theme/hugo.preview.yaml @@ -0,0 +1,6 @@ +baseURL: https://preview.nkdagility.com +Environment: "preview" +minifyOutput: true + +buildDrafts: true +buildFuture: true diff --git a/theme/hugo.production.yaml b/theme/hugo.production.yaml new file mode 100644 index 0000000..96665bc --- /dev/null +++ b/theme/hugo.production.yaml @@ -0,0 +1,3 @@ +baseURL: https://nkdagility.com +Environment: "production" +minifyOutput: true diff --git a/theme/layouts/404.html b/theme/layouts/404.html new file mode 100644 index 0000000..ff29764 --- /dev/null +++ b/theme/layouts/404.html @@ -0,0 +1,59 @@ +{{- define "breadcrumbs" }} +
+{{- end }} + +{{- define "headline" }} +
+
+
+

404 Page not Found.

+

Engineering Excellence: Minimising Errors, Maximising Outcomes

+

At Naked Agility, we’re all about delivering robust solutions and measurable outcomes. While even the best systems occasionally hit a snag, we see every 404 as a chance to adapt and improve. Excellence isn’t about perfection—it’s about learning fast, fixing faster, and always striving for better. Let’s get you back on track!

+

Spot a Problem? Let’s Fix It Together!

+

+ Think you’ve found a glitch? Head over to our + + GitHub Issues  + + to report it — we’re always keen to hear from you. Feeling adventurous? Submit a pull request and be part of the solution! Together, we can make things better. 🚀 +

+
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+{{- end }} + +{{- define "main" }} +
+
+
+ +
+
+ +
+
+
+ + +
+

What we do?

+

Explore our comprehensive range of services and capabilities designed to empower organizations and teams. With our expert solutions, you can efficiently address and resolve immediate challenges, driving success across your operations.

+ {{- partial "cards-section.html" (dict "context" . "SectionName" "capabilities") }} +
+{{- end }} + +{{- define "template" }} + page/404.html +{{- end }} diff --git a/theme/layouts/_default/_markup/render-image.html b/theme/layouts/_default/_markup/render-image.html new file mode 100644 index 0000000..2eea4df --- /dev/null +++ b/theme/layouts/_default/_markup/render-image.html @@ -0,0 +1,3 @@ + + {{- .Page.Title }} + diff --git a/theme/layouts/_default/_markup/render-link.html b/theme/layouts/_default/_markup/render-link.html new file mode 100644 index 0000000..a7f7bbe --- /dev/null +++ b/theme/layouts/_default/_markup/render-link.html @@ -0,0 +1,8 @@ +{{- $isExternal := strings.HasPrefix .Destination "http" -}} +{{- if $isExternal }} + + {{- .Text }}  +{{- else }} + {{- .Text }} +{{- end }} diff --git a/theme/layouts/_default/baseof.html b/theme/layouts/_default/baseof.html new file mode 100644 index 0000000..68f44fe --- /dev/null +++ b/theme/layouts/_default/baseof.html @@ -0,0 +1,195 @@ + +{{ if eq .Sites.Default.Home . }} + {{ partialCached "infrastructure/publish-redirects.html" . }} +{{ end }} + + + + + + {{- partial "infrastructure/head.html" . }} + + + + + + {{- partial "infrastructure/header.html" . }} + + {{- block "breadcrumbs" . }} +
+ {{- partial "infrastructure/breadcrumbs.html" . }} +
+ {{- end }} + + {{- block "headline" . }} +
+ {{- partial "headline.html" . }} +
+ {{- if not .IsHome }} +
+ {{- partial "trustpilot/scrumorg-trustpilot-carousel.html" . }} +
+ {{- end }} + {{- end }} + + {{- block "main" . }} +
+ {{- partial "page-sections/sections.html" . }} +
+ {{- end }} + + {{- partial "connect.html" . }} + + {{- block "siteSectionCallback" . }} +
+ {{- end }} + + {{- if not .IsHome }} + {{- partial "our-customers.html" . }} + {{- end }} + + {{- partial "infrastructure/footer.html" . }} + {{- if ne hugo.Environment "production" }} +
+
+ + +
+

This is content visible only in development.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NodeValue
debug
{{- debug.Dump .Params }}
This PageTemplate not set
Template{{- block "template" . }}{{- end }}
Layout{{- .Layout }}
Kind{{- .Kind }}
Type{{- .Type }}
Section{{- .Section }}
CurrentSection{{- .CurrentSection }}
Section Info + {{- if and .IsSection .Parent }} +

This is a subsection (child section of {{- .Parent.Title }}).

+ {{- else if .IsSection }} +

This is a top-level section.

+ {{- end }} +
Child Sections + {{- if gt (len .Sections) 0 }} +

This section has child sections.

+ + {{- else }} +

This section has no child sections.

+ {{- end }} +
Ancestors +
    + {{- range .Ancestors.Reverse }} +
  1. {{- .Title }}
  2. + {{- end }} +
  3. {{- .Title }}
  4. +
+
Subsections +
    + {{- range .Sections }} +
  • + {{- .Title }} () + {{- if .IsSection }} + + {{- end }} +
  • + {{- end }} +
+
Pages +
    + {{- $groups := .Pages.GroupBy "Type" }} + {{- range $groups }} +
  • + {{- .Key }} +
      + {{- range .Pages }} +
    • + {{- .Title }} + [Type: {{- .Type }}] + {{- if .Layout }}[Layout: {{- .Layout }}]{{- end }} +
    • + {{- end }} +
    +
  • + {{- end }} +
+
+
+
+
+ {{- end }} + + + diff --git a/theme/layouts/_default/list.html b/theme/layouts/_default/list.html new file mode 100644 index 0000000..2ba7a53 --- /dev/null +++ b/theme/layouts/_default/list.html @@ -0,0 +1,8 @@ +{{- define "main" }} +
+ {{- .Content }} +
+{{- end }} +{{- define "template" }} + _default/list.html +{{- end }} diff --git a/theme/layouts/_default/list.json b/theme/layouts/_default/list.json new file mode 100644 index 0000000..2d94e2a --- /dev/null +++ b/theme/layouts/_default/list.json @@ -0,0 +1,61 @@ +{{- $.Scratch.Add "pages" slice -}} + +{{- if .IsHome -}} + {{- range .Site.RegularPages -}} + {{- $.Scratch.Add "pages" (dict + "title" .Title + "url" .Permalink + "date" .Date + "summary" .Summary + "contentCatagory" .Section + "contentType" .Type + ) -}} + {{- end -}} + +{{- else if .IsSection -}} + {{- range .Pages -}} + {{- $.Scratch.Add "pages" (dict + "title" .Title + "url" .Permalink + "date" .Date + "summary" .Summary + "contentCatagory" .Section + "contentType" .Type + ) -}} + {{- end -}} + +{{- else if .IsTaxonomy -}} + {{- range .Data.Pages -}} + {{- $.Scratch.Add "pages" (dict + "title" .Title + "url" .Permalink + "date" .Date + "summary" .Summary + "contentCatagory" .Section + "contentType" .Type + ) -}} + {{- end -}} + +{{- else if .IsTaxonomyTerm -}} + {{- range .Data.Terms -}} + {{- $.Scratch.Add "pages" (dict + "title" .Title + "url" .Permalink + "contentCatagory" .Name + "contentType" "taxonomyTerm" + ) -}} + {{- end -}} + +{{- else if .IsPage -}} + {{- $.Scratch.Add "pages" (dict + "title" .Title + "url" .Permalink + "date" .Date + "summary" .Summary + "contentCatagory" .Section + "contentType" .Type + ) -}} + +{{- end -}} + +{{- $.Scratch.Get "pages" | jsonify -}} diff --git a/theme/layouts/_default/section.rss.xml b/theme/layouts/_default/section.rss.xml new file mode 100644 index 0000000..65900a0 --- /dev/null +++ b/theme/layouts/_default/section.rss.xml @@ -0,0 +1,24 @@ +{{ printf "" | safeHTML }} + + + {{ .Title | htmlEscape }} | {{ .Site.Title | htmlEscape }} + {{ .Permalink | absURL }} + {{ .Description | default .Site.Params.description | htmlEscape }} + {{ .Site.Language.Lang }} + + {{ range first 50 .Pages.ByDate.Reverse }} + + {{ .Title | htmlEscape }} + {{ .Permalink }} + {{ .Permalink }} + {{ .Date.Format "Mon, 02 Jan 2006 15:04:05 MST" }} + + {{ $keywords := partial "functions/keywords.html" . }} + {{ range $keywords }} + {{ . | htmlEscape }} + {{- end -}} + + {{ end }} + + + \ No newline at end of file diff --git a/theme/layouts/_default/single.html b/theme/layouts/_default/single.html new file mode 100644 index 0000000..90653f1 --- /dev/null +++ b/theme/layouts/_default/single.html @@ -0,0 +1,3 @@ +{{- define "template" }} + _default/single.html +{{- end }} diff --git a/theme/layouts/_default/sitemap.xml b/theme/layouts/_default/sitemap.xml new file mode 100644 index 0000000..e4b72cc --- /dev/null +++ b/theme/layouts/_default/sitemap.xml @@ -0,0 +1,32 @@ +{{ printf "" | safeHTML }} + + {{ range where .Pages "Sitemap.Disable" "ne" true }} + {{- if .Permalink -}} + + {{ .Permalink }} + {{ if not .Lastmod.IsZero }} + {{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05-07:00" ) }} + {{ end }} + {{ with .Sitemap.ChangeFreq }} + {{ . }} + {{ end }} + {{ if ge .Sitemap.Priority 0.0 }} + {{ .Sitemap.Priority }} + {{ end }} + {{ if .IsTranslated }} + {{ range .Translations }} + {{ end }} + {{ end }} + + {{- end -}} + {{ end }} + \ No newline at end of file diff --git a/theme/layouts/index.html b/theme/layouts/index.html new file mode 100644 index 0000000..e2421d1 --- /dev/null +++ b/theme/layouts/index.html @@ -0,0 +1,88 @@ +{{- define "breadcrumbs" }} +
+{{- end }} + +{{- define "headline" }} +
+
+
+

The home of technical leadership and engineering excellence for software development teams.

+

We focus on first principles, delivering measurable outcomes with evidence-based engineering. Our international experts help companies build software more effectively.

+

We guide you in adopting a multi-method approach to agility, evolving beyond defined processes to a co-adaptive flow of value.

+

Our expertise in Lean, Agile, DevOps, Scrum, and Kanban optimises customer value and revenue. Our deep technical knowledge of Azure DevOps, GitHub, .NET, Azure, and AI enables smarter decisions and better support.

+
+
+
+
+
+
+

How Usable Working Products Are Your Ultimate Weapon Against Risks

+

The only way to mitigate risk when employing Agile practices is by continuously delivering a usable working product. Agile is not about spinning the hamster wheels but delivering shippable increments that users can interact with.

+ Read more... +
+
+
+
+
+
+
+
+
+
+
+
+
+ {{- partial "trustpilot/scrumorg-trustpilot-carousel.html" . }} +
+
+ {{- partial "headline.html" . }} +
+{{- end }} +{{- define "main" }} +
+

Our Proven Track Record

+

Over the years, we have successfully served hundreds of clients across the globe.​

+
+
+

{{- sub (now.Year) 2013 }}

+

Years in Business​

+
+
+

{{- sub (now.Year) 2008 }}

+

Years as Microsoft MVP​

+
+
+

{{- sub (now.Year) 2010 }}

+

Years as a Scrum.org trainer

+
+
+

13

+

Countries Covered​

+
+
+

200+

+

Clients Served​

+
+
+

2,600+

+

People trained

+
+
+
+ {{- partial "our-customers.html" . }} + {{- partial "connect.html" . }} +
+

What you get?

+

+ We empower organizations to achieve unparalleled success in today's dynamic market. Our proven strategies enhance Team Effectiveness, deliver Higher Quality Products, and foster a culture of Continuous Improvement. Discover how our tailored approaches can significantly reduce your Time to Market and boost Market Adaptability, setting your business apart. Explore the transformative outcomes we offer and see why companies trust NKDAgility to + elevate their performance and thrive in competitive environments. +

+ {{- partial "cards-section.html" (dict "context" . "SectionName" "outcomes") . }} +
+
+

What we do?

+

Explore our comprehensive range of services and capabilities designed to empower organizations and teams. With our expert solutions, you can efficiently address and resolve immediate challenges, driving success across your operations.

+ {{- partial "cards-section.html" (dict "context" . "SectionName" "capabilities") }} +
+ {{- partial "homepage-people.html" . }} +{{- end }} diff --git a/theme/layouts/index.json b/theme/layouts/index.json new file mode 100644 index 0000000..5847bae --- /dev/null +++ b/theme/layouts/index.json @@ -0,0 +1,7 @@ +{{- $.Scratch.Add "index" slice -}} +{{- range site.RegularPages -}} +{{- if ne .Layout "search" -}} + {{- $.Scratch.Add "index" (dict "title" .Title "permalink" .Permalink "summary" .Summary "content" .Plain) -}} +{{- end -}} +{{- end -}} +{{- $.Scratch.Get "index" | jsonify -}} \ No newline at end of file diff --git a/theme/layouts/partials/card.html b/theme/layouts/partials/card.html new file mode 100644 index 0000000..9e9411a --- /dev/null +++ b/theme/layouts/partials/card.html @@ -0,0 +1,23 @@ + +
+
+
+

{{- .title }}

+
+ {{- .content | markdownify }} +
+ details... +
+ {{- if .permalink }} + + {{- end }} + + {{- if .link }} + + {{- end }} +
+
diff --git a/theme/layouts/partials/cards-course.html b/theme/layouts/partials/cards-course.html new file mode 100644 index 0000000..19f5d0d --- /dev/null +++ b/theme/layouts/partials/cards-course.html @@ -0,0 +1,35 @@ +
+

{{- .context.Title }}

+
+ {{- if .context.Params.preview }} + {{- $image := .context.Resources.GetMatch (printf "images/%s" .context.Params.preview) }} + {{- if $image }} + {{- .context.Params.Title }} + {{- else }} + {{- $image := .resources.Get "images/NKDAgility-Courses-Template-16x9-1-jpg.webp" }} + {{- .context.Params.Title }} + {{- end }} + {{- else }} + {{- $image := .resources.Get "images/NKDAgility-Courses-Template-16x9-1-jpg.webp" }} + {{- .context.Params.Title }} + {{- end }} +
+
+
+ {{- if .context.Params.previewIcon }} + {{- $courseIcon := .context.Resources.GetMatch (printf "images/%s" .context.Params.previewIcon) }} + {{- if $courseIcon }} + {{- .context.Params.Title }} + {{- end }} + {{- end }} +
+
+

{{- .context.Params.card.content | markdownify }}

+
+
+ +
diff --git a/theme/layouts/partials/cards-section.html b/theme/layouts/partials/cards-section.html new file mode 100644 index 0000000..99989f0 --- /dev/null +++ b/theme/layouts/partials/cards-section.html @@ -0,0 +1,17 @@ +{{- $sectionName := .SectionName }} +{{- $PagePermalink := .context.Page.Permalink }} +{{- $section := .context.Site.GetPage "section" $sectionName }} +
+
+ {{- range $section.Pages.ByWeight }} + {{- if not .Draft }} + {{- if ne .Permalink $PagePermalink }} +
+ + {{- partial "card.html" (dict "Page" . "class" "sectioncards" "title" .Params.card.title "content" .Params.card.content "buttonContent" .Params.card.button.content "permalink" .Permalink ) }} +
+ {{- end }} + {{- end }} + {{- end }} +
+
diff --git a/theme/layouts/partials/connect.html b/theme/layouts/partials/connect.html new file mode 100644 index 0000000..0b21f86 --- /dev/null +++ b/theme/layouts/partials/connect.html @@ -0,0 +1,31 @@ +
+
+
+ +
+
+

Connect with Martin Hinshelwood

+

If you've made it this far, it's worth connecting with our principal consultant and coach, Martin Hinshelwood, for a 30-minute 'ask me anything' call.

+ + +
+
+
+
+
+ + + diff --git a/theme/layouts/partials/delivery-parts/carousel.html b/theme/layouts/partials/delivery-parts/carousel.html new file mode 100644 index 0000000..d456218 --- /dev/null +++ b/theme/layouts/partials/delivery-parts/carousel.html @@ -0,0 +1,112 @@ +{{- $site := .Site }} +{{- $Resources := .Resources }} +{{- if .Params.carousel }} +
+
+ {{- range $index, $item := .Params.carousel }} +
+ {{- if eq $item.type "video" }} + + {{- $resourcePage := $site.GetPage $item.link }} + {{- if $resourcePage }} +
+ +
+ {{- else }} +

Video `{{- $item.link }}` not found.

+ {{- end }} + {{- else if eq $item.type "image" }} + + {{- $image := $Resources.GetMatch $item.link }} + {{- if $image }} + {{- if eq $image.MediaType.SubType "svg" }} + + {{- else }} + {{- $resized := $image.Fit "800x550 webp" }} + + {{- end }} + {{- else }} + + carousel image + {{- end }} + {{- end }} +
+ {{- end }} +
+ + +
+
+
+
+ + + + +{{- else }} + {{- if .Params.preview }} + {{- $image := $Resources.GetMatch (printf "images/%s" .Params.preview) }} + {{- if not $image }} + {{- $image = $Resources.GetMatch .Params.preview }} + {{- end }} + {{- if $image }} + {{- if eq $image.MediaType.SubType "svg" }} + + {{- else }} + {{- $resized := $image.Fit "800x550 webp" }} + + {{- end }} + {{- else }} + + + {{- end }} + {{- else }} + + + {{- end }} +{{- end }} diff --git a/theme/layouts/partials/delivery-parts/deliveryfound-summery.html b/theme/layouts/partials/delivery-parts/deliveryfound-summery.html new file mode 100644 index 0000000..e01f034 --- /dev/null +++ b/theme/layouts/partials/delivery-parts/deliveryfound-summery.html @@ -0,0 +1,267 @@ + +
+
+ +
+
+
+ {{- .Params.code }} | + {{- $courseVendors := index .Params "course-vendors" }} + {{- range $index, $vendor := $courseVendors }} + {{- if gt $index 0 }},{{- end }}{{- $vendor }} + {{- end }} + | + {{- .Params.level | title }} +
+
+

GOAL: 6 participants

+
+
+
+
+
+
0 Participants
+
Rolling delivery
+
+
+
+ {{- if .Params.previewIcon }} + {{- $image := .Resources.GetMatch (printf "images/%s" .Params.previewIcon) }} + {{- if $image }} + {{- $resized := $image.Fit "100x100 webp" }} + + {{- else }} + NOICON + {{- end }} + {{- end }} +
+
+

Preordering grants you extra coaching, future discounts, and input on scheduling and format.

+
+
+
+ {{- if eq hugo.Environment "development" }} + {{/*
+ + */}} +
+ {{- else }} + Preorder + {{- end }} + + + Follow + + learn more +
+
+
+
+ + +
+
+ +
+
About Our Funding Model
+

Our funding model supports busy professionals by ensuring courses are fully funded and guaranteed.

+
Funding Mode
+

We proceed with courses only when the participant goal is met, ensuring value and support for everyone involved. Funds are only taken once the target is met.

+
Collaboration Benefits
+

Pre-ordering gives exclusive access to help shape the course’s start date, schedule, and format.

+ Get more information +
+
+
+
+
+ + + + + + + + + + +{{- if eq hugo.Environment "development" }} + + + + + +{{- end }} diff --git a/theme/layouts/partials/delivery-parts/events-schedule-single.html b/theme/layouts/partials/delivery-parts/events-schedule-single.html new file mode 100644 index 0000000..8ff342f --- /dev/null +++ b/theme/layouts/partials/delivery-parts/events-schedule-single.html @@ -0,0 +1,100 @@ +{{- if eq hugo.Environment "development" }} +
+
+

Upcoming Dates

+
+ {{/* Define the number of months for future events */}} + {{- $numMonths := 5 }} + {{- $fakeEvents := slice }} + + {{/* Collect months that already have real events */}} + {{- $existingMonths := slice }} + {{- if .Page.Params.events }} + {{- range .Page.Params.events }} + {{- $month := .start_date | time.Format "2006-01" }} + {{- $existingMonths = $existingMonths | append $month }} + {{- end }} + {{- end }} + + {{/* Generate dynamic events starting from the current month */}} + {{- $generatedCount := 0 }} + {{- range seq 0 12 }} + + {{- if eq (int $generatedCount) $numMonths }} + {{- break }} + {{- end }} + + {{- $startDate := now.AddDate 0 (int .) 0 | time.Format "2006-01-02" }} + {{- $monthKey := now.AddDate 0 (int .) 0 | time.Format "2006-01" }} + + {{/* Check if this month already has a real event */}} + {{- if not (in $existingMonths $monthKey) }} + {{/* Create a new fake event for this month */}} + {{- $event := dict "start_date" $startDate "location" "Virtual" "type" "preorder" "registration_link" "" "sessions" (slice + (dict "start_datetime" (printf "%sT09:00:00" $startDate) "end_datetime" (printf "%sT17:00:00" $startDate) "timezone" "Europe/London" "syllabus_ids" (slice 1 2 3)) + (dict "start_datetime" (printf "%sT09:00:00" (now.AddDate 0 (int .) 1 | time.Format "2006-01-08")) "end_datetime" (printf "%sT17:00:00" (now.AddDate 0 (int .) 1 | time.Format "2006-01-08")) "timezone" "Europe/London" "syllabus_ids" (slice 4 5 6)) + ) + }} + {{- $fakeEvents = $fakeEvents | append $event }} + {{- $generatedCount = add (int $generatedCount) 1 }} + {{- end }} + {{- end }} + + {{/* Combine existing events with the fake events */}} + {{- $allEvents := $fakeEvents }} + {{- if .Page.Params.events }} + {{- $allEvents = $allEvents | append .Page.Params.events }} + {{- end }} + + {{/* Sort events by start_date in descending order (latest first) */}} + {{- $allEvents = sort $allEvents "start_date" "asc" }} + + {{- if gt (len $allEvents) 0 }} + {{- range $index, $card := $allEvents }} +
+ +
+
+ +
+
+ SOLD OUT + SEATS + learning experience +
+
+
+ {{/* + + + */}} + {{- if eq .type "preorder" }} + Preorder + {{- else }} + Book + {{- end }} +
+
+
+
+
+ +
+ {{- end }} + {{- else }} +
+ We don't have any dates for public events for {{- .Page.Title }} right now. + Sign-up to be the first to know, or contact us for discounts or private training. +
+ {{- end }} +
+

If we don't have dates that suit you then please sign-up to be the first to know, or contact us for discounts or private training.

+
+
+
+
+{{- end }} diff --git a/theme/layouts/partials/delivery-parts/syllabus-single.html b/theme/layouts/partials/delivery-parts/syllabus-single.html new file mode 100644 index 0000000..09a6b55 --- /dev/null +++ b/theme/layouts/partials/delivery-parts/syllabus-single.html @@ -0,0 +1,230 @@ +
+

Syllabus

+

+ Our comprehensive syllabus is designed to accommodate various learning styles, whether you prefer a traditional in-depth session, a flexible immersive experience, or a structured mentor program. Each module can be delivered in the following formats: +

+ +

+ Our immersive programs empower growth through incremental learning, outcome-based assignments, and facilitated reflections, ensuring that the skills you gain are practical, applicable, and ready to be used in your work environment. Facilitated + Reflections are a cornerstone of our immersive approach, allowing participants to engage deeply with the material. In each session, you will have the opportunity to reflect on your learning, discuss challenges and successes with peers, and gain + actionable feedback from professional trainers. These reflective sessions enhance understanding, foster continuous improvement, and support your growth in a collaborative environment. +

+ +
+ {{- if and (not (eq .Page.Params.syllabus nil)) (gt (len .Page.Params.syllabus) 0) }} + + {{- if eq hugo.Environment "development" }} +
+ +
+
+

How it Works?

+
+ This Training is an interactive practical experience, and you will be participating in numerous exercises and collaborations during this class. Without being able to see the participants a lot of non-verbal, and visual queues are missed. + We therefore as that you have your cameras on as much as possible. Every discussion should feel like an invitation to a conversation, and it is up to you and your team members to facilitate conversations. Bring your A-game, and above all, + have fun. +
+
+
+
    +
  • +
    5m
    +
    + How we run our courses? +
  • + {{- if .Page.Params.syllabusBefore }} + {{- range sort .Page.Params.syllabusBefore "weight" }} +
  • +
    {{- .duration }}m
    +
    + {{- $isExternal := strings.HasPrefix .link "http" -}} + {{- if $isExternal }} + + {{- .title }}  + + + {{- else }} + {{- .title }} + {{- end }} +
  • + {{- end }} + {{- end }} +
+
+
+
+ +
+ {{- end }} + + {{- range $index, $syllabi := .Page.Params.syllabus }} +
+ +
+
+

+ {{- .title }} +

+
+ Module {{- .id }} + {{- .duration }}+ +
+
+ {{- .content | markdownify }} +
+ {{- if .learningResources }} +
+
+ + +
+
    + {{- range sort .learningResources "weight" }} + {{- if not .draft }} +
  • +
    {{- .duration }}m
    +
    + {{- $isExternal := strings.HasPrefix .link "http" -}} + {{- if $isExternal }} + + {{- .title }}  + + + {{- else }} + {{- .title }} + {{- end }} +
  • + {{- end }} + {{- end }} +
+
+
+ {{- end }} + {{- if .assignment }} +
+
+ {{- if .assignment.content }} + +
+
+ {{- .assignment.content | markdownify }} +
+ {{- if .assignment.examples }} +
+ + View Examples + + + +
+ {{- end }} +
+ {{- else }} +

+ Assignment*: + {{- .assignment.title }} +

+ {{- end }} +
+ {{- end }} +
+
+ +
+ {{- end }} + +
+ +
+
+

Catchup & After

+
+ Two weeks after completion, participants are invited to join a follow-up catch-up session designed to address any remaining questions, ideas, or challenges that have emerged since the training. This session provides an opportunity to reflect + on your experiences applying the concepts learned in the course, share insights, and receive additional support. +
+
+ +
+
+ +
+ + {{- else }} +
The syllabus is available upon request.
+ {{- end }} +
+

* Assignments are part of our Immersive Training Programs, encouraging participants to apply their learning practically between sessions for a more hands-on experience.

+

If we don't have dates that suit you, please sign up to be the first to know, or contact us for discounts or private training.

+

For those looking for a more guided, continuous learning journey, explore our Mentor Programs, where you can engage in a series of immersive learning sessions combined with ongoing mentorship.

+
+
+
+
+ + + diff --git a/theme/layouts/partials/functions/breadcrumbs.html b/theme/layouts/partials/functions/breadcrumbs.html new file mode 100644 index 0000000..c44954b --- /dev/null +++ b/theme/layouts/partials/functions/breadcrumbs.html @@ -0,0 +1,48 @@ +{{- $breadcrumbs := slice }} +{{- $built := false }} + +{{- if or (eq .Type "course") (eq .Type "course-topics") (eq .Type "course-vendors") }} + + {{- $breadcrumbs = $breadcrumbs | append (site.GetPage "home") }} + {{- $breadcrumbs = $breadcrumbs | append (site.GetPage "capabilities") }} + {{- $breadcrumbs = $breadcrumbs | append (site.GetPage "capabilities/training-courses") }} + {{- if eq .Type "course" }} + {{- $urlParts := split .Params.url "/" }} + {{- $courseTopic := index $urlParts 3 }} + {{- $search := (printf "course-topics/%s" $courseTopic) }} + {{- $termPage := site.GetPage $search }} + {{- if $termPage }} + + {{- $breadcrumbs = $breadcrumbs | append $termPage }} + {{- end }} + {{- end }} + {{- $breadcrumbs = $breadcrumbs | append . }} + {{- $built = true }} +{{- else if eq .Type "delivery-audiences" }} + + {{- $breadcrumbs = $breadcrumbs | append (site.GetPage "home") }} + {{- $breadcrumbs = $breadcrumbs | append (site.GetPage "capabilities") }} + {{- $breadcrumbs = $breadcrumbs | append . }} + {{- $built = true }} +{{- end }} + +{{- if not $built }} + + {{- $currentPage := . }} + {{- $breadcrumbs = $breadcrumbs | append $currentPage }} + + {{- $parent := .Parent }} + {{- range (seq 1 10) }} + + {{- if $parent }} + + {{- $breadcrumbs = $breadcrumbs | append $parent }} + {{- $parent = $parent.Parent }} + {{- else }} + {{- break }} + {{- end }} + {{- end }} + + {{- $breadcrumbs = $breadcrumbs.Reverse }} +{{- end }} +{{- return $breadcrumbs -}} diff --git a/theme/layouts/partials/functions/keywords.html b/theme/layouts/partials/functions/keywords.html new file mode 100644 index 0000000..ce905a9 --- /dev/null +++ b/theme/layouts/partials/functions/keywords.html @@ -0,0 +1,8 @@ +{{ $keywords := slice -}} +{{- range $taxonomy, $terms := .Page.Site.Taxonomies -}} + {{- $pageTerms := index $.Params $taxonomy -}} + {{- if $pageTerms -}} + {{- $keywords = $keywords | append $pageTerms -}} + {{- end -}} +{{- end -}} +{{- return $keywords -}} diff --git a/theme/layouts/partials/headline.html b/theme/layouts/partials/headline.html new file mode 100644 index 0000000..9cd972c --- /dev/null +++ b/theme/layouts/partials/headline.html @@ -0,0 +1,113 @@ +{{- $resources := .Resources }} +{{- $page := .Page }} +
+
+

+ {{- if .Params.headline }} + {{- .Params.headline.title | markdownify }} + {{- else if .Params.card }} + {{- .Params.card.title | markdownify }} + {{- else }} + {{- .Title | markdownify }} + {{- end }} +

+

+ {{- if .Params.headline }} + {{- .Params.headline.subtitle | markdownify }} + {{- else if .Params.card }} + {{- .Params.card.subtitle | markdownify }} + {{- else }} + {{- .Params.subtitle | markdownify }} + {{- end }} +

+ + {{- if .Params.headline.images }} + {{- $colClass := "col-xl-4" }} + {{- $count := len .Params.headline.images }} + {{- if eq (mod $count 3) 0 }} + {{- $colClass = "col-xl-4" }} + + {{- else if eq (mod $count 2) 0 }} + {{- $colClass = "col-xl-6" }} + + {{- end }} +
+
+ {{- range .Params.headline.images }} +
+ {{- if strings.HasPrefix . "/images/" }} + + + + {{- else }} + {{- $image := $page.Resources.GetMatch . }} + {{- if $image }} + {{- $resized := $image.Fit "25x25 jpg" }} + + + + {{- else }} + failed to get image + {{- . }} + {{- end }} + {{- end }} +
+ {{- end }} +
+
+ {{- end }} + + {{- if .Params.headline.buttons }} +
+ {{- range .Params.headline.buttons }} + {{- $isExternal := strings.HasPrefix .link "http" -}} + {{- if $isExternal }} + + {{- .content }}  + + {{- else }} + {{- .content }} + {{- end }} + {{- end }} +
+ {{- end }} +
+
+

+ {{- if .Params.headline }} + {{- .Params.headline.content | markdownify }} + {{- else if .Params.card }} + {{- .Params.card.content | markdownify }} + {{- else }} + {{- end }} +

+
+
+
+ {{- if and (isset .Params.headline "cards") (not (eq .Params.headline.cards nil)) }} + {{- if gt (len .Params.headline.cards) 0 }} + {{- range .Params.headline.cards }} +
+ + {{- partial "card.html" (dict "class" "herocards" "isExpandable" true "title" .title "content" .content "link" .link) }} +
+ {{- end }} + {{- else }} + {{- end }} + {{- else }} + {{/* If headline.cards is null or not set, display the default cards */}} +
+ + {{- partial "card.html" (dict "class" "herocards" "title" "Deep Technical Knowledge" "content" "Our team has extensive experience with agile within the context of many different team settings.") }} +
+
+ + {{- partial "card.html" (dict "class" "herocards" "title" "Tailored Strategies" "content" "Whatever your needs we can provide a tailored experience.") }} +
+
+ + {{- partial "card.html" (dict "class" "herocards" "title" "Comprehensive Support" "content" "We offer continuous support throughout the engagement process, including post-engagement assistance to ensure everything runs smoothly.") }} +
+ {{- end }} + +
diff --git a/theme/layouts/partials/homepage-people.html b/theme/layouts/partials/homepage-people.html new file mode 100644 index 0000000..15b23a8 --- /dev/null +++ b/theme/layouts/partials/homepage-people.html @@ -0,0 +1,26 @@ +
+

Who are we?

+

+ At NKD Agility, we unite a team of the world’s most seasoned and dynamic Agile coaches, consultants, Professional Scrum Trainers, and Kanban experts, who are globally respected for mastering Agile in some of the most challenging Agile environments on + Earth. Unmatched expertise balanced with warmth and passion. +

+ {{- $PagePermalink := .Page.Permalink }} + {{- $section := .Site.GetPage "section" "company/people" }} +
+
+ {{- len $section.Pages }} + + {{- len (where $section.Pages (index .Params "people-abilities") "intersect" (slice "Trainer" "Consultant")) }} + {{- range where (where $section.Pages.ByWeight "Type" "people") (index .Params "people-abilities") "in" (slice "Trainer" "Consultant") }} + {{- if not .Draft }} + {{- if ne .Permalink $PagePermalink }} +
+ + {{- partial "card.html" (dict "Page" . "class" "sectioncards" "title" .Params.card.title "content" .Params.card.content "buttonContent" .Params.card.button.content "permalink" .Permalink ) }} +
+ {{- end }} + {{- end }} + {{- end }} +
+
+
diff --git a/theme/layouts/partials/infrastructure/breadcrumbs.html b/theme/layouts/partials/infrastructure/breadcrumbs.html new file mode 100644 index 0000000..12a0277 --- /dev/null +++ b/theme/layouts/partials/infrastructure/breadcrumbs.html @@ -0,0 +1,83 @@ +{{ $breadcrumbs := partial "functions/breadcrumbs.html" . }} + +{{- if ne hugo.Environment "production" }} + +{{ end }} + + + diff --git a/theme/layouts/partials/infrastructure/footer.html b/theme/layouts/partials/infrastructure/footer.html new file mode 100644 index 0000000..1eb1d59 --- /dev/null +++ b/theme/layouts/partials/infrastructure/footer.html @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + +{{/* + + +*/}} diff --git a/theme/layouts/partials/infrastructure/head.html b/theme/layouts/partials/infrastructure/head.html new file mode 100644 index 0000000..08529dc --- /dev/null +++ b/theme/layouts/partials/infrastructure/head.html @@ -0,0 +1,55 @@ + + +{{ if eq .Kind "404" }} + +{{ end }} + +{{- partial "infrastructure/seo.html" . }} + +{{ if .Params.videoId }} + +{{ else }} + +{{ end }} + + + + + + + + + + + + + + + + + + + + + + + + +{{/* + +*/}} +{{/* */}} + + + + + + + + + + +{{/* */}} diff --git a/theme/layouts/partials/infrastructure/header.html b/theme/layouts/partials/infrastructure/header.html new file mode 100644 index 0000000..6cbec36 --- /dev/null +++ b/theme/layouts/partials/infrastructure/header.html @@ -0,0 +1,53 @@ +{{ if ne hugo.Environment "production" }} +
+ {{ if .Page.Draft }}!!DRAFT!! |{{ end }} + {{ if eq hugo.Environment "development" }} + Local | v0.0.0 | No Robots + {{ else }} + #{nkdAgility_AzureSitesConfig}# | v#{GitVersion.SemVer}# | No Robots + {{ end }} + | Debug Info +
+{{ end }} + + +
+ +
+
+ +
+
+ {{- partial "main-menu.html" . }} + {{- partial "main-menu2.html" . }} +
diff --git a/theme/layouts/partials/infrastructure/image-resize.html b/theme/layouts/partials/infrastructure/image-resize.html new file mode 100644 index 0000000..6eb9f02 --- /dev/null +++ b/theme/layouts/partials/infrastructure/image-resize.html @@ -0,0 +1,12 @@ +{{ $resources := .resources }} +{{ $src := .src }} +{{ $alt := .alt | default "" }} +{{ $width := .width | default 800 }} +{{ $height := .height | default 600 }} +{{ $class := .class | default "" }} +{{ with $resources.GetMatch $src }} + {{ $resized := .Resize (printf "%dx%d lanczos q85" $width $height) }} + {{ $alt }} +{{ else }} + {{ $alt }} +{{ end }} diff --git a/theme/layouts/partials/infrastructure/publish-redirects.html b/theme/layouts/partials/infrastructure/publish-redirects.html new file mode 100644 index 0000000..66ecce0 --- /dev/null +++ b/theme/layouts/partials/infrastructure/publish-redirects.html @@ -0,0 +1,27 @@ +{{- $.Scratch.Add "routes" slice -}} +{{- range $course := where .Site.RegularPages "Type" "course" -}} + {{- $alias := printf "%s*" $course.RelPermalink }} + {{- $.Scratch.Add "routes" (dict + "route" $alias + "redirect" $course.RelPermalink + "statusCode" 301 + ) + -}} +{{- end }} +{{- range $p := sort site.AllPages "Path" }} + {{- range $alias := sort $p.Aliases }} + {{- if not (strings.HasSuffix $alias "/") }} + {{- $alias = printf "%s/" $alias }} + {{- end }} + {{- $.Scratch.Add "routes" (dict + "route" $alias + "redirect" $p.RelPermalink + "statusCode" 301 + ) + -}} + {{- end }} +{{- end }} +{{- $routes := dict "routes" ($.Scratch.Get "routes") }} +{{- $jsonOutput := $routes | jsonify }} +{{- (resources.FromString "staticwebapp.config.routes.json" $jsonOutput).Publish }} +{{- (resources.FromString "routes.json" $jsonOutput).Publish }} diff --git a/theme/layouts/partials/infrastructure/seo.html b/theme/layouts/partials/infrastructure/seo.html new file mode 100644 index 0000000..2ebc094 --- /dev/null +++ b/theme/layouts/partials/infrastructure/seo.html @@ -0,0 +1,310 @@ + +{{- .Title }} | {{ .Site.Title }} + +{{- $canonicalUrl := "" -}} +{{- if .Params.canonicalUrl -}} + {{- $canonicalUrl = .Params.canonicalUrl -}} +{{- else if .Params.external_url -}} + {{- $canonicalUrl = .Params.external_url -}} +{{- else -}} + {{- $canonicalUrl = .RelPermalink | add "https://nkdagility.com" -}} +{{- end -}} + + + + + +{{ $keywords := partial "functions/keywords.html" . }} +{{- if eq (len $keywords) 0 }} + {{- if .Site.Params.keywords -}} + {{- $keywords = $keywords | append .Site.Params.keywords -}} + {{- end -}} +{{- end -}} + + + + + + + + + + + + + + + +{{- if .IsHome }} + +{{- else if .Section | eq "resources" }} + +{{- else if .IsPage }} + +{{- end }} + + + + + + + + + + + +{{ $breadcrumbs := partial "functions/breadcrumbs.html" . }} + {{ define "course-hasCourseInstance" }} + "hasCourseInstance": [ + {{- $learningExperiences := (index .Params "course-learning-experiences") -}} + {{- $length := len $learningExperiences -}} + {{- range $index, $experience := $learningExperiences }} + { "@type": "CourseInstance", "courseMode": "blended", "location": "Microsoft Teams", "courseSchedule": { "@type": "Schedule", + {{- if eq $experience "Traditional" -}} + "duration": "PT4H", "repeatCount": 4, "repeatFrequency": "Daily" + {{- else if eq $experience "Immersive" -}} + "duration": "PT6H", "repeatCount": 8, "repeatFrequency": "Weekly" + {{- else -}} + "duration": "PT4H", "repeatCount": 4, "repeatFrequency": "Daily" + {{- end }} + }, "instructor": [{ "@type": "Person", "name": "Martin Hinshelwood", "description": "Professional Scrum Trainer, Professional Kanban Trainer, Microsoft MVP: GitHub & DevOps", "image": "https://nkdagility.com/images/nkdagility-with-martin-hinshelwood-light.png" }] + }{{ if lt (add $index 1) $length }},{{ end }} + {{- end }} + ], +{{ end }} + +{{ define "course-offers" }} + {{- $site := .Site }} + "offers": [ + {{- $zones := (index $site.Data "pricing-zone") -}} + {{- $level := lower .Params.level -}} + {{- range $index, $zone := $zones -}} + {{- if and $zone.enabled (gt (len $zone.countries) 0) -}} + {{- if $index -}},{{ end }} + { "@type": "Offer", "category": "Paid", "url": {{ .RelPermalink }}, "priceCurrency": {{ $zone.currency }}, "availability": "https://schema.org/InStock", "eligibleRegion": [{{- range $i, $country := $zone.countries }}{{- if $i -}},{{- end -}}{ "@type": "Place", "name": "{{ $country }}" }{{- end -}}] } + {{- end -}} + {{ end }} + ], +{{- end -}} + +{{ define "course-syllabus" }} + {{ if .Params.syllabus }} + "syllabusSections": [ + {{- $length := len .Params.syllabus -}} + {{- range $index, $section := .Params.syllabus }} + { "@type": "Syllabus", "name": {{- $section.title }}, "description": {{- $section.content | plainify -}}, "timeRequired": + {{- partial "jsonld/duration-to-iso.html" $section.duration -}}}{{ if lt (add $index 1) $length }},{{ end }} + {{- end }} + ], + {{- end -}} +{{- end -}} +{{/* "price": "{{ partial "jsonld/price-in-currency.html" (dict "Site" $site "currency" $zone.currency "level" $level) }} ", */}} + diff --git a/theme/layouts/partials/jsonld/duration-to-iso.html b/theme/layouts/partials/jsonld/duration-to-iso.html new file mode 100644 index 0000000..2ab8eae --- /dev/null +++ b/theme/layouts/partials/jsonld/duration-to-iso.html @@ -0,0 +1,11 @@ +{{- $durationParam := default 0 . -}} +{{- if gt $durationParam 0 -}} + {{- $duration := mul $durationParam 3 -}} + {{- $hours := div $duration 3600 -}} + {{- $remainingSeconds := mod $duration 3600 -}} + {{- $minutes := div $remainingSeconds 60 -}} + {{- $seconds := mod $remainingSeconds 60 -}} + {{- printf "PT%dH%dM%dS" $hours $minutes $seconds -}} +{{- else -}} + {{- printf "Invalid or zero duration value" -}} +{{- end -}} diff --git a/theme/layouts/partials/jsonld/price-in-currency.html b/theme/layouts/partials/jsonld/price-in-currency.html new file mode 100644 index 0000000..f4adf59 --- /dev/null +++ b/theme/layouts/partials/jsonld/price-in-currency.html @@ -0,0 +1,20 @@ +{{- $site := .Site -}} +{{- $zoneCurrency := .currency -}} +{{- $level := .level -}} +{{- $exchangeRates := $site.Data.exchangeRates -}} +{{- $coursePrices := $site.Data.coursePrices -}} +{{- $price := index (index $coursePrices "private") $level -}} +{{- $totalPrice := $price.TotalPrice -}} +{{- if eq $zoneCurrency "GBP" -}} + {{- $totalPriceFormatted := printf "%.2f" $totalPrice -}} + {{- $totalPriceFormatted -}} +{{- else -}} + {{- $exchangeRate := index (where $exchangeRates "TargetCurrency" $zoneCurrency) 0 -}} + {{- if $exchangeRate -}} + {{- $convertedPrice := mul $totalPrice $exchangeRate.ExchangeRate -}} + {{- $convertedPriceFormatted := printf "%.2f" $convertedPrice -}} + {{- $convertedPriceFormatted -}} + {{- else -}} + 0 + {{- end -}} +{{- end -}} diff --git a/theme/layouts/partials/jsonld/youtube.html b/theme/layouts/partials/jsonld/youtube.html new file mode 100644 index 0000000..a672bf2 --- /dev/null +++ b/theme/layouts/partials/jsonld/youtube.html @@ -0,0 +1,17 @@ + diff --git a/theme/layouts/partials/main-menu-company.html b/theme/layouts/partials/main-menu-company.html new file mode 100644 index 0000000..e84e42f --- /dev/null +++ b/theme/layouts/partials/main-menu-company.html @@ -0,0 +1,72 @@ +{{- $people := .Site.GetPage "section" "company/people" }} +{{- $company := .Site.GetPage "section" "company" }} + diff --git a/theme/layouts/partials/main-menu-resources.html b/theme/layouts/partials/main-menu-resources.html new file mode 100644 index 0000000..a0497ad --- /dev/null +++ b/theme/layouts/partials/main-menu-resources.html @@ -0,0 +1,41 @@ +{{- $resources := .Site.GetPage "section" "resources" }} + diff --git a/theme/layouts/partials/main-menu-section.html b/theme/layouts/partials/main-menu-section.html new file mode 100644 index 0000000..a1f16a5 --- /dev/null +++ b/theme/layouts/partials/main-menu-section.html @@ -0,0 +1,31 @@ +{{- $sectionName := .SectionName }} +{{- $section := .context.Site.GetPage "section" $sectionName }} +{{- if not $section.Draft }} + +{{- end }} diff --git a/theme/layouts/partials/main-menu.html b/theme/layouts/partials/main-menu.html new file mode 100644 index 0000000..be331ae --- /dev/null +++ b/theme/layouts/partials/main-menu.html @@ -0,0 +1,31 @@ +
+
+ +
+
+ diff --git a/theme/layouts/partials/main-menu2.html b/theme/layouts/partials/main-menu2.html new file mode 100644 index 0000000..e69de29 diff --git a/theme/layouts/partials/our-customers.html b/theme/layouts/partials/our-customers.html new file mode 100644 index 0000000..8ace2cc --- /dev/null +++ b/theme/layouts/partials/our-customers.html @@ -0,0 +1,41 @@ +
+ {{- $customers := where .Site.RegularPages "Type" "customers" }} + {{- $specificIndustries := slice "Government" }} + {{- $generalCustomers := where $customers "Params.customer-industries" "not in" $specificIndustries | union (where $customers "Params.customer-industries" "==" nil) }} + {{- $governmentCustomers := where $customers "Params.customer-industries" "in" $specificIndustries }} +

Our Happy Clients​

+

We partner with businesses across diverse industries, including finance, insurance, healthcare, pharmaceuticals, technology, engineering, transportation, hospitality, entertainment, legal, government, and military sectors.​

+
+ {{- range first 18 ( shuffle $generalCustomers) }} + {{- template "inline-partial-example" . }} + {{- end }} +
+ {{- range first 6 ( shuffle $governmentCustomers) }} + {{- template "inline-partial-example" . }} + {{- end }} +
+
+ {{- range first 6 (after 18 (shuffle $generalCustomers)) }} + {{- template "inline-partial-example" . }} + {{- end }} +
+
+ + {{- define "inline-partial-example" }} +
+
+ {{- if .Params.preview }} + {{- $image := .Resources.GetMatch (printf "images/%s" .Params.preview) }} + {{- if $image }} + {{- $resized := $image.Fit "150x50 jpg" }} + {{- .Title }} Logo + {{- else }} + ??? + {{- end }} + {{- else }} +

{{- .Title }}

+ {{- end }} +
+
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-audiences.html b/theme/layouts/partials/page-sections/sections-audiences.html new file mode 100644 index 0000000..dab1851 --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-audiences.html @@ -0,0 +1,10 @@ +{{- $site := .site }} +{{- $context := .context }} + +
+ {{- range where $site.RegularPages "Type" "audience" }} +
+ {{- partial "card.html" (dict "class" "sectioncards" "title" .Params.card.title "content" .Params.card.content "buttonContent" .Params.card.button.content "permalink" .RelPermalink ) }} +
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-cards.html b/theme/layouts/partials/page-sections/sections-cards.html new file mode 100644 index 0000000..05f5061 --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-cards.html @@ -0,0 +1,26 @@ +{{- $site := .site }} + +
+ {{- if and (not (eq .context.cards nil)) (gt (len .context.cards) 0) }} + {{- $count := len .context.cards }} + {{- $colClass := "col-xl-4" }} + {{- if eq (mod $count 3) 0 }} + {{- $colClass = "col-xl-4" }} + + {{- else if eq (mod $count 2) 0 }} + {{- $colClass = "col-xl-6" }} + + {{- end }} + + {{- range $index, $card := .context.cards }} +
+ + {{- partial "card.html" (dict "class" "sectioncards" "title" $card.title "content" $card.content "buttonContent" $card.button.content "permalink" $card.button.link ) }} +
+ {{- end }} + {{- else }} +
+
No courses
+
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-counters.html b/theme/layouts/partials/page-sections/sections-counters.html new file mode 100644 index 0000000..d4b789d --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-counters.html @@ -0,0 +1,35 @@ +{{- $site := .site }} + +
+ {{- if and (not (eq .context.counters nil)) (gt (len .context.counters) 0) }} + {{- $numCounters := len .context.counters }} + {{- $colSize := math.Floor (div 12 $numCounters) }} + + + + {{- if lt $colSize 2 }} + {{- $colSize = 2 }} + {{- end }} + + + + {{- $headingFontSize := printf "clamp(1.4rem, %v.5vw, 4rem)" $colSize }} + {{- $subFontSize := printf "clamp(1.0rem, %v.5vw, 2rem)" $colSize }} + + {{- range $index, $counter := .context.counters }} + {{- $combined := printf "%v%v" $counter.counter $counter.counterAfter }} +
+

+ {{- $combined }} +

+

+ {{- $counter.content | markdownify }} +

+
+ {{- end }} + {{- else }} +
+
No counters
+
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-courses.html b/theme/layouts/partials/page-sections/sections-courses.html new file mode 100644 index 0000000..05b5b5c --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-courses.html @@ -0,0 +1,21 @@ +{{- $site := .site }} + +
+ {{- if and (not (eq .context.related nil)) (gt (len .context.related) 0) }} + {{- range $index, $courses := .context.related }} +
+ + {{- $courseItem := $site.GetPage $courses }} + {{- if $courseItem }} + {{- partial "cards-course.html" (dict "site" $site "context" $courseItem) }} + {{- else }} +

Course `{{- $courses }}` not found.

+ {{- end }} +
+ {{- end }} + {{- else }} +
+
No courses
+
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-features.html b/theme/layouts/partials/page-sections/sections-features.html new file mode 100644 index 0000000..5442a0e --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-features.html @@ -0,0 +1,115 @@ +{{- $context := .context }} +{{- $Page := .Page }} +
+ {{- with $context.features }} + {{- if gt (len .) 0 }} + {{- range $index, $features := . }} + {{- if .media }} + {{- if modBool $index 2 }} + +
+
+
+
+ {{- if .media }} + {{- if strings.HasPrefix .media "/images/" }} + + {{- .title }} + + {{- else }} + {{- $image := $Page.Resources.GetMatch .media }} + {{- if $image }} + + {{- .title }} + + {{- else }} + failed to get image + {{- .media }} + {{- end }} + {{- end }} + {{- end }} +
+
+
+
{{- .title | markdownify }}
+

{{- .content | markdownify }}

+
+
+
+
+
+ {{- else }} + +
+
+
+
+
+
{{- .title | markdownify }}
+

{{- .content | markdownify }}

+
+
+
+ {{- if .media }} + {{- if strings.HasPrefix .media "/images/" }} + + {{- .title }} + + {{- else }} + {{- $image := $Page.Resources.GetMatch .media }} + {{- if $image }} + + {{- .title }} + + {{- else }} + failed to get image + {{- .media }} + {{- end }} + {{- end }} + {{- end }} +
+
+
+
+ {{- end }} + {{- else }} + +
+
+
+
{{- .title | markdownify }}
+

{{- .content | markdownify }}

+
+
+
+ {{- end }} + {{- end }} + {{- end }} + {{- else }} +
+
+
+
+

What we beleive!

+

+ We believe that every company deserves high quality software delivered on a regular cadence that meets its customers needs. Our goal is to help you reduce your cycle time, improve your time to market, and minimise any organisational friction + in achieving your goals. +

+
+
+
+
+
+
+

Who are we?

+

naked Agility Limited is a professional company that offers training, coaching, mentoring, and facilitation to help people and teams evolve, integrate, and continuously improve.

+

+ We recognise the positive impact that a happy AND motivated workforce, that has purpose, has on client experience. We help change mindsets towards a people-first culture where everyone encourages others to learn and grow. The resulting + divergent thinking leads to many different ideas and opportunities for the success of the organisation. +

+
+
+
+
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-otherpages.html b/theme/layouts/partials/page-sections/sections-otherpages.html new file mode 100644 index 0000000..365c37d --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-otherpages.html @@ -0,0 +1,23 @@ +{{- $site := .site }} +{{- $context := .context }} + +
+ {{- $filteredItems := index .context .context.source }} + {{- $count := len $filteredItems }} + {{- $colClass := "col-lg-4" }} + {{- if eq (mod $count 3) 0 }} + {{- $colClass = "col-lg-4" }} + + {{- else if eq (mod $count 2) 0 }} + {{- $colClass = "col-lg-6" }} + + {{- end }} + {{- range $filteredItems }} + {{- $page := $site.GetPage . }} + {{- with $page }} +
+ {{- partial "card.html" (dict "class" "sectioncards" "title" .Params.card.title "content" .Params.card.content "buttonContent" "view" "permalink" .Permalink ) }} +
+ {{- end }} + {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-phases.html b/theme/layouts/partials/page-sections/sections-phases.html new file mode 100644 index 0000000..7266973 --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-phases.html @@ -0,0 +1,17 @@ +{{- $site := .site }} + +
+ {{- if and (not (eq .context.sylabus nil)) (gt (len .context.sylabus) 0) }} + {{- range $index, $sylabi := .context.sylabus }} +
+ +

{{- $sylabi.title }}

+ {{- $sylabi.content | markdownify }} +
+ {{- end }} + {{- else }} +
+
No Sylabus
+
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-taxonomies-card.html b/theme/layouts/partials/page-sections/sections-taxonomies-card.html new file mode 100644 index 0000000..7f8921e --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-taxonomies-card.html @@ -0,0 +1,58 @@ + +{{- $context := .context }} +{{- $subTypes := .subTypes }} +{{- $pagesToDisplay := .context.Pages.ByWeight }} +{{- if $subTypes }} + {{- $pagesToDisplay = where .context.Pages.ByWeight "Type" "in" $subTypes }} +{{- end }} +
+
+

+ {{- if $context.Page.Params.card }} + {{- $context.Page.Params.card.title }} + {{- else }} + {{- $context.Page.Title }} + {{- end }} +

+
+ {{- if $context.Page.Params.card }} + {{- $context.Page.Params.card.content | markdownify }} + {{- else }} + {{- .content | markdownify }} + {{- end }} +
+
+ + {{- if $context.Page.RelPermalink }} + + {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-taxonomies.html b/theme/layouts/partials/page-sections/sections-taxonomies.html new file mode 100644 index 0000000..5ca5bdb --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-taxonomies.html @@ -0,0 +1,31 @@ +{{- $site := .site }} +{{- $context := .context }} + +
+ {{- range $taxonomyName, $terms := .site.Taxonomies }} + {{- if eq $taxonomyName $context.source }} + {{- $filteredTerms := slice }} + {{- range $terms.ByCount }} + {{- $matchingPages := where .Pages "Type" "in" $context.types }} + {{- if and (gt (len .Pages) 0) (gt (len $matchingPages) 0) }} + {{- $filteredTerms = $filteredTerms | append . }} + {{- end }} + {{- end }} + {{- $sortedTerms := first 6 (sort $filteredTerms "Page.Weight" "desc") }} + {{- $count := len $sortedTerms }} + {{- $colClass := "col-lg-4" }} + {{- if eq (mod $count 3) 0 }} + {{- $colClass = "col-lg-4" }} + + {{- else if eq (mod $count 2) 0 }} + {{- $colClass = "col-lg-6" }} + + {{- end }} + {{- range $sortedTerms }} +
+ {{- partial "page-sections/sections-taxonomies-card.html" (dict "site" $site "context" . "subTypes" $context.types) }} +
+ {{- end }} + {{- end }} + {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-trustpilot.html b/theme/layouts/partials/page-sections/sections-trustpilot.html new file mode 100644 index 0000000..4f3e6a7 --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-trustpilot.html @@ -0,0 +1,17 @@ +{{- $site := .site }} +{{- $context := .context }} + +
+ Trustpilot +
+ diff --git a/theme/layouts/partials/page-sections/sections-types.html b/theme/layouts/partials/page-sections/sections-types.html new file mode 100644 index 0000000..e28a012 --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-types.html @@ -0,0 +1,25 @@ +{{- $site := .site }} +{{- $context := .context }} + +
+ {{- $filteredItems := where $site.RegularPages.ByWeight "Type" $context.source }} + {{- $colClass := "col-xl-4" }} + {{- if $context.columns }} + {{- $size := int (math.Round (div 12 (int $context.columns))) }} + {{- $colClass = printf "col-xl-%d" $size }} + {{- else }} + {{- $count := len $filteredItems }} + {{- if eq (mod $count 3) 0 }} + {{- $colClass = "col-xl-4" }} + + {{- else if eq (mod $count 2) 0 }} + {{- $colClass = "col-xl-6" }} + + {{- end }} + {{- end }} + {{- range $filteredItems }} +
+ {{- partial "card.html" (dict "class" "sectioncards" "title" .Params.card.title "content" .Params.card.content "buttonContent" .Params.card.button.content "permalink" .Permalink ) }} +
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections-videos.html b/theme/layouts/partials/page-sections/sections-videos.html new file mode 100644 index 0000000..11890c5 --- /dev/null +++ b/theme/layouts/partials/page-sections/sections-videos.html @@ -0,0 +1,50 @@ +{{- $site := .site }} + +
+ {{- if and (not (eq .context.related nil)) (gt (len .context.related) 0) }} + {{- range $index, $videos := .context.related }} +
+ + {{- $videoItem := $site.GetPage $videos }} + {{- if $videoItem }} +
+ +
+ {{- else }} +

Video `{{- $videos }}` not found.

+ {{- end }} +
+ {{- end }} + {{- else }} +
+
+
+ +
+
+
+
+ +
+
+
+ {{- end }} +
diff --git a/theme/layouts/partials/page-sections/sections.html b/theme/layouts/partials/page-sections/sections.html new file mode 100644 index 0000000..469fa58 --- /dev/null +++ b/theme/layouts/partials/page-sections/sections.html @@ -0,0 +1,51 @@ +{{- $site := .Site }} +{{- $Resources := .Resources }} +{{- $page := . }} +{{- if .Params.sections }} + {{- range $index, $section := .Params.sections }} + +
+
+

{{- $section.title | markdownify }}

+
+

{{- $section.content | markdownify }}

+
+ + {{- if eq $section.type "features" }} + + {{- partial "page-sections/sections-features.html" (dict "Page" $page "context" . ) }} + {{- else if eq $section.type "courses" }} + {{- partial "page-sections/sections-courses.html" (dict "site" $site "context" .) }} + {{/* {{- else if eq $section.type "audiences" }} + {{- partial "page-sections/sections-audiences.html" (dict "site" $site "context" .) }} */}} + {{- else if eq $section.type "taxonomies" }} + {{- partial "page-sections/sections-taxonomies.html" (dict "site" $site "context" $section) }} + {{- else if eq $section.type "phases" }} + {{- partial "page-sections/sections-phases.html" (dict "site" $site "context" $section) }} + {{- else if eq $section.type "types" }} + {{- partial "page-sections/sections-types.html" (dict "site" $site "context" $section) }} + {{- else if eq $section.type "otherpages" }} + {{- partial "page-sections/sections-otherpages.html" (dict "site" $site "context" $section) }} + {{- else if eq $section.type "trustpilot" }} + {{- partial "page-sections/sections-trustpilot.html" (dict "site" $site "context" $section) }} + {{- else if eq $section.type "videos" }} + {{- partial "page-sections/sections-videos.html" (dict "site" $site "context" .) }} + {{- else if eq $section.type "cards" }} + {{- partial "page-sections/sections-cards.html" (dict "site" $site "context" .) }} + {{- else if eq $section.type "counters" }} + {{- partial "page-sections/sections-counters.html" (dict "site" $site "context" .) }} + {{- else if eq $section.type "none" }} + {{- else }} + +
+

Other section type content.

+
+ {{- end }} +
+
+ {{- end }} +{{- else }} +
+ {{- .Content }} +
+{{- end }} diff --git a/theme/layouts/partials/publications/comments.html b/theme/layouts/partials/publications/comments.html new file mode 100644 index 0000000..784cfbc --- /dev/null +++ b/theme/layouts/partials/publications/comments.html @@ -0,0 +1,21 @@ +
+ +
diff --git a/theme/layouts/partials/publications/craft.html b/theme/layouts/partials/publications/craft.html new file mode 100644 index 0000000..b2d11cb --- /dev/null +++ b/theme/layouts/partials/publications/craft.html @@ -0,0 +1,18 @@ +
+
+ {{ if .Draft }} + Draft + {{ else if gt .Date (now) }} + Scheduled for + {{ else }} + Published on + {{ end }} + {{- if gt .ExpiryDate (now) }} + and expires on + {{- end }} +
+
+
{{- partial "resources/contributors.html" . }}
+
+
{{- .ReadingTime }} minute read
+
diff --git a/theme/layouts/partials/publications/resourceType.html b/theme/layouts/partials/publications/resourceType.html new file mode 100644 index 0000000..9ab1e99 --- /dev/null +++ b/theme/layouts/partials/publications/resourceType.html @@ -0,0 +1,9 @@ +
+ {{ if reflect.IsSlice .Params.resourcetypes }} + {{ range .Params.resourcetypes }} + {{- . }} + {{ end }} + {{ else }} + {{- .Params.resourcetypes }} + {{ end }} +
diff --git a/theme/layouts/partials/publications/share-bar.html b/theme/layouts/partials/publications/share-bar.html new file mode 100644 index 0000000..b7f9e07 --- /dev/null +++ b/theme/layouts/partials/publications/share-bar.html @@ -0,0 +1,54 @@ +
+ +
+ +
+ + + +
+ + + + + +
+ +
diff --git a/theme/layouts/partials/publications/tag-cloud.html b/theme/layouts/partials/publications/tag-cloud.html new file mode 100644 index 0000000..f5ba29a --- /dev/null +++ b/theme/layouts/partials/publications/tag-cloud.html @@ -0,0 +1,6 @@ +
+ {{ $keywords := partial "functions/keywords.html" . }} + {{ range $keywords }} + {{- . }} + {{ end }} +
diff --git a/theme/layouts/partials/resources-card.html b/theme/layouts/partials/resources-card.html new file mode 100644 index 0000000..3f93fb9 --- /dev/null +++ b/theme/layouts/partials/resources-card.html @@ -0,0 +1,36 @@ +
+ {{- $resourceType := .CurrentSection.Params.resourceType }} + {{- if .Params.preview }} + {{- $isExternal := strings.HasPrefix .Params.preview "http" }} + {{- if $isExternal }} + {{- .Title }} + {{- else }} + {{- $image := .Resources.GetMatch (printf "images/%s" .Params.preview) }} + {{- if $image }} + {{- .Title }} + {{- else }} + {{- .Params.Title }} + {{- end }} + {{- end }} + {{- else }} + {{- .Params.Title }} + {{- end }} +
+
+

{{- .Title }}

+

{{- .Summary | plainify }}

+
+
+ +
diff --git a/theme/layouts/partials/resources-list.html b/theme/layouts/partials/resources-list.html new file mode 100644 index 0000000..d5a0c67 --- /dev/null +++ b/theme/layouts/partials/resources-list.html @@ -0,0 +1,20 @@ +
+ {{- $sortedResources := .Pages.ByDate.Reverse }} + {{- $paginator := .Paginate $sortedResources }} + {{- range $index, $resource := $paginator.Pages }} +
+ {{- partial "resources-card.html" $resource }} +
+ {{- end }} +
+ + +
+
diff --git a/theme/layouts/partials/resources/contributors.html b/theme/layouts/partials/resources/contributors.html new file mode 100644 index 0000000..6e10028 --- /dev/null +++ b/theme/layouts/partials/resources/contributors.html @@ -0,0 +1,28 @@ +
+ Written by + {{- $creator := .Params.creator | default "Martin Hinshelwood" }} + {{- with site.GetPage "company/people" (anchorize $creator) }} + {{- .Title }} + {{- else }} + {{- $creator }} + {{- end }} + + {{- if (and .Params.contributors (gt (len .Params.contributors) 0)) }} + and contributed to by + {{- range $index, $contributor := .Params.contributors }} + {{- with site.GetPage "company/people" (anchorize $contributor.name) }} + {{- .Title }} + {{- else }} + {{- if $contributor.external }} + + {{- $contributor.name }} + + {{- else }} + {{- $contributor.name }} + {{- end }} + {{- end }} + + {{- if lt (add $index 1) (len $.Params.contributors) }},{{- end }} + {{- end }} + {{- end }} +
diff --git a/theme/layouts/partials/trustpilot/scrumorg-trustpilot-carousel.html b/theme/layouts/partials/trustpilot/scrumorg-trustpilot-carousel.html new file mode 100644 index 0000000..a2e4018 --- /dev/null +++ b/theme/layouts/partials/trustpilot/scrumorg-trustpilot-carousel.html @@ -0,0 +1,14 @@ + +
+ Trustpilot +
+ diff --git a/theme/layouts/partials/trustpilot/scrumorg-trustpilot.html b/theme/layouts/partials/trustpilot/scrumorg-trustpilot.html new file mode 100644 index 0000000..ac11797 --- /dev/null +++ b/theme/layouts/partials/trustpilot/scrumorg-trustpilot.html @@ -0,0 +1,15 @@ + +
+ Trustpilot +
+ diff --git a/theme/layouts/shortcodes/events-schedule-single.html b/theme/layouts/shortcodes/events-schedule-single.html new file mode 100644 index 0000000..5fd8323 --- /dev/null +++ b/theme/layouts/shortcodes/events-schedule-single.html @@ -0,0 +1 @@ +{{- partial "delivery-parts/events-schedule-single.html" . }} diff --git a/theme/layouts/shortcodes/section-cards.html b/theme/layouts/shortcodes/section-cards.html new file mode 100644 index 0000000..1e85cdd --- /dev/null +++ b/theme/layouts/shortcodes/section-cards.html @@ -0,0 +1,15 @@ +{{- $data := transform.Unmarshal .Inner }} +
+
+ {{- if and (not (eq $data nil)) (gt (len $data) 0) }} + {{- range $index, $card := $data }} +
+ + {{- partial "card.html" (dict "class" "sectioncards" "title" $card.title "content" $card.content "buttonContent" $card.button.content "permalink" $card.button.link ) }} +
+ {{- end }} + {{- else }} +
No cards
+ {{- end }} +
+
diff --git a/theme/layouts/shortcodes/section-videos.html b/theme/layouts/shortcodes/section-videos.html new file mode 100644 index 0000000..e409668 --- /dev/null +++ b/theme/layouts/shortcodes/section-videos.html @@ -0,0 +1,58 @@ +{{- $cleanedString := .Inner | replaceRE "(?s).*```YAML(.*)```.*" "$1" }} +{{- $data := transform.Unmarshal (trim $cleanedString "`") }} +
+
+ {{- if and (not (eq $data.items nil)) (gt (len $data.items) 0) }} + {{- range $index, $videos := $data.items }} +
+ + {{- $videoItem := $.Page.Site.GetPage $videos }} + {{- if $videoItem }} +
+ +
+ {{- else }} +

Video `{{- $videos }}` not found.

+ {{- end }} +
+ {{- end }} + {{- else }} +
+
+
+ +
+
+
+
+ +
+
+
+ {{- end }} +
+
diff --git a/theme/layouts/shortcodes/syllabus-single.html b/theme/layouts/shortcodes/syllabus-single.html new file mode 100644 index 0000000..dc2b5cb --- /dev/null +++ b/theme/layouts/shortcodes/syllabus-single.html @@ -0,0 +1 @@ +{{- partial "delivery-parts/syllabus-single.html" . }} diff --git a/theme/layouts/shortcodes/youtube.html b/theme/layouts/shortcodes/youtube.html new file mode 100644 index 0000000..4aeaeac --- /dev/null +++ b/theme/layouts/shortcodes/youtube.html @@ -0,0 +1,11 @@ +
+ +
+{{- $videoPath := (print "/resources/videos/youtube/" (index .Params 0)) }} +{{- if .Page.Params.videoId -}} + {{- partial "jsonld/youtube.html" . -}} +{{- else }} + {{- with .Site.GetPage (print "/resources/videos/youtube/" (index .Params 0)) -}} + {{- partial "jsonld/youtube.html" . }} + {{- end -}} +{{- end -}} diff --git a/theme/static/css/cards.css b/theme/static/css/cards.css new file mode 100644 index 0000000..47b4799 --- /dev/null +++ b/theme/static/css/cards.css @@ -0,0 +1,81 @@ + + + +.sectioncards .ttl-nkdagility { + color: var(--primary-color); + font-size: 1.4em; + font-weight: bold; + } + + .nkdagility-primary { + color: var(--primary-color); + } + +.sectioncards .btn-nkdagility { + border-color: var(--primary-color); + background-color: var(--primary-color); + color: white; +} + +.herocards .card { + border: none; +} + +.herocards .btn-nkdagility:hover { + border-color: var(--primary-color); + background-color: var(--primary-color); + color: white; +} + +.herocards .ttl-nkdagility { + color: var(--primary-color); + font-size: 1.4em; + font-weight: bold; +} + +.herocards .btn-nkdagility { +border-color: var(--primary-color); +background-color: var(--primary-color); +color: white; +} + +.sectioncards .btn-nkdagility:hover { +border-color: var(--primary-color); +background-color: var(--primary-color); +color: white; +} + +.card-left-border { + border-left: 8px solid var(--secondary-color) +} + + + /* When expanded, show all paragraphs */ + .card.expanded .card-text p { + display: block; + } + + /* Limit the initial height */ + .card-text-expander { + overflow: hidden; + } + + /* Expand when the card is clicked */ + .card.expanded .card-text-expander { + max-height: none; /* Remove height restriction when expanded */ + } + + /* Hide all paragraphs except the first */ + .card:not(.expanded) .card-text-expander p:not(:first-child) { + display: none; + } + + + .show-more { + cursor: pointer; + color: #007bff; + text-decoration: underline; + display: inline-block; + margin-top: 10px; + display: none; + } \ No newline at end of file diff --git a/theme/static/css/main-menu.css b/theme/static/css/main-menu.css new file mode 100644 index 0000000..abb12ad --- /dev/null +++ b/theme/static/css/main-menu.css @@ -0,0 +1,19 @@ +/* Main Menu Item Styles */ +.nkda-main-menu-item { + list-style: none; + position: relative; /* Ensure the mega menu is positioned relative to this item */ +} + +/* Main Menu List Item Styles */ +.nkda-main-menu-top-item { + text-transform: uppercase !important; + +} + +/* Ensure the hover effect covers the menu */ +.nav-item:hover .nkda-main-menu-top-item { + color: white; + background: #713183; + +} + diff --git a/theme/static/css/main.css b/theme/static/css/main.css new file mode 100644 index 0000000..eb2ed7e --- /dev/null +++ b/theme/static/css/main.css @@ -0,0 +1,186 @@ +/* Define branding color variables */ +:root { + --primary-color: #713183; /* Primary branding color */ + --primary-color-light: #dbb0e6; /* Primary branding color */ + --secondary-color: #007acc; /* Secondary branding color */ + --secondary-color-light: #9fd2f4; /* Secondary branding color */ + --accent-color: #e37f01; /* Accent color */ + --accent-color-light: #ffcd35; /* Accent color */ + --background-color: #ecf0f1; /* Background color */ + --primary-color-text: #ffffff; /* Primary text color */ + } + + body { + font-family: 'Calibri', 'Open Sans', sans-serif; + font-size: clamp(0.8rem, 1vw + 1rem, 1.2rem); + line-height: 1.6; + } + + +h1, h2, h3, h4, h5, h6 { + color: var(--primary-color); + } + +/* More specific selector */ +.nkd-heading-primary-light h1, .nkd-heading-primary-light h2, .nkd-heading-primary-light h3, .nkd-heading-primary-light h4, .nkd-heading-primary-light h5, .nkd-heading-primary-light h6 { + color: whitesmoke; +} + + +.nkda-heading-primary { + color: var(--primary-color); + font-size: clamp(1rem, 4vw + 1rem, 2.5rem); +} + +.nkda-heading-primary-content { + font-size: clamp(0.8rem, 2vw + 1rem, 1.2rem); +} + +.nkda-heading-secondary { + color: initial; + font-size: clamp(0.8rem, 2vw + 1rem, 1.2rem); +} + +.mainContainer +{ + max-width: 1768px; + margin-left: auto; + margin-right: auto; +} + +.bg-brand-primary +{ + background-color: #713183; +} +.bg-brand-secondary { + background-color: #54595F; +} + +.bg-brand-development { + background-color: green; + color: white; +} + +.bg-brand-canary { + background-color: darkgoldenrod; + color: white; +} + +.bg-brand-preview { + background-color: darkorange; + color: white; +} + + +.bg-brand-menu +{ + background-color: #43464c; +} + +pre { + max-width: 1024px;; +} + + +/* navigration start */ + +.navbar-brand-topbaritem { + font-size: 0.9em; +} + +.nkda-text-primary { + color: #713183; +} + +.bg-nkdagility-primary { + background-color: var(--primary-color) !important; +} + +.bg-nkdagility-accent { + background-color: var(--accent-color) !important; +} + +.btn-nkdprimary { + background-color: #713183; + color: white; +} + +.btn-nkdprimary:hover { + background-color: #54595F; + color: white; +} + +.text-bg-nkdagility-primary { + color: #ffffff; /* Custom text color */ + background-color: var(--primary-color); /* Custom background color */ + } + + +/* buttons */ + +.btn-nkdagility-primary { + background-color: var(--primary-color); + color: white; +} + +.btn-nkdagility-primary:hover { + background-color: var(--primary-color-light); + color: white; +} + +.btn-nkdagility-primary-light { + background-color: var(--primary-color-light); + color: white; +} + +.btn-nkdagility-primary-light:hover { + background-color: var(--primary-color); + color: white; +} + +.btn-nkdagility-secondary { + background-color: var(--secondary-color); + color: white; +} + +.btn-nkdagility-secondary:hover { + background-color: var(--secondary-color-light); + color: white; +} + +.btn-nkdagility-secondary-light { + background-color: var(--secondary-color-light); + color: white; +} + +.btn-nkdagility-secondary-light:hover { + background-color: var(--secondary-color); + color: white; +} + + + +/* end buttons */ + + + +.post-img { + text-align: center; /* Centers the image inside the .post-img container */ +} + +.post-img img { + max-width: 50%; /* Ensures the image will not exceed 50% of the container width */ + height: auto; /* Maintains the aspect ratio of the image */ + display: block; /* Makes the image a block-level element */ + margin: 0 auto; /* Centers the block element horizontally */ +} + +img { + max-width: 100%; /* Ensures the image will not exceed the width of its container */ + height: auto; /* Maintains the aspect ratio of the image */ + display: block; /* Removes any inline-block gap */ + } + + .text-bg-nkdagility-primary { + background-color: var(--primary-color); +} \ No newline at end of file diff --git a/theme/static/css/sections.css b/theme/static/css/sections.css new file mode 100644 index 0000000..641b5b9 --- /dev/null +++ b/theme/static/css/sections.css @@ -0,0 +1,6 @@ + + .two-column-text { + column-count: 2; + column-gap: 30px; /* Adjust the gap between the columns as needed */ + } + diff --git a/theme/static/favicon.ico b/theme/static/favicon.ico new file mode 100644 index 0000000..5acb80b Binary files /dev/null and b/theme/static/favicon.ico differ diff --git a/theme/static/images/404.webp b/theme/static/images/404.webp new file mode 100644 index 0000000..a6cf3c9 Binary files /dev/null and b/theme/static/images/404.webp differ diff --git a/theme/static/images/MVP_BlueOnly.png b/theme/static/images/MVP_BlueOnly.png new file mode 100644 index 0000000..d4c44fe Binary files /dev/null and b/theme/static/images/MVP_BlueOnly.png differ diff --git a/theme/static/images/MVP_Horizontal_FullColor.webp b/theme/static/images/MVP_Horizontal_FullColor.webp new file mode 100644 index 0000000..5f6e68c Binary files /dev/null and b/theme/static/images/MVP_Horizontal_FullColor.webp differ diff --git a/theme/static/images/NKDAgility-Courses-Template-16x9-1-jpg.webp b/theme/static/images/NKDAgility-Courses-Template-16x9-1-jpg.webp new file mode 100644 index 0000000..3a339fe Binary files /dev/null and b/theme/static/images/NKDAgility-Courses-Template-16x9-1-jpg.webp differ diff --git a/theme/static/images/NKDAgility-Mentor-Programs-Template-16x9.jpg b/theme/static/images/NKDAgility-Mentor-Programs-Template-16x9.jpg new file mode 100644 index 0000000..471e4c0 Binary files /dev/null and b/theme/static/images/NKDAgility-Mentor-Programs-Template-16x9.jpg differ diff --git a/theme/static/images/NKDAgility-Scrum-Framework-jpg copy.webp b/theme/static/images/NKDAgility-Scrum-Framework-jpg copy.webp new file mode 100644 index 0000000..2f6bd01 Binary files /dev/null and b/theme/static/images/NKDAgility-Scrum-Framework-jpg copy.webp differ diff --git a/theme/static/images/NKDAgility-Scrum-Framework-jpg.webp b/theme/static/images/NKDAgility-Scrum-Framework-jpg.webp new file mode 100644 index 0000000..2f6bd01 Binary files /dev/null and b/theme/static/images/NKDAgility-Scrum-Framework-jpg.webp differ diff --git a/theme/static/images/NKDAgility-technically-Background.jpg b/theme/static/images/NKDAgility-technically-Background.jpg new file mode 100644 index 0000000..930b705 Binary files /dev/null and b/theme/static/images/NKDAgility-technically-Background.jpg differ diff --git a/theme/static/images/PK-Email@0.5x-1.png b/theme/static/images/PK-Email@0.5x-1.png new file mode 100644 index 0000000..35f7373 Binary files /dev/null and b/theme/static/images/PK-Email@0.5x-1.png differ diff --git a/theme/static/images/PK-PKT@2x.png b/theme/static/images/PK-PKT@2x.png new file mode 100644 index 0000000..64f2d2b Binary files /dev/null and b/theme/static/images/PK-PKT@2x.png differ diff --git a/theme/static/images/PST-Badge-v2-web-transparent.webp b/theme/static/images/PST-Badge-v2-web-transparent.webp new file mode 100644 index 0000000..66b61ed Binary files /dev/null and b/theme/static/images/PST-Badge-v2-web-transparent.webp differ diff --git a/theme/static/images/Scrumorg-PST_licensed-1000.png b/theme/static/images/Scrumorg-PST_licensed-1000.png new file mode 100644 index 0000000..44fbed7 Binary files /dev/null and b/theme/static/images/Scrumorg-PST_licensed-1000.png differ diff --git a/theme/static/images/naked-agility-with-martin-hinshelwood-immersive-programs.png b/theme/static/images/naked-agility-with-martin-hinshelwood-immersive-programs.png new file mode 100644 index 0000000..30bbffc Binary files /dev/null and b/theme/static/images/naked-agility-with-martin-hinshelwood-immersive-programs.png differ diff --git a/theme/static/images/nkdAgility-logo-180x180.jpg b/theme/static/images/nkdAgility-logo-180x180.jpg new file mode 100644 index 0000000..5c1a22c Binary files /dev/null and b/theme/static/images/nkdAgility-logo-180x180.jpg differ diff --git a/theme/static/images/nkdAgility-logo-192x192.jpg b/theme/static/images/nkdAgility-logo-192x192.jpg new file mode 100644 index 0000000..f5dcc94 Binary files /dev/null and b/theme/static/images/nkdAgility-logo-192x192.jpg differ diff --git a/theme/static/images/nkdAgility-logo-32x32.jpg b/theme/static/images/nkdAgility-logo-32x32.jpg new file mode 100644 index 0000000..f1f6e37 Binary files /dev/null and b/theme/static/images/nkdAgility-logo-32x32.jpg differ diff --git a/theme/static/images/nkdagility-devops.png b/theme/static/images/nkdagility-devops.png new file mode 100644 index 0000000..058110e Binary files /dev/null and b/theme/static/images/nkdagility-devops.png differ diff --git a/theme/static/images/nkdagility-kanban-stratagy-overview-800x367.webp b/theme/static/images/nkdagility-kanban-stratagy-overview-800x367.webp new file mode 100644 index 0000000..176cc5e Binary files /dev/null and b/theme/static/images/nkdagility-kanban-stratagy-overview-800x367.webp differ diff --git a/theme/static/images/nkdagility-with-martin-hinshelwood-dark-300px.png b/theme/static/images/nkdagility-with-martin-hinshelwood-dark-300px.png new file mode 100644 index 0000000..7d5a952 Binary files /dev/null and b/theme/static/images/nkdagility-with-martin-hinshelwood-dark-300px.png differ diff --git a/theme/static/images/nkdagility-with-martin-hinshelwood-light-300px.png b/theme/static/images/nkdagility-with-martin-hinshelwood-light-300px.png new file mode 100644 index 0000000..ca647b7 Binary files /dev/null and b/theme/static/images/nkdagility-with-martin-hinshelwood-light-300px.png differ diff --git a/theme/static/images/nkdagility-with-martin-hinshelwood-light.png b/theme/static/images/nkdagility-with-martin-hinshelwood-light.png new file mode 100644 index 0000000..dc3e7c5 Binary files /dev/null and b/theme/static/images/nkdagility-with-martin-hinshelwood-light.png differ diff --git a/theme/static/images/resources b/theme/static/images/resources new file mode 100644 index 0000000..e69de29 diff --git a/theme/static/js/.auth/check.js b/theme/static/js/.auth/check.js new file mode 100644 index 0000000..877c3d6 --- /dev/null +++ b/theme/static/js/.auth/check.js @@ -0,0 +1,21 @@ +async function checkAuth() { + try { + const response = await fetch("/.auth/me"); + const data = await response.json(); + + if (data.length > 0) { + // User is authenticated, show sign-out button + document.getElementById("signInButton").style.display = "none"; + document.getElementById("signOutButton").style.display = "block"; + } else { + // User is not authenticated, show sign-in button + document.getElementById("signInButton").style.display = "block"; + document.getElementById("signOutButton").style.display = "none"; + } + } catch (error) { + console.error("Error checking authentication:", error); + } +} + +// Call checkAuth on page load to set initial button visibility +checkAuth(); diff --git a/theme/static/js/MSAL/authConfig.js b/theme/static/js/MSAL/authConfig.js new file mode 100644 index 0000000..e3112ba --- /dev/null +++ b/theme/static/js/MSAL/authConfig.js @@ -0,0 +1,69 @@ +/** + * Configuration object to be passed to MSAL instance on creation. + * For a full list of MSAL.js configuration parameters, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/configuration.md + */ +const msalConfig = { + auth: { + clientId: "f18a31b9-50fb-45ea-ae5a-d904d0b19c67", // This is the ONLY mandatory field that you need to supply. + authority: "https://login.microsoftonline.com/658c1a65-f834-4a61-b68c-91b9a7b3e481", // Replace the placeholder with your tenant subdomain + knownAuthorities: ["login.ciamlogin.com", "login.nkdagility.com"], // Mark your tenant's authority as a known authority + redirectUri: "http://localhost:1313", // You must register this URI on Microsoft Entra admin center/App Registration. Defaults to window.location.href e.g. http://localhost:3000/ + navigateToLoginRequestUrl: true, // If "true", will navigate back to the original request location before processing the auth code response. + }, + cache: { + cacheLocation: "sessionStorage", // Configures cache location. "sessionStorage" is more secure, but "localStorage" gives you SSO. + storeAuthStateInCookie: false, // set this to true if you have to support IE + }, + system: { + loggerOptions: { + loggerCallback: (level, message, containsPii) => { + if (containsPii) { + return; + } + switch (level) { + case msal.LogLevel.Error: + console.error(message); + return; + case msal.LogLevel.Info: + console.info(message); + return; + case msal.LogLevel.Verbose: + console.debug(message); + return; + case msal.LogLevel.Warning: + console.warn(message); + return; + } + }, + }, + }, +}; + +/** + * Scopes you add here will be prompted for user consent during sign-in. + * By default, MSAL.js will add OIDC scopes (openid, profile, email) to any login request. + * For more information about OIDC scopes, visit: + * https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes + */ +const loginRequest = { + scopes: [], +}; + +/** + * An optional silentRequest object can be used to achieve silent SSO + * between applications by providing a "login_hint" property. + */ + +// const silentRequest = { +// scopes: ["openid", "profile"], +// loginHint: "example@domain.net" +// }; + +// exporting config object for jest +if (typeof exports !== "undefined") { + module.exports = { + msalConfig: msalConfig, + loginRequest: loginRequest, + }; +} diff --git a/theme/static/js/MSAL/authPopup.js b/theme/static/js/MSAL/authPopup.js new file mode 100644 index 0000000..841d1e0 --- /dev/null +++ b/theme/static/js/MSAL/authPopup.js @@ -0,0 +1,101 @@ +// Create the main myMSALObj instance +// configuration parameters are located at authConfig.js +const myMSALObj = new msal.PublicClientApplication(msalConfig); + +myMSALObj + .handleRedirectPromise() + .then((response) => { + if (response) { + handleResponse(response); + } else { + selectAccount(); + } + }) + .catch((error) => { + console.error("Error handling redirect:", error); + }); + +let username = ""; + +function selectAccount() { + /** + * See here for more info on account retrieval: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md + */ + + const currentAccounts = myMSALObj.getAllAccounts(); + + if (!currentAccounts || currentAccounts.length < 1) { + return; + } else if (currentAccounts.length > 1) { + // Add your account choosing logic here + console.warn("Multiple accounts detected."); + } else if (currentAccounts.length === 1) { + username = currentAccounts[0].username; + welcomeUser(currentAccounts[0].username); + updateTable(currentAccounts[0]); + } +} + +function handleResponse(response) { + /** + * To see the full list of response object properties, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#response + */ + + if (response !== null) { + username = response.account.username; + welcomeUser(username); + updateTable(response.account); + } else { + selectAccount(); + + /** + * If you already have a session that exists with the authentication server, you can use the ssoSilent() API + * to make request for tokens without interaction, by providing a "login_hint" property. To try this, comment the + * line above and uncomment the section below. + */ + + // myMSALObj.ssoSilent(silentRequest). + // then((response) => { + // welcomeUser(response.account.username); + // updateTable(response.account); + // }).catch(error => { + // console.error("Silent Error: " + error); + // if (error instanceof msal.InteractionRequiredAuthError) { + // signIn(); + // } + // }); + } +} + +function signIn() { + /** + * You can pass a custom request object below. This will override the initial configuration. For more information, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request + */ + loginRequest.redirectUri = "/redirect"; + myMSALObj + .loginPopup(loginRequest) + .then(handleResponse) + .catch((error) => { + console.error(error); + }); +} + +function signOut() { + /** + * You can pass a custom request object below. This will override the initial configuration. For more information, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request + */ + + // Choose which account to logout from by passing a username. + const logoutRequest = { + account: myMSALObj.getAccount({ username: username }), + mainWindowRedirectUri: "/signout", + }; + + myMSALObj.logoutPopup(logoutRequest); +} + +selectAccount(); diff --git a/theme/static/js/MSAL/authRedirect.js b/theme/static/js/MSAL/authRedirect.js new file mode 100644 index 0000000..bd190b0 --- /dev/null +++ b/theme/static/js/MSAL/authRedirect.js @@ -0,0 +1,95 @@ +// Create the main myMSALObj instance +// configuration parameters are located at authConfig.js + +const myMSALObj = new msal.PublicClientApplication(msalConfig); + +myMSALObj + .handleRedirectPromise() + .then((response) => { + if (response) { + handleResponse(response); // Handle the response if it exists + } else { + selectAccount(); // Select an account if no response + } + }) + .catch((error) => { + console.error("Error handling redirect:", error); // Log any errors + }); + +let username = ""; + +function selectAccount() { + /** + * See here for more info on account retrieval: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-common/docs/Accounts.md + */ + + const currentAccounts = myMSALObj.getAllAccounts(); + + if (!currentAccounts) { + return; + } else if (currentAccounts.length > 1) { + // Add your account choosing logic here + console.warn("Multiple accounts detected."); + } else if (currentAccounts.length === 1) { + username = currentAccounts[0].username; + welcomeUser(currentAccounts[0].username); + updateTable(currentAccounts[0]); + } +} + +function handleResponse(response) { + /** + * To see the full list of response object properties, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#response + */ + + if (response !== null) { + username = response.account.username; + welcomeUser(username); + updateTable(response.account); + } else { + selectAccount(); + + /** + * If you already have a session that exists with the authentication server, you can use the ssoSilent() API + * to make request for tokens without interaction, by providing a "login_hint" property. To try this, comment the + * line above and uncomment the section below. + */ + + // myMSALObj.ssoSilent(silentRequest). + // then((response) => { + // welcomeUser(response.account.username); + // updateTable(response.account); + // }).catch(error => { + // console.error("Silent Error: " + error); + // if (error instanceof msal.InteractionRequiredAuthError) { + // signIn(); + // } + // }); + } +} + +function signIn() { + /** + * You can pass a custom request object below. This will override the initial configuration. For more information, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request + */ + + myMSALObj.loginRedirect(loginRequest); +} + +function signOut() { + /** + * You can pass a custom request object below. This will override the initial configuration. For more information, visit: + * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/request-response-object.md#request + */ + + // Choose which account to logout from by passing a username. + const logoutRequest = { + account: myMSALObj.getAccount({ username: username }), + postLogoutRedirectUri: "/signout", // remove this line if you would like navigate to index page after logout. + }; + + myMSALObj.logoutRedirect(logoutRequest); +} diff --git a/theme/static/js/MSAL/claimUtils.js b/theme/static/js/MSAL/claimUtils.js new file mode 100644 index 0000000..9c19f0e --- /dev/null +++ b/theme/static/js/MSAL/claimUtils.js @@ -0,0 +1,224 @@ +/** + * Populate claims table with appropriate description + * @param {Object} claims ID token claims + * @returns claimsObject + */ +const createClaimsTable = (claims) => { + let claimsObj = {}; + let index = 0; + + Object.keys(claims).forEach((key) => { + if (typeof claims[key] !== 'string' && typeof claims[key] !== 'number') return; + switch (key) { + case 'aud': + populateClaim( + key, + claims[key], + "Identifies the intended recipient of the token. In ID tokens, the audience is your app's Application ID, assigned to your app in the Microsoft Entra admin center.", + index, + claimsObj + ); + index++; + break; + case 'iss': + populateClaim( + key, + claims[key], + 'Identifies the issuer, or authorization server that constructs and returns the token. It also identifies the external tenant for which the user was authenticated. If the token was issued by the v2.0 endpoint, the URI will end in /v2.0. The GUID that indicates that the user is a consumer user from a Microsoft account is 9188040d-6c67-4c5b-b112-36a304b66dad.', + index, + claimsObj + ); + index++; + break; + case 'iat': + populateClaim( + key, + changeDateFormat(claims[key]), + 'Issued At indicates when the authentication for this token occurred.', + index, + claimsObj + ); + index++; + break; + case 'nbf': + populateClaim( + key, + changeDateFormat(claims[key]), + 'The nbf (not before) claim identifies the time (as UNIX timestamp) before which the JWT must not be accepted for processing.', + index, + claimsObj + ); + index++; + break; + case 'exp': + populateClaim( + key, + changeDateFormat(claims[key]), + "The exp (expiration time) claim identifies the expiration time (as UNIX timestamp) on or after which the JWT must not be accepted for processing. It's important to note that in certain circumstances, a resource may reject the token before this time. For example, if a change in authentication is required or a token revocation has been detected.", + index, + claimsObj + ); + index++; + break; + case 'name': + populateClaim( + key, + claims[key], + "The principal about which the token asserts information, such as the user of an application. This value is immutable and can't be reassigned or reused. It can be used to perform authorization checks safely, such as when the token is used to access a resource. By default, the subject claim is populated with the object ID of the user in the directory", + index, + claimsObj + ); + index++; + break; + case 'preferred_username': + populateClaim( + key, + claims[key], + 'The primary username that represents the user. It could be an email address, phone number, or a generic username without a specified format. Its value is mutable and might change over time. Since it is mutable, this value must not be used to make authorization decisions. It can be used for username hints, however, and in human-readable UI as a username. The profile scope is required in order to receive this claim.', + index, + claimsObj + ); + index++; + break; + case 'nonce': + populateClaim( + key, + claims[key], + 'The nonce matches the parameter included in the original /authorize request to the IDP. If it does not match, your application should reject the token.', + index, + claimsObj + ); + index++; + break; + case 'oid': + populateClaim( + key, + claims[key], + 'The oid (user’s object id) is the only claim that should be used to uniquely identify a user in an external tenant. The token might have one or more of the following claim, that might seem like a unique identifier, but is not and should not be used as such.', + index, + claimsObj + ); + index++; + break; + case 'tid': + populateClaim( + key, + claims[key], + 'The tenant ID. You will use this claim to ensure that only users from the current external tenant can access this app.', + index, + claimsObj + ); + index++; + break; + case 'upn': + populateClaim( + key, + claims[key], + '(user principal name) – might be unique amongst the active set of users in a tenant but tend to get reassigned to new employees as employees leave the organization and others take their place or might change to reflect a personal change like marriage.', + index, + claimsObj + ); + index++; + break; + case 'email': + populateClaim( + key, + claims[key], + 'Email might be unique amongst the active set of users in a tenant but tend to get reassigned to new employees as employees leave the organization and others take their place.', + index, + claimsObj + ); + index++; + break; + case 'acct': + populateClaim( + key, + claims[key], + 'Available as an optional claim, it lets you know what the type of user (homed, guest) is. For example, for an individual’s access to their data you might not care for this claim, but you would use this along with tenant id (tid) to control access to say a company-wide dashboard to just employees (homed users) and not contractors (guest users).', + index, + claimsObj + ); + index++; + break; + case 'sid': + populateClaim(key, claims[key], 'Session ID, used for per-session user sign-out.', index, claimsObj); + index++; + break; + case 'sub': + populateClaim( + key, + claims[key], + 'The sub claim is a pairwise identifier - it is unique to a particular application ID. If a single user signs into two different apps using two different client IDs, those apps will receive two different values for the subject claim.', + index, + claimsObj + ); + index++; + break; + case 'ver': + populateClaim( + key, + claims[key], + 'Version of the token issued by the Microsoft identity platform', + index, + claimsObj + ); + index++; + break; + case 'auth_time': + populateClaim( + key, + claims[key], + 'The time at which a user last entered credentials, represented in epoch time. There is no discrimination between that authentication being a fresh sign-in, a single sign-on (SSO) session, or another sign-in type.', + index, + claimsObj + ); + index++; + break; + case 'at_hash': + populateClaim( + key, + claims[key], + 'An access token hash included in an ID token only when the token is issued together with an OAuth 2.0 access token. An access token hash can be used to validate the authenticity of an access token', + index, + claimsObj + ); + index++; + break; + case 'uti': + case 'rh': + index++; + break; + default: + populateClaim(key, claims[key], '', index, claimsObj); + index++; + } + }); + + return claimsObj; +}; + +/** + * Populates claim, description, and value into an claimsObject + * @param {string} claim + * @param {string} value + * @param {string} description + * @param {number} index + * @param {Object} claimsObject + */ +const populateClaim = (claim, value, description, index, claimsObject) => { + let claimsArray = []; + claimsArray[0] = claim; + claimsArray[1] = value; + claimsArray[2] = description; + claimsObject[index] = claimsArray; +}; + +/** + * Transforms Unix timestamp to date and returns a string value of that date + * @param {string} date Unix timestamp + * @returns + */ +const changeDateFormat = (date) => { + let dateObj = new Date(date * 1000); + return `${date} - [${dateObj.toString()}]`; +}; \ No newline at end of file diff --git a/theme/static/js/MSAL/ui.js b/theme/static/js/MSAL/ui.js new file mode 100644 index 0000000..f82fde1 --- /dev/null +++ b/theme/static/js/MSAL/ui.js @@ -0,0 +1,32 @@ +// Select DOM elements to work with +const signInButton = document.getElementById('signIn'); +const signOutButton = document.getElementById('signOut'); +const titleDiv = document.getElementById('title-div'); +const welcomeDiv = document.getElementById('welcome-div'); +const tableDiv = document.getElementById('table-div'); +const tableBody = document.getElementById('table-body-div'); + +function welcomeUser(username) { + console.log('welcome called'); + signInButton.classList.add('d-none'); + signOutButton.classList.remove('d-none'); + titleDiv.classList.add('d-none'); + welcomeDiv.classList.remove('d-none'); + welcomeDiv.innerHTML = `Welcome ${username}!`; +}; + +function updateTable(account) { + tableDiv.classList.remove('d-none'); + + const tokenClaims = createClaimsTable(account.idTokenClaims); + + Object.keys(tokenClaims).forEach((key) => { + let row = tableBody.insertRow(0); + let cell1 = row.insertCell(0); + let cell2 = row.insertCell(1); + let cell3 = row.insertCell(2); + cell1.innerHTML = tokenClaims[key][0]; + cell2.innerHTML = tokenClaims[key][1]; + cell3.innerHTML = tokenClaims[key][2]; + }); +}; \ No newline at end of file diff --git a/theme/static/js/algolia/search.js b/theme/static/js/algolia/search.js new file mode 100644 index 0000000..5038ee2 --- /dev/null +++ b/theme/static/js/algolia/search.js @@ -0,0 +1,29 @@ +const searchClient = algoliasearch("RSKJJ82EIK", "8b32e7e6c23096e5e958ddc581c14aee"); + +const search = instantsearch({ + indexName: "pages", + searchClient, +}); + +search.addWidgets([ + instantsearch.widgets.searchBox({ + container: "#searchbox", + }), + + instantsearch.widgets.hits({ + container: "#hits", + templates: { + item: ` +         
+

Name: {{#helpers.highlight}}{ "attribute": "title", "highlightedTagName": "mark" }{{/helpers.highlight}}

+
+       `, + }, + }), + instantsearch.widgets.refinementList({ + container: "#refinement", + attribute: "company", + }), +]); + +search.start(); diff --git a/theme/static/js/cards.js b/theme/static/js/cards.js new file mode 100644 index 0000000..4f4ae79 --- /dev/null +++ b/theme/static/js/cards.js @@ -0,0 +1,31 @@ +document.addEventListener("DOMContentLoaded", function () { + document.querySelectorAll(".card").forEach(function (card) { + var textContainer = card.querySelector(".card-text-expander"); + if (textContainer) { + var paragraphs = textContainer ? textContainer.querySelectorAll("p") : []; + + // Find the "More" button + var showMoreButton = card.querySelector(".show-more"); + + // Check if showMoreButton exists + if (showMoreButton && paragraphs.length <= 1) { + showMoreButton.style.display = "none"; // Hide the button if there's 1 or fewer paragraphs + } else if (showMoreButton) { + showMoreButton.style.display = "inline-block"; // Ensure button is visible when needed + } + + // Expand/collapse logic if the button exists + if (showMoreButton) { + showMoreButton.addEventListener("click", function () { + if (card.classList.contains("expanded")) { + card.classList.remove("expanded"); + this.textContent = "details..."; + } else { + card.classList.add("expanded"); + this.textContent = "hide details..."; + } + }); + } + } + }); +}); diff --git a/theme/static/js/shopify/shopify.js b/theme/static/js/shopify/shopify.js new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/theme/static/js/shopify/shopify.js @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/theme/theme.yaml b/theme/theme.yaml new file mode 100644 index 0000000..1897ed5 --- /dev/null +++ b/theme/theme.yaml @@ -0,0 +1,2 @@ +name: "NKDAgility-Codex" +version: "1.0.0"