├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── renovate.json └── workflows │ ├── ci.yml │ ├── release.yml │ ├── security.yml │ └── sonar.yml ├── .gitignore ├── .releaserc.js ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DEV_GUIDELINES.md ├── LICENSE ├── README.md ├── angular.json ├── e2e ├── protractor.conf.js ├── src │ ├── app.e2e-spec.ts │ └── app.po.ts └── tsconfig.e2e.json ├── karma.conf.js ├── ng-package.json ├── ng-package.prod.json ├── package-lock.json ├── package.json ├── src ├── lib │ ├── aem-angular-editable-components.module.ts │ ├── layout │ │ ├── aem-allowed-components-container │ │ │ ├── aem-allowed-components-container.component.css │ │ │ ├── aem-allowed-components-container.component.html │ │ │ ├── aem-allowed-components-container.component.spec.ts │ │ │ └── aem-allowed-components-container.component.ts │ │ ├── aem-component.directive.spec.ts │ │ ├── aem-component.directive.ts │ │ ├── aem-container │ │ │ ├── aem-container.component.html │ │ │ ├── aem-container.component.spec.ts │ │ │ └── aem-container.component.ts │ │ ├── aem-model-provider │ │ │ ├── aem-model-provider.component.spec.ts │ │ │ └── aem-model-provider.component.ts │ │ ├── aem-page │ │ │ └── aem-page.component.ts │ │ ├── aem-remote │ │ │ ├── README.md │ │ │ ├── aem-remote.component.html │ │ │ ├── aem-remote.component.spec.ts │ │ │ └── aem-remote.component.ts │ │ ├── aem-responsivegrid │ │ │ ├── aem-responsivegrid.component.html │ │ │ ├── aem-responsivegrid.component.spec.ts │ │ │ └── aem-responsivegrid.component.ts │ │ ├── component-mapping.spec.ts │ │ ├── component-mapping.ts │ │ ├── constants.ts │ │ └── utils.ts │ ├── routing │ │ ├── AemPageDataResolver.spec.ts │ │ ├── AemPageDataResolver.ts │ │ ├── AemPageRouteReuseStrategy.spec.ts │ │ └── AemPageRouteReuseStrategy.ts │ └── test │ │ ├── data │ │ └── layout.json │ │ ├── lazy-component-wrapper │ │ ├── lazy.component.ts │ │ ├── lazy.entry.ts │ │ └── lazy.module.ts │ │ ├── mapping.ts │ │ ├── test-comp.type.ts │ │ ├── test-comp1.component.ts │ │ ├── test-comp2.component.ts │ │ └── test-comp3.component.ts ├── public_api.ts └── test.ts ├── tsconfig.json ├── tsconfig.lib.json ├── tsconfig.spec.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | charset = utf-8 8 | indent_size = 2 9 | indent_style = space 10 | 11 | [*.{json,xml}] 12 | insert_final_newline = false 13 | 14 | [package.json] 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* 2 | 👋 Hi! This file was autogenerated by tslint-to-eslint-config. 3 | https://github.com/typescript-eslint/tslint-to-eslint-config 4 | 5 | It represents the closest reasonable ESLint configuration to this 6 | project's original TSLint configuration. 7 | 8 | We recommend eventually switching this configuration to extend from 9 | the recommended rulesets in typescript-eslint. 10 | https://github.com/typescript-eslint/tslint-to-eslint-config/blob/master/docs/FAQs.md 11 | 12 | Happy linting! 💖 13 | */ 14 | module.exports = { 15 | "env": { 16 | "browser": true, 17 | "es6": true, 18 | "node": true 19 | }, 20 | "ignorePatterns": [ 21 | "!.*", 22 | "package-lock.json", 23 | "node_modules/", 24 | "dist/", 25 | ".git/", 26 | "!.*", 27 | "package-lock.json", 28 | "node_modules/", 29 | "dist/", 30 | ".git/", 31 | "tsconfig.json" 32 | ], 33 | "parser": "@typescript-eslint/parser", 34 | "parserOptions": { 35 | "project": "tsconfig.json", 36 | "sourceType": "module" 37 | }, 38 | "plugins": [ 39 | "eslint-plugin-import", 40 | "@angular-eslint/eslint-plugin", 41 | "@typescript-eslint", 42 | "@typescript-eslint/tslint" 43 | ], 44 | "root": true, 45 | "rules": { 46 | "@angular-eslint/component-class-suffix": "error", 47 | "@angular-eslint/component-selector": [ 48 | "error", 49 | { 50 | "type": "element", 51 | "prefix": "aem", 52 | "style": "kebab-case" 53 | } 54 | ], 55 | "@angular-eslint/directive-class-suffix": "error", 56 | "@angular-eslint/directive-selector": [ 57 | "error", 58 | { 59 | "type": "attribute", 60 | "prefix": "aem", 61 | "style": "camelCase" 62 | } 63 | ], 64 | "@angular-eslint/no-host-metadata-property": "error", 65 | "@angular-eslint/no-input-rename": "error", 66 | "@angular-eslint/no-inputs-metadata-property": "error", 67 | "@angular-eslint/no-output-on-prefix": "error", 68 | "@angular-eslint/no-output-rename": "error", 69 | "@angular-eslint/no-outputs-metadata-property": "error", 70 | "@angular-eslint/use-lifecycle-interface": "error", 71 | "@angular-eslint/use-pipe-transform-interface": "error", 72 | "@typescript-eslint/consistent-type-definitions": "error", 73 | "@typescript-eslint/dot-notation": "off", 74 | "@typescript-eslint/explicit-member-accessibility": [ 75 | "off", 76 | { 77 | "accessibility": "explicit" 78 | } 79 | ], 80 | "@typescript-eslint/indent": "error", 81 | "@typescript-eslint/member-delimiter-style": [ 82 | "error", 83 | { 84 | "multiline": { 85 | "delimiter": "semi", 86 | "requireLast": true 87 | }, 88 | "singleline": { 89 | "delimiter": "semi", 90 | "requireLast": false 91 | } 92 | } 93 | ], 94 | "@typescript-eslint/member-ordering": "error", 95 | "@typescript-eslint/naming-convention": [ 96 | "error", 97 | { 98 | "selector": "variable", 99 | "format": [ 100 | "camelCase", 101 | "UPPER_CASE" 102 | ], 103 | "leadingUnderscore": "forbid", 104 | "trailingUnderscore": "forbid" 105 | } 106 | ], 107 | "@typescript-eslint/no-empty-function": "off", 108 | "@typescript-eslint/no-empty-interface": "error", 109 | "@typescript-eslint/no-inferrable-types": [ 110 | "error", 111 | { 112 | "ignoreParameters": true 113 | } 114 | ], 115 | "@typescript-eslint/no-misused-new": "error", 116 | "@typescript-eslint/no-non-null-assertion": "error", 117 | "@typescript-eslint/no-shadow": [ 118 | "error", 119 | { 120 | "hoist": "all" 121 | } 122 | ], 123 | "@typescript-eslint/no-unused-expressions": "error", 124 | "@typescript-eslint/no-use-before-define": "error", 125 | "@typescript-eslint/prefer-function-type": "error", 126 | "@typescript-eslint/quotes": [ 127 | "error", 128 | "single" 129 | ], 130 | "@typescript-eslint/semi": [ 131 | "error", 132 | "always" 133 | ], 134 | "@typescript-eslint/type-annotation-spacing": "error", 135 | "@typescript-eslint/unified-signatures": "error", 136 | "arrow-body-style": "error", 137 | "brace-style": [ 138 | "error", 139 | "1tbs" 140 | ], 141 | "constructor-super": "error", 142 | "curly": "error", 143 | "dot-notation": "off", 144 | "eol-last": "error", 145 | "eqeqeq": [ 146 | "error", 147 | "smart" 148 | ], 149 | "guard-for-in": "error", 150 | "id-denylist": "off", 151 | "id-match": "off", 152 | "import/no-deprecated": "warn", 153 | "indent": "off", 154 | "max-len": [ 155 | "error", 156 | { 157 | "code": 140 158 | } 159 | ], 160 | "no-bitwise": "error", 161 | "no-caller": "error", 162 | "no-console": [ 163 | "error", 164 | { 165 | "allow": [ 166 | "log", 167 | "warn", 168 | "dir", 169 | "timeLog", 170 | "assert", 171 | "clear", 172 | "count", 173 | "countReset", 174 | "group", 175 | "groupEnd", 176 | "table", 177 | "dirxml", 178 | "error", 179 | "groupCollapsed", 180 | "Console", 181 | "profile", 182 | "profileEnd", 183 | "timeStamp", 184 | "context" 185 | ] 186 | } 187 | ], 188 | "no-debugger": "error", 189 | "no-empty": "off", 190 | "no-empty-function": "off", 191 | "no-eval": "error", 192 | "no-fallthrough": "error", 193 | "no-new-wrappers": "error", 194 | "no-restricted-imports": [ 195 | "error", 196 | "rxjs/Rx" 197 | ], 198 | "no-shadow": "off", 199 | "no-throw-literal": "error", 200 | "no-trailing-spaces": "error", 201 | "no-undef-init": "error", 202 | "no-underscore-dangle": "off", 203 | "no-unused-expressions": "off", 204 | "no-unused-labels": "error", 205 | "no-use-before-define": "off", 206 | "no-var": "error", 207 | "prefer-const": "error", 208 | "quotes": "off", 209 | "radix": "error", 210 | "semi": "off", 211 | "spaced-comment": [ 212 | "error", 213 | "always", 214 | { 215 | "markers": [ 216 | "/" 217 | ] 218 | } 219 | ], 220 | "@typescript-eslint/tslint/config": [ 221 | "error", 222 | { 223 | "rules": { 224 | "import-spacing": true, 225 | "whitespace": [ 226 | true, 227 | "check-branch", 228 | "check-decl", 229 | "check-operator", 230 | "check-separator", 231 | "check-type" 232 | ] 233 | } 234 | } 235 | ] 236 | } 237 | }; 238 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json binary 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[bug]" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Package version** 14 | Provide a package version where the bug occurs. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. Go to '...' 19 | 2. Click on '....' 20 | 3. Scroll down to '....' 21 | 4. See error 22 | 23 | **Expected behavior** 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[feature] " 5 | labels: feature-request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "timezone": "Europe/Zurich", 3 | "packageRules": [ 4 | { 5 | "groupName": "@adobe fixes", 6 | "updateTypes": ["patch", "pin", "digest", "minor"], 7 | "automerge": true, 8 | "packagePatterns": ["^@adobe/"], 9 | "schedule": ["at any time"] 10 | }, 11 | { 12 | "groupName": "@adobe major", 13 | "updateTypes": ["major"], 14 | "packagePatterns": ["^@adobe/"], 15 | "automerge": false, 16 | "schedule": ["at any time"] 17 | }, 18 | { 19 | "groupName": "external fixes", 20 | "updateTypes": ["patch", "pin", "digest", "minor"], 21 | "automerge": false, 22 | "schedule": ["after 1pm on Monday"], 23 | "packagePatterns": ["^.+"], 24 | "excludePackagePatterns": ["^@adobe/"] 25 | }, 26 | { 27 | "groupName": "external major", 28 | "updateTypes": ["major"], 29 | "automerge": false, 30 | "packagePatterns": ["^.+"], 31 | "excludePackagePatterns": ["^@adobe/"], 32 | "schedule": ["after 1pm on Monday"] 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: [ pull_request ] 3 | 4 | jobs: 5 | test: 6 | name: Build & Test 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout source code 10 | uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v1 15 | with: 16 | node-version: '16' 17 | - name: Install dependencies 18 | run: npm ci 19 | - name: Build the project 20 | run: npm run build 21 | # - name: Run tests and do code coverage check 22 | # run: npm run test:coverage 23 | - name: Upload code coverage report to workflow as an artifact 24 | uses: actions/upload-artifact@v2 25 | with: 26 | name: istanbul-code-coverage.zip 27 | path: coverage 28 | - name: Upload code coverage report to codecov.io and comment in pull request 29 | uses: codecov/codecov-action@v1 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | jobs: 8 | release: 9 | name: Release and publish module 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout source code 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: 16 20 | - name: Install dependencies 21 | run: npm ci 22 | - name: Build the project 23 | run: npm run build 24 | # - name: Run tests and do code coverage check 25 | # run: npm run test:coverage 26 | - name: Upload code coverage report to codecov.io and comment in pull request 27 | uses: codecov/codecov-action@v1 28 | - name: Upload Sonar report to sonarcloud.io 29 | uses: sonarsource/sonarcloud-github-action@master 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 33 | with: 34 | args: > 35 | -Dsonar.organization=adobeinc 36 | -Dsonar.projectKey=adobe_aem-angular-editable-components 37 | -Dsonar.sources=src 38 | -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info 39 | -Dsonar.coverage.exclusions=src/public_api.ts,src/test.ts,src/lib/aem-angular-editable-components.module.ts,**/*.spec.ts 40 | - name: Release module and publish it in github.com and npmjs.com 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | NPM_TOKEN: ${{ secrets.ADOBE_BOT_NPM_TOKEN }} 44 | run: npm run semantic-release 45 | - name: Build documentation 46 | run: npm run docs 47 | - name: Publish documentation to github pages 48 | uses: JamesIves/github-pages-deploy-action@3.7.1 49 | with: 50 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 51 | BRANCH: gh-pages-documentation 52 | FOLDER: dist/docs -------------------------------------------------------------------------------- /.github/workflows/security.yml: -------------------------------------------------------------------------------- 1 | name: Vulnerability check 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request_target: 7 | 8 | jobs: 9 | security: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout source code 13 | uses: actions/checkout@master 14 | - name: Run Snyk to check for vulnerabilities 15 | uses: snyk/actions/node@master 16 | env: 17 | SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 18 | with: 19 | command: monitor 20 | -------------------------------------------------------------------------------- /.github/workflows/sonar.yml: -------------------------------------------------------------------------------- 1 | name: Sonar 2 | on: 3 | workflow_run: 4 | workflows: ["Continuous Integration"] 5 | types: 6 | - completed 7 | jobs: 8 | sonar: 9 | name: Sonar 10 | runs-on: ubuntu-latest 11 | if: github.event.workflow_run.conclusion == 'success' 12 | steps: 13 | - name: Checkout source code 14 | uses: actions/checkout@v2 15 | with: 16 | repository: ${{ github.event.workflow_run.head_repository.full_name }} 17 | ref: ${{ github.event.workflow_run.head_branch }} 18 | fetch-depth: 0 19 | - name: "Get PR information" 20 | uses: potiuk/get-workflow-origin@v1 21 | id: source-run-info 22 | with: 23 | token: ${{ secrets.GITHUB_TOKEN }} 24 | sourceRunId: ${{ github.event.workflow_run.id }} 25 | - name: Upload Sonar report to sonarcloud.io and comment in pull request 26 | uses: sonarsource/sonarcloud-github-action@master 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 29 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 30 | with: 31 | args: > 32 | -Dsonar.organization=adobeinc 33 | -Dsonar.projectKey=adobe_aem-angular-editable-components 34 | -Dsonar.sources=src 35 | -Dsonar.javascript.lcov.reportPaths=coverage/lcov.info 36 | -Dsonar.coverage.exclusions=src/public_api.ts,src/test.ts,src/lib/aem-angular-editable-components.module.ts,**/*.spec.ts -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.angular/cache 2 | node_modules/ 3 | dist/ 4 | .scannerwork/ 5 | **/*.log 6 | *.tgz 7 | coverage/* 8 | -------------------------------------------------------------------------------- /.releaserc.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | module.exports = { 14 | plugins: [ 15 | '@semantic-release/commit-analyzer', 16 | '@semantic-release/release-notes-generator', 17 | [ 18 | '@semantic-release/changelog', { 19 | changelogFile: 'CHANGELOG.md' 20 | } 21 | ], 22 | [ 23 | '@semantic-release/npm', { 24 | pkgRoot: 'dist/aem-angular-editable-components/' 25 | } 26 | ], 27 | [ 28 | '@semantic-release/github', { 29 | assets: [ 30 | 'package.json', 31 | 'CHANGELOG.md' 32 | ], 33 | message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}' 34 | } 35 | ], 36 | [ 37 | '@semantic-release/git', { 38 | assets: [ 39 | 'package.json', 40 | 'CHANGELOG.md' 41 | ] 42 | } 43 | ] 44 | ], 45 | branch: 'master', 46 | branches: [ 'master' ] 47 | }; 48 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [2.0.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.4.0...v2.0.0) (2023-09-13) 2 | 3 | 4 | ### Features 5 | 6 | * trigger release ([d909c3f](https://github.com/adobe/aem-angular-editable-components/commit/d909c3f0233d6594b191c72636a89e39553b2031)) 7 | 8 | 9 | * feat!: update angular ([1759bfb](https://github.com/adobe/aem-angular-editable-components/commit/1759bfb73a58b36d1a3c805dd108aeba859b2a4a)) 10 | * feat!: update angular version ([d352425](https://github.com/adobe/aem-angular-editable-components/commit/d3524257772aa743beeb21014cfde648b32ddb54)) 11 | * feat!: updated the commit message for breaking changes ([9903864](https://github.com/adobe/aem-angular-editable-components/commit/9903864aef0d61a2720aae6a12bf5344e214fd22)) 12 | 13 | 14 | ### Reverts 15 | 16 | * Revert "Added ModelManager.initialize" ([018cc79](https://github.com/adobe/aem-angular-editable-components/commit/018cc79958399961ffc375925a2e419f55a1ff16)) 17 | 18 | 19 | ### BREAKING CHANGES 20 | 21 | * angular version updated 22 | * angular update to non bacnward-compatible version 23 | * angular version update to non backward-compatible version 24 | * The project was upgraded to Angular v13 25 | 26 | # [2.0.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.4.0...v2.0.0) (2023-09-13) 27 | 28 | 29 | ### Features 30 | 31 | * trigger release ([d909c3f](https://github.com/adobe/aem-angular-editable-components/commit/d909c3f0233d6594b191c72636a89e39553b2031)) 32 | 33 | 34 | * feat!: update angular ([1759bfb](https://github.com/adobe/aem-angular-editable-components/commit/1759bfb73a58b36d1a3c805dd108aeba859b2a4a)) 35 | * feat!: update angular version ([d352425](https://github.com/adobe/aem-angular-editable-components/commit/d3524257772aa743beeb21014cfde648b32ddb54)) 36 | * feat!: updated the commit message for breaking changes ([9903864](https://github.com/adobe/aem-angular-editable-components/commit/9903864aef0d61a2720aae6a12bf5344e214fd22)) 37 | 38 | 39 | ### Reverts 40 | 41 | * Revert "Added ModelManager.initialize" ([018cc79](https://github.com/adobe/aem-angular-editable-components/commit/018cc79958399961ffc375925a2e419f55a1ff16)) 42 | 43 | 44 | ### BREAKING CHANGES 45 | 46 | * angular version updated 47 | * angular update to non bacnward-compatible version 48 | * angular version update to non backward-compatible version 49 | * The project was upgraded to Angular v13 50 | 51 | # [2.0.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.4.0...v2.0.0) (2023-09-13) 52 | 53 | 54 | ### Features 55 | 56 | * trigger release ([d909c3f](https://github.com/adobe/aem-angular-editable-components/commit/d909c3f0233d6594b191c72636a89e39553b2031)) 57 | 58 | 59 | * feat!: update angular ([1759bfb](https://github.com/adobe/aem-angular-editable-components/commit/1759bfb73a58b36d1a3c805dd108aeba859b2a4a)) 60 | * feat!: update angular version ([d352425](https://github.com/adobe/aem-angular-editable-components/commit/d3524257772aa743beeb21014cfde648b32ddb54)) 61 | * feat!: updated the commit message for breaking changes ([9903864](https://github.com/adobe/aem-angular-editable-components/commit/9903864aef0d61a2720aae6a12bf5344e214fd22)) 62 | 63 | 64 | ### Reverts 65 | 66 | * Revert "Added ModelManager.initialize" ([018cc79](https://github.com/adobe/aem-angular-editable-components/commit/018cc79958399961ffc375925a2e419f55a1ff16)) 67 | 68 | 69 | ### BREAKING CHANGES 70 | 71 | * angular version updated 72 | * angular update to non bacnward-compatible version 73 | * angular version update to non backward-compatible version 74 | * The project was upgraded to Angular v13 75 | 76 | # [1.4.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.3.0...v1.4.0) (2021-09-20) 77 | 78 | 79 | ### Features 80 | 81 | * basic support for remote SPA ([e34c124](https://github.com/adobe/aem-angular-editable-components/commit/e34c124bb472c5ed24b320da8b41fcb0cf4f301d)) 82 | * support for remote angular app editing in AEM ([1671ec2](https://github.com/adobe/aem-angular-editable-components/commit/1671ec2989ae0ae340c84a1c3767b45b829ddd62)) 83 | 84 | # [1.3.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.2.1...v1.3.0) (2021-09-02) 85 | 86 | 87 | ### Features 88 | 89 | * **stylesytem:** StyleSystem Support ([3816ec1](https://github.com/adobe/aem-angular-editable-components/commit/3816ec12f578dce4e7f199a2cebc66033cd74c18)) 90 | 91 | ## [1.2.1](https://github.com/adobe/aem-angular-editable-components/compare/v1.2.0...v1.2.1) (2021-07-14) 92 | 93 | 94 | ### Bug Fixes 95 | 96 | * **types:** fixing some wrong return types on aemallowedcontainercomponent and aemcontainer component ([47a3505](https://github.com/adobe/aem-angular-editable-components/commit/47a35055df74cd521138fd0f562427da5cbeaac7)) 97 | 98 | # [1.2.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.1.0...v1.2.0) (2021-01-12) 99 | 100 | 101 | ### Features 102 | 103 | * Typed MapTo / LazyMapTo ([a2f2e6b](https://github.com/adobe/aem-angular-editable-components/commit/a2f2e6b4e5482bee73ab411342080be66beda742)) 104 | 105 | # [1.1.0](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.7...v1.1.0) (2021-01-11) 106 | 107 | 108 | ### Features 109 | 110 | * support lazy loading ([55452ff](https://github.com/adobe/aem-angular-editable-components/commit/55452ff211270be440bd178aab91c08b711bd0c2)) 111 | 112 | ## [1.0.7](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.6...v1.0.7) (2021-01-05) 113 | 114 | 115 | ### Bug Fixes 116 | 117 | * **docs:** remove --mode from docs build after a typedoc breaking change ([3baa981](https://github.com/adobe/aem-angular-editable-components/commit/3baa98167627213a193fce4794dc0ef4a17a699b)) 118 | 119 | ## [1.0.6](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.5...v1.0.6) (2020-11-05) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * docs generation command typo, releasing from dist directory ([78bde15](https://github.com/adobe/aem-angular-editable-components/commit/78bde15d29405ea9c82c0cdfa99ab7f3394662fb)) 125 | 126 | ## [1.0.5](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.4...v1.0.5) (2020-11-05) 127 | 128 | 129 | ### Bug Fixes 130 | 131 | * proper pkgRoot for semantic-release ([9051a12](https://github.com/adobe/aem-angular-editable-components/commit/9051a128453218fa403d1e93f9300fe48fe92137)) 132 | 133 | ## [1.0.4](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.3...v1.0.4) (2020-11-04) 134 | 135 | 136 | ### Bug Fixes 137 | 138 | * **eslint:** adapt code according to eslint reports ([6fa889c](https://github.com/adobe/aem-angular-editable-components/commit/6fa889c2d45682d65ef48b00c5773890a6b3df95)) 139 | * **package.json:** cleanup ([e22e411](https://github.com/adobe/aem-angular-editable-components/commit/e22e411087e2aeef90a72179a10bd1c2f413a134)) 140 | 141 | ## [1.0.3](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.2...v1.0.3) (2020-09-03) 142 | 143 | 144 | ### Bug Fixes 145 | 146 | * **semantic-release:** fix the build and release process ([4c0e647](https://github.com/adobe/aem-angular-editable-components/commit/4c0e647026ac96a3e8545df869a67b43150ef31a)) 147 | * **semantic-release:** release from dist in a proper way ([133bde0](https://github.com/adobe/aem-angular-editable-components/commit/133bde098e84750c402e17b96b3b7fe0a43157f8)) 148 | 149 | ## [1.0.2](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.1...v1.0.2) (2020-09-03) 150 | 151 | 152 | ### Bug Fixes 153 | 154 | * **semantic-release:** release angular library from compiled files ([66e01e1](https://github.com/adobe/aem-angular-editable-components/commit/66e01e16edcd8bcd6d4f01e4a64bebf820962cc3)) 155 | * **semantic-release:** release proper package ([972e361](https://github.com/adobe/aem-angular-editable-components/commit/972e3618d43009186cdf25450fd6cf1dda8c3523)) 156 | 157 | ## [1.0.1](https://github.com/adobe/aem-angular-editable-components/compare/v1.0.0...v1.0.1) (2020-09-02) 158 | 159 | 160 | ### Bug Fixes 161 | 162 | * **semantic-release:** fix automatic release process ([b8c0d70](https://github.com/adobe/aem-angular-editable-components/commit/b8c0d70b3aab4f0804013c9b010c39c2e51e4244)) 163 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Adobe Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language. 18 | * Being respectful of differing viewpoints and experiences. 19 | * Gracefully accepting constructive criticism. 20 | * Focusing on what is best for the community. 21 | * Showing empathy towards other community members. 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances. 27 | * Trolling, insulting/derogatory comments, and personal or political attacks. 28 | * Public or private harassment. 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission. 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting. 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at Grp-opensourceoffice@adobe.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [https://contributor-covenant.org/version/1/4][version]. 72 | 73 | [homepage]: https://contributor-covenant.org 74 | [version]: https://contributor-covenant.org/version/1/4/ 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for choosing to contribute! 4 | 5 | The following are a set of guidelines to follow when contributing to this project. 6 | 7 | ## Code Of Conduct 8 | 9 | This project adheres to the Adobe [code of conduct](../CODE_OF_CONDUCT.md). By participating, 10 | you are expected to uphold this code. Please report unacceptable behavior to 11 | [Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com). 12 | 13 | ## Have A Question? 14 | 15 | Start by filing an issue. The existing committers on this project work to reach 16 | consensus around project direction and issue solutions within issue threads 17 | (when appropriate). 18 | 19 | ## Contributor License Agreement 20 | 21 | All third-party contributions to this project must be accompanied by a signed contributor 22 | license agreement. This gives Adobe permission to redistribute your contributions 23 | as part of the project. [Sign our CLA](https://opensource.adobe.com/cla.html). You 24 | only need to submit an Adobe CLA one time, so if you have submitted one previously, 25 | you are good to go! 26 | 27 | ## Code Reviews 28 | 29 | All submissions should come in the form of pull requests and need to be reviewed 30 | by project committers. Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/) 31 | for more information on sending pull requests. 32 | 33 | Lastly, please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when 34 | submitting a pull request! 35 | 36 | ## From Contributor To Committer 37 | 38 | We love contributions from our community! If you'd like to go a step beyond contributor 39 | and become a committer with full write access and a say in the project, you must 40 | be invited to the project. The existing committers employ an internal nomination 41 | process that must reach lazy consensus (silence is approval) before invitations 42 | are issued. If you feel you are qualified and want to get more deeply involved, 43 | feel free to reach out to existing committers to have a conversation about that. 44 | 45 | ## Security Issues 46 | 47 | Security issues shouldn't be reported on this issue tracker. Instead, [file an issue to our security experts](https://helpx.adobe.com/security/alertus.html). 48 | 49 | ## Developer Guidelines 50 | 51 | * [Developer Guidelines](DEV_GUIDELINES.md) 52 | -------------------------------------------------------------------------------- /DEV_GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | Run `npm install` to get all node_modules that are necessary for development. Refer to scripts under `package.json` for more useful commands. 4 | 5 | 6 | ## Build 7 | 8 | ```sh 9 | $ npm run build:dev 10 | ``` 11 | 12 | ### Test 13 | 14 | ```sh 15 | $ npm run test 16 | ``` 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 Adobe 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spa Angular Editable Components 2 | 3 | [![License](https://img.shields.io/badge/license-Apache%202-blue)](https://github.com/adobe/aem-angular-editable-components/blob/master/LICENSE) 4 | [![NPM Version](https://img.shields.io/npm/v/@adobe/aem-angular-editable-components.svg)](https://www.npmjs.com/package/@adobe/aem-angular-editable-components) 5 | [![Documentation](https://img.shields.io/badge/docs-api-blue)](https://opensource.adobe.com/aem-angular-editable-components/) 6 | 7 | [![codecov](https://codecov.io/gh/adobe/aem-angular-editable-components/branch/master/graph/badge.svg)](https://codecov.io/gh/adobe/aem-angular-editable-components) 8 | [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=adobe_aem-angular-editable-components&metric=alert_status)](https://sonarcloud.io/dashboard?id=adobe_aem-angular-editable-components) 9 | [![Known Vulnerabilities](https://snyk.io/test/github/adobe/aem-angular-editable-components/badge.svg)](https://snyk.io/test/github/adobe/aem-angular-editable-components) 10 | [![Dependencies](https://badges.renovateapi.com/github/adobe/aem-angular-editable-components)](https://app.renovatebot.com/dashboard#github/adobe/aem-angular-editable-components) 11 | 12 | This project provides the Angular components and integration layer to get you started with the Adobe Experience Manager Site Editor. 13 | 14 | ## Installation 15 | ``` 16 | npm install @adobe/aem-angular-editable-components 17 | ``` 18 | ## Documentation 19 | 20 | * [SPA Editor Overview](https://www.adobe.com/go/aem6_5_docs_spa_en) 21 | * [SPA Architecture](https://docs.adobe.com/content/help/en/experience-manager-65/developing/headless/spas/spa-architecture.html) 22 | * [Getting Started with the AEM SPA Editor and Angular](https://docs.adobe.com/content/help/en/experience-manager-learn/spa-angular-tutorial/overview.html) 23 | * [Getting Started with the AEM SPA Editor and React](https://docs.adobe.com/content/help/en/experience-manager-learn/spa-react-tutorial/overview.html) 24 | 25 | ## Contributing 26 | 27 | Contributions are welcome! Read the [Contributing Guide](CONTRIBUTING.md) for more information. 28 | 29 | ## Licensing 30 | 31 | This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information. 32 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "spa-angular-editable-components-e2e": { 7 | "root": "e2e/", 8 | "projectType": "application", 9 | "architect": { 10 | "e2e": { 11 | "builder": "@angular-devkit/build-angular:protractor", 12 | "options": { 13 | "protractorConfig": "e2e/protractor.conf.js", 14 | "devServerTarget": "spa-angular-editable-components:serve" 15 | } 16 | } 17 | } 18 | }, 19 | "aem-angular-editable-components": { 20 | "root": "", 21 | "sourceRoot": "src", 22 | "projectType": "library", 23 | "prefix": "aem", 24 | "architect": { 25 | "build": { 26 | "builder": "@angular-devkit/build-angular:ng-packagr", 27 | "options": { 28 | "tsConfig": "tsconfig.lib.json", 29 | "project": "ng-package.json" 30 | }, 31 | "configurations": { 32 | "production": { 33 | "project": "ng-package.prod.json" 34 | } 35 | } 36 | }, 37 | "test": { 38 | "builder": "@angular-devkit/build-angular:karma", 39 | "options": { 40 | "main": "src/test.ts", 41 | "tsConfig": "tsconfig.spec.json", 42 | "codeCoverageExclude": [ 43 | "src/test.ts", 44 | "src/lib/test/**/*" 45 | ], 46 | "karmaConfig": "karma.conf.js" 47 | } 48 | } 49 | } 50 | } 51 | }, 52 | "defaultProject": "spa-angular-editable-components", 53 | "cli": { 54 | "analytics": false 55 | } 56 | } -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // Protractor configuration file, see link for more information 14 | // https://github.com/angular/protractor/blob/master/lib/config.ts 15 | 16 | const { SpecReporter } = require('jasmine-spec-reporter'); 17 | 18 | exports.config = { 19 | allScriptsTimeout: 11000, 20 | specs: [ 21 | './src/**/*.e2e-spec.ts' 22 | ], 23 | capabilities: { 24 | browserName: 'chrome' 25 | }, 26 | directConnect: true, 27 | baseUrl: 'http://localhost:4200/', 28 | framework: 'jasmine', 29 | jasmineNodeOpts: { 30 | showColors: true, 31 | defaultTimeoutInterval: 30000 32 | }, 33 | onPrepare() { 34 | require('ts-node').register({ 35 | project: require('path').join(__dirname, './tsconfig.e2e.json') 36 | }); 37 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { AppPage } from './app.po'; 14 | 15 | describe('workspace-project App', () => { 16 | let page: AppPage; 17 | 18 | beforeEach(() => { 19 | page = new AppPage(); 20 | }); 21 | 22 | it('should display welcome message', () => { 23 | page.navigateTo(); 24 | expect(page.getParagraphText()).toEqual('Welcome to app!'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { browser, by, element } from 'protractor'; 14 | 15 | export class AppPage { 16 | navigateTo(): any { 17 | return browser.get('/'); 18 | } 19 | 20 | getParagraphText(): any { 21 | return element(by.css('aem-root h1')).getText(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // Karma configuration file, see link for more information 14 | // https://karma-runner.github.io/1.0/config/configuration-file.html 15 | 16 | const plugins = [ 17 | require('karma-jasmine'), 18 | require('karma-chrome-launcher'), 19 | require('karma-jasmine-html-reporter'), 20 | require('karma-coverage-istanbul-reporter'), 21 | require('@angular-devkit/build-angular/plugins/karma') 22 | ]; 23 | 24 | 25 | const customLaunchers = { 26 | ChromeCI: { 27 | base: 'ChromeHeadless', 28 | flags: [ '--no-sandbox', '--headless' ] 29 | } 30 | }; 31 | 32 | module.exports = function(config) { 33 | config.set({ 34 | basePath: '', 35 | frameworks: [ 'jasmine', '@angular-devkit/build-angular' ], 36 | plugins, 37 | client: { 38 | clearContext: false // leave Jasmine Spec Runner output visible in browser 39 | }, 40 | coverageIstanbulReporter: { 41 | dir: require('path').join(__dirname, 'coverage'), 42 | reports: [ 'text', 'html', 'lcovonly' ], 43 | fixWebpackSourcePaths: true 44 | }, 45 | reporters: [ 'progress', 'kjhtml', 'coverage-istanbul' ], 46 | port: 9876, 47 | colors: true, 48 | logLevel: config.LOG_INFO, 49 | autoWatch: true, 50 | browsers: [ 'ChromeCI' ], 51 | singleRun: true, 52 | customLaunchers 53 | }); 54 | }; 55 | -------------------------------------------------------------------------------- /ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "dist/aem-angular-editable-components", 4 | "deleteDestPath": false, 5 | "lib": { 6 | "entryFile": "src/public_api.ts" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /ng-package.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "dist/aem-angular-editable-components", 4 | "lib": { 5 | "entryFile": "src/public_api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@adobe/aem-angular-editable-components", 3 | "version": "1.0.5", 4 | "description": "Provides Angular components and integration layer with Adobe Experience Manager Page Editor.", 5 | "keywords": [ 6 | "spa", 7 | "aem", 8 | "angular", 9 | "adobe" 10 | ], 11 | "author": "Adobe Systems Inc. ", 12 | "license": "Apache-2.0", 13 | "repository": "github:adobe/aem-angular-editable-components", 14 | "homepage": "https://docs.adobe.com/content/help/en/experience-manager-65/developing/headless/spas/spa-reference-materials.html", 15 | "bugs": { 16 | "url": "https://github.com/adobe/aem-angular-editable-components/issues" 17 | }, 18 | "engines": { 19 | "npm": ">=8.14.0", 20 | "node": ">=16.14.2" 21 | }, 22 | "scripts": { 23 | "build": "ng build aem-angular-editable-components --configuration production", 24 | "build:dev": "npm run test && ng build aem-angular-editable-components", 25 | "clean": "rm -rf dist/", 26 | "docs": "npm i && npx typedoc --excludePrivate ./src --out ./dist/docs --exclude '**/test/**/*' --exclude '**/*.spec.ts' --exclude src/test.ts", 27 | "e2e": "ng e2e", 28 | "ng": "ng", 29 | "semantic-release": "semantic-release", 30 | "start": "ng serve", 31 | "test:coverage": "ng test aem-angular-editable-components --code-coverage", 32 | "test:debug": "ng test aem-angular-editable-components --watch=true --code-coverage", 33 | "test": "ng test aem-angular-editable-components" 34 | }, 35 | "peerDependencies": { 36 | "@adobe/aem-spa-component-mapping": "~1.1.0", 37 | "@adobe/aem-spa-page-model-manager": "~1.5.0" 38 | }, 39 | "devDependencies": { 40 | "@adobe/aem-spa-component-mapping": "~1.1.0", 41 | "@adobe/aem-spa-page-model-manager": "~1.5.0", 42 | "@adobe/eslint-config-editorxp": "^1.0.7", 43 | "@angular-devkit/build-angular": "^13.3.11", 44 | "@angular-eslint/eslint-plugin": "^0.5.0-beta.3", 45 | "@angular/cli": "^13.3.11", 46 | "@angular/common": "^13.4.0", 47 | "@angular/compiler": "^13.4.0", 48 | "@angular/compiler-cli": "^13.4.0", 49 | "@angular/core": "^13.4.0", 50 | "@angular/forms": "^13.4.0", 51 | "@angular/language-service": "^13.4.0", 52 | "@angular/localize": "^13.4.0", 53 | "@angular/platform-browser": "^13.4.0", 54 | "@angular/platform-browser-dynamic": "^13.4.0", 55 | "@angular/router": "^13.4.0", 56 | "@ngtools/webpack": "^13.3.11", 57 | "@octokit/core": "^3.1.2", 58 | "@semantic-release/changelog": "^5.0.1", 59 | "@semantic-release/git": "^9.0.0", 60 | "@semantic-release/github": "^7.0.7", 61 | "@types/jasmine": "^3.5.10", 62 | "@types/jasminewd2": "~2.0.8", 63 | "@types/node": "^14.0.11", 64 | "@typescript-eslint/eslint-plugin": "^4.4.1", 65 | "@typescript-eslint/parser": "^4.4.1", 66 | "codelyzer": "^6.0.2", 67 | "commitizen": "^4.2.1", 68 | "core-js": "^3.23.3", 69 | "cosmiconfig": "^7.1.0", 70 | "cz-conventional-changelog": "^3.3.0", 71 | "eslint": "^7.11.0", 72 | "eslint-plugin-angular": "^4.0.1", 73 | "eslint-plugin-header": "^3.1.0", 74 | "eslint-plugin-json": "^2.1.2", 75 | "jasmine": "^5.0.0", 76 | "jasmine-core": "^5.0.0", 77 | "jasmine-spec-reporter": "^7.0.0", 78 | "karma": "^6.4.2", 79 | "karma-chrome-launcher": "^3.2.0", 80 | "karma-coverage-istanbul-reporter": "^3.0.3", 81 | "karma-jasmine": "^5.1.0", 82 | "karma-jasmine-html-reporter": "^2.1.0", 83 | "ng-packagr": "^13.3.1", 84 | "protractor": "^7.0.0", 85 | "rxjs": "^6.5.5", 86 | "sass": "^1.63.3", 87 | "semantic-release": "^17.1.1", 88 | "ts-node": "^10.0.0", 89 | "tslib": "^2.5.3", 90 | "typedoc": "^0.22.15", 91 | "typescript": "^4.6.4", 92 | "zone.js": "~0.11.4" 93 | }, 94 | "config": { 95 | "commitizen": { 96 | "path": "./node_modules/cz-conventional-changelog" 97 | } 98 | }, 99 | "eslintConfig": { 100 | "root": true, 101 | "extends": [ 102 | "@adobe/eslint-config-editorxp/angular" 103 | ] 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/lib/aem-angular-editable-components.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { NgModule } from '@angular/core'; 14 | import { AEMComponentDirective } from './layout/aem-component.directive'; 15 | import { AEMModelProviderComponent } from './layout/aem-model-provider/aem-model-provider.component'; 16 | import { AEMContainerComponent } from './layout/aem-container/aem-container.component'; 17 | import { AEMPageComponent } from './layout/aem-page/aem-page.component'; 18 | import { AEMResponsiveGridComponent } from './layout/aem-responsivegrid/aem-responsivegrid.component'; 19 | import { CommonModule } from '@angular/common'; 20 | import { AEMAllowedComponentsContainerComponent } from './layout/aem-allowed-components-container/aem-allowed-components-container.component'; 21 | import { AEMRemoteComponent } from './layout/aem-remote/aem-remote.component'; 22 | 23 | @NgModule({ 24 | imports: [ 25 | CommonModule 26 | ], 27 | declarations: [ 28 | AEMContainerComponent, 29 | AEMAllowedComponentsContainerComponent, 30 | AEMResponsiveGridComponent, 31 | AEMComponentDirective, 32 | AEMModelProviderComponent, 33 | AEMPageComponent, 34 | AEMRemoteComponent 35 | ], 36 | exports: [ 37 | AEMContainerComponent, 38 | AEMAllowedComponentsContainerComponent, 39 | AEMResponsiveGridComponent, 40 | AEMComponentDirective, 41 | AEMModelProviderComponent, 42 | AEMPageComponent, 43 | AEMRemoteComponent 44 | ] 45 | }) 46 | export class SpaAngularEditableComponentsModule {} 47 | -------------------------------------------------------------------------------- /src/lib/layout/aem-allowed-components-container/aem-allowed-components-container.component.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adobe/aem-angular-editable-components/d797aaded60c2813fec833463d1dcfe7f29507ab/src/lib/layout/aem-allowed-components-container/aem-allowed-components-container.component.css -------------------------------------------------------------------------------- /src/lib/layout/aem-allowed-components-container/aem-allowed-components-container.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
7 |
8 |
9 | -------------------------------------------------------------------------------- /src/lib/layout/aem-allowed-components-container/aem-allowed-components-container.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 14 | 15 | import { 16 | AEMAllowedComponentsContainerComponent, 17 | ALLOWED_COMPONENT_TITLE_CLASS_NAMES, 18 | ALLOWED_PLACEHOLDER_CLASS_NAMES 19 | } from './aem-allowed-components-container.component'; 20 | 21 | import { ModelManager } from '@adobe/aem-spa-page-model-manager'; 22 | import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; 23 | import { AEMContainerComponent } from '../aem-container/aem-container.component'; 24 | import { AEMComponentDirective } from '../aem-component.directive'; 25 | import { AEMModelProviderComponent } from '../aem-model-provider/aem-model-provider.component'; 26 | 27 | describe('AemAllowedComponentsContainerComponent', () => { 28 | const TEST_COMPONENT_TITLE = 'test container title'; 29 | let component: AEMAllowedComponentsContainerComponent; 30 | let fixture: ComponentFixture; 31 | 32 | /** 33 | * Test the inner DOM structure of an applicable allowed component list 34 | * @param componentTitle Title of the component 35 | * @param elementText Exposed text of the allowed component list 36 | * @param allowedComponents Data structure relative to the allowed components 37 | * @param expectedCount Number of expected allowed component placeholders 38 | */ 39 | function testApplicableAllowedComponentList(componentTitle, elementText, allowedComponents, expectedCount) { 40 | component.title = componentTitle; 41 | component.allowedComponents = allowedComponents; 42 | fixture.detectChanges(); 43 | 44 | const element = fixture.nativeElement.firstElementChild; 45 | const titleElement = element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES); 46 | 47 | expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBe(true); 48 | expect(titleElement).not.toBeNull(); 49 | expect(titleElement.dataset.text).toEqual(elementText); 50 | expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(expectedCount); 51 | } 52 | 53 | beforeEach(() => { 54 | spyOn(ModelManager, 'addListener').and.returnValue(undefined); 55 | 56 | TestBed.configureTestingModule({ 57 | declarations: [ 58 | AEMContainerComponent, 59 | AEMModelProviderComponent, 60 | AEMComponentDirective, 61 | AEMAllowedComponentsContainerComponent 62 | ] 63 | }).overrideModule(BrowserDynamicTestingModule, { 64 | set: {} 65 | }).compileComponents(); 66 | 67 | fixture = TestBed.createComponent(AEMAllowedComponentsContainerComponent); 68 | component = fixture.componentInstance; 69 | fixture.detectChanges(); 70 | }); 71 | 72 | it('should create the component with its default content structure and attributes', () => { 73 | const element = fixture.nativeElement.firstElementChild; 74 | const titleElement = element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES); 75 | 76 | expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBe(true); 77 | expect(titleElement).not.toBeNull(); 78 | expect(titleElement.dataset.text).toEqual('No allowed components'); 79 | expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(0); 80 | }); 81 | 82 | it('should create the component with the default title and no allowed component', () => { 83 | const expectedCount = 0; 84 | const allowedComponents = { 85 | applicable: true, 86 | components: [] 87 | }; 88 | 89 | testApplicableAllowedComponentList(TEST_COMPONENT_TITLE, 'No allowed components', allowedComponents, expectedCount); 90 | }); 91 | 92 | it('should create the component with a custom title and allowed components', () => { 93 | const expectedAllowedComponentPlaceholderCount = 2; 94 | const allowedComponents = { 95 | applicable: true, 96 | components: [ 97 | { 98 | path: 'test/components/component1', 99 | title: 'Test component title 1' 100 | }, 101 | { 102 | path: 'test/components/component2', 103 | title: 'Test component title 2' 104 | } 105 | ] 106 | }; 107 | 108 | testApplicableAllowedComponentList(TEST_COMPONENT_TITLE, TEST_COMPONENT_TITLE, allowedComponents, expectedAllowedComponentPlaceholderCount); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /src/lib/layout/aem-allowed-components-container/aem-allowed-components-container.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | import { AEMContainerComponent, AEMContainerComponentProperties } from '../aem-container/aem-container.component'; 15 | 16 | /** 17 | * @private 18 | */ 19 | export const ALLOWED_PLACEHOLDER_CLASS_NAMES = 'aem-AllowedComponent--list'; 20 | 21 | /** 22 | * @private 23 | */ 24 | export const ALLOWED_COMPONENT_TITLE_CLASS_NAMES = 'aem-AllowedComponent--title'; 25 | 26 | /** 27 | * @private 28 | */ 29 | export const ALLOWED_COMPONENT_PLACEHOLDER_CLASS_NAMES = 'aem-AllowedComponent--component cq-placeholder placeholder'; 30 | 31 | /** 32 | * Component that is allowed to be used on the page by the editor 33 | */ 34 | export interface AllowedComponent { 35 | /** 36 | * Path to the component under apps 37 | */ 38 | path: string; 39 | 40 | /** 41 | * Title of the component 42 | */ 43 | title: string; 44 | } 45 | 46 | /** 47 | * AllowedComponents collection 48 | */ 49 | export interface AllowedComponents { 50 | applicable: boolean; 51 | 52 | /** 53 | * List of allowed components 54 | */ 55 | components: AllowedComponent[]; 56 | } 57 | 58 | /** 59 | * Properties for the allowed components container 60 | */ 61 | export interface AEMAllowedComponentsContainerComponentProperties extends AEMContainerComponentProperties { 62 | /** 63 | * List of allowed components for the container 64 | */ 65 | allowedComponents: AllowedComponents; 66 | 67 | /** 68 | * Label to display when there are no allowed components 69 | */ 70 | _allowedComponentPlaceholderListEmptyLabel?: string; 71 | 72 | /** 73 | * Title of the placeholder list 74 | */ 75 | title: string; 76 | } 77 | 78 | @Component({ 79 | selector: 'aem-allowed-components-container', 80 | templateUrl: './aem-allowed-components-container.component.html', 81 | styleUrls: [ './aem-allowed-components-container.component.css' ] 82 | }) 83 | export class AEMAllowedComponentsContainerComponent extends AEMContainerComponent implements AEMAllowedComponentsContainerComponentProperties{ 84 | 85 | @Input() title: string; 86 | 87 | @Input() emptyLabel = 'No allowed components'; 88 | 89 | @Input() allowedComponents: { 90 | applicable: boolean, 91 | components 92 | }; 93 | 94 | isAllowedComponentsApplicable(): boolean { 95 | return this.isInEditMode && this.allowedComponents && this.allowedComponents.applicable; 96 | } 97 | 98 | getAllowedComponentListPlaceholderClassNames(): string { 99 | return super.getPlaceholderClassNames() + ' ' + ALLOWED_PLACEHOLDER_CLASS_NAMES; 100 | } 101 | 102 | getAllowedComponentListLabel(): string { 103 | const hasComponents = this.allowedComponents && this.allowedComponents.components && this.allowedComponents.components.length > 0; 104 | 105 | return hasComponents ? this.title : this.emptyLabel; 106 | } 107 | 108 | getAllowedComponents(): AllowedComponent[] { 109 | return this.allowedComponents && this.allowedComponents.components || []; 110 | } 111 | 112 | get allowedComponentListTitleClassNames(): string { 113 | return ALLOWED_COMPONENT_TITLE_CLASS_NAMES; 114 | } 115 | 116 | get allowedComponentClassNames(): string { 117 | return ALLOWED_COMPONENT_PLACEHOLDER_CLASS_NAMES; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/lib/layout/aem-component.directive.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 14 | import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; 15 | import { AEMComponentDirective } from './aem-component.directive'; 16 | import { Component, Input } from '@angular/core'; 17 | import { ComponentMapping, MapTo, LazyMapTo, AbstractMappedComponentDirective } from './component-mapping'; 18 | import { Utils } from './utils'; 19 | import { LazyComponentType } from "../test/lazy-component-wrapper/lazy.component"; 20 | 21 | @Component({ 22 | selector: 'test-component', 23 | template: `` 24 | }) 25 | class AEMDirectiveTestComponent { 26 | @Input() data; 27 | } 28 | 29 | @Component({ 30 | selector: 'directive-component', 31 | host: { 32 | '[attr.attr1]': 'attr1', 33 | '[attr.attr2]': 'attr2' 34 | }, 35 | template: `
` 36 | }) 37 | class DirectiveComponent extends AbstractMappedComponentDirective { 38 | @Input() attr1; 39 | @Input() attr2; 40 | } 41 | MapTo('directive/comp')(DirectiveComponent); 42 | LazyMapTo('some/lazy/comp')(() => import('../test/lazy-component-wrapper/lazy.component').then((m) => m.LazyComponent)); 43 | 44 | describe('AEMComponentDirective', () => { 45 | 46 | const EDIT_CONFIG_EMPTY_LABEL = 'Edit config empty label'; 47 | 48 | const TEST_EDIT_CONFIG_EMPTY = { 49 | emptyLabel: EDIT_CONFIG_EMPTY_LABEL, 50 | isEmpty: () => { 51 | return true; 52 | } 53 | }; 54 | 55 | const TEST_EDIT_CONFIG_NOT_EMPTY = { 56 | emptyLabel: EDIT_CONFIG_EMPTY_LABEL, 57 | isEmpty: function() { 58 | return false; 59 | } 60 | }; 61 | 62 | let component: AEMDirectiveTestComponent; 63 | let fixture: ComponentFixture; 64 | 65 | let isInEditorSpy; 66 | let getEditConfigSpy; 67 | 68 | beforeEach(async(() => { 69 | isInEditorSpy = spyOn(Utils, 'isInEditor').and.returnValue(false); 70 | getEditConfigSpy = spyOn(ComponentMapping, 'getEditConfig').and.returnValue(undefined); 71 | 72 | TestBed.configureTestingModule({ 73 | declarations: [ AEMDirectiveTestComponent, DirectiveComponent, AEMComponentDirective ] 74 | }).overrideModule(BrowserDynamicTestingModule, { 75 | set: { 76 | entryComponents: [ DirectiveComponent ] 77 | } 78 | }) 79 | .compileComponents(); 80 | })); 81 | 82 | beforeEach(() => { 83 | fixture = TestBed.createComponent(AEMDirectiveTestComponent); 84 | component = fixture.componentInstance; 85 | }); 86 | 87 | it('should correctly pass the inputs', () => { 88 | const componentData = { 89 | attr1: 'Some value', 90 | attr2: 'Another value', 91 | ':type': 'directive/comp', 92 | appliedCssClassNames: 'applied-css-class1' 93 | }; 94 | 95 | component.data = componentData; 96 | fixture.detectChanges(); 97 | 98 | const element = fixture.nativeElement; 99 | const dynamicElement = element.firstElementChild; 100 | 101 | expect(dynamicElement.getAttribute('attr1')).toEqual(componentData['attr1']); 102 | expect(dynamicElement.getAttribute('attr2')).toEqual(componentData['attr2']); 103 | expect(dynamicElement.getAttribute('class')).toEqual(componentData['appliedCssClassNames']); 104 | 105 | }); 106 | 107 | it('should not resolve if incoming type is non existing', () => { 108 | const componentData = { 109 | attr1: 'Some value', 110 | attr2: 'Another value', 111 | ':type': 'directive/unknown-comp', 112 | appliedCssClassNames: 'applied-css-class1' 113 | }; 114 | 115 | component.data = componentData; 116 | fixture.detectChanges(); 117 | 118 | const element = fixture.nativeElement; 119 | const dynamicElement = element.firstElementChild; 120 | 121 | expect(dynamicElement).toBeNull(); 122 | 123 | }); 124 | 125 | it('should correctly pass the inputs for lazy component', async() => { 126 | const componentData = { 127 | some: 'Some value', 128 | ':type': 'some/lazy/comp' 129 | }; 130 | 131 | component.data = componentData; 132 | 133 | await import('../test/lazy-component-wrapper/lazy.component'); 134 | fixture.detectChanges(); 135 | 136 | }); 137 | 138 | it('should setup the placeholder', () => { 139 | isInEditorSpy.and.returnValue(true); 140 | getEditConfigSpy.and.returnValue(TEST_EDIT_CONFIG_EMPTY); 141 | 142 | const componentData = { 143 | attr1: 'Some value', 144 | attr2: 'Another value', 145 | ':type': 'directive/comp' 146 | }; 147 | 148 | component.data = componentData; 149 | fixture.detectChanges(); 150 | 151 | const element = fixture.nativeElement; 152 | const dynamicElement = element.firstElementChild; 153 | 154 | expect(dynamicElement.dataset.emptytext).toEqual(TEST_EDIT_CONFIG_EMPTY.emptyLabel); 155 | }); 156 | 157 | it('should NOT setup the placeholder', () => { 158 | isInEditorSpy.and.returnValue(true); 159 | getEditConfigSpy.and.returnValue(TEST_EDIT_CONFIG_NOT_EMPTY); 160 | 161 | const componentData = { 162 | attr1: 'Some value', 163 | attr2: 'Another value', 164 | ':type': 'directive/comp' 165 | }; 166 | 167 | component.data = componentData; 168 | fixture.detectChanges(); 169 | 170 | const element = fixture.nativeElement; 171 | const dynamicElement = element.firstElementChild; 172 | 173 | expect(dynamicElement.dataset.emptytext).toBeUndefined(); 174 | }); 175 | 176 | it('should correctly update the inputs', () => { 177 | const componentData1 = { 178 | attr2: 'Initial value', 179 | ':type': 'directive/comp' 180 | }; 181 | 182 | const componentData2 = { 183 | attr1: 'New value', 184 | attr2: 'Updated value', 185 | ':type': 'directive/comp' 186 | }; 187 | 188 | component.data = componentData1; 189 | fixture.detectChanges(); 190 | 191 | const element = fixture.nativeElement; 192 | const dynamicElement = element.firstElementChild; 193 | 194 | fixture.detectChanges(); 195 | expect(dynamicElement.getAttribute('attr1')).toEqual(null); 196 | expect(dynamicElement.getAttribute('attr2')).toEqual(componentData1['attr2']); 197 | 198 | component.data = componentData2; 199 | fixture.detectChanges(); 200 | expect(dynamicElement.getAttribute('attr1')).toEqual(componentData2['attr1']); 201 | expect(dynamicElement.getAttribute('attr2')).toEqual(componentData2['attr2']); 202 | }); 203 | }); 204 | -------------------------------------------------------------------------------- /src/lib/layout/aem-component.directive.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { 14 | AfterViewInit, 15 | ChangeDetectorRef, 16 | Compiler, 17 | ComponentFactory, 18 | ComponentFactoryResolver, 19 | ComponentRef, 20 | Directive, 21 | Injector, 22 | Input, 23 | OnChanges, 24 | OnDestroy, 25 | OnInit, 26 | Renderer2, 27 | Type, 28 | ViewContainerRef 29 | } from '@angular/core'; 30 | 31 | import { ComponentMapping, MappedComponentProperties } from './component-mapping'; 32 | import { Constants } from './constants'; 33 | import { Utils } from './utils'; 34 | 35 | /** 36 | * @private 37 | */ 38 | const PLACEHOLDER_CLASS_NAME = 'cq-placeholder'; 39 | 40 | @Directive({ 41 | selector: '[aemComponent]' 42 | }) 43 | /** 44 | * The current directive provides advanced capabilities among which are 45 | * 46 | * - The management of the component placeholder in the Page Editor 47 | * - The dynamic instantiation of components based on a component definition 48 | * - The conversion from model fields to properties and injection in the component instance 49 | * - The management of HTMLElement attributes and class names on the native element 50 | */ 51 | export class AEMComponentDirective implements AfterViewInit, OnInit, OnDestroy, OnChanges { 52 | 53 | /** 54 | * Dynamically created component 55 | */ 56 | private _component: ComponentRef; 57 | /** 58 | * Model item that corresponds to the current component 59 | */ 60 | private _cqItem: any; 61 | 62 | get cqItem(): any { 63 | return this._cqItem; 64 | } 65 | 66 | @Input() 67 | set cqItem(value: any) { 68 | this._cqItem = value; 69 | } 70 | 71 | get changeDetectorRef(): ChangeDetectorRef { 72 | return this._changeDetectorRef; 73 | } 74 | 75 | /** 76 | * Path to the model structure associated with the current component 77 | */ 78 | @Input() cqPath: string; 79 | 80 | /** 81 | * Name of the current instance of the component 82 | */ 83 | @Input() itemName: string; 84 | 85 | /** 86 | * HtmlElement attributes for the current instance of the component 87 | */ 88 | @Input() itemAttrs: any; 89 | 90 | @Input() loaded: boolean; 91 | 92 | @Input() aemComponent; 93 | 94 | constructor( 95 | private renderer: Renderer2, 96 | private viewContainer: ViewContainerRef, 97 | private compiler: Compiler, 98 | private injector: Injector, 99 | private factoryResolver: ComponentFactoryResolver, 100 | private _changeDetectorRef: ChangeDetectorRef) { 101 | } 102 | 103 | async ngOnInit() { 104 | 105 | if (this.type) { 106 | const mappedFn:Type = ComponentMapping.get(this.type); 107 | 108 | if (mappedFn) { 109 | this.renderComponent(mappedFn); 110 | } else { 111 | await this.initializeAsync(); 112 | } 113 | } else { 114 | console.warn('no type on ' + this.cqPath); 115 | } 116 | 117 | } 118 | 119 | async initializeAsync() { 120 | const lazyMappedPromise: Promise> = ComponentMapping.lazyGet(this.type); 121 | 122 | try { 123 | const LazyResolvedComponent = await lazyMappedPromise; 124 | 125 | this.renderComponent(LazyResolvedComponent); 126 | this.loaded = true; 127 | this._changeDetectorRef.detectChanges(); 128 | } catch (err) { 129 | console.warn(err); 130 | } 131 | } 132 | 133 | ngOnChanges(): void { 134 | this.updateComponentData(); 135 | } 136 | 137 | /** 138 | * Returns the type of the cqItem if exists. 139 | */ 140 | get type(): string | undefined { 141 | return this.cqItem && this.cqItem[Constants.TYPE_PROP]; 142 | } 143 | 144 | /** 145 | * Renders a component dynamically based on the component definition 146 | * 147 | * @param componentDefinition The component definition to render 148 | */ 149 | private renderComponent(componentDefinition: Type) { 150 | if (componentDefinition) { 151 | const factory = this.factoryResolver.resolveComponentFactory(componentDefinition); 152 | 153 | this.renderWithFactory(factory); 154 | } else { 155 | throw new Error('No component definition!!'); 156 | } 157 | } 158 | 159 | private renderWithFactory(factory: ComponentFactory) { 160 | this.viewContainer.clear(); 161 | this._component = this.viewContainer.createComponent(factory); 162 | this.updateComponentData(); 163 | } 164 | 165 | /** 166 | * Updates the data of the component based the data of the directive 167 | */ 168 | private updateComponentData() { 169 | if (!this._component || !this._component.instance || !this.cqItem) { 170 | return; 171 | } 172 | 173 | const keys = Object.getOwnPropertyNames(this.cqItem); 174 | 175 | keys.forEach((key) => { 176 | let propKey = key; 177 | 178 | if (propKey.startsWith(':')) { 179 | // Transformation of internal properties namespaced with [:] to [cq] 180 | // :myProperty => cqMyProperty 181 | const tempKey = propKey.substr(1); 182 | 183 | propKey = 'cq' + tempKey.substr(0, 1).toUpperCase() + tempKey.substr(1); 184 | } 185 | 186 | this._component.instance[propKey] = this.cqItem[key]; 187 | }); 188 | 189 | this._component.instance.cqPath = this.cqPath; 190 | this._component.instance.itemName = this.itemName || (this.cqItem && this.cqItem.id); 191 | this.includeAppliedCSSClasses(); 192 | 193 | const editConfig = ComponentMapping.getEditConfig(this.type); 194 | 195 | if (editConfig && Utils.isInEditor) { 196 | this.setupPlaceholder(editConfig); 197 | } 198 | 199 | this._changeDetectorRef.detectChanges(); 200 | } 201 | 202 | /** 203 | * Adds the applied css class names in to the element 204 | */ 205 | private includeAppliedCSSClasses() { 206 | const appliedCssClassNames = this.cqItem[Constants.APPLIED_CLASS_NAMES] || ''; 207 | 208 | if (appliedCssClassNames && this._component) { 209 | this.renderer.setAttribute(this._component.location.nativeElement, 'class', appliedCssClassNames); 210 | } 211 | } 212 | 213 | /** 214 | * Adds the specified item attributes to the element 215 | */ 216 | private setupItemAttrs() { 217 | if (this.itemAttrs) { 218 | const keys = Object.getOwnPropertyNames(this.itemAttrs); 219 | 220 | keys.forEach((key) => { 221 | if (key === 'class') { 222 | const classes = this.itemAttrs[key].split(' '); 223 | 224 | classes.forEach((itemClass) => { 225 | this.renderer.addClass(this._component.location.nativeElement, itemClass); 226 | }); 227 | } else { 228 | this.renderer.setAttribute(this._component.location.nativeElement, key, this.itemAttrs[key]); 229 | } 230 | }); 231 | } 232 | } 233 | 234 | /** 235 | * Determines if the placeholder should e displayed. 236 | * 237 | * @param editConfig - the edit config of the directive 238 | */ 239 | private usePlaceholder(editConfig) { 240 | return editConfig.isEmpty && typeof editConfig.isEmpty === 'function' && editConfig.isEmpty(this.cqItem); 241 | } 242 | 243 | /** 244 | * Setups the placeholder of needed for the AEM editor 245 | * 246 | * @param editConfig - the editConfig, which will dictate the classes to be added on. 247 | */ 248 | private setupPlaceholder(editConfig) { 249 | if (this.usePlaceholder(editConfig)) { 250 | this.renderer.addClass(this._component.location.nativeElement, PLACEHOLDER_CLASS_NAME); 251 | this.renderer.setAttribute(this._component.location.nativeElement, 'data-emptytext', editConfig.emptyLabel); 252 | } else { 253 | this.renderer.removeClass(this._component.location.nativeElement, PLACEHOLDER_CLASS_NAME); 254 | this.renderer.removeAttribute(this._component.location.nativeElement, 'data-emptytext'); 255 | } 256 | } 257 | 258 | ngAfterViewInit(): void { 259 | this.setupItemAttrs(); 260 | } 261 | 262 | ngOnDestroy(): void { 263 | if (this._component) { 264 | this._component.destroy(); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/lib/layout/aem-container/aem-container.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 |
9 | -------------------------------------------------------------------------------- /src/lib/layout/aem-container/aem-container.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 14 | import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; 15 | import { AEMContainerComponent } from './aem-container.component'; 16 | import { AEMComponentDirective } from '../aem-component.directive'; 17 | import { AEMModelProviderComponent } from '../aem-model-provider/aem-model-provider.component'; 18 | import { AEMResponsiveGridComponent } from '../aem-responsivegrid/aem-responsivegrid.component'; 19 | import { ModelManager } from '@adobe/aem-spa-page-model-manager'; 20 | import { Test1Component } from '../../test/test-comp1.component'; 21 | import { Test2Component } from '../../test/test-comp2.component'; 22 | import { Test3Component } from '../../test/test-comp3.component'; 23 | import { Constants } from '../constants'; 24 | import '../../test/mapping'; 25 | 26 | // describe('AEMContainerComponent', () => { 27 | // let component: AEMContainerComponent; 28 | // let fixture: ComponentFixture; 29 | // const layout = require('../../test/data/layout.json'); 30 | 31 | // beforeEach(() => { 32 | // spyOn(ModelManager, 'addListener').and.returnValue(undefined); 33 | 34 | // const config = { 35 | // declarations: [ 36 | // AEMContainerComponent, 37 | // AEMComponentDirective, 38 | // AEMModelProviderComponent, 39 | // Test1Component, 40 | // Test2Component, 41 | // Test3Component, 42 | // AEMResponsiveGridComponent 43 | // ] 44 | // }; 45 | 46 | // const override = { 47 | // set: { 48 | // entryComponents: [ 49 | // Test1Component, 50 | // Test2Component, 51 | // Test3Component, 52 | // AEMResponsiveGridComponent 53 | // ] 54 | // } 55 | // }; 56 | 57 | // TestBed.configureTestingModule(config).overrideModule(BrowserDynamicTestingModule, override).compileComponents(); 58 | 59 | // fixture = TestBed.createComponent(AEMContainerComponent); 60 | // component = fixture.componentInstance; 61 | // fixture.detectChanges(); 62 | // }); 63 | 64 | // const checkComponent = function(element, elementName, dataPath, cssClass, content?) { 65 | // expect(element.matches(`${elementName}${cssClass}[data-cq-data-path="${dataPath}"]`)).toBeTruthy(); 66 | 67 | // if (content) { 68 | // expect(element.getAttribute('data-title')).toEqual(content); 69 | // } 70 | 71 | // return element; 72 | // }; 73 | 74 | // it('should generate the correct layout', () => { 75 | // component.cqItems = layout[Constants.ITEMS_PROP]; 76 | // component.cqItemsOrder = layout[Constants.ITEMS_ORDER_PROP]; 77 | // component.cqPath = layout[Constants.PATH_PROP]; 78 | // fixture.detectChanges(); 79 | 80 | // fixture.whenStable().then(() => { 81 | // let element = fixture.nativeElement; 82 | 83 | // element = checkComponent(element.firstElementChild.firstElementChild, 84 | // 'aem-responsivegrid', 'root', '.aem-container.test-class-names.aem-Grid\\/root'); 85 | 86 | // element = checkComponent(element.firstElementChild.firstElementChild, 'aem-responsivegrid', 87 | // 'root/responsivegrid', '.aem-container.aem-Grid\\/root\\/responsivegrid'); 88 | 89 | // element = checkComponent(element.firstElementChild, 'div', 90 | // 'root/responsivegrid/component1', 91 | // '.aem-GridColumn\\/root\\/responsivegrid\\/component1'); 92 | 93 | // expect(element.firstElementChild.matches(`test-comp1[data-title="Component1"]`)).toBeTruthy(); 94 | 95 | // element = checkComponent(element.nextElementSibling, 'div', 96 | // 'root/responsivegrid/component3', 97 | // '.aem-GridColumn\\/root\\/responsivegrid\\/component3'); 98 | 99 | // expect(element.firstElementChild.matches(`test-comp3[data-title="Component3"]`)).toBeTruthy(); 100 | 101 | // element = checkComponent(element.nextElementSibling, 'div', 102 | // 'root/responsivegrid/component5', 103 | // '.aem-GridColumn\\/root\\/responsivegrid\\/component5'); 104 | 105 | // expect(element.firstElementChild.matches(`test-comp1[data-title="Component5"]`)).toBeTruthy(); 106 | 107 | // element = checkComponent(element.nextElementSibling, 'div', 108 | // 'root/responsivegrid/component2', 109 | // '.aem-GridColumn\\/root\\/responsivegrid\\/component2'); 110 | 111 | // expect(element.firstElementChild.matches(`test-comp2[data-title="Component2"]`)).toBeTruthy(); 112 | 113 | // element = checkComponent(element.nextElementSibling, 'div', 114 | // 'root/responsivegrid/component4', 115 | // '.aem-GridColumn\\/root\\/responsivegrid\\/component4'); 116 | 117 | // expect(element.firstElementChild.matches(`test-comp2[data-title="Component4"]`)).toBeTruthy(); 118 | 119 | // expect(component).toBeTruthy(); 120 | // }); 121 | // }); 122 | 123 | // it('should create placeholder', () => { 124 | // component.cqItems = layout[Constants.ITEMS_PROP]; 125 | // component.cqItemsOrder = layout[Constants.ITEMS_ORDER_PROP]; 126 | // component.classNames = layout.classNames; 127 | // fixture.detectChanges(); 128 | 129 | // fixture.whenStable().then(() => { 130 | // let element = fixture.nativeElement; 131 | 132 | // element = element.firstElementChild.firstElementChild; 133 | // expect(element.querySelector('div[data-cq-data-path="root/*"][class="new section"]')).toBeDefined(); 134 | 135 | // element = element.firstElementChild.firstElementChild; 136 | // expect(element.querySelector('div[data-cq-data-path="root/responsivegrid/*"][class="new section"]')).toBeDefined(); 137 | // }); 138 | // }); 139 | // }); 140 | -------------------------------------------------------------------------------- /src/lib/layout/aem-container/aem-container.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | import { Constants } from '../constants'; 15 | import { Utils } from '../utils'; 16 | import { AbstractMappedComponentDirective, ComponentMapping, MappedComponentProperties } from '../component-mapping'; 17 | import { Model } from "@adobe/aem-spa-page-model-manager"; 18 | 19 | /** 20 | * @private 21 | */ 22 | const PLACEHOLDER_CLASS_NAMES = Constants.NEW_SECTION_CLASS_NAMES; 23 | 24 | /** 25 | * @private 26 | */ 27 | const PLACEHOLDER_ITEM_NAME = '*'; 28 | 29 | /** 30 | * @private 31 | */ 32 | const CONTAINER_CLASS_NAMES = 'aem-container'; 33 | 34 | /** 35 | * Properties corresponding to the AEMContainerComponent 36 | */ 37 | export interface AEMContainerComponentProperties extends MappedComponentProperties { 38 | componentMapping?: typeof ComponentMapping; 39 | /** 40 | * Map of model items included in the current container 41 | */ 42 | cqItems: { [key: string]: Model }; 43 | /** 44 | * Array of model item keys 45 | */ 46 | cqItemsOrder: string[]; 47 | /** 48 | * Class names of the current component 49 | */ 50 | classNames: string; 51 | } 52 | 53 | @Component({ 54 | selector: 'aem-container', 55 | host: { 56 | '[class]': 'hostClasses', 57 | '[attr.data-cq-data-path]': 'cqPath' 58 | }, 59 | templateUrl: './aem-container.component.html' 60 | }) 61 | /** 62 | * The current component provides the base presentational logic common to containers such as a grid or a page. 63 | * Container have in common the notion of item holders. Items are represented in the model by the fields _:items_ and _:itemsOrder_ 64 | */ 65 | export class AEMContainerComponent extends AbstractMappedComponentDirective implements AEMContainerComponentProperties{ 66 | 67 | @Input() cqItems; 68 | 69 | @Input() cqItemsOrder; 70 | 71 | @Input() classNames; 72 | /** 73 | * Key of the model structure 74 | */ 75 | @Input() modelName = ''; 76 | 77 | /** 78 | * Returns weather of not we are in the editor 79 | */ 80 | get isInEditMode(): boolean { 81 | return Utils.isInEditor(); 82 | } 83 | 84 | /** 85 | * Returns the aggregated path of this container path and the provided path 86 | * 87 | * @param path - the provided path to aggregate with the container path 88 | */ 89 | getDataPath(path: string): string { 90 | return this.cqPath ? this.cqPath + '/' + path : path; 91 | } 92 | 93 | /** 94 | * Returns the item data from the cqModel 95 | * 96 | * @param itemKey - the itemKey to look for in the items. 97 | */ 98 | getItem(itemKey: string): Model { 99 | return this.cqItems && this.cqItems[itemKey]; 100 | } 101 | 102 | /** 103 | * Returns the class names of the container based on the data from the cqModel 104 | */ 105 | getHostClassNames(): string { 106 | return CONTAINER_CLASS_NAMES; 107 | } 108 | 109 | get hostClasses(): string { 110 | return this.getHostClassNames(); 111 | } 112 | 113 | /** 114 | * Returns the placeholder classes 115 | */ 116 | getPlaceholderClassNames(): string { 117 | return PLACEHOLDER_CLASS_NAMES; 118 | } 119 | 120 | /** 121 | * Returns the placeholder path 122 | */ 123 | get placeholderPath(): string { 124 | return this.cqPath && this.cqPath + '/' + PLACEHOLDER_ITEM_NAME; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/lib/layout/aem-model-provider/aem-model-provider.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 14 | import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; 15 | import { AEMModelProviderComponent } from '../aem-model-provider/aem-model-provider.component'; 16 | import { Model, ModelManager } from '@adobe/aem-spa-page-model-manager'; 17 | import { AEMComponentDirective } from '../aem-component.directive'; 18 | import '../../test/mapping'; 19 | 20 | // describe('AEMModelProviderComponent', () => { 21 | 22 | // const TEST_MODEL_DATA = { 23 | // text: 'Test model data' 24 | // }; 25 | 26 | // let component: AEMModelProviderComponent; 27 | // let fixture: ComponentFixture; 28 | // let getDataSpy; 29 | 30 | // beforeEach(() => { 31 | // spyOn(ModelManager, 'addListener').and.returnValue(undefined); 32 | // getDataSpy = spyOn(ModelManager, 'getData').and.returnValue(Promise.resolve(TEST_MODEL_DATA as Model)); 33 | 34 | // TestBed.configureTestingModule({ 35 | // declarations: [ AEMComponentDirective, AEMModelProviderComponent ] 36 | // }).overrideModule(BrowserDynamicTestingModule, { 37 | // set: {} 38 | // }).compileComponents(); 39 | 40 | // fixture = TestBed.createComponent(AEMModelProviderComponent); 41 | // component = fixture.componentInstance; 42 | // }); 43 | 44 | // it('should call ModelManager#getData when updateItem is called', () => { 45 | // fixture.detectChanges(); 46 | // expect(getDataSpy.calls.count()).toEqual(0); 47 | // component.updateItem(); 48 | // expect(getDataSpy.calls.count()).toEqual(1); 49 | // }); 50 | 51 | // it('should emit event to update path for remote spa', () => { 52 | // spyOn(component.updateDataPath, 'emit'); 53 | // component.pagePath = '/test'; 54 | // fixture.detectChanges(); 55 | // expect(component.updateDataPath.emit).toHaveBeenCalledWith({ cqPath: '/test' }); 56 | // }); 57 | 58 | // it('should fetch model for remote spa', () => { 59 | // expect(getDataSpy.calls.count()).toEqual(0); 60 | // component.pagePath = '/test'; 61 | // fixture.detectChanges(); 62 | // expect(getDataSpy.calls.count()).toEqual(1); 63 | // }); 64 | // }); 65 | -------------------------------------------------------------------------------- /src/lib/layout/aem-model-provider/aem-model-provider.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input, Output, ViewChild, EventEmitter } from '@angular/core'; 14 | import { ModelManager, PathUtils } from '@adobe/aem-spa-page-model-manager'; 15 | import { AEMComponentDirective } from '../aem-component.directive'; 16 | import { Constants } from '../constants'; 17 | import { Utils } from '../utils'; 18 | 19 | @Component({ 20 | selector: 'aem-model-provider,[aemModelProvider]', 21 | template: `` 22 | }) 23 | /** 24 | * The current component is responsible for providing access to the ModelManager and the model of a component 25 | */ 26 | export class AEMModelProviderComponent { 27 | /** 28 | * Path to the model associated with the current instance of the component 29 | */ 30 | @Input() cqPath; 31 | /** 32 | * Model item associated with the current model provider component 33 | */ 34 | @Input() cqItem; 35 | /** 36 | * Name of the item associated with the current model provider component 37 | */ 38 | @Input() itemName; 39 | 40 | @Input() aemModelProvider; 41 | 42 | @Input() pagePath; 43 | 44 | @Input() itemPath; 45 | 46 | @Output() updateDataPath = new EventEmitter(); 47 | 48 | cqItemLoaded: Promise; 49 | 50 | @ViewChild(AEMComponentDirective) aemComponent: AEMComponentDirective; 51 | 52 | constructor() { /* void */ } 53 | 54 | /** 55 | * Updates the item data 56 | */ 57 | updateItem(): void { 58 | ModelManager.getData({ path: this.cqPath }).then(model => { 59 | this.cqItemLoaded = Promise.resolve(true); 60 | this.cqItem = model; 61 | if (this.pagePath && Utils.isInEditor()) { 62 | PathUtils.dispatchGlobalCustomEvent(Constants.ASYNC_CONTENT_LOADED_EVENT, {}); 63 | } 64 | if (this.aemComponent) { 65 | this.aemComponent.changeDetectorRef.markForCheck(); 66 | } 67 | }); 68 | } 69 | 70 | async ngOnInit(): Promise { 71 | await ModelManager.initialize(); 72 | if (!this.cqItem && this.pagePath) { 73 | this.cqPath = Utils.getCQPath(this.pagePath, this.itemPath); 74 | this.updateDataPath.emit({ cqPath: this.cqPath }); 75 | this.updateItem(); 76 | } else { 77 | this.cqItemLoaded = Promise.resolve(true); 78 | } 79 | ModelManager.addListener(this.cqPath, this.updateItem.bind(this)); 80 | } 81 | 82 | ngDestroy(): void { 83 | ModelManager.removeListener(this.cqPath, this.updateItem.bind(this)); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/lib/layout/aem-page/aem-page.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component } from '@angular/core'; 14 | import { AEMContainerComponent } from '../aem-container/aem-container.component'; 15 | 16 | /** 17 | * @private 18 | */ 19 | const PAGE_MODEL_SEPARATOR = '/jcr:content/'; 20 | 21 | @Component({ 22 | selector: 'aem-page', 23 | host: { 24 | '[class]': 'hostClasses', 25 | '[attr.data-cq-data-path]': 'cqPath' 26 | }, 27 | templateUrl: '../aem-container/aem-container.component.html' 28 | }) 29 | /** 30 | * The current component carries the base presentational logic of page component 31 | */ 32 | export class AEMPageComponent extends AEMContainerComponent { 33 | /** 34 | * Returns the aggregated path of this container path and the provided path 35 | * 36 | * @param path - the provided path to aggregate with the container path 37 | */ 38 | getDataPath(path: string): string { 39 | return this.cqPath ? this.cqPath + PAGE_MODEL_SEPARATOR + path : path; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/layout/aem-remote/README.md: -------------------------------------------------------------------------------- 1 | # AEM Remote Component 2 | 3 | The selector can now be used to fetch existing content from AEM and render it in the remote SPA. For example, to render the text content at `/content/test/home/jcr:content/root/text` on your AEM instance, the following snippet can be inserted at the desired location within the SPA. 4 | 5 | ```html 6 | 7 | ``` 8 | 9 | ## Prerequisites 10 | 11 | - ModelManager is initialized 12 | - AEM instance is up and running 13 | - Data exists at path provided to the component 14 | - Component corresponding to the fetched data is present on the SPA side as well and mapped to the resourceType. 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/lib/layout/aem-remote/aem-remote.component.html: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /src/lib/layout/aem-remote/aem-remote.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 14 | import { AEMRemoteComponent } from './aem-remote.component'; 15 | import { AEMModelProviderComponent } from '../aem-model-provider/aem-model-provider.component'; 16 | import { AEMComponentDirective } from '../aem-component.directive'; 17 | 18 | // describe('AEMRemoteComponent', () => { 19 | // let component: AEMRemoteComponent; 20 | // let fixture: ComponentFixture; 21 | 22 | // beforeEach(() => { 23 | // TestBed.configureTestingModule({ 24 | // declarations: [ AEMRemoteComponent, AEMModelProviderComponent, AEMComponentDirective ] 25 | // }).compileComponents(); 26 | // fixture = TestBed.createComponent(AEMRemoteComponent); 27 | // component = fixture.componentInstance; 28 | // fixture.detectChanges(); 29 | // }); 30 | 31 | // it('should create', () => { 32 | // expect(component).toBeTruthy(); 33 | // }); 34 | // }); 35 | -------------------------------------------------------------------------------- /src/lib/layout/aem-remote/aem-remote.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | 15 | @Component({ 16 | selector: 'aem-remote-component', 17 | templateUrl: './aem-remote.component.html' 18 | }) 19 | 20 | export class AEMRemoteComponent { 21 | @Input() 22 | pagePath; 23 | @Input() 24 | itemPath; 25 | @Input() 26 | cqPath; 27 | 28 | constructor() { /* void */ } 29 | 30 | setDataPath(e): void { 31 | this.cqPath = e.cqPath; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/layout/aem-responsivegrid/aem-responsivegrid.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 |
8 |
9 |
10 |
11 | 12 | 13 |
19 |
20 |
23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /src/lib/layout/aem-responsivegrid/aem-responsivegrid.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 14 | import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing'; 15 | import { AEMContainerComponent } from '../aem-container/aem-container.component'; 16 | import { AEMComponentDirective } from '../aem-component.directive'; 17 | import { AEMModelProviderComponent } from '../aem-model-provider/aem-model-provider.component'; 18 | import { ModelManager } from '@adobe/aem-spa-page-model-manager'; 19 | import { AEMResponsiveGridComponent } from './aem-responsivegrid.component'; 20 | import { Test1Component } from '../../test/test-comp1.component'; 21 | import { Test2Component } from '../../test/test-comp2.component'; 22 | import { Test3Component } from '../../test/test-comp3.component'; 23 | import { Constants } from '../constants'; 24 | import { 25 | AEMAllowedComponentsContainerComponent, 26 | ALLOWED_COMPONENT_TITLE_CLASS_NAMES, 27 | ALLOWED_PLACEHOLDER_CLASS_NAMES 28 | } from '../aem-allowed-components-container/aem-allowed-components-container.component'; 29 | import { Utils } from '../utils'; 30 | 31 | describe('AEMResponsiveGrid', () => { 32 | const TEST_COMPONENT_TITLE = 'test container title'; 33 | const LAYOUT = require('../../test/data/layout.json'); 34 | let component: AEMResponsiveGridComponent; 35 | let fixture: ComponentFixture; 36 | let isInEditorSpy; 37 | 38 | beforeEach(() => { 39 | spyOn(ModelManager, 'addListener').and.returnValue(undefined); 40 | isInEditorSpy = spyOn(Utils, 'isInEditor').and.returnValue(false); 41 | 42 | const config = { 43 | declarations: [ 44 | AEMContainerComponent, 45 | AEMComponentDirective, 46 | AEMModelProviderComponent, 47 | Test1Component, 48 | Test2Component, 49 | Test3Component, 50 | AEMAllowedComponentsContainerComponent, 51 | AEMResponsiveGridComponent 52 | ] 53 | }; 54 | 55 | const override = { 56 | set: { 57 | entryComponents: [ 58 | Test1Component, 59 | Test2Component, 60 | Test3Component, 61 | AEMResponsiveGridComponent 62 | ] 63 | } 64 | }; 65 | 66 | TestBed.configureTestingModule(config).overrideModule(BrowserDynamicTestingModule, override).compileComponents(); 67 | 68 | fixture = TestBed.createComponent(AEMResponsiveGridComponent); 69 | component = fixture.componentInstance; 70 | fixture.detectChanges(); 71 | }); 72 | 73 | it('should create placeholder', () => { 74 | isInEditorSpy.and.returnValue(true); 75 | component.cqItems = LAYOUT[Constants.ITEMS_PROP]; 76 | component.cqItemsOrder = LAYOUT[Constants.ITEMS_ORDER_PROP]; 77 | component.gridClassNames = LAYOUT.gridClassNames; 78 | component.columnClassNames = LAYOUT.columnClassNames; 79 | component.classNames = LAYOUT.classNames; 80 | fixture.detectChanges(); 81 | 82 | let element = fixture.nativeElement; 83 | 84 | element = element.firstElementChild; 85 | expect(element.querySelector('div[data-cq-data-path="root"][class="test-class-names"]')).toBeDefined(); 86 | expect(element.querySelector('div[data-cq-data-path="root/*"][class="new section aem-Grid-newComponent"]')).toBeDefined(); 87 | expect(element.querySelector('div[data-cq-data-path="root/responsivegrid/*"][class="new section aem-Grid-newComponent"]')).toBeDefined(); 88 | }); 89 | 90 | // it('should create the allowed components with the default title and no allowed component', () => { 91 | // isInEditorSpy.and.returnValue(true); 92 | // component.title = TEST_COMPONENT_TITLE; 93 | // component.allowedComponents = { 94 | // applicable: true, 95 | // components: [] 96 | // }; 97 | 98 | // fixture.detectChanges(); 99 | 100 | // const element = fixture.nativeElement.firstElementChild; 101 | // const titleElement = element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES); 102 | 103 | // expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBeTruthy(); 104 | // expect(titleElement).toBeTruthy(); 105 | // expect(titleElement.dataset.text).toEqual('No allowed components'); 106 | // expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(0); 107 | // }); 108 | 109 | it('should create the allowed components with a custom title and allowed components', () => { 110 | isInEditorSpy.and.returnValue(true); 111 | component.title = TEST_COMPONENT_TITLE; 112 | component.allowedComponents = { 113 | applicable: true, 114 | components: [ 115 | { 116 | path: 'test/components/component1', 117 | title: 'Test component title 1' 118 | }, 119 | { 120 | path: 'test/components/component2', 121 | title: 'Test component title 2' 122 | } 123 | ] 124 | }; 125 | 126 | fixture.detectChanges(); 127 | 128 | const element = fixture.nativeElement.firstElementChild; 129 | const titleElement = element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES); 130 | 131 | expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBeTruthy(); 132 | expect(titleElement).toBeTruthy(); 133 | expect(titleElement.dataset.text).toEqual(TEST_COMPONENT_TITLE); 134 | expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(2); 135 | }); 136 | 137 | // it('should NOT create the allowed components if not in the editor', () => { 138 | // component.title = TEST_COMPONENT_TITLE; 139 | // component.allowedComponents = { 140 | // applicable: true, 141 | // components: [ 142 | // { 143 | // path: 'test/components/component1', 144 | // title: 'Test component title 1' 145 | // }, 146 | // { 147 | // path: 'test/components/component2', 148 | // title: 'Test component title 2' 149 | // } 150 | // ] 151 | // }; 152 | 153 | // fixture.detectChanges(); 154 | 155 | // const element = fixture.nativeElement; 156 | 157 | // expect(element.querySelector('.' + ALLOWED_COMPONENT_TITLE_CLASS_NAMES)).toBeNull(); 158 | // expect(element.classList.contains(ALLOWED_PLACEHOLDER_CLASS_NAMES)).toBeFalsy(); 159 | // expect(element.querySelectorAll('.aem-AllowedComponent--component.cq-placeholder.placeholder').length).toEqual(0); 160 | // expect(element.querySelector('div[data-cq-data-path="root/*"][class="new section aem-Grid-newComponent"]')).toBeDefined(); 161 | // expect(element.querySelector('div[data-cq-data-path="root/responsivegrid/*"][class="new section aem-Grid-newComponent"]')).toBeDefined(); 162 | // }); 163 | }); 164 | -------------------------------------------------------------------------------- /src/lib/layout/aem-responsivegrid/aem-responsivegrid.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | import { Constants } from '../constants'; 15 | import { 16 | AEMAllowedComponentsContainerComponent, 17 | AEMAllowedComponentsContainerComponentProperties 18 | } from '../aem-allowed-components-container/aem-allowed-components-container.component'; 19 | 20 | /** 21 | * @private 22 | */ 23 | const PLACEHOLDER_CLASS_NAMES = 'aem-Grid-newComponent'; 24 | 25 | /** 26 | * @private 27 | */ 28 | const RESPONSIVE_GRID_TYPE = 'wcm/foundation/components/responsivegrid'; 29 | 30 | /** 31 | * Properties corresponding to the AEMResponsiveGridComponent. 32 | * The AEMResponsiveGridComponent carries the base presentational logic of the AEM Layout Container. 33 | */ 34 | export interface AEMResponsiveGridComponentProperties extends AEMAllowedComponentsContainerComponentProperties { 35 | /** 36 | * Class names associated with the current responsive grid 37 | */ 38 | gridClassNames: string; 39 | /** 40 | * Map of class names corresponding to each child of the current responsive grid 41 | */ 42 | columnClassNames: { [key: string]: string }; 43 | 44 | /** 45 | * Current number of columns of the grid 46 | */ 47 | columnCount: number; 48 | } 49 | 50 | 51 | @Component({ 52 | selector: 'aem-responsivegrid', 53 | host: { 54 | '[class]': 'hostClasses', 55 | '[attr.data-cq-data-path]': 'cqPath' 56 | }, 57 | templateUrl: './aem-responsivegrid.component.html' 58 | }) 59 | /** 60 | * The current class carries the base presentational logic of the AEM Layout Container (aka. Responsive grid) 61 | */ 62 | export class AEMResponsiveGridComponent extends AEMAllowedComponentsContainerComponent implements AEMResponsiveGridComponentProperties { 63 | 64 | @Input() gridClassNames; 65 | @Input() columnClassNames; 66 | @Input() classNames; 67 | @Input() columnCount; 68 | 69 | /** 70 | * Returns the column class names for a given column 71 | * @param itemKey - The key of the column item 72 | */ 73 | getColumnClassNames(itemKey: string): string { 74 | return this.columnClassNames && this.columnClassNames[itemKey]; 75 | } 76 | 77 | /** 78 | * Returns the placeholder classes 79 | */ 80 | getPlaceholderClassNames(): string { 81 | return super.getPlaceholderClassNames() + ' ' + PLACEHOLDER_CLASS_NAMES; 82 | } 83 | 84 | /** 85 | * Returns the class names of the responsive grid based on the data from the cqModel 86 | */ 87 | getHostClassNames(): string { 88 | let classNames = super.getHostClassNames(); 89 | 90 | if (this.classNames) { 91 | classNames += ' ' + (this.classNames || ''); 92 | } 93 | 94 | return classNames + ' ' + this.gridClassNames; 95 | } 96 | 97 | /** 98 | * Returns the aggregated path of this container path and the provided path 99 | * 100 | * @param path - the provided path to aggregate with the container path 101 | */ 102 | getAttrDataPath(path: string): string | null { 103 | const item = this.getItem(path); 104 | 105 | if (item && item[Constants.TYPE_PROP] === RESPONSIVE_GRID_TYPE) { 106 | // We don't want to add the path for the wrapper for a reponsivegrid 107 | // The reponsivegrid adds the path on it's own 108 | return null; 109 | } 110 | 111 | return this.getDataPath(path); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/lib/layout/component-mapping.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ComponentMapping, MapTo, MappedComponentProperties, EditConfig, AbstractMappedComponentDirective, LazyMapTo } from './component-mapping'; 14 | import { Input, Type } from '@angular/core'; 15 | import LazyComponent, { LazyComponentType } from '../test/lazy-component-wrapper/lazy.component'; 16 | 17 | interface TestProperties extends MappedComponentProperties { 18 | some: string; 19 | } 20 | 21 | interface TestProperties2 { 22 | some: string; 23 | } 24 | 25 | 26 | class ComponentTest1 extends AbstractMappedComponentDirective implements TestProperties { 27 | @Input() some = 'defaultValue'; 28 | } 29 | 30 | class ComponentTest2 extends AbstractMappedComponentDirective implements TestProperties2 { 31 | @Input() some = 'otherDefaultValue'; 32 | } 33 | 34 | describe('Component Mapping', () => { 35 | it('stores configuration - loosely typed for backwards compatibility', async () => { 36 | 37 | const editConfig1:EditConfig = { isEmpty: (props) => !!props.some }; 38 | const editConfig2:EditConfig = { isEmpty: (props) => !!props.some }; 39 | 40 | const editConfig3:EditConfig = { isEmpty: (props) => !!props.otherValue }; 41 | MapTo('component1')(ComponentTest1, editConfig1); 42 | MapTo('component2')(ComponentTest2, editConfig2); 43 | LazyMapTo('lazycomponent3')(() => new Promise>((resolve, reject) => { 44 | import('../test/lazy-component-wrapper/lazy.component') 45 | .then((Module) => { resolve(Module.LazyComponent); }) 46 | .catch(reject); 47 | })); 48 | LazyMapTo('lazycomponent4')(() => import('../test/lazy-component-wrapper/lazy.component').then((m) => m.LazyComponent), editConfig3); 49 | 50 | const LoadedComp = await ComponentMapping.lazyGet('lazycomponent3'); 51 | 52 | expect(LoadedComp).toBe(LazyComponent); 53 | 54 | expect(ComponentMapping.get('component1')).toBe(ComponentTest1); 55 | expect(ComponentMapping.get('component2')).toBe(ComponentTest2); 56 | const DefaultComp = await ComponentMapping.lazyGet('lazycomponent4'); 57 | expect(DefaultComp).toBe(LazyComponent); 58 | 59 | expect(ComponentMapping.getEditConfig('component1')).toBe(editConfig1); 60 | expect(ComponentMapping.getEditConfig('component2')).toBe(editConfig2); 61 | }); 62 | 63 | 64 | // it('stores configuration - strongly typed with a generic for better typechecking', async () => { 65 | 66 | // const editConfig1:EditConfig = { isEmpty: (props) => !!props.some }; 67 | // const editConfig2:EditConfig = { isEmpty: (props) => !!props.some }; 68 | // const editConfig3:EditConfig = { isEmpty: (props) => !!props.otherValue }; 69 | 70 | 71 | // MapTo('component1')(ComponentTest1, editConfig1); 72 | // MapTo('component2')(ComponentTest2, editConfig2); 73 | // LazyMapTo('lazycomponent3')(() => new Promise>((resolve, reject) => { 74 | // import('../test/lazy-component-wrapper/lazy.component') 75 | // .then((Module) => { resolve(Module.LazyComponent); }) 76 | // .catch(reject); 77 | // })); 78 | 79 | // LazyMapTo('lazycomponent4')(() => import('../test/lazy-component-wrapper/lazy.component').then((m) => m.LazyComponent), editConfig3); 80 | 81 | // const LoadedComp:Type = await ComponentMapping.lazyGet('lazycomponent3'); 82 | 83 | // expect(LoadedComp).toBe(LazyComponent); 84 | 85 | // expect(ComponentMapping.get('component1')).toBe(ComponentTest1); 86 | // expect(ComponentMapping.get('component2')).toBe(ComponentTest2); 87 | // const DefaultComp:Type = await ComponentMapping.lazyGet('lazycomponent4'); 88 | // expect(DefaultComp).toBe(LazyComponent); 89 | 90 | // expect(ComponentMapping.getEditConfig('component1')).toBe(editConfig1); 91 | // expect(ComponentMapping.getEditConfig('component2')).toBe(editConfig2); 92 | // }); 93 | }); 94 | -------------------------------------------------------------------------------- /src/lib/layout/component-mapping.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ComponentMapping as SPAComponentMapping } from '@adobe/aem-spa-component-mapping'; 14 | import { Directive, Input, Type } from '@angular/core'; 15 | 16 | /** 17 | * Indicated whether force reload is turned on, forcing the model to be refetched on every MapTo instantiation. 18 | */ 19 | export interface ReloadForceAble { 20 | cqForceReload?: boolean; 21 | } 22 | 23 | /** 24 | * MappedComponentProperties 25 | * Properties given to every component runtime by the SPA editor. 26 | */ 27 | export interface MappedComponentProperties extends ReloadForceAble { 28 | /** 29 | * Path to the model associated with the current instance of the component 30 | */ 31 | cqPath: string; 32 | 33 | /** 34 | * Angular item name 35 | */ 36 | itemName: string; 37 | } 38 | 39 | /** 40 | * EditConfiguration for a MappedComponent 41 | * @type

Type of the MappedComponent, used in isEmpty 42 | */ 43 | export interface EditConfig

{ 44 | /** 45 | * Label to display if the component is considered empty in author mode 46 | */ 47 | emptyLabel?: string; 48 | 49 | /** 50 | * Return whether the component should be considered 'empty'. 51 | * If empty, the component will not be rendered. In author mode, the empty label will be displayed. 52 | * @param props 53 | @type

Type of the MappedComponent 54 | */ 55 | isEmpty(props: P): boolean; 56 | } 57 | 58 | /** 59 | * Provides standard implementation for the MappedComponentProperties using @Input 60 | */ 61 | @Directive() 62 | export abstract class AbstractMappedComponentDirective implements MappedComponentProperties { 63 | @Input() isInEditor = false; 64 | @Input() cqPath = ''; 65 | @Input() itemName = ''; 66 | } 67 | 68 | /** 69 | * The current class extends the @adobe/cq-spa-component-mapping#Mapto library and features with Angular specifics such as 70 | * 71 | * - Storing the editing configurations for each resource type 72 | */ 73 | export class ComponentMappingWithConfig { 74 | /** 75 | * Store of EditConfig structures 76 | */ 77 | private editConfigMap : { [key: string]: EditConfig; } = {}; 78 | 79 | constructor(private spaMapping: SPAComponentMapping) {} 80 | 81 | /** 82 | * Stores a component class for the given resource types and also allows to provide an EditConfig object 83 | * @param resourceTypes - List of resource types 84 | * @param clazz - Component class to be stored 85 | * @param [editConfig] - Edit configuration to be stored for the given resource types 86 | * @type Model - The Model interface / class type bound to the editconfig object. 87 | */ 88 | map(resourceTypes: string | string[], clazz:Type, editConfig:EditConfig = null) : void { 89 | const innerClass = clazz; 90 | 91 | const resourceList = (typeof resourceTypes === 'string') ? [ resourceTypes ] : resourceTypes; 92 | 93 | resourceList.forEach((entry) => { 94 | if (editConfig) { 95 | this.editConfigMap[entry] = editConfig; 96 | } 97 | this.spaMapping.map(entry, innerClass); 98 | }); 99 | 100 | } 101 | 102 | /** 103 | * Stores a component class for the given resource types and also allows to provide an EditConfig object in a Lazy Manner 104 | * @param resourceTypes - List of resource types 105 | * @param lazyClassFunction - A function that returns a promise to give back the designated type / class 106 | * @param [editConfig] - Edit configuration to be stored for the given resource types 107 | * @type Model - The Model interface / class type bound to the editconfig object. 108 | */ 109 | lazyMap(resourceTypes, lazyClassFunction: () => Promise>, editConfig:EditConfig = null) { 110 | const innerFunction = lazyClassFunction; 111 | 112 | if (editConfig) { 113 | this.editConfigMap[resourceTypes] = editConfig; 114 | } 115 | this.spaMapping.lazyMap(resourceTypes, innerFunction); 116 | } 117 | 118 | /** 119 | * Returns the component class for the given resourceType 120 | * @param resourceType - Resource type for which the component class has been stored 121 | * @type Model - The Model interface / class type bound to the editconfig object. 122 | */ 123 | get(resourceType:string):Type { 124 | return this.spaMapping.get(resourceType) as Type; 125 | } 126 | 127 | /** 128 | * Returns the component class Promise for the given resourceType 129 | * @param resourceType - Resource type for which the component class has been stored 130 | * @type Model - The Model interface / class type bound to the editconfig object. 131 | */ 132 | lazyGet(resourceType: string): Promise> { 133 | return this.spaMapping.getLazy(resourceType) as Promise>; 134 | } 135 | 136 | /** 137 | * Returns the EditConfig structure for the given type 138 | * @param resourceType - Resource type for which the configuration has been stored 139 | * @type Model - The Model interface / class type bound to the editconfig object. 140 | */ 141 | getEditConfig(resourceType:string):EditConfig { 142 | return this.editConfigMap[resourceType]; 143 | } 144 | } 145 | 146 | const componentMapping = new ComponentMappingWithConfig(SPAComponentMapping); 147 | 148 | /** 149 | * Stores a component class for the given resource types and also allows to provide an EditConfig object 150 | * @param resourceTypes - List of resource types 151 | * @type Model - The Model interface / class type that will be Mapped. Bound to the EditConfig configuration. 152 | */ 153 | function MapTo(resourceTypes: string | string[]) { 154 | /** 155 | * @param clazz - Component class to be stored 156 | * @param [editConfig] - Edit configuration to be stored for the given resource types 157 | */ 158 | return (clazz:Type, editConfig:EditConfig = null): void => 159 | componentMapping.map(resourceTypes, clazz, editConfig); 160 | } 161 | 162 | /** 163 | * Stores a clazz the lazy way for dynamic imports / code splitting.function that returns a promise 164 | * @param resourceTypes - List of resource types 165 | * @type Model - The Model interface / class type that will be Mapped. Bound to the EditConfig configuration. 166 | */ 167 | function LazyMapTo(resourceTypes: string | string[]) { 168 | /** 169 | * @param lazyClassPromise - Function that returns a promise resolving a class 170 | * @param [editConfig] - Edit configuration to be stored for the given resource types 171 | */ 172 | return (lazyClassFunction: () => Promise>, editConfig: EditConfig = null): void => 173 | componentMapping.lazyMap(resourceTypes, lazyClassFunction, editConfig); 174 | } 175 | 176 | export { componentMapping as ComponentMapping, MapTo, LazyMapTo }; 177 | -------------------------------------------------------------------------------- /src/lib/layout/constants.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Constants as PMConstants } from '@adobe/aem-spa-page-model-manager'; 14 | 15 | export const Constants = { 16 | 17 | /** 18 | * Class names associated with a new section component 19 | * 20 | */ 21 | NEW_SECTION_CLASS_NAMES: 'new section', 22 | 23 | /** 24 | * CSS Class names applied to a component. 25 | */ 26 | APPLIED_CLASS_NAMES: 'appliedCssClassNames', 27 | 28 | TYPE_PROP: PMConstants.TYPE_PROP, 29 | 30 | /** 31 | * List of child items of an item 32 | * 33 | */ 34 | ITEMS_PROP: PMConstants.ITEMS_PROP, 35 | 36 | /** 37 | * Order in which the items should be listed 38 | * 39 | */ 40 | ITEMS_ORDER_PROP: PMConstants.ITEMS_ORDER_PROP, 41 | 42 | /** 43 | * Path of the item 44 | * 45 | */ 46 | PATH_PROP: PMConstants.PATH_PROP, 47 | 48 | /** 49 | * Children of an item 50 | * 51 | */ 52 | CHILDREN_PROP: PMConstants.CHILDREN_PROP, 53 | 54 | /** 55 | * Path of the resource in the model 56 | * 57 | */ 58 | DATA_PATH_PROP: ':dataPath', 59 | 60 | /** 61 | * Hierarchical type of the item 62 | */ 63 | HIERARCHY_TYPE_PROP: PMConstants.HIERARCHY_TYPE_PROP, 64 | 65 | /** 66 | * Event which indicates that content of remote component has been fetched and loaded in the app 67 | */ 68 | ASYNC_CONTENT_LOADED_EVENT: 'cq-async-content-loaded' 69 | }; 70 | -------------------------------------------------------------------------------- /src/lib/layout/utils.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | /** 14 | * Selector that identifies the node that contains the WCM mode state. 15 | * @private 16 | */ 17 | const WCM_MODE_META_SELECTOR = 'meta[property="cq:wcmmode"]'; 18 | 19 | /** 20 | * The editor is in one of the edition modes. 21 | * @private 22 | */ 23 | const EDIT_MODE = 'edit'; 24 | 25 | /** 26 | * The editor is in preview mode. 27 | * @private 28 | */ 29 | const PREVIEW_MODE = 'preview'; 30 | 31 | /** 32 | * Returns if we are in the browser context or not by checking for the 33 | * existence of the window object. 34 | * @private 35 | */ 36 | function isBrowser() { 37 | try { 38 | return typeof window !== 'undefined'; 39 | } catch (e) { 40 | return false; 41 | } 42 | } 43 | 44 | /** 45 | * Returns the current WCM mode 46 | * 47 | *

Note that the value isn't, as of the date of this writing, updated by the editor

48 | * @private 49 | */ 50 | function getWCMMode() { 51 | if (isBrowser()) { 52 | const wcmModeMeta: HTMLMetaElement = document.head.querySelector(WCM_MODE_META_SELECTOR); 53 | 54 | return wcmModeMeta && wcmModeMeta.content; 55 | } 56 | } 57 | 58 | /** 59 | * Helper functions for interacting with the AEM environment. 60 | */ 61 | export const Utils = { 62 | /** 63 | * Is the app used in the context of the AEM Page editor. 64 | */ 65 | isInEditor(): boolean { 66 | const wcmMode = getWCMMode(); 67 | 68 | return wcmMode && (EDIT_MODE === wcmMode || PREVIEW_MODE === wcmMode); 69 | }, 70 | 71 | /** 72 | * Determines the cqPath of a component given its props 73 | * 74 | * @private 75 | * @returns cqPath of the component 76 | */ 77 | getCQPath(pagePath: string, itemPath?: string): string { 78 | let path = (itemPath ? `${pagePath}/jcr:content/${itemPath}` : pagePath); 79 | 80 | path = path.replace(/\/+/g, '/'); 81 | 82 | return path; 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /src/lib/routing/AemPageDataResolver.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ActivatedRouteSnapshot } from '@angular/router'; 14 | import { AemPageDataResolver } from './AemPageDataResolver'; 15 | 16 | describe('AemPageDataResolver', () => { 17 | it('should return absolute resource path without extension (/conteent/abc)', () => { 18 | const route = ({ url: [ 'content', 'abc' ] } as any) as ActivatedRouteSnapshot; 19 | const aemPageDataResolver = new AemPageDataResolver(); 20 | 21 | expect(aemPageDataResolver.resolve(route)).toBe('/content/abc'); 22 | }); 23 | 24 | it('should return absolute resource path without extension (/content/abc/def/ghi)', () => { 25 | const route = ({ url: [ 'content', 'abc', 'def', 'ghi.html' ] } as any) as ActivatedRouteSnapshot; 26 | const aemPageDataResolver = new AemPageDataResolver(); 27 | 28 | expect(aemPageDataResolver.resolve(route)).toBe('/content/abc/def/ghi'); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/lib/routing/AemPageDataResolver.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Injectable } from '@angular/core'; 14 | import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; 15 | 16 | @Injectable() 17 | export class AemPageDataResolver implements Resolve < string > { 18 | constructor() { /* empty */ } 19 | 20 | /** 21 | * Returns the absolute resource path without extension. 22 | * @example 23 | * // returns: '/content/aa/bb' for route.url [ 'content', 'aa', 'bb.html' ] 24 | * resolve(route) 25 | * @param route - route 26 | * @returns absolute resource path without extension 27 | */ 28 | resolve(route: ActivatedRouteSnapshot): string { 29 | return '/' + route.url.join('/').replace(/\.[^/.]+$/, ''); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lib/routing/AemPageRouteReuseStrategy.spec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ActivatedRouteSnapshot, DetachedRouteHandle } from '@angular/router'; 14 | import { AemPageRouteReuseStrategy } from './AemPageRouteReuseStrategy'; 15 | 16 | describe('AemPageRouteReuseStrategy', () => { 17 | let aemPageRouteReuseStrategy: AemPageRouteReuseStrategy; 18 | let route: ActivatedRouteSnapshot; 19 | 20 | beforeEach(() => { 21 | aemPageRouteReuseStrategy = new AemPageRouteReuseStrategy(); 22 | route = ({} as any) as ActivatedRouteSnapshot; 23 | }); 24 | 25 | it('should return false when calling shouldDetach(route)', () => { 26 | expect(aemPageRouteReuseStrategy.shouldDetach(route)).toBe(false); 27 | }); 28 | 29 | it('should return false when calling shouldDetach(route)', () => { 30 | expect(aemPageRouteReuseStrategy.store({} as ActivatedRouteSnapshot, {} as DetachedRouteHandle)).toBeUndefined(); 31 | }); 32 | 33 | it('should return false when calling shouldAttach(route)', () => { 34 | expect(aemPageRouteReuseStrategy.shouldAttach(route)).toBe(false); 35 | }); 36 | 37 | it('should return null when calling retrieve(route)', () => { 38 | expect(aemPageRouteReuseStrategy.retrieve(route)).toBeNull(); 39 | }); 40 | 41 | it('should return false when calling shouldReuseRoute(route)', () => { 42 | expect(aemPageRouteReuseStrategy.shouldReuseRoute(route, route)).toBe(false); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/lib/routing/AemPageRouteReuseStrategy.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle } from '@angular/router'; 14 | 15 | /* eslint-disable @typescript-eslint/no-unused-vars */ 16 | 17 | /** 18 | * Implements RouteReuseStrategy to customize route reuse. 19 | */ 20 | export class AemPageRouteReuseStrategy implements RouteReuseStrategy { 21 | /** Determines if this route (and its subtree) should be detached to be reused later. */ 22 | shouldDetach(route: ActivatedRouteSnapshot): boolean { 23 | return false; 24 | } 25 | 26 | /** Not storing deteached route. */ 27 | store(route: ActivatedRouteSnapshot, detachedTree: DetachedRouteHandle): void { /* void */ } 28 | 29 | /** Determines if this route (and its subtree) should be reattached. */ 30 | shouldAttach(route: ActivatedRouteSnapshot): boolean { 31 | return false; 32 | } 33 | 34 | /** Retrieves the previously stored route. */ 35 | retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle | null { 36 | return null; 37 | } 38 | 39 | /** Determines if a route should be reused */ 40 | shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean { 41 | return false; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/test/data/layout.json: -------------------------------------------------------------------------------- 1 | { 2 | ":items": 3 | { 4 | "root": 5 | { 6 | "gridClassNames": "aem-Grid/root", 7 | "classNames": "test-class-names", 8 | "columnClassNames": { 9 | "responsivegrid": "aem-GridColumn/root/responsivegrid" 10 | }, 11 | "columnCount": 12, 12 | ":items": 13 | { 14 | "responsivegrid": 15 | { 16 | "gridClassNames": "aem-Grid/root/responsivegrid", 17 | "columnClassNames": { 18 | "component1": "aem-GridColumn/root/responsivegrid/component1", 19 | "component2": "aem-GridColumn/root/responsivegrid/component2", 20 | "component3": "aem-GridColumn/root/responsivegrid/component3", 21 | "component4": "aem-GridColumn/root/responsivegrid/component4", 22 | "component5": "aem-GridColumn/root/responsivegrid/component5" 23 | }, 24 | "columnCount": 12, 25 | ":items": 26 | { 27 | "component1": 28 | { 29 | "title": "Component1", 30 | ":type": "app/components/comp1" 31 | }, 32 | "component2": 33 | { 34 | "title": "Component2", 35 | ":type": "app/components/comp2" 36 | }, 37 | "component3": 38 | { 39 | "title": "Component3", 40 | ":type": "app/components/comp3" 41 | }, 42 | "component4": 43 | { 44 | "title": "Component4", 45 | ":type": "app/components/comp2" 46 | }, 47 | "component5": 48 | { 49 | "title": "Component5", 50 | ":type": "app/components/comp1" 51 | } 52 | }, 53 | ":itemsOrder": ["component1", "component3", "component5", "component2", "component4"], 54 | ":type": "wcm/foundation/components/responsivegrid" 55 | } 56 | }, 57 | ":itemsOrder": ["responsivegrid"], 58 | ":type": "wcm/foundation/components/responsivegrid" 59 | } 60 | }, 61 | ":itemsOrder": ["root"] 62 | } 63 | -------------------------------------------------------------------------------- /src/lib/test/lazy-component-wrapper/lazy.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | import { AbstractMappedComponentDirective, MappedComponentProperties } from '../../layout/component-mapping'; 15 | 16 | 17 | export interface LazyComponentType extends MappedComponentProperties { 18 | otherValue: string; 19 | } 20 | 21 | @Component({ 22 | selector: 'lazy-comp', 23 | template: `
{{ otherValue }}
` 24 | }) 25 | /** 26 | * The current class carries the base presentational logic of the AEM Layout Container (aka. Responsive grid) 27 | */ 28 | export class LazyComponent extends AbstractMappedComponentDirective implements LazyComponentType { 29 | @Input() otherValue; 30 | } 31 | 32 | 33 | export default LazyComponent; 34 | -------------------------------------------------------------------------------- /src/lib/test/lazy-component-wrapper/lazy.entry.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | export * from './lazy.module'; 13 | export * from './lazy.component'; 14 | -------------------------------------------------------------------------------- /src/lib/test/lazy-component-wrapper/lazy.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | import { NgModule } from '@angular/core'; 13 | import { CommonModule } from '@angular/common'; 14 | import { BrowserModule } from '@angular/platform-browser'; 15 | import { RouterModule } from '@angular/router'; 16 | import { LazyComponent } from './lazy.component'; 17 | 18 | @NgModule({ 19 | imports: [ CommonModule, BrowserModule, RouterModule ], 20 | declarations: [ 21 | LazyComponent 22 | ] 23 | }) 24 | export class LazyModule { 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/test/mapping.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { MapTo, EditConfig } from '../layout/component-mapping'; 14 | 15 | import { Test1Component } from './test-comp1.component'; 16 | import { Test2Component } from './test-comp2.component'; 17 | import { Test3Component } from './test-comp3.component'; 18 | import { AEMResponsiveGridComponent } from '../layout/aem-responsivegrid/aem-responsivegrid.component'; 19 | import { TestCompProperties } from './test-comp.type'; 20 | 21 | const config:EditConfig = { 22 | isEmpty: (props) => !! props.title 23 | }; 24 | 25 | MapTo('app/components/comp1')(Test1Component, config); 26 | MapTo('app/components/comp2')(Test2Component, config); 27 | MapTo('app/components/comp3')(Test3Component, config); 28 | MapTo('wcm/foundation/components/responsivegrid')(AEMResponsiveGridComponent); 29 | -------------------------------------------------------------------------------- /src/lib/test/test-comp.type.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { MappedComponentProperties } from '../layout/component-mapping'; 14 | 15 | export interface TestCompProperties extends MappedComponentProperties { 16 | title: string; 17 | } -------------------------------------------------------------------------------- /src/lib/test/test-comp1.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | import { AbstractMappedComponentDirective } from '../layout/component-mapping'; 15 | 16 | @Component({ 17 | selector: 'test-comp1', 18 | host: { 19 | '[attr.data-title]': 'title' 20 | }, 21 | template: `
{{ title }}
` 22 | }) 23 | export class Test1Component extends AbstractMappedComponentDirective { 24 | @Input() title: string; 25 | 26 | constructor() { 27 | super(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/test/test-comp2.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | import { AbstractMappedComponentDirective } from '../layout/component-mapping'; 15 | 16 | @Component({ 17 | selector: 'test-comp2', 18 | host: { 19 | '[attr.data-title]': 'title' 20 | }, 21 | template: `
{{ title }}
` 22 | }) 23 | export class Test2Component extends AbstractMappedComponentDirective { 24 | @Input() title: string; 25 | 26 | constructor() { 27 | super(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/lib/test/test-comp3.component.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { Component, Input } from '@angular/core'; 14 | import { AbstractMappedComponentDirective } from '../layout/component-mapping'; 15 | import { TestCompProperties } from './test-comp.type'; 16 | 17 | @Component({ 18 | selector: 'test-comp3', 19 | host: { 20 | '[attr.data-title]': 'title' 21 | }, 22 | template: `
{{ title }}
` 23 | }) 24 | export class Test3Component extends AbstractMappedComponentDirective implements TestCompProperties { 25 | @Input() title: string; 26 | } 27 | -------------------------------------------------------------------------------- /src/public_api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | /* 13 | * Public API Surface of aem-angular-editable-components 14 | */ 15 | export * from './lib/layout/aem-component.directive'; 16 | export * from './lib/layout/aem-container/aem-container.component'; 17 | export * from './lib/layout/aem-responsivegrid/aem-responsivegrid.component'; 18 | export * from './lib/layout/aem-allowed-components-container/aem-allowed-components-container.component'; 19 | export * from './lib/layout/aem-model-provider/aem-model-provider.component'; 20 | export * from './lib/layout/aem-page/aem-page.component'; 21 | export * from './lib/layout/component-mapping'; 22 | export * from './lib/layout/constants'; 23 | export * from './lib/layout/utils'; 24 | export * from './lib/aem-angular-editable-components.module'; 25 | export * from './lib/routing/AemPageDataResolver'; 26 | export * from './lib/routing/AemPageRouteReuseStrategy'; 27 | export * from './lib/layout/aem-remote/aem-remote.component'; 28 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 14 | 15 | import 'core-js/es7/reflect'; 16 | import 'zone.js'; 17 | import 'zone.js/testing'; 18 | import { getTestBed } from '@angular/core/testing'; 19 | import { 20 | BrowserDynamicTestingModule, 21 | platformBrowserDynamicTesting 22 | } from '@angular/platform-browser-dynamic/testing'; 23 | 24 | declare const require: any; 25 | 26 | // First, initialize the Angular testing environment. 27 | getTestBed().initTestEnvironment( 28 | BrowserDynamicTestingModule, 29 | platformBrowserDynamicTesting(), { 30 | teardown: { destroyAfterEach: false } 31 | } 32 | ); 33 | 34 | // Then we find all the tests. 35 | const context = require.context('./', true, /\.spec\.ts$/); 36 | 37 | // And load the modules. 38 | context.keys().map(context); 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "downlevelIteration": true, 6 | "importHelpers": true, 7 | "module": "esnext", 8 | "outDir": "./dist/out-tsc", 9 | "sourceMap": true, 10 | "declaration": false, 11 | "moduleResolution": "node", 12 | "emitDecoratorMetadata": true, 13 | "experimentalDecorators": true, 14 | "target": "es2015", 15 | "typeRoots": [ 16 | "node_modules/@types" 17 | ], 18 | "lib": [ 19 | "es2017", 20 | "dom" 21 | ], 22 | "paths": { 23 | "core-js/es7/reflect": [ 24 | "./node_modules/core-js/proposals/reflect-metadata" 25 | ], 26 | "spa-angular-editable-components": [ 27 | "dist/spa-angular-editable-components" 28 | ], 29 | "spa-angular-editable-components/*": [ 30 | "dist/spa-angular-editable-components/*" 31 | ] 32 | } 33 | }, 34 | "angularCompilerOptions": { 35 | "compilationMode": "partial", 36 | "strictTemplates": true 37 | }, 38 | "include": [ 39 | "src/**/*", 40 | "e2e/**/*", 41 | "*.js", 42 | ".*.js", 43 | ".*/**/*.json", 44 | "**/*.json" 45 | ], 46 | "exclude": [ 47 | "tsconfig.json" 48 | ] 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "out-tsc/lib", 5 | "target": "es2015", 6 | "module": "es2015", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "sourceMap": true, 10 | "inlineSources": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "importHelpers": true, 14 | "types": [], 15 | "lib": [ 16 | "dom", 17 | "es2015" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "skipTemplateCodegen": true, 22 | "strictMetadataEmit": true, 23 | "fullTemplateTypeCheck": true, 24 | "strictInjectionParameters": true 25 | }, 26 | "exclude": [ 27 | "src/test.ts", 28 | "**/*.spec.ts" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node", 8 | "@angular/localize" 9 | ] 10 | }, 11 | "files": [ 12 | "src/test.ts" 13 | ], 14 | "include": [ 15 | "**/*.spec.ts", 16 | "**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "arrow-return-shorthand": true, 7 | "callable-types": true, 8 | "class-name": true, 9 | "comment-format": [ 10 | true, 11 | "check-space" 12 | ], 13 | "curly": true, 14 | "deprecation": { 15 | "severity": "warn" 16 | }, 17 | "eofline": true, 18 | "forin": true, 19 | "import-blacklist": [ 20 | true, 21 | "rxjs/Rx" 22 | ], 23 | "import-spacing": true, 24 | "indent": [ 25 | true, 26 | "spaces" 27 | ], 28 | "interface-over-type-literal": true, 29 | "label-position": true, 30 | "max-line-length": [ 31 | true, 32 | 140 33 | ], 34 | "member-access": false, 35 | "member-ordering": [ 36 | true, 37 | { 38 | "order": [ 39 | "static-field", 40 | "instance-field", 41 | "static-method", 42 | "instance-method" 43 | ] 44 | } 45 | ], 46 | "no-arg": true, 47 | "no-bitwise": true, 48 | "no-console": [ 49 | true, 50 | "debug", 51 | "info", 52 | "time", 53 | "timeEnd", 54 | "trace" 55 | ], 56 | "no-construct": true, 57 | "no-debugger": true, 58 | "no-duplicate-super": true, 59 | "no-empty": false, 60 | "no-empty-interface": true, 61 | "no-eval": true, 62 | "no-inferrable-types": [ 63 | true, 64 | "ignore-params" 65 | ], 66 | "no-misused-new": true, 67 | "no-non-null-assertion": true, 68 | "no-shadowed-variable": true, 69 | "no-string-literal": false, 70 | "no-string-throw": true, 71 | "no-switch-case-fall-through": true, 72 | "no-trailing-whitespace": true, 73 | "no-unnecessary-initializer": true, 74 | "no-unused-expression": true, 75 | "no-use-before-declare": true, 76 | "no-var-keyword": true, 77 | "object-literal-sort-keys": false, 78 | "one-line": [ 79 | true, 80 | "check-open-brace", 81 | "check-catch", 82 | "check-else", 83 | "check-whitespace" 84 | ], 85 | "prefer-const": true, 86 | "quotemark": [ 87 | true, 88 | "single" 89 | ], 90 | "radix": true, 91 | "semicolon": [ 92 | true, 93 | "always" 94 | ], 95 | "triple-equals": [ 96 | true, 97 | "allow-null-check" 98 | ], 99 | "typedef-whitespace": [ 100 | true, 101 | { 102 | "call-signature": "nospace", 103 | "index-signature": "nospace", 104 | "parameter": "nospace", 105 | "property-declaration": "nospace", 106 | "variable-declaration": "nospace" 107 | } 108 | ], 109 | "unified-signatures": true, 110 | "variable-name": false, 111 | "whitespace": [ 112 | true, 113 | "check-branch", 114 | "check-decl", 115 | "check-operator", 116 | "check-separator", 117 | "check-type" 118 | ], 119 | "no-output-on-prefix": true, 120 | "no-inputs-metadata-property": true, 121 | "no-outputs-metadata-property": true, 122 | "no-host-metadata-property": true, 123 | "no-input-rename": true, 124 | "no-output-rename": true, 125 | "use-lifecycle-interface": true, 126 | "use-pipe-transform-interface": true, 127 | "component-class-suffix": true, 128 | "directive-class-suffix": true, 129 | "directive-selector": [ 130 | true, 131 | "attribute", 132 | "aem", 133 | "camelCase" 134 | ], 135 | "component-selector": [ 136 | true, 137 | "element", 138 | "aem", 139 | "kebab-case" 140 | ] 141 | } 142 | } 143 | --------------------------------------------------------------------------------