diff --git a/.github/workflows/on-push-or-pull.yml b/.github/workflows/on-push-or-pull.yml index 7378ad05e1..a729dd209a 100644 --- a/.github/workflows/on-push-or-pull.yml +++ b/.github/workflows/on-push-or-pull.yml @@ -14,7 +14,6 @@ env: MOZ_HEALESS: 1 SAUCE_USERNAME_PR: valorkinpr SAUCE_ACCESS_KEY_PR: e0a97bd3-4b74-4408-89bf-cce1b44a8bf1 - FIREBASE_CHANNEL: ${{ fromJSON('["", "live"]')[!github.base_ref] }} PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 0 CACHE_NODE_MODULES_PATH: | @@ -29,7 +28,7 @@ jobs: runs-on: ubuntu-22.04 steps: - name: Cancel Previous Runs - uses: styfle/cancel-workflow-action@0.11.0 + uses: styfle/cancel-workflow-action@0.12.1 with: access_token: ${{ secrets.GITHUB_TOKEN }} @@ -38,8 +37,8 @@ jobs: runs-on: ubuntu-22.04 needs: one_run steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 id: cache with: path: ${{ env.CACHE_NODE_MODULES_PATH }} @@ -52,12 +51,12 @@ jobs: needs: install runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_NODE_MODULES_PATH }} key: node_modules-${{ hashFiles('**/package-lock.json') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_DIST_PATH }} key: dist-${{ github.run_id }} @@ -74,12 +73,12 @@ jobs: runs-on: ubuntu-22.04 needs: build steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_NODE_MODULES_PATH }} key: node_modules-${{ hashFiles('**/package-lock.json') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_DIST_PATH }} key: dist-${{ github.run_id }} @@ -93,53 +92,96 @@ jobs: runs-on: ubuntu-22.04 needs: install steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_NODE_MODULES_PATH }} key: node_modules-${{ hashFiles('**/package-lock.json') }} # - run: npm run lint -- --runner=cloud - run: npm run lint -- - # firebase deploy preview - firebase_preview: + # deploy to cloudflare pages + cloudflare_deploy: runs-on: ubuntu-22.04 needs: build outputs: - output_url: ${{ steps.firebase_hosting_preview.outputs.details_url }} + deployment_url: ${{ steps.cloudflare_deploy.outputs.deployment-url }} + preview_url: ${{ steps.extract_preview_url.outputs.preview_url }} steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 - with: - path: ${{ env.CACHE_DIST_PATH }} - key: dist-${{ github.run_id }} - - uses: FirebaseExtended/action-hosting-deploy@v0 - continue-on-error: true - id: firebase_hosting_preview - with: - repoToken: '${{ secrets.GITHUB_TOKEN }}' - firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_NGX_BOOTSTRAP_DEMO }}' - projectId: ngx-bootstrap-demo - channelId: ${{ env.FIREBASE_CHANNEL }} - expires: 7d + - uses: actions/checkout@v4 + + # Wait for Cloudflare deployment with intelligent polling + - name: Wait for Cloudflare deployment + id: wait_deployment + run: | + echo "Waiting for Cloudflare deployment to complete..." + MAX_ATTEMPTS=30 + ATTEMPT=0 + BRANCH_NAME=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + + while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + echo "Attempt $((ATTEMPT + 1))/$MAX_ATTEMPTS: Checking deployment status..." + + # Get deployments for the project + RESPONSE=$(curl -s -H "Authorization: Bearer ${{ secrets.CLOUDFLARE_API_TOKEN }}" \ + "https://api.cloudflare.com/client/v4/accounts/${{ secrets.CLOUDFLARE_ACCOUNT_ID }}/pages/projects/ngx-bootstrap/deployments") + + # Check if we have a successful deployment for this branch/commit + DEPLOYMENT_URL=$(echo "$RESPONSE" | jq -r --arg branch "$BRANCH_NAME" --arg commit "$GITHUB_SHA" \ + '.result[] | select(.deployment_trigger.metadata.branch == $branch and .deployment_trigger.metadata.commit_hash == $commit and .latest_stage.status == "success") | .url' | head -1) + + if [ "$DEPLOYMENT_URL" != "null" ] && [ -n "$DEPLOYMENT_URL" ]; then + echo "✅ Deployment ready! URL: $DEPLOYMENT_URL" + echo "deployment_url=$DEPLOYMENT_URL" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if deployment failed + FAILED=$(echo "$RESPONSE" | jq -r --arg branch "$BRANCH_NAME" --arg commit "$GITHUB_SHA" \ + '.result[] | select(.deployment_trigger.metadata.branch == $branch and .deployment_trigger.metadata.commit_hash == $commit and .latest_stage.status == "failure") | .id' | head -1) + + if [ "$FAILED" != "null" ] && [ -n "$FAILED" ]; then + echo "❌ Deployment failed!" + exit 1 + fi + + echo "⏳ Deployment still in progress, waiting 20 seconds..." + sleep 20 + ATTEMPT=$((ATTEMPT + 1)) + done + + echo "❌ Timeout waiting for deployment" + exit 1 + + - name: Set preview URL output + id: get_preview_url + run: | + if [ -n "${{ steps.cloudflare_deploy.outputs.deployment-url }}" ]; then + # Extract just the hostname from the full URL + PREVIEW_URL=$(echo "${{ steps.cloudflare_deploy.outputs.deployment-url }}" | sed 's|https://||') + echo "preview_url=$PREVIEW_URL" >> $GITHUB_OUTPUT + echo "Preview URL: https://$PREVIEW_URL" + else + echo "No preview URL available" + fi # run playwright e2e_smoke: name: e2e smoke (${{ matrix.shard }}/${{ strategy.job-total }}) runs-on: ubuntu-22.04 - needs: [install, build, firebase_preview] + needs: [install, build, cloudflare_deploy] strategy: fail-fast: false matrix: shard: [1, 2] steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_NODE_MODULES_PATH }} key: node_modules-${{ hashFiles('**/package-lock.json') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_DIST_PATH }} key: dist-${{ github.run_id }} @@ -149,12 +191,12 @@ jobs: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npx playwright install npx playwright install-deps chromium - - name: smoke e2e on firebase - if: ${{ needs.firebase_preview.outputs.output_url }} - run: PLAYWRIGHT_TEST_BASE_URL="${{ needs.firebase_preview.outputs.output_url }}/ngx-bootstrap/" npx nx run ngx-bootstrap-docs-e2e:e2e --pwProject=chromium-integration --skipServe --shard=${{ matrix.shard }}/${{ strategy.job-total }} + - name: smoke e2e on cloudflare pages + if: ${{ needs.cloudflare_deploy.outputs.preview_url }} + run: PLAYWRIGHT_TEST_BASE_URL="https://${{ needs.cloudflare_deploy.outputs.preview_url }}" npx nx run ngx-bootstrap-docs-e2e:e2e --pwProject=chromium-integration --skipServe --shard=${{ matrix.shard }}/${{ strategy.job-total }} - name: smoke e2e local - if: ${{ !needs.firebase_preview.outputs.output_url }} + if: ${{ !needs.cloudflare_deploy.outputs.preview_url }} run: npx nx run ngx-bootstrap-docs-e2e:e2e --pwProject=chromium-integration --shard=${{ matrix.shard }}/${{ strategy.job-total }} - uses: actions/upload-artifact@v4 @@ -167,19 +209,19 @@ jobs: e2e_full: name: e2e full runs-on: ubuntu-22.04 - needs: [e2e_smoke] + needs: [e2e_smoke, cloudflare_deploy] strategy: fail-fast: false matrix: shard: [1, 2] steps: - - uses: actions/checkout@v3 - - uses: actions/cache@v3 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_NODE_MODULES_PATH }} key: node_modules-${{ hashFiles('**/package-lock.json') }} - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ${{ env.CACHE_DIST_PATH }} key: dist-${{ github.run_id }} @@ -189,13 +231,13 @@ jobs: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npx playwright install npx playwright install-deps chromium - - name: full e2e on firebase - if: ${{ needs.firebase_preview.outputs.output_url }} + - name: full e2e on cloudflare pages + if: ${{ needs.cloudflare_deploy.outputs.preview_url }} continue-on-error: true - run: npx nx run ngx-bootstrap-docs-e2e:e2e --pwProject=chromium-full --baseUrl=${{ needs.firebase_preview.outputs.output_url }}/ngx-bootstrap/ --skipServe --shard=${{ matrix.shard }}/${{ strategy.job-total }} + run: npx nx run ngx-bootstrap-docs-e2e:e2e --pwProject=chromium-full --baseUrl=https://${{ needs.cloudflare_deploy.outputs.preview_url }} --skipServe --shard=${{ matrix.shard }}/${{ strategy.job-total }} - name: full e2e on local - if: ${{ !needs.firebase_preview.outputs.output_url }} + if: ${{ !needs.cloudflare_deploy.outputs.preview_url }} continue-on-error: true run: npx nx run ngx-bootstrap-docs-e2e:e2e --pwProject=chromium-full --shard=${{ matrix.shard }}/${{ strategy.job-total }} diff --git a/UPGRADE.md b/UPGRADE.md new file mode 100644 index 0000000000..ec00a151c3 --- /dev/null +++ b/UPGRADE.md @@ -0,0 +1,229 @@ +# ngx-bootstrap Dependency Upgrade Guide + +This document outlines the necessary steps to upgrade outdated dependencies in the ngx-bootstrap project. The analysis is based on the output of `npm outdated` as of December 2024. + +## Overview + +The project has several packages that can be updated, ranging from minor patches to major version upgrades that may require migration work. + +## Angular Ecosystem Updates + +### Angular Core & CLI (Minor Updates) +**Current:** 19.0.1-19.0.2 → **Latest:** 19.2.13 + +All Angular packages can be safely updated to the latest 19.x version: +- `@angular/animations`, `@angular/common`, `@angular/core`, etc. +- `@angular-devkit/build-angular`, `@angular/cli` +- `@angular-eslint/*` packages + +**Migration:** These are patch/minor updates within the same major version - should be safe to update. + +```bash +ng update @angular/core @angular/cli +ng update @angular-eslint/schematics +``` + +## Major Version Upgrades (Breaking Changes) + +### 1. Nx Monorepo Tools +**Current:** 20.2.0 → **Latest:** 21.1.2 + +**Breaking Changes:** +- Angular CLI v20.0.0-rc.3 support added +- Migrated to Angular Rspack for build and module federation +- Enhanced task runner and process management +- Improved cache and remote cache handling + +**Migration Steps:** +```bash +npx nx migrate latest +npm install +npx nx migrate --run-migrations +``` + +**Action Required:** +- Review module federation configurations +- Test parallel task execution +- Verify cache and remote cache settings + +### 2. ESLint +**Current:** 8.57.0 → **Latest:** 9.27.0 + +**Breaking Changes:** +- New configuration format with `defineConfig()` +- Flat config is now preferred +- Some older configuration methods deprecated +- Enhanced TypeScript syntax support + +**Migration Steps:** +1. Update ESLint configuration to use flat config format +2. Review and update deprecated rule usages +3. Test custom rules for compatibility + +```bash +npm install eslint@9 --save-dev +# Review and update .eslintrc configuration +``` + +### 3. TypeScript ESLint +**Current:** 7.18.0 → **Latest:** 8.32.1 + +**Breaking Changes:** +- New standalone packages: "project-service" and "tsconfig-utils" +- New ESLint rules: `no-unnecessary-type-conversion`, updated `prefer-nullish-coalescing` +- Enhanced type safety and code quality rules + +**Migration Steps:** +```bash +npm install @typescript-eslint/eslint-plugin@8 @typescript-eslint/parser@8 --save-dev +``` + +**Action Required:** +- Review ESLint configurations for new rule options +- Test thoroughly for new rule behaviors + +### 4. Playwright +**Current:** 1.35.1 → **Latest:** 1.52.0 + +**Breaking Changes:** +- New headless mode for Chrome and Edge channels +- `expect(locator).toBeEditable()` API changes +- Glob URL pattern changes in `page.route()` (no `?` wildcard support) +- `route.continue()` cannot override `Cookie` header +- macOS 13 deprecated for WebKit + +**Migration Steps:** +```bash +npm install @playwright/test@latest --save-dev +npx playwright install +``` + +**Action Required:** +- Review and update test configurations +- Check routing and snapshot modifications +- Update any glob patterns in route handling + +### 5. Express.js +**Current:** 4.21.2 → **Latest:** 5.1.0 + +**Breaking Changes:** +- Requires Node.js 18+ +- Updated to `path-to-regexp@8.x` +- Removed sub-expression regex patterns for security +- Middleware can return rejected promises +- Removed deprecated API methods from v3/v4 + +**Migration Steps:** +```bash +npm install express@5 --save +``` + +**Action Required:** +- Ensure Node.js version is 18+ +- Review middleware error handling with promises +- Check for deprecated method signatures +- Follow [official migration guide](https://expressjs.com/en/guide/migrating-5.html) + +### 6. @ngneat/spectator +**Current:** 11.1.0 → **Latest:** 19.6.1 + +**Major Version Jump:** This is a significant upgrade spanning multiple major versions. + +**Action Required:** +- Review the project's changelog for breaking changes +- Test all spectator-based tests thoroughly +- Consider gradual migration approach + +## Recommended Safe Updates + +### Minor/Patch Updates (Low Risk) +These can be updated without breaking changes: + +```bash +# Development tools +npm install --save-dev \ + prettier@3.5.3 \ + ts-jest@29.3.4 \ + ts-node@10.9.2 \ + webpack-bundle-analyzer@4.10.2 \ + jest-preset-angular@14.5.5 + +# Runtime dependencies +npm install \ + rxjs@7.8.2 \ + zone.js@0.15.1 \ + moment@2.30.1 \ + ajv@8.17.1 +``` + +## High-Risk Updates (Major Versions) + +### marked.js +**Current:** 4.0.18 → **Latest:** 15.0.12 + +This is a massive version jump (11 major versions). Recommend: +1. Review changelog carefully +2. Test markdown rendering thoroughly +3. Consider migration in separate PR + +### Other Notable Updates +- `@stackblitz/sdk`: 1.8.0 → 1.11.0 (safe minor update) +- `husky`: 8.0.1 → 9.1.7 (review configuration changes) +- `release-it`: 16.1.0 → 19.0.2 (review release workflow) + +## Migration Strategy + +### Phase 1: Safe Updates +1. Update Angular packages to 19.2.13 +2. Update minor/patch versions of development tools +3. Update TypeScript to 5.8.3 + +### Phase 2: Medium Risk +1. Update Nx to 21.x (use migration tools) +2. Update Playwright to 1.52.0 +3. Update TypeScript ESLint to 8.x + +### Phase 3: High Risk +1. Update ESLint to 9.x +2. Update Express to 5.x +3. Update @ngneat/spectator to 19.x +4. Update marked.js to 15.x + +## Testing Checklist + +After each phase: +- [ ] Run all unit tests: `npm test` +- [ ] Run all e2e tests: `npm run e2e` +- [ ] Run linting: `npm run lint` +- [ ] Build the project: `npm run build` +- [ ] Test SSR functionality: `npm run build:ssr` +- [ ] Verify documentation generation works + +## Command Summary + +```bash +# Phase 1: Safe updates +ng update @angular/core @angular/cli +npm update # For patch updates + +# Phase 2: Medium risk +npx nx migrate latest +npm install @playwright/test@latest --save-dev +npm install @typescript-eslint/eslint-plugin@8 @typescript-eslint/parser@8 --save-dev + +# Phase 3: High risk (do separately) +npm install eslint@9 --save-dev +npm install express@5 --save +npm install @ngneat/spectator@latest --save-dev +npm install marked@latest --save-dev +``` + +## Notes + +- Always create feature branches for major version upgrades +- Consider updating packages individually rather than all at once +- Have rollback plan ready for each major update +- Update CI/CD pipelines if Node.js version requirements change +- Review and update documentation after migrations + +Generated on: December 2024 \ No newline at end of file diff --git a/apps/ngx-bootstrap-docs/project.json b/apps/ngx-bootstrap-docs/project.json index 0ad234cb00..e8d5d9abbe 100644 --- a/apps/ngx-bootstrap-docs/project.json +++ b/apps/ngx-bootstrap-docs/project.json @@ -33,6 +33,38 @@ } }, "configurations": { + "production-cf": { + "fileReplacements": [ + { + "replace": "apps/ngx-bootstrap-docs/src/environments/environment.ts", + "with": "apps/ngx-bootstrap-docs/src/environments/environment.prod.ts" + } + ], + "baseHref": "", + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false, + "buildOptimizer": true, + "budgets": [ + { + "type": "initial", + "maximumWarning": "2mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "6kb", + "maximumError": "10kb" + } + ], + "outputPath": "dist/apps/ngx-bootstrap", + "index": "apps/ngx-bootstrap-docs/src/index.html", + "main": "apps/ngx-bootstrap-docs/src/main.ts", + "tsConfig": "apps/ngx-bootstrap-docs/tsconfig.app.json" + }, "production": { "fileReplacements": [ { @@ -166,6 +198,22 @@ } }, "defaultConfiguration": "production" + }, + "fix-404": { + "executor": "nx:run-commands", + "options": { + "command": "node scripts/fix-404.js" + } + }, + "build-with-404-fix": { + "executor": "nx:run-commands", + "options": { + "commands": [ + "nx build ngx-bootstrap-docs --configuration=production-cf", + "nx run ngx-bootstrap-docs:fix-404" + ], + "parallel": false + } } } } diff --git a/apps/ngx-bootstrap-docs/src/404.html b/apps/ngx-bootstrap-docs/src/404.html index b3fa8d35b2..e929f2c539 100644 --- a/apps/ngx-bootstrap-docs/src/404.html +++ b/apps/ngx-bootstrap-docs/src/404.html @@ -43,5 +43,5 @@ - + diff --git a/apps/ngx-bootstrap-docs/src/app/app.component.ts b/apps/ngx-bootstrap-docs/src/app/app.component.ts index be57918975..53d08d73ba 100644 --- a/apps/ngx-bootstrap-docs/src/app/app.component.ts +++ b/apps/ngx-bootstrap-docs/src/app/app.component.ts @@ -1,5 +1,5 @@ -import { DOCUMENT } from '@angular/common'; -import { AfterContentInit, Component, Inject } from '@angular/core'; +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; +import { AfterContentInit, Component, Inject, PLATFORM_ID } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router, UrlSerializer } from '@angular/router'; import { Analytics } from '@ngx-bootstrap-doc/docs'; import { filter } from 'rxjs/operators'; @@ -18,7 +18,8 @@ export class AppComponent implements AfterContentInit { private router: Router, private urlSerializer: UrlSerializer, private analytics: Analytics, - @Inject(DOCUMENT) private document: any + @Inject(DOCUMENT) private document: any, + @Inject(PLATFORM_ID) private platformId: object ) {} // almost same logic exists in top-menu component @@ -30,25 +31,28 @@ export class AppComponent implements AfterContentInit { const justDoIt = (): void => { const _cur = getUrl(this.router); this.showSidebar = !!getUrl(this.router); - if (typeof PR !== 'undefined' && _prev !== _cur) { - _prev = _cur; - // google code-prettify - PR.prettyPrint(); - } + + if (isPlatformBrowser(this.platformId)) { + if (typeof (window as any).PR !== 'undefined' && _prev !== _cur) { + _prev = _cur; + // google code-prettify + (window as any).PR.prettyPrint(); + } - const hash = this.route.snapshot.fragment; - if (hash) { - const target: HTMLElement | null = this.document.getElementById(hash); - const header: HTMLElement | null = this.document.getElementById('header'); - if (target && header) { - setTimeout(() => { - const sidebar: HTMLElement | null = this.document.getElementById('sidebar'); - const targetPosY: number = innerWidth <= 991 ? target.offsetTop - header.offsetHeight - 6 - (sidebar?.offsetHeight || 0) : target.offsetTop - header.offsetHeight - 6; - window.scrollTo({top: targetPosY, behavior: 'smooth'}); - }, 100); + const hash = this.route.snapshot.fragment; + if (hash) { + const target: HTMLElement | null = this.document.getElementById(hash); + const header: HTMLElement | null = this.document.getElementById('header'); + if (target && header) { + setTimeout(() => { + const sidebar: HTMLElement | null = this.document.getElementById('sidebar'); + const targetPosY: number = window.innerWidth <= 991 ? target.offsetTop - header.offsetHeight - 6 - (sidebar?.offsetHeight || 0) : target.offsetTop - header.offsetHeight - 6; + window.scrollTo({top: targetPosY, behavior: 'smooth'}); + }, 100); + } + } else { + window.scrollTo({top: 0, behavior: 'smooth'}); } - } else { - window.scrollTo({top: 0, behavior: 'smooth'}); } }; diff --git a/apps/ngx-bootstrap-docs/src/assets/css/adaptive.scss b/apps/ngx-bootstrap-docs/src/assets/css/adaptive.scss index 95ea88f7e8..d2b37de623 100644 --- a/apps/ngx-bootstrap-docs/src/assets/css/adaptive.scss +++ b/apps/ngx-bootstrap-docs/src/assets/css/adaptive.scss @@ -1,3 +1,5 @@ +@use "variables" as *; + /*** Media Query ***/ /* above 768px */ diff --git a/apps/ngx-bootstrap-docs/src/assets/css/common-styles.scss b/apps/ngx-bootstrap-docs/src/assets/css/common-styles.scss index b997f3a595..98944ce67b 100644 --- a/apps/ngx-bootstrap-docs/src/assets/css/common-styles.scss +++ b/apps/ngx-bootstrap-docs/src/assets/css/common-styles.scss @@ -1,4 +1,4 @@ -@import "variables"; +@use "variables" as *; html { touch-action: manipulation; diff --git a/apps/ngx-bootstrap-docs/src/assets/css/sidebar.scss b/apps/ngx-bootstrap-docs/src/assets/css/sidebar.scss index 0ac51e72e8..7338e5958e 100644 --- a/apps/ngx-bootstrap-docs/src/assets/css/sidebar.scss +++ b/apps/ngx-bootstrap-docs/src/assets/css/sidebar.scss @@ -1,8 +1,8 @@ -@import "manrope"; -@import "open-sans.css"; -@import "reboot"; -@import "variables"; -@import "common-styles.scss"; +@use "manrope"; +@use "open-sans.css"; +@use "reboot"; +@use "variables" as *; +@use "common-styles.scss"; /* sidebar */ diff --git a/apps/ngx-bootstrap-docs/src/assets/css/style.scss b/apps/ngx-bootstrap-docs/src/assets/css/style.scss index 0138517b16..d78bb96fc5 100644 --- a/apps/ngx-bootstrap-docs/src/assets/css/style.scss +++ b/apps/ngx-bootstrap-docs/src/assets/css/style.scss @@ -1,5 +1,7 @@ -@import "sidebar"; -@import "bs-datepicker"; +@use "variables" as *; +@use "sidebar"; +@use "bs-datepicker"; +@use "adaptive"; /* HEADER */ .bs-datepicker-head { @@ -1000,5 +1002,4 @@ footer { } /*Imported adaptive styles*/ -@import "adaptive"; diff --git a/firebase.json b/firebase.json deleted file mode 100644 index 24d781ec7b..0000000000 --- a/firebase.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "hosting": { - "public": "dist/apps", - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ], - "rewrites": [ - { - "source": "**", - "destination": "/ngx-bootstrap/index.html" - } - ], - "redirects": [ - { - "source": "/", - "destination": "/ngx-bootstrap/", - "type": 302 - } - ] - } -} diff --git a/libs/common-docs/src/lib/common/add-nav/add-nav.component.ts b/libs/common-docs/src/lib/common/add-nav/add-nav.component.ts index 52ae3585cf..df07e473ad 100644 --- a/libs/common-docs/src/lib/common/add-nav/add-nav.component.ts +++ b/libs/common-docs/src/lib/common/add-nav/add-nav.component.ts @@ -9,7 +9,8 @@ import { SimpleChanges, ViewChildren } from "@angular/core"; -import { DOCUMENT } from '@angular/common'; +import { DOCUMENT, isPlatformBrowser } from '@angular/common'; +import { PLATFORM_ID } from '@angular/core'; import { ContentSection } from '../../models/content-section.model'; import { Router } from "@angular/router"; interface IComponentContent { @@ -38,6 +39,7 @@ export class AddNavComponent implements OnChanges, AfterViewChecked, AfterViewIn // eslint-disable-next-line @typescript-eslint/no-empty-function constructor( @Inject(DOCUMENT) private document: Document, + @Inject(PLATFORM_ID) private platformId: object, private _renderer: Renderer2, private router: Router, ){} @@ -71,7 +73,7 @@ export class AddNavComponent implements OnChanges, AfterViewChecked, AfterViewIn } goToSectionWIthAnchor(anchor?: string | null) { - if (!anchor) { + if (!anchor || !isPlatformBrowser(this.platformId)) { return; } @@ -86,28 +88,34 @@ export class AddNavComponent implements OnChanges, AfterViewChecked, AfterViewIn } initActiveMenuTab() { - if (this.scrollElementsList?.length) { - this.scrollElementsList.map(item => { - const min = item.nativeElement.getAttribute('data-min-scroll-value'); - const max = item.nativeElement.getAttribute('data-max-scroll-value'); - const position = window.pageYOffset; - if (position >= min && position <= max) { - this._renderer.addClass(item.nativeElement.parentElement, 'active'); - } else { - this._renderer.removeClass(item.nativeElement.parentElement, 'active'); - } - }); + if (!isPlatformBrowser(this.platformId) || !this.scrollElementsList?.length) { + return; } + + this.scrollElementsList.map(item => { + const min = item.nativeElement.getAttribute('data-min-scroll-value'); + const max = item.nativeElement.getAttribute('data-max-scroll-value'); + const position = window.pageYOffset; + if (position >= min && position <= max) { + this._renderer.addClass(item.nativeElement.parentElement, 'active'); + } else { + this._renderer.removeClass(item.nativeElement.parentElement, 'active'); + } + }); } setScrollAttributes() { + if (!isPlatformBrowser(this.platformId)) { + return; + } + const header: number = this.document.querySelector('header')?.offsetHeight || 0; this.scrollElementsList?.map(item => { const id = item.nativeElement.getAttribute('data-anchor'); const target: HTMLElement | null = this.document.getElementById(id); - if (target) { + if (target && target.parentElement) { const targetPosY: number = target.offsetTop - header - 10; - const parentHeight = (target.parentElement).getBoundingClientRect().height + 6 || 0; + const parentHeight = target.parentElement.getBoundingClientRect().height + 6 || 0; this._renderer.setAttribute(item.nativeElement, 'data-max-scroll-value', (targetPosY + parentHeight).toString()); this._renderer.setAttribute(item.nativeElement, 'data-min-scroll-value', (targetPosY).toString()); } diff --git a/libs/common-docs/src/lib/common/sidebar/sidebar.component.ts b/libs/common-docs/src/lib/common/sidebar/sidebar.component.ts index 79d322563a..5cf094905f 100644 --- a/libs/common-docs/src/lib/common/sidebar/sidebar.component.ts +++ b/libs/common-docs/src/lib/common/sidebar/sidebar.component.ts @@ -1,5 +1,7 @@ import { ActivatedRoute, NavigationEnd, Router, Routes, UrlSegment } from '@angular/router'; import { Component, HostBinding, Inject, inject, Renderer2 } from '@angular/core'; +import { isPlatformBrowser } from '@angular/common'; +import { PLATFORM_ID } from '@angular/core'; import { AvailableBsVersions, currentBsVersion, getBsVer, IBsVersion, setTheme } from 'ngx-bootstrap/utils'; import { StyleManager } from '../../theme/style-manager'; @@ -47,9 +49,10 @@ export class SidebarComponent { private themeStorage: ThemeStorage, public styleManager: StyleManager, @Inject(DOCS_TOKENS) _routes: Routes, - @Inject(SIDEBAR_ROUTES) sidebarRoutesStructure: SidebarRoutesType + @Inject(SIDEBAR_ROUTES) sidebarRoutesStructure: SidebarRoutesType, + @Inject(PLATFORM_ID) private platformId: object ) { - if (innerWidth <= 991) { + if (isPlatformBrowser(this.platformId) && innerWidth <= 991) { this.menuIsOpened = false; } this.bodyElement = inject(Renderer2).selectRootElement('body', true); @@ -140,7 +143,7 @@ export class SidebarComponent { } closeAdaptiveMenu() { - if (innerWidth <= 991) { + if (isPlatformBrowser(this.platformId) && innerWidth <= 991) { this.menuIsOpened = false; this.toggleSideBar(false); } diff --git a/scripts/fix-404.js b/scripts/fix-404.js new file mode 100644 index 0000000000..726acdf3df --- /dev/null +++ b/scripts/fix-404.js @@ -0,0 +1,64 @@ +const fs = require('fs'); +const path = require('path'); + +function fix404Html() { + const distPath = path.join(__dirname, '../dist/apps/ngx-bootstrap'); + const indexPath = path.join(distPath, 'index.html'); + const notFoundPath = path.join(distPath, '404.html'); + + // Check if files exist + if (!fs.existsSync(indexPath)) { + console.error('❌ index.html not found at:', indexPath); + process.exit(1); + } + + if (!fs.existsSync(notFoundPath)) { + console.error('❌ 404.html not found at:', notFoundPath); + process.exit(1); + } + + // Read both files + const indexContent = fs.readFileSync(indexPath, 'utf8'); + const notFoundContent = fs.readFileSync(notFoundPath, 'utf8'); + + // Extract script tags from index.html (everything after the last before ) + const scriptRegex = /]*src="[^"]*\.(js|mjs)"[^>]*><\/script>/g; + const scripts = indexContent.match(scriptRegex) || []; + + if (scripts.length === 0) { + console.warn('⚠️ No script tags found in index.html'); + return; + } + + // Check if 404.html already has these scripts + const hasScripts = scripts.some(script => notFoundContent.includes(script)); + if (hasScripts) { + console.log('✅ 404.html already has the required scripts'); + return; + } + + // Insert scripts before in 404.html + const scriptsString = scripts.join(''); + const updatedNotFoundContent = notFoundContent.replace( + '', + `${scriptsString}` + ); + + // Write the updated 404.html + fs.writeFileSync(notFoundPath, updatedNotFoundContent, 'utf8'); + + console.log('✅ Successfully injected scripts into 404.html:'); + scripts.forEach(script => { + const src = script.match(/src="([^"]+)"/)?.[1]; + console.log(` 📄 ${src}`); + }); +} + +// Run the script +try { + fix404Html(); +} catch (error) { + console.error('❌ Error fixing 404.html:', error.message); + process.exit(1); +} + diff --git a/src/datepicker/bs-datepicker.scss b/src/datepicker/bs-datepicker.scss index c756262d85..15d4a84c98 100644 --- a/src/datepicker/bs-datepicker.scss +++ b/src/datepicker/bs-datepicker.scss @@ -1,5 +1,5 @@ -@import 'utils/scss/variables'; -@import 'utils/scss/mixins'; +@use 'utils/scss/variables' as *; +@use 'utils/scss/mixins' as *; /* .bs-datepicker */ .bs-datepicker { diff --git a/src/datepicker/utils/scss/mixins.scss b/src/datepicker/utils/scss/mixins.scss index 227038fa99..33ce21b7ad 100644 --- a/src/datepicker/utils/scss/mixins.scss +++ b/src/datepicker/utils/scss/mixins.scss @@ -1,4 +1,4 @@ -@import 'variables'; +@use 'variables' as *; @mixin theming($name, $color) { .theme-#{$name} { diff --git a/src/utils/theme-provider.ts b/src/utils/theme-provider.ts index d3bcb9ce1b..d407d02b68 100644 --- a/src/utils/theme-provider.ts +++ b/src/utils/theme-provider.ts @@ -19,6 +19,11 @@ export enum BsVerions { let guessedVersion: AvailableBsVersions; function _guessBsVersion(): AvailableBsVersions { + // Return default version during SSR or when document is not available + if (!window.document || !window.document.createElement) { + return 'bs5'; // default to bs5 + } + const spanEl = window.document.createElement('span'); spanEl.innerText = 'testing bs version'; spanEl.classList.add('d-none');