├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ ├── log-publish.yml
│ ├── test.yml
│ ├── track-publishes.yml
│ └── update-importmaps.yml
├── .gitignore
├── .hlxignore
├── .husky
└── pre-commit
├── .renovaterc.json
├── .stylelintignore
├── .stylelintrc.json
├── 404.html
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── blocks
├── accordion
│ ├── accordion.css
│ └── accordion.js
├── author-box
│ ├── author-box.css
│ └── author-box.js
├── availability
│ ├── availability.css
│ ├── availability.js
│ └── map.svg
├── card-list
│ ├── README.md
│ ├── card-list.css
│ └── card-list.js
├── cards
│ ├── cards.css
│ └── cards.js
├── columns
│ ├── README.md
│ ├── columns.css
│ └── columns.js
├── copy
│ ├── copy.css
│ └── copy.js
├── deprecation
│ ├── deprecation.css
│ └── deprecation.js
├── doc-search
│ ├── doc-search.css
│ └── doc-search.js
├── embed
│ ├── embed.css
│ └── embed.js
├── faq
│ ├── faq.css
│ └── faq.js
├── feature-list
│ ├── feature-list.css
│ └── feature-list.js
├── feed
│ ├── feed.css
│ └── feed.js
├── footer
│ ├── README.md
│ ├── footer.css
│ └── footer.js
├── footnotes
│ ├── footnotes.css
│ └── footnotes.js
├── form
│ ├── checkbox-checked.svg
│ ├── checkbox.svg
│ ├── form.css
│ └── form.js
├── fragment
│ ├── fragment.css
│ └── fragment.js
├── get-started
│ ├── get-started.css
│ └── get-started.js
├── header
│ ├── README.md
│ ├── adobe-franklin-logo.svg
│ ├── adobe-logo.svg
│ ├── cta-placeholder.png
│ ├── gnav-profile.js
│ ├── gnav-search.js
│ ├── header.css
│ └── header.js
├── hero
│ ├── README.md
│ ├── hero.css
│ └── hero.js
├── labs
│ ├── labs.css
│ └── labs.js
├── logo-wall
│ ├── README.md
│ ├── logo-wall.css
│ └── logo-wall.js
├── marquee
│ ├── README.md
│ ├── marquee.css
│ └── marquee.js
├── pagination
│ ├── README.md
│ ├── pagination.css
│ └── pagination.js
├── publication-time
│ ├── publication-time.css
│ └── publication-time.js
├── questionnaire
│ ├── questionnaire.css
│ └── questionnaire.js
├── releases
│ ├── releases.css
│ └── releases.js
├── roi-calculator
│ ├── README.md
│ ├── roi-calculator.css
│ └── roi-calculator.js
├── service-status
│ ├── service-status.css
│ └── service-status.js
├── side-navigation
│ ├── README.md
│ ├── side-navigation.css
│ └── side-navigation.js
├── sidekick-generator
│ ├── sidekick-generator.css
│ └── sidekick-generator.js
├── sidekick-library
│ ├── sidekick-library.css
│ └── sidekick-library.js
├── svg-doctor
│ ├── svg-doctor.css
│ ├── svg-doctor.js
│ └── w3color.js
├── table
│ ├── table.css
│ └── table.js
├── tabs
│ ├── README.md
│ ├── tabs.css
│ └── tabs.js
├── testimonials
│ ├── README.md
│ ├── testimonials.css
│ └── testimonials.js
├── venn
│ ├── venn.css
│ └── venn.js
├── video
│ ├── video.css
│ └── video.js
├── videotext
│ ├── videotext.css
│ └── videotext.js
└── z-pattern
│ ├── README.md
│ ├── z-pattern.css
│ └── z-pattern.js
├── default-meta-image.jpg
├── default-meta-image.png
├── docs
├── admin-preview.html
└── admin.html
├── fonts
├── adobe-clean-300-italic.woff2
├── adobe-clean-300.woff2
├── adobe-clean-400-italic.woff2
├── adobe-clean-400.woff2
├── adobe-clean-700.woff2
├── adobe-clean-900.woff2
├── fonts.css
├── source-code-pro-300-italic.otf.woff2
├── source-code-pro-300.otf.woff2
├── source-code-pro-400-italic.otf.woff2
├── source-code-pro-400.otf.woff2
├── source-code-pro-700.otf.woff2
└── source-code-pro-900.otf.woff2
├── head.html
├── helix-query.yaml
├── icons
├── icon-admin-html.svg
├── icon-aec.svg
├── icon-arrow.svg
├── icon-author.svg
├── icon-back-arrow.svg
├── icon-bin.svg
├── icon-browser.svg
├── icon-build-colored.svg
├── icon-build.svg
├── icon-caret-down.svg
├── icon-caret-right.svg
├── icon-caret-up.svg
├── icon-code.svg
├── icon-composability.svg
├── icon-content.svg
├── icon-developer.svg
├── icon-document.svg
├── icon-faq.svg
├── icon-fast-deliver.svg
├── icon-fast-publish.svg
├── icon-github.svg
├── icon-heart.svg
├── icon-img.svg
├── icon-lamp.svg
├── icon-linkedin.svg
├── icon-magicstick.svg
├── icon-people.svg
├── icon-person.svg
├── icon-scale.svg
├── icon-sidekick.svg
├── icon-speed.svg
├── icon-star.svg
├── icon-status-live.svg
├── icon-webvitals.svg
├── icon-work-colored.svg
├── icon-work.svg
├── icon-world-colored.svg
├── icon-world.svg
└── safari.svg
├── img
├── bg-docs-1080.png
├── bg-docs-assets
│ └── bg-docs.png
├── bg-docs.png
├── chrome-web-store.svg
├── chrome.svg
├── colorful-bg.jpg
├── colorful-bg.png
├── franklin-favicon.png
├── icon-admin-html.png
├── icon-aec.png
├── icon-build-colored.png
├── icon-faq.png
├── icon-sidekick.png
├── icon-status-live.png
├── icon-work-colored.png
└── icon-world-colored.png
├── libs
└── highlight
│ ├── atom-one-dark.min.css
│ ├── highlight.min.js
│ └── rainbow.min.css
├── llms.txt
├── package-lock.json
├── package.json
├── plugins
├── experimentation
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── .stylelintrc.json
│ ├── CODE_OF_CONDUCT.md
│ ├── LICENSE.md
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ └── src
│ │ ├── index.js
│ │ ├── preview.css
│ │ ├── preview.js
│ │ └── ued.js
├── performance.js
└── time-decorator.js
├── scripts
├── delayed.js
├── indexing-test.js
├── lib-franklin.js
├── redirects.js
└── scripts.js
├── styles
├── lazy-styles.css
└── styles.css
├── tests
├── blocks
│ ├── fragment
│ │ ├── fragment.mock.html
│ │ └── fragment.test.js
│ ├── publication-time
│ │ ├── publication-time.mock.html
│ │ └── publication-time.test.js
│ ├── sidekick-generator
│ │ ├── sidekick-generator.mock.html
│ │ ├── sidekick-generator.mock.json
│ │ └── sidekick-generator.test.js
│ └── z-pattern
│ │ ├── z-pattern.mock.html
│ │ └── z-pattern.test.js
└── scripts
│ ├── block.mock.plain.html
│ ├── body.html
│ ├── config.html
│ ├── dummy.html
│ ├── head.html
│ ├── lib-franklin.test.js
│ ├── media_mock.png
│ ├── mock.svg
│ ├── redirects.test.js
│ └── test.css
├── tools
├── imports.sh
├── oversight
│ ├── charts
│ │ ├── barchart.js
│ │ ├── chart.js
│ │ ├── cwvperf.js
│ │ ├── piechart.js
│ │ ├── sankey.js
│ │ └── skyline.js
│ ├── colors.css
│ ├── cwvperf.html
│ ├── elements
│ │ ├── conversion-tracker.js
│ │ ├── daterange-picker.js
│ │ ├── facetsidebar.js
│ │ ├── incognito-checkbox.js
│ │ ├── link-facet.js
│ │ ├── list-facet.js
│ │ ├── literal-facet.js
│ │ ├── number-format.js
│ │ ├── thumbnail-facet.js
│ │ ├── url-selector.js
│ │ └── vitals-facet.js
│ ├── explorer.html
│ ├── flow.html
│ ├── list.html
│ ├── loader.js
│ ├── package-lock.json
│ ├── package.json
│ ├── rum-slicer.css
│ ├── share.html
│ ├── single.html
│ ├── slicer.js
│ ├── test
│ │ └── utils.test.js
│ ├── utils.js
│ └── website.svg
├── patches.sh
├── rum
│ ├── admin
│ │ ├── orgs.css
│ │ ├── orgs.html
│ │ ├── orgs.js
│ │ └── store.js
│ ├── charts
│ │ ├── chart.js
│ │ └── skyline.js
│ ├── colors.css
│ ├── elements
│ │ ├── daterange-picker.js
│ │ ├── facetsidebar.js
│ │ ├── file-facet.js
│ │ ├── incognito-checkbox.js
│ │ ├── link-facet.js
│ │ ├── list-facet.js
│ │ ├── literal-facet.js
│ │ ├── thumbnail-facet.js
│ │ ├── url-selector.js
│ │ └── vitals-facet.js
│ ├── explorer.html
│ ├── loader.js
│ ├── package-lock.json
│ ├── package.json
│ ├── rum-slicer.css
│ ├── slicer.js
│ ├── test
│ │ └── utils.test.js
│ ├── utils.js
│ └── website.svg
└── sidekick
│ ├── _locales
│ ├── de
│ │ └── messages.json
│ ├── en
│ │ └── messages.json
│ ├── es
│ │ └── messages.json
│ ├── fr
│ │ └── messages.json
│ ├── it
│ │ └── messages.json
│ ├── ja
│ │ └── messages.json
│ ├── ko-kr
│ │ └── messages.json
│ ├── ko
│ │ └── messages.json
│ ├── pt-br
│ │ └── messages.json
│ ├── pt_BR
│ │ └── messages.json
│ ├── zh-cn
│ │ └── messages.json
│ ├── zh-tw
│ │ └── messages.json
│ ├── zh_CN
│ │ └── messages.json
│ └── zh_TW
│ │ └── messages.json
│ ├── app.css
│ ├── app.js
│ ├── config.schema.json
│ ├── de.css
│ ├── fr.css
│ ├── lib
│ └── process-queue.js
│ ├── library
│ ├── events
│ │ └── events.js
│ ├── focus-visible.js
│ ├── focus-visible.js.map
│ ├── index.js
│ ├── index.js.map
│ ├── locales
│ │ └── en
│ │ │ └── messages.json
│ ├── overlay.js
│ ├── overlay.js.map
│ ├── plugins
│ │ └── blocks
│ │ │ ├── blocks.css
│ │ │ ├── blocks.js
│ │ │ └── utils.js
│ └── utils
│ │ ├── dom.js
│ │ └── rum.js
│ ├── module.js
│ ├── rum.js
│ └── view
│ ├── json.js
│ └── json
│ ├── json.css
│ ├── json.html
│ └── json.js
├── utils
├── console.js
├── env.js
├── helpers.js
├── property.js
└── tag.js
└── web-components
└── relative-time.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | tools/sidekick
2 | libs
3 | plugins/
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: 'airbnb-base',
4 | env: {
5 | browser: true,
6 | },
7 | parser: '@babel/eslint-parser',
8 | parserOptions: {
9 | allowImportExportEverywhere: true,
10 | sourceType: 'module',
11 | requireConfigFile: false,
12 | },
13 | rules: {
14 | // allow reassigning param
15 | 'no-param-reassign': [2, { props: false }],
16 | 'linebreak-style': ['error', 'unix'],
17 | 'import/extensions': ['error', {
18 | js: 'always',
19 | }],
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/.github/workflows/log-publish.yml:
--------------------------------------------------------------------------------
1 | on:
2 | repository_dispatch:
3 | types:
4 | - resource-published-native
5 | jobs:
6 | print:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - run: |
10 | echo "Status: ${{ github.event.client_payload.status }}"
11 | echo "Path: ${{ github.event.client_payload.path }}"
12 | slack:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Format path
16 | id: format
17 | uses: frabert/replace-string-action@v2
18 | with:
19 | pattern: '\.md'
20 | string: ${{ github.event.client_payload.path }}
21 | replace-with: ''
22 | - name: Notify Slack (JSON)
23 | if: ${{ endsWith(github.event.client_payload.path, '.json') }}
24 | uses: slackapi/slack-github-action@v2.1.0
25 | env:
26 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
27 | with:
28 | # For posting a rich message using Block Kit
29 | payload: |
30 | {
31 | "text": "Just published: `${{ github.event.client_payload.path }}`"
32 | }
33 | webhook-type: webhook-trigger
34 | webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
35 | - name: Notify Slack (Markdown)
36 | if: ${{ endsWith(github.event.client_payload.path, '.md') }}
37 | uses: slackapi/slack-github-action@v2.1.0
38 | env:
39 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
40 | with:
41 | # For posting a rich message using Block Kit
42 | payload: |
43 | {
44 | "text": "Just published: https://www.aem.live${{ steps.format.outputs.replaced }}"
45 | }
46 | webhook-type: webhook-trigger
47 | webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
48 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Run tests and linting
2 | on: [pull_request]
3 |
4 | jobs:
5 | build:
6 | runs-on: ubuntu-latest
7 | steps:
8 | - uses: actions/checkout@v4
9 | - name: Use Node.js 20
10 | uses: actions/setup-node@v4
11 | with:
12 | node-version: 20
13 |
14 | - name: Install XVFB
15 | run: sudo apt-get install xvfb
16 |
17 | - name: Install dependencies
18 | run: npm install
19 |
20 | - name: Run the tests
21 | run: xvfb-run -a npm test
22 |
23 | - name: run the linters
24 | run: npm run lint
25 |
--------------------------------------------------------------------------------
/.github/workflows/update-importmaps.yml:
--------------------------------------------------------------------------------
1 | name: Update import maps
2 | on:
3 | pull_request:
4 | branches:
5 | - 'main'
6 |
7 | jobs:
8 | build:
9 | if: startsWith(github.head_ref, 'r-')
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | pull-requests: write
14 | steps:
15 | - uses: actions/checkout@v4
16 | with:
17 | token: ${{ secrets.GITHUB_TOKEN }}
18 | fetch-depth: 0
19 | ref: ${{ github.head_ref }}
20 | - name: Configure Git
21 | run: |
22 | git config user.name 'github-actions[bot]'
23 | git config user.email 'github-actions[bot]@users.noreply.github.com'
24 | - name: Install dependencies
25 | run: npm install
26 | - name: Update import maps
27 | run: tools/imports.sh
28 | - name: Commit changes
29 | run: |
30 | if git diff --quiet; then
31 | echo "No changes to commit"
32 | else
33 | git add -A
34 | git commit -m "chore(oversight): update import maps"
35 | git push origin HEAD:${{ github.head_ref }}
36 | fi
37 | - name: Update PR comment to include aem.live test URL
38 | env:
39 | GH_TOKEN: ${{ github.token }}
40 | run: |
41 | OLD_BODY=$(gh pr view --json body --jq ".body")
42 | BRANCH_NAME=$(echo ${{ github.head_ref }})
43 | TEST_URL="https://$BRANCH_NAME--helix-website--adobe.aem.live/tools/oversight/explorer.html?domain=emigrationbrewing.com&view=month&domainkey=open"
44 | printf -v NEW_BODY "%s\n\nTest at: %s" "$OLD_BODY" "$TEST_URL"
45 | gh pr edit ${{ github.event.number }} --body "$NEW_BODY"
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # OS Files
19 | .DS_Store
20 |
21 | # Directory for instrumented libs generated by jscoverage/JSCover
22 | lib-cov
23 |
24 | # Coverage directory used by tools like istanbul
25 | coverage
26 | *.lcov
27 | lcov.info
28 |
29 | # nyc test coverage
30 | .nyc_output
31 |
32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
33 | .grunt
34 |
35 | # Bower dependency directory (https://bower.io/)
36 | bower_components
37 |
38 | # node-waf configuration
39 | .lock-wscript
40 |
41 | # Compiled binary addons (https://nodejs.org/api/addons.html)
42 | build/Release
43 |
44 | # Dependency directories
45 | node_modules/
46 | jspm_packages/
47 |
48 | # TypeScript v1 declaration files
49 | typings/
50 |
51 | # TypeScript cache
52 | *.tsbuildinfo
53 |
54 | # Optional npm cache directory
55 | .npm
56 |
57 | # Optional eslint cache
58 | .eslintcache
59 |
60 | # Microbundle cache
61 | .rpt2_cache/
62 | .rts2_cache_cjs/
63 | .rts2_cache_es/
64 | .rts2_cache_umd/
65 |
66 | # Optional REPL history
67 | .node_repl_history
68 |
69 | # Output of 'npm pack'
70 | *.tgz
71 |
72 | # Yarn Integrity file
73 | .yarn-integrity
74 |
75 | # dotenv environment variables file
76 | .env
77 | .env.test
78 |
79 | # parcel-bundler cache (https://parceljs.org/)
80 | .cache
81 |
82 | # Next.js build output
83 | .next
84 |
85 | # Nuxt.js build / generate output
86 | .nuxt
87 | dist
88 |
89 | # Gatsby files
90 | .cache/
91 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
92 | # https://nextjs.org/blog/next-9-1#public-directory-support
93 | # public
94 |
95 | # vuepress build output
96 | .vuepress/dist
97 |
98 | # Serverless directories
99 | .serverless/
100 |
101 | # FuseBox cache
102 | .fusebox/
103 |
104 | # DynamoDB Local files
105 | .dynamodb/
106 |
107 | # TernJS port file
108 | .tern-port
109 | *.crt
110 | *.key
111 | .hlx
112 |
113 | # Test results
114 | junit.xml
115 | # jetbrains files
116 | .idea/
117 |
--------------------------------------------------------------------------------
/.hlxignore:
--------------------------------------------------------------------------------
1 | .*
2 | CODE_OF_CONDUCT.md
3 | CONTRIBUTING.md
4 | LICENSE
5 | package-lock.json
6 | package.json
7 | README.md
8 | test/
9 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npx lint-staged
5 |
--------------------------------------------------------------------------------
/.renovaterc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:recommended"],
3 | "enabled": true,
4 | "baseBranches": ["main"],
5 | "branchNameStrict": true,
6 | "branchName": "r_{{{packageName}}}_{{version}}",
7 | "packageRules": [{
8 | "matchDepTypes": ["devDependencies"],
9 | "labels": ["ignore-psi-check"],
10 | "automerge": true
11 | },{
12 | "matchManagers": ["github-actions"],
13 | "labels": ["ignore-psi-check"],
14 | "automerge": true
15 | },{
16 | "matchDepNames": ["@adobe/rum-distiller"],
17 | "labels": ["rum-distiller"],
18 | "automerge": false,
19 | "automergeType": "pr",
20 | "fileMatch": [
21 | "package.json"
22 | ]
23 | },
24 | {
25 | "matchDepNames": ["chartjs", "chartjs-adapter-luxon"],
26 | "enabled": false
27 | }
28 | ]
29 | }
--------------------------------------------------------------------------------
/.stylelintignore:
--------------------------------------------------------------------------------
1 | /libs/highlight/*.css
2 | /tools/sidekick
3 | plugins/
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["stylelint-config-standard"]
3 | }
--------------------------------------------------------------------------------
/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 | Page not found
11 |
15 |
16 |
17 |
18 |
34 |
40 |
41 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
66 |
Page Not Found
67 |
68 | Go home
69 |
70 |
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Project Helix Website
2 | [www.aem.live](https://www.aem.live)
3 |
4 | [](https://codecov.io/gh/adobe/helix-website)
5 | ## Introduction
6 | This repo is what powers the helix website.
7 |
8 | ## Developing
9 | Install the Helix CLI:
10 |
11 | sudo npm install -g @adobe/aem-cli
12 |
13 | Run it in this repo:
14 |
15 | aem up
16 |
17 | ## Testing
18 | ### Install dependencies:
19 |
20 | npm install
21 | ### Run tests:
22 |
23 | npm test
24 | ### Watch tests:
25 |
26 | npm test:watch
27 |
28 | This will run through all your tests, but then only run against any changed tests.
29 |
30 |
31 |
--------------------------------------------------------------------------------
/blocks/accordion/accordion.css:
--------------------------------------------------------------------------------
1 | .accordion {
2 | background: #efefef;
3 | border: 1px solid #ddd;
4 | border-radius: 6px;
5 | }
6 |
7 | .item-title {
8 | cursor: pointer;
9 | padding: 12px;
10 | border-bottom: 1px solid #ddd;
11 | display: flex;
12 | justify-content: space-between;
13 | align-items: center;
14 | }
15 |
16 | .item-title::after {
17 | width: 10px;
18 | height: 10px;
19 | border-bottom: 2px solid #a7a7a7;
20 | border-right: 2px solid #a7a7a7;
21 | transform-origin: 75% 75%;
22 | transform: rotate(-45deg) translateY(-50%);
23 | transition: transform 0.1s ease;
24 | content: "";
25 | }
26 |
27 | .item-title.open {
28 | background: #ddd;
29 | }
30 |
31 | .item-title.open::after {
32 | transform: rotate(-135deg) translateX(50%);
33 | }
34 |
35 | .item-content {
36 | display: none;
37 | grid-template-columns: 1fr 1fr;
38 | gap: 12px;
39 | padding: 12px;
40 | border-bottom: 1px solid #ddd;
41 | }
42 |
43 | .item-title.open + .item-content {
44 | display: grid;
45 | }
46 |
--------------------------------------------------------------------------------
/blocks/accordion/accordion.js:
--------------------------------------------------------------------------------
1 | export default function init(el) {
2 | const titles = el.querySelectorAll(':scope > div:nth-child(odd)');
3 | titles.forEach((title) => {
4 | // Add a class to the title container
5 | title.classList.add('item-title');
6 | // Remove the empty div
7 | title.querySelector(':scope > div:last-of-type').remove();
8 | // Add a class to the content
9 | title.nextElementSibling.classList.add('item-content');
10 | // Add a click handler to open the content
11 | title.addEventListener('click', () => {
12 | title.classList.toggle('open');
13 | });
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/blocks/card-list/README.md:
--------------------------------------------------------------------------------
1 | ## Card List
2 |
3 | Notes: This block closely resembles the layout of the cards block in the helix boilerplate.
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | input link only in doc & render `cards` block based on content |
9 | | .image-card-listing | decorate the card list using content including image & text |
10 |
11 |
12 | #### Example:
13 |
14 | See Live output (Link TBD)
15 |
16 | #### Content Structure:
17 |
18 | [See Content in Document](https://docs.google.com/document/d/1k2UqwSl9x2QcDWRR2juTLE60EX963N82sWz50ThtFsA/edit)
19 |
20 | #### Code:
21 | Card List is styled in the block CSS code.
22 |
23 | There is Javascript code for decoration purposes and apply any link used on the card to the entire card itself.
24 |
25 | [Decoration Code](card-list.js)
26 |
27 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
28 |
29 | [Styling Code](card-list.css)
--------------------------------------------------------------------------------
/blocks/card-list/card-list.css:
--------------------------------------------------------------------------------
1 | .card-list > ul {
2 | list-style: none;
3 | margin: 0;
4 | padding: 24px 0;
5 | display: grid;
6 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
7 | grid-gap: 16px;
8 | }
9 |
10 | .card-list > ul > li {
11 | border: 1px solid var(--highlight-background-color);
12 | background-color: var(--background-color);
13 | }
14 |
15 | .card-list > ul > li > a {
16 | text-decoration: none;
17 | display: block;
18 | }
19 |
20 | .card-list .cards-card-body {
21 | font-size: var(--body-font-size-s);
22 | color: var(--color-gray-100);
23 | }
24 |
25 | .card-list .cards-card-image {
26 | width: 100%;
27 | aspect-ratio: 4 / 3;
28 | line-height: 0;
29 | border-radius: var(--image-border-radius-l);
30 | overflow: hidden;
31 |
32 | /* fix for safari overflow with border-radius not working */
33 | isolation: isolate;
34 | }
35 |
36 | .card-list.cards-card-body > *:first-child {
37 | margin-top: 0;
38 | }
39 |
40 | .card-list > ul > li img {
41 | width: 100%;
42 | height: 100%;
43 | object-fit: cover;
44 | transition: var(--transition-ease-in-out);
45 | }
46 |
47 | .card-list .card-wrapper:hover img {
48 | transform: scale(1.07);
49 | }
50 |
51 | .card-list .cards-card-body h3 {
52 | font-size: var(--type-heading-m-size);
53 | line-height: var(--type-heading-m-lh);
54 | margin-top: var(--spacing-s);
55 | margin-bottom: var(--spacing-xs);
56 | }
57 |
58 | @media screen and (width >= 768px) {
59 | .card-list .cards-card-body h3 {
60 | font-size: var(--type-heading-l-size);
61 | line-height: var(--type-heading-l-lh);
62 | margin-top: var(--spacing-m);
63 | }
64 | }
65 |
66 | @media screen and (width >= 900px) {
67 | .card-list > ul {
68 | grid-gap: var(--spacing-s);
69 | }
70 | }
71 |
72 | /* block party card list style */
73 | .card-list-wrapper .cards .cards-card-details p.description:not(.noextra) {
74 | cursor: pointer;
75 | }
76 |
77 | .card-list-wrapper .cards .cards-card-details p.description:not([aria-expanded="true"], .noextra)::after {
78 | content: "...";
79 | }
80 |
81 | .card-list-wrapper .cards .cards-card-details span.extra:not([aria-expanded="true"]) {
82 | display: none;
83 | }
84 |
85 | .card-list-wrapper .cards .cards-card-details span.extra[aria-expanded="true"] {
86 | display: contents;
87 | color: inherit;
88 | }
89 |
90 | .card-list-wrapper .cards .cards-card-details {
91 | overflow-x: hidden;
92 | }
93 |
94 | /* card cateogry filtering */
95 | .cards-wrapper .category-filter-wrapper {
96 | display: grid;
97 | grid-template-columns: auto 1fr;
98 | gap: 1rem;
99 | justify-content: start;
100 | align-items: center;
101 | }
102 |
103 | .cards-wrapper .category-filter-wrapper select {
104 | font-size: var(--type-body-l-size);
105 | max-width: 20rem;
106 | }
107 |
--------------------------------------------------------------------------------
/blocks/cards/cards.js:
--------------------------------------------------------------------------------
1 | export default function decorate(block) {
2 | const observer = new IntersectionObserver((entries) => {
3 | entries.forEach((entry) => {
4 | if (entry.isIntersecting) {
5 | entry.target.classList.add('enter');
6 | }
7 | });
8 | });
9 |
10 | const classes = ['one', 'two', 'three', 'four', 'five'];
11 | const row = block.children[0];
12 | if (row) {
13 | if (classes[row.children.length - 1]) block.classList.add(classes[row.children.length - 1]);
14 | }
15 | block.querySelectorAll(':scope > div > div').forEach((cell) => {
16 | if (cell.firstChild) {
17 | const details = document.createElement('div');
18 | details.classList.add('cards-card-details');
19 | cell.classList.add('cards-card');
20 | while (cell.firstChild) details.append(cell.firstChild);
21 | const picture = details.querySelector('picture');
22 | if (picture) {
23 | cell.prepend(picture);
24 | } else if (details.querySelector('h3')) {
25 | cell.classList.add('cards-card-highlight');
26 | }
27 | // highlight styling
28 | const links = details.querySelectorAll('a');
29 | links.forEach((link) => {
30 | link.className = 'link-highlight-colorful-effect-hover-wrapper';
31 | link.innerHTML = `${link.textContent}`;
32 | });
33 | cell.append(details);
34 | observer.observe(cell);
35 | }
36 | });
37 | }
38 |
--------------------------------------------------------------------------------
/blocks/columns/README.md:
--------------------------------------------------------------------------------
1 | ## Columns
2 |
3 | Notes: Builds upon the Columns block in the Helix Boilerplate project. See details [here](https://www.hlx.live/developer/block-collection/columns).
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | two, three, four | The number of columns expected |
9 | | icon-sm | Layout for a small icon (max height 50px) |
10 | | tall | Layout for tall columns with an image in layout |
11 | | center | center the text & .icon in the column cell |
12 | | colored-icon | come from global class, add colored icon to element with `.icon` class (svg)
13 |
14 |
15 |
--------------------------------------------------------------------------------
/blocks/copy/copy.css:
--------------------------------------------------------------------------------
1 | .copy {
2 | background: var(--highlight-gradient);
3 | border-radius: 25px;
4 | padding: 20px 30px;
5 | color: white;
6 | margin: 64px 0;
7 | }
8 |
9 | .copy:empty {
10 | display: none;
11 | }
12 |
13 | .copy p {
14 | margin: 0;
15 | line-height: 1em;
16 | font-size: var(--type-body-xxs-size);
17 | }
18 |
19 | .copy > div {
20 | display: flex;
21 | flex-direction: column;
22 | margin: 30px 0;
23 | }
24 |
25 | .copy > div > span {
26 | font-size: var(--type-body-xs-size);
27 | }
28 |
29 | .copy > div > input {
30 | border-radius: 5px;
31 | padding: 5px 10px;
32 | border: 0;
33 | margin: 0 0 10px;
34 | }
35 |
36 | .copy > div > button {
37 | color: white;
38 | border-radius: 20px;
39 | background-color: unset;
40 | border: 2px solid white;
41 | padding: 5px 20px;
42 | }
43 |
44 | @media (width >= 900px) {
45 | .copy > div {
46 | flex-direction: unset;
47 | justify-content: stretch;
48 | margin: 30px 0;
49 | }
50 |
51 | .copy > div > span {
52 | flex: 1 1 200px;
53 | }
54 |
55 | .copy > div > input {
56 | flex: 1 1 100%;
57 | margin: unset;
58 | }
59 |
60 | .copy > div > button {
61 | margin-left: 40px;
62 | flex: 1 1 60px;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/blocks/copy/copy.js:
--------------------------------------------------------------------------------
1 | import { readBlockConfig } from '../../scripts/lib-franklin.js';
2 |
3 | export default function decorate(block) {
4 | const config = readBlockConfig(block);
5 | const usp = new URLSearchParams(window.location.search);
6 | const keys = Object.keys(config);
7 | block.textContent = '';
8 | if (keys.every((key) => usp.get(key))) {
9 | block.innerHTML = 'For your conveniece find the values for this screen below to copy/paste
';
10 | keys.forEach((key) => {
11 | const div = document.createElement('div');
12 | div.innerHTML = `${config[key]}`;
13 | const input = document.createElement('input');
14 | input.value = usp.get(key);
15 | div.append(input);
16 | const button = document.createElement('button');
17 | button.textContent = 'copy';
18 | button.addEventListener('click', () => {
19 | input.select();
20 | document.execCommand('copy');
21 | });
22 | div.append(button);
23 | block.append(div);
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/blocks/deprecation/deprecation.css:
--------------------------------------------------------------------------------
1 | .deprecation {
2 | padding: 10px 16px;
3 | margin-bottom: 16px;
4 | background-color: #d31510;
5 | color: #fff;
6 | border-radius: var(--image-border-radius-l);
7 | font-size: var(--type-body-s-size);
8 | line-height: var(--type-body-s-lh);
9 | }
10 |
11 | .deprecation .header {
12 | font-weight: 700;
13 | font-size: var(--type-body-m-size);
14 | text-transform: uppercase;
15 | }
16 |
17 | .deprecation > div > div > p,
18 | .deprecation a:any-link,
19 | .deprecation code {
20 | color: #fff;
21 | }
22 |
23 | .deprecation code {
24 | background-color: rgb(255 255 255 / 20%);
25 | border: none;
26 | }
27 |
--------------------------------------------------------------------------------
/blocks/deprecation/deprecation.js:
--------------------------------------------------------------------------------
1 | export default function decorate(block) {
2 | const h3 = block.querySelector('h3');
3 | let title = '';
4 | if (h3) {
5 | title = h3.textContent;
6 | h3.remove();
7 | } else {
8 | title = 'Deprecation notice';
9 | }
10 | const header = document.createElement('div');
11 | header.className = 'header';
12 | header.textContent = title;
13 | block.prepend(header);
14 | }
15 |
--------------------------------------------------------------------------------
/blocks/embed/embed.css:
--------------------------------------------------------------------------------
1 | .gist .gist-file.gist-file {
2 | margin: 12px 0;
3 | }
4 |
5 | .youtube-wrapper {
6 | width: 100%;
7 | height: 0;
8 | position: relative;
9 | padding-bottom: 56.25%;
10 | }
11 |
12 | .gist .gist-meta a {
13 | font-size: 12px;
14 | }
15 |
16 | .embed.block.google-form > div > div {
17 | min-height: 1700px;
18 | }
--------------------------------------------------------------------------------
/blocks/embed/embed.js:
--------------------------------------------------------------------------------
1 | import { loadCSS } from '../../scripts/lib-franklin.js';
2 |
3 | const jsonpGist = (url, callback) => {
4 | // Setup a unique name that cane be called & destroyed
5 | const callbackName = `jsonp_${Math.round(100000 * Math.random())}`;
6 |
7 | // Create the script tag
8 | const script = document.createElement('script');
9 | script.src = `${url}${(url.indexOf('?') >= 0 ? '&' : '?')}callback=${callbackName}`;
10 |
11 | // Define the function that the script will call
12 | window[callbackName] = (data) => {
13 | delete window[callbackName];
14 | document.body.removeChild(script);
15 | callback(data);
16 | };
17 |
18 | // Append to the document
19 | document.body.appendChild(script);
20 | };
21 |
22 | const gist = (element) => {
23 | const { href } = element;
24 | const url = href.slice(-2) === 'js' ? `${href}on` : `${href}.json`;
25 |
26 | jsonpGist(url, (data) => {
27 | loadCSS(data.stylesheet);
28 | element.insertAdjacentHTML('afterend', data.div);
29 | element.remove();
30 | });
31 | };
32 |
33 | const youtube = (element) => {
34 | const url = new URL(element.href);
35 | const vid = url.searchParams.get('v');
36 | const html = `
37 |
38 |
`;
39 | element.parentElement.insertAdjacentHTML('afterend', html);
40 | element.parentElement.remove();
41 | };
42 |
43 | const iframe = (element) => {
44 | const url = new URL(element.href);
45 | const html = `
46 |
55 |
`;
56 | element.parentElement.insertAdjacentHTML('afterend', html);
57 | element.parentElement.remove();
58 | };
59 |
60 | const initObserver = (callback) => new IntersectionObserver((entries) => {
61 | entries.forEach((entry) => {
62 | if (entry.isIntersecting) {
63 | callback(entry.target);
64 | }
65 | });
66 | }, { root: null, rootMargin: '200px', threshold: 0 });
67 |
68 | export default function decorate(block) {
69 | const a = block.querySelector('a');
70 | const { hostname } = new URL(a.href);
71 | if (hostname.includes('youtu')) {
72 | initObserver(() => {
73 | youtube(a);
74 | }).observe(a);
75 | } else if (hostname.includes('gist')) {
76 | initObserver(() => {
77 | gist(a);
78 | }).observe(a);
79 | } else {
80 | initObserver(() => {
81 | iframe(a);
82 | }).observe(a);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/blocks/faq/faq.css:
--------------------------------------------------------------------------------
1 | main .faq dt {
2 | margin-top: var(--spacing-m);
3 | font-weight: 700;
4 | margin-bottom: var(--spacing-xxs);
5 | scroll-margin: calc(var(--nav-height) + var(--breadcrumb-height));
6 | }
7 |
8 | main .faq dt > a {
9 | font-size: var(--type-heading-s-size);
10 | line-height: var(--type-heading-s-lh);
11 | }
12 |
13 | main .faq .category {
14 | font-size: var(--type-body-xs-size);
15 | font-weight: 700;
16 | line-height: var(--type-body-xs-lh);
17 | margin: 0 var(--spacing-xxxs);
18 | padding: var(--spacing-xxxs) var(--spacing-xxs);
19 | border-radius: 12px;
20 | background-color: var(--bg-color-grey-2);
21 | color: currentcolor;
22 | }
23 |
24 | main .faq .category.style-1 {
25 | background-color: var(--brand-color-dark-purple);
26 | color: var(--color-white);
27 | }
28 |
29 | main .faq .category.style-2 {
30 | background-color: var(--color-accent-pink-content);
31 | color: var(--color-white);
32 | }
33 |
34 | main .faq .category.style-3 {
35 | background-color: var(--color-accent-pink-bg);
36 | }
37 |
38 | main .faq .category.style-4 {
39 | background-color: var(--color-accent-lightgreen-bg);
40 | }
41 |
42 | main .faq dd {
43 | font-size: var(--type-body-s-size);
44 | line-height: var(--type-body-s-lh);
45 | }
46 |
47 | body.guides-template main .faq dt a.anchor-link:any-link::before {
48 | top: 0;
49 | }
50 |
51 | main .faq .filter-controls {
52 | display: flex;
53 | font-size: var(--type-body-xs-size);
54 | background-color: var(--bg-color-lightgrey);
55 | padding: var(--spacing-xs) var(--spacing-s);
56 | border: solid 1px var(--bg-color-grey-2);
57 | border-radius: var(--image-border-radius-l);
58 | }
59 |
60 | main .faq .filter-control label {
61 | cursor: pointer;
62 | user-select: none;
63 | }
64 |
65 | main .faq .filter-control input[type="checkbox"] {
66 | display: none;
67 | }
68 |
69 | main .faq .filter-control input[type="checkbox"]:not(:checked) + label {
70 | background-color: var(--color-bg);
71 | color: currentcolor;
72 | }
73 |
74 | main .faq .hidden-by-filter {
75 | display: none;
76 | }
77 |
--------------------------------------------------------------------------------
/blocks/feature-list/feature-list.css:
--------------------------------------------------------------------------------
1 | .feature-list {
2 | background: #f5f5f5;
3 | padding: var(--spacing-xxl) 0;
4 | color: rgb(63 63 63);
5 | }
6 |
7 | .feature-list.contained > div {
8 | max-width: var(--grid-mobile-container-width);
9 | margin: 0 auto;
10 | }
11 |
12 | .feature-list > div:last-of-type {
13 | display: grid;
14 | grid-template-columns: 1fr;
15 | grid-template-areas:
16 | "image"
17 | "copy";
18 | grid-gap: var(--spacing-s);
19 | align-items: center;
20 | }
21 |
22 | .feature-list h2 {
23 | text-align: center;
24 | margin-bottom: var(--spacing-s);
25 | }
26 |
27 | .feature-list > div:last-of-type > div:first-of-type {
28 | grid-area: copy;
29 | }
30 |
31 | .feature-list > div:last-of-type > div:last-of-type {
32 | grid-area: image;
33 | }
34 |
35 | .feature-list ul {
36 | list-style: none;
37 | margin: 0;
38 | padding: 0;
39 | }
40 |
41 | .feature-list li {
42 | padding-bottom: var(--spacing-xs);
43 | }
44 |
45 | .feature-list li:last-of-type {
46 | padding-bottom: 0;
47 | }
48 |
49 | .feature-list img {
50 | max-width: 100%;
51 | }
52 |
53 | @media screen and (width >= 1200px) {
54 | .feature-list > div:last-of-type {
55 | grid-template-columns: 1fr 1fr;
56 | grid-template-areas: "copy image";
57 | grid-gap: var(--spacing-xxl);
58 | }
59 | }
60 |
61 | @media screen and (width >= 1440px) {
62 | .feature-list.contained > div {
63 | max-width: var(--grid-desktop-container-width);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/blocks/feature-list/feature-list.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/blocks/feature-list/feature-list.js
--------------------------------------------------------------------------------
/blocks/footer/README.md:
--------------------------------------------------------------------------------
1 | ## Footer
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | Default Footer |
9 |
10 | #### Example:
11 |
12 | See Live output:
13 | https://redesign-new-footer--helix-website--adobe.hlx.page/drafts/redesign/new-home
14 |
15 | #### Content Structure:
16 |
17 | See Content in Document (Link):
18 | https://docs.google.com/document/d/1Qr26pZVperklcaRuErsE1qZ1ebl1lL6tNodMN28SdEU/edit
19 |
20 | - the new footer styles are now shown based on the path in the url. When migrate, we'll need to update the code and release the logic to global
21 | - the styles are handled separately in mobile vs desktop, where there's one div for mobile version, and another div for desktop version.
22 |
23 | top section (wrapped by
in document):
24 | - h3 -> heading link
25 | - ul -> sublinks under the heading link
26 | - link in bolded -> cta button, which only shows on desktop only
27 |
28 | bottom section (wrapped by
in document:
29 | - p -> copyright text, separate lines on mobile, same line on desktop
30 |
31 | Animation styles
32 | - from global class: `.link-underline-effect`
33 |
34 | [Decoration Code](footer.js)
35 |
36 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
37 |
38 | [Styling Code](footer.css)
--------------------------------------------------------------------------------
/blocks/footnotes/footnotes.css:
--------------------------------------------------------------------------------
1 | .footnotes {
2 | margin: var(--spacing-ml) 0;
3 | }
4 |
5 | .footnotes > div {
6 | display: flex;
7 | }
8 |
9 | .footnotes > div,
10 | .footnotes .footnote-text > a {
11 | font-size: var(--type-body-xs-size);
12 | line-height: var(--type-body-xs-lh);
13 | }
14 |
15 | .footnotes .footnote-num {
16 | text-align: right;
17 | padding-right: 20px;
18 | }
19 |
--------------------------------------------------------------------------------
/blocks/footnotes/footnotes.js:
--------------------------------------------------------------------------------
1 | export default async function decorate($block) {
2 | // enumerate all footnote references
3 | [...document.querySelectorAll('main sup')]
4 | .forEach((sup, i) => {
5 | if (sup.textContent === '#') {
6 | sup.textContent = i + 1;
7 | }
8 | });
9 |
10 | // enumerate footnotes
11 | document.querySelector('.section.content').append($block);
12 | [...$block.children].forEach(($footnote, i) => {
13 | $footnote.firstElementChild.classList.add('footnote-text');
14 | const $num = document.createElement('div');
15 | $num.classList.add('footnote-num');
16 | $num.innerHTML = `${i + 1}`;
17 | $footnote.prepend($num);
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/blocks/form/checkbox-checked.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
--------------------------------------------------------------------------------
/blocks/form/checkbox.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/blocks/fragment/fragment.css:
--------------------------------------------------------------------------------
1 | /* nothing here */
2 |
--------------------------------------------------------------------------------
/blocks/fragment/fragment.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Fragment Block
3 | * Include content from one Helix page in another.
4 | * https://www.hlx.live/developer/block-collection/fragment
5 | */
6 |
7 | import { decorateMain } from '../../scripts/scripts.js';
8 | import { loadBlocks } from '../../scripts/lib-franklin.js';
9 |
10 | /**
11 | * Loads a fragment.
12 | * @param {string} path The path to the fragment
13 | * @returns {HTMLElement} The root element of the fragment
14 | */
15 | async function loadFragment(path) {
16 | if (path && path.startsWith('/')) {
17 | const resp = await fetch(`${path}.plain.html`);
18 | if (resp.ok) {
19 | const main = document.createElement('main');
20 | main.innerHTML = await resp.text();
21 | decorateMain(main);
22 | await loadBlocks(main);
23 | return main;
24 | }
25 | }
26 | return null;
27 | }
28 |
29 | export default async function decorate(block) {
30 | const link = block.querySelector('a');
31 | const path = link ? link.getAttribute('href') : block.textContent.trim();
32 | const fragment = await loadFragment(path);
33 | if (fragment) {
34 | const fragmentSection = fragment.querySelector(':scope .section');
35 | if (fragmentSection) {
36 | block.closest('.fragment-wrapper').replaceWith(...fragmentSection.childNodes);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/blocks/get-started/get-started.css:
--------------------------------------------------------------------------------
1 | .get-started {
2 | background: linear-gradient(45deg, rgb(0 0 0 / 100%) 0%, rgb(38 18 57 / 100%) 42%, rgb(154 26 10 / 100%) 100%);
3 | color: white;
4 | padding: var(--spacing-xxl) 0;
5 | text-align: center;
6 | }
7 |
8 | .get-started > div {
9 | max-width: 83.4%;
10 | margin: 0 auto;
11 | }
12 |
13 | .get-started > div + div > div:not(:last-of-type) {
14 | margin: var(--spacing-l) 0;
15 | }
16 |
17 | .get-started h2 {
18 | margin-bottom: var(--spacing-l);
19 | }
20 |
21 | .get-started h3 {
22 | margin-bottom: var(--spacing-s);
23 | }
24 |
25 | .get-started p {
26 | margin: 0 0 6px;
27 | }
28 |
29 | .get-started a {
30 | color: white;
31 | }
32 |
33 | .get-started img,
34 | .get-started svg {
35 | max-width: 128px;
36 | }
37 |
38 | @media (width >= 900px) {
39 | .get-started > div:last-of-type {
40 | display: grid;
41 | grid-template-columns: 1fr 1fr;
42 | }
43 |
44 | .get-started > div + div > div:not(:last-of-type) {
45 | margin: 0;
46 | }
47 | }
48 |
49 | @media (width >= 1440px) {
50 | .get-started > div {
51 | max-width: 1200px;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/blocks/get-started/get-started.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/blocks/get-started/get-started.js
--------------------------------------------------------------------------------
/blocks/header/README.md:
--------------------------------------------------------------------------------
1 | # GlobalNav
2 | ## Content Contract
3 | ```
4 | | Gnav Brand |
5 | | ------------------------------------ |
6 | | [Adobe Blog](https://blog.adobe.com) |
7 | ---
8 | ## [News](https://www.adobe.com/news)
9 | ---
10 | ## Basics
11 |
12 | * Nav Item
13 | * Nav Item
14 | * Nav Item
15 | ---
16 | ## Insights
17 |
18 | * Nav Item
19 | * Nav Item
20 | * Nav Item
21 |
22 | | Gnav Promo |
23 | | ------------- |
24 | | Awesomeness |
25 | ---
26 | | Search |
27 | | ------------- |
28 | | Search |
29 |
30 | | Profile |
31 | | ------------- |
32 | | Sign In |
33 | | ------------- |
34 | | View Account |
35 | | Sign Out |
36 |
37 | | Adobe Logo |
38 | | ------------------------------ |
39 | | [Adobe](https://www.adobe.com) |
40 |
41 | ```
42 | ### Optional Content
43 | * Nav Items
44 | * Gnav Promo
45 | * Search
46 | * Profile
47 |
48 | ## Features
49 | * Allows for full IMS integration
50 | * Support small, medium, large menu sizing
51 | * UX generally aligns with [Consonant Navigation](https://consonant.adobe.com/1975e5ba1/p/48adfd-navigation).
52 |
53 | ## Behaviors
54 | ### Mobile (0-1199px)
55 | 1. Hamburger will toggle curtain, search, and nav menu.
56 | 2. Scroll is permitted.
57 | 3. Re-size will hide curtain, search, and nav menu.
58 |
59 | ### Desktop (1200-∞)
60 | 1. Nav menus will close on scroll and on document click.
61 | 1. Only one nav menu can be open at a time.
62 | 1. Opening search will close any open nav menu.
63 | 1. Opening a menu will close the search bar.
64 | 1. Search will not close on scroll.
--------------------------------------------------------------------------------
/blocks/header/adobe-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/blocks/header/cta-placeholder.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/blocks/header/cta-placeholder.png
--------------------------------------------------------------------------------
/blocks/hero/README.md:
--------------------------------------------------------------------------------
1 | ## Hero
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | Default Home Hero: text in center, image at bottom, colorful checkerboard pattern background |
9 | | side-by-side | 50% detail, 50% image in same row |
10 | | square-image | image will become 1:1 in aspect ratio |
11 | | mutiple-cta | decorate multiple cta buttons with black border styles (ul a) |
12 | | page-not-found | page not found ui |
13 |
14 | #### Example:
15 |
16 | See Live output (Link):
17 | https://redesign-hero-and-logo-wall--helix-website--adobe.hlx.page/drafts/redesign/blocks/hero
18 | TBC: https://website-redesign--helix-website--adobe.hlx.page/drafts/redesign/blocks/hero
19 |
20 | #### Content Structure:
21 |
22 | See Content in Document (Link):
23 | https://docs.google.com/document/d/1LfT3loAme82XIWhWAUOaWK1aPP42XzZ1GVjwHYtk0Zc/edit#
24 |
25 | #### Code:
26 | - Background Image: display image from 1st table row as cover. If no image provided, it will fallback to default grey checkerboard background
27 | - Text Content from 2nd table row
28 | - h1 -> heading
29 | - a -> cta-button
30 | - p -> description
31 | - ul list with multiple elements -> cta button list (need to use with .multple-cta on block)
32 | - Content Image from 3rd table row
33 |
34 | [Decoration Code](hero.js)
35 |
36 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
37 |
38 | [Styling Code](hero.css)
--------------------------------------------------------------------------------
/blocks/hero/hero.js:
--------------------------------------------------------------------------------
1 | import createTag from '../../utils/tag.js';
2 |
3 | export default function decorate(block) {
4 | const backgroundImageWrapper = block.children[0].querySelector('div');
5 | const backgroundImage = backgroundImageWrapper.querySelector('img');
6 | backgroundImageWrapper.setAttribute('class', 'background-image-wrapper');
7 |
8 | const innerContent = block.children[1].querySelector('div');
9 | innerContent.setAttribute('class', 'inner-content');
10 |
11 | if (innerContent) {
12 | const eyebrow = innerContent.querySelector('h3');
13 | if (eyebrow && eyebrow.querySelector('.icon')) {
14 | eyebrow.classList.add('icon-eyebrow');
15 | }
16 | }
17 |
18 | if (!block.classList.contains('multiple-cta')) {
19 | const ctaButton = innerContent.querySelector('a');
20 | if (ctaButton) {
21 | ctaButton.classList.add('button', 'large');
22 | ctaButton.closest('p').replaceWith(ctaButton);
23 | }
24 | } else {
25 | const ctaButtonList = innerContent.querySelector('ul');
26 | ctaButtonList.classList.add('cta-button-list');
27 | const ctaButtons = ctaButtonList.querySelectorAll('ul a');
28 | ctaButtons.forEach((btn) => {
29 | btn.classList.add('button', 'large', 'black-border');
30 | });
31 | }
32 |
33 | const imageWrapper = block.children[2].querySelector('div');
34 | imageWrapper.setAttribute('class', 'image-wrapper');
35 |
36 | block.innerHTML = '';
37 | if (backgroundImage) {
38 | backgroundImageWrapper.classList.add('with-bg-img');
39 | block.style.background = 'transparent';
40 | block.append(backgroundImageWrapper);
41 | } else {
42 | block.classList.add('colorful-bg');
43 | const checkerBoardGuide = createTag('div', {
44 | class: 'checker-board-guide',
45 | });
46 | block.append(checkerBoardGuide);
47 | }
48 |
49 | if (block.classList.contains('side-by-side')) {
50 | // for hero.side-by-side
51 | const containedWrapper = createTag('div', {
52 | class: 'contained-wrapper',
53 | });
54 | containedWrapper.append(innerContent, imageWrapper);
55 | block.append(containedWrapper);
56 | } else {
57 | // default hero
58 | block.append(innerContent, imageWrapper);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/blocks/labs/labs.css:
--------------------------------------------------------------------------------
1 | .labs {
2 | padding: 10px 16px;
3 | margin-bottom: 16px;
4 | background-color: #e6118a;
5 | color: #fff;
6 | border-radius: var(--image-border-radius-l);
7 | font-size: var(--type-body-s-size);
8 | line-height: var(--type-body-s-lh);
9 | }
10 |
11 | .labs .header {
12 | font-weight: 700;
13 | font-size: var(--type-heading-m-size);
14 | text-transform: uppercase;
15 | }
16 |
17 | .labs a {
18 | color: #fff;
19 | text-decoration: underline;
20 | font-weight: 700;
21 | }
22 |
23 | .home-template .labs {
24 | margin-left: 25%;
25 | margin-right: 25%;
26 | margin-top: -2em;
27 | z-index: 10;
28 | position: relative;
29 | text-align: center;
30 | }
31 |
--------------------------------------------------------------------------------
/blocks/labs/labs.js:
--------------------------------------------------------------------------------
1 | export default function decorate(block) {
2 | const header = document.createElement('div');
3 | header.className = 'header';
4 | let parts;
5 | if (block.querySelector('h1, h2, h3, h4, h5, h6')) {
6 | parts = [block.querySelector('h1, h2, h3, h4, h5, h6').innerText];
7 | block.querySelector('h1, h2, h3, h4, h5, h6').remove();
8 | parts.push(block.innerHTML);
9 | } else {
10 | parts = block.innerText.split(/:/)
11 | .map((p) => p.trim());
12 | }
13 | if (parts.length === 1) {
14 | parts.unshift('Early-access technology');
15 | parts[1] = `Ask us about this feature from the ${parts[1]} labs on your Slack channel!`;
16 | }
17 | [header.textContent, block.innerHTML] = parts;
18 | block.prepend(header);
19 | }
20 |
--------------------------------------------------------------------------------
/blocks/logo-wall/README.md:
--------------------------------------------------------------------------------
1 | ## Logo Wall
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | Default Logo Wall Layout |
9 |
10 | #### Example:
11 |
12 | See Live output (Link):
13 | https://redesign-hero-and-logo-wall--helix-website--adobe.hlx.page/drafts/redesign/blocks/logo-wall
14 | TBC: https://website-redesign--helix-website--adobe.hlx.page/drafts/redesign/blocks/logo-wall
15 |
16 | #### Content Structure:
17 |
18 | See Content in Document (Link):
19 | https://docs.google.com/document/d/1LuMFbPFHBkKc09WN8HCHbkqhCLM6kyiMrJKk7GAtPVg/edit#
20 |
21 | #### Code:
22 | - Heading: h1/h2/h3/h4/h5/h6 in the first row
23 | - Logo item: image(.png) + link in each row -> direct to linked url in a new page
24 |
25 | [Decoration Code](logo-wall.js)
26 |
27 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
28 |
29 | [Styling Code](logo-wall.css)
--------------------------------------------------------------------------------
/blocks/logo-wall/logo-wall.css:
--------------------------------------------------------------------------------
1 | .logo-wall {
2 | position: relative;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | align-items: center;
7 | padding: var(--spacing-xl) 0;
8 | }
9 |
10 | .logo-wall .logo-wall-title {
11 | max-width: 75vw;
12 | text-align: center;
13 | padding-bottom: var(--spacing-s);
14 | }
15 |
16 | .logo-wall .logo-wall-list {
17 | display: flex;
18 | flex-wrap: wrap;
19 | justify-content: center;
20 | gap: var(--spacing-s);
21 | width: 100%;
22 | max-width: 740px;
23 | padding-inline-start: 0;
24 | }
25 |
26 | .logo-wall .logo-wall-list-item {
27 | list-style-type: none;
28 | display: flex;
29 | justify-content: center;
30 | align-items: center;
31 | align-self: center;
32 | height: 100%;
33 | }
34 |
35 | .logo-wall .logo-wall-list-item a {
36 | transition: opacity 0.3s ease;
37 | }
38 |
39 | .logo-wall .logo-wall-list-item a:hover {
40 | opacity: 0.7;
41 | }
42 |
43 | .logo-wall .logo-wall-list-item img {
44 | margin: 0 auto;
45 | display: block;
46 | width: 100%;
47 | height: 100%;
48 | max-width: 150px;
49 | max-height: 54px;
50 | object-fit: contain;
51 | }
52 |
53 | .logo-wall.meriative-height-adjustment .logo-wall-list-item img {
54 | max-height: 80px;
55 | }
56 |
57 | @media (width >= 900px) {
58 | .logo-wall {
59 | padding: var(--spacing-xxl) 0;
60 | }
61 |
62 | .logo-wall .logo-wall-title {
63 | padding-bottom: var(--spacing-xl);
64 | }
65 |
66 | .logo-wall .logo-wall-list {
67 | gap: var(--spacing-l) var(--spacing-xl);
68 | max-width: 1200px;
69 | }
70 |
71 | .logo-wall.technology .logo-wall-list {
72 | max-width: 970px;
73 | }
74 |
75 | .logo-wall .logo-wall-list-item img {
76 | max-width: clamp(180px, 20vw, 280px);
77 | max-height: 63px;
78 | }
79 |
80 | .logo-wall.meriative-height-adjustment .logo-wall-list-item img {
81 | max-height: 96px;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/blocks/logo-wall/logo-wall.js:
--------------------------------------------------------------------------------
1 | import createTag from '../../utils/tag.js';
2 | import { addInViewAnimationToMultipleElements } from '../../utils/helpers.js';
3 |
4 | const animationConfig = {
5 | staggerTime: 0.4,
6 | items: [
7 | {
8 | selector: '.logo-wall-title',
9 | animatedClass: 'slide-reveal-up',
10 | },
11 | {
12 | selector: '.logo-wall-list',
13 | animatedClass: 'fade-up',
14 | },
15 | ],
16 | };
17 |
18 | export default function decorate(block) {
19 | block.classList.add('contained');
20 |
21 | const logoWallList = document.createElement('ul');
22 | logoWallList.setAttribute('class', 'logo-wall-list');
23 |
24 | [...block.children].forEach((row) => {
25 | [...row.children].forEach((div) => {
26 | const title = div.querySelector('h1,h2,h3,h4,h5,h6');
27 | const picture = div.querySelector('picture');
28 | const svg = div.querySelector('a[href$=".svg"]');
29 | const listItem = document.createElement('li');
30 |
31 | if (title) {
32 | title.setAttribute('class', 'logo-wall-title');
33 | row.replaceWith(title);
34 | } else if (svg) {
35 | const svgHref = new URL(svg.href).pathname;
36 | const linkEl = div.querySelectorAll('a')[1];
37 | listItem.setAttribute('class', 'logo-wall-list-item');
38 |
39 | const svgEl = createTag('img', {
40 | src: svgHref,
41 | alt: linkEl.textContent,
42 | class: 'logo-wall-item-svg',
43 | });
44 |
45 | if (linkEl) {
46 | const svgLink = createTag('a', {
47 | href: linkEl.href,
48 | title: linkEl.title,
49 | target: '_blank',
50 | class: 'logo-wall-item-link',
51 | 'aria-label': linkEl.textContent,
52 | }, svgEl);
53 |
54 | listItem.append(svgLink);
55 | logoWallList.append(listItem);
56 | row.remove();
57 | }
58 | } else if (picture) {
59 | listItem.setAttribute('class', 'logo-wall-list-item');
60 |
61 | const linkEl = div.querySelector('a');
62 | if (linkEl) {
63 | const pictureLink = createTag('a', {
64 | href: linkEl.href,
65 | title: linkEl.title,
66 | target: '_blank',
67 | class: 'logo-wall-item-link',
68 | 'aria-label': linkEl.textContent,
69 | }, picture);
70 |
71 | listItem.append(pictureLink);
72 | }
73 |
74 | logoWallList.append(listItem);
75 | row.remove();
76 | }
77 | });
78 | });
79 | block.append(logoWallList);
80 |
81 | if (block.classList.contains('inview-animation')) {
82 | addInViewAnimationToMultipleElements(animationConfig.items, block, animationConfig.staggerTime);
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/blocks/marquee/README.md:
--------------------------------------------------------------------------------
1 | ## Marquee
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | Default Marquee |
9 | | landscape-image | Turn image in image-side into 4:3 |
10 | | testimonial | Turn Marquee into testimonial layout |
11 |
12 | #### Example:
13 |
14 | See Live output:
15 |
16 | Marquee (testimonial) Link:
17 | https://redesign-tabs-and-marquee--helix-website--adobe.hlx.page/drafts/redesign/blocks/customer-stories-tabs-marquee
18 | TBC: https://website-redesign--helix-website--adobe.hlx.page/drafts/redesign/blocks/customer-stories-tabs-marquee
19 |
20 | #### Content Structure:
21 |
22 | See Content in Document (Link):
23 | https://docs.google.com/document/d/1_I1mwiaSk0CG-wikoknyjKPqEIjGmJMWINdJ8omKQBE/edit#heading=h.9p0k02gwynj6
24 |
25 | #### Code For Marquee in Testimonial format:
26 | - Background Image: 1st row from table
27 | - Side Image: any column in 2nd row without headings
28 | - Detail: any column in 2nd row with headings
29 | - h4 -> testimonial description
30 | - image -> avatar icon
31 | - h5 -> name + position (italic)
32 | - link -> button
33 | - nested unordered list -> for statistics
34 |
35 | [Decoration Code](marquee.js)
36 |
37 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
38 |
39 | [Styling Code](marquee.css)
--------------------------------------------------------------------------------
/blocks/marquee/marquee.js:
--------------------------------------------------------------------------------
1 | import createTag from '../../utils/tag.js';
2 | import { returnLinkTarget } from '../../utils/helpers.js';
3 |
4 | const extractCustomerInfo = (detailContainer) => {
5 | const customerInfo = createTag('div', {
6 | class: 'customer-info',
7 | }, '');
8 |
9 | const icon = detailContainer.querySelector('img');
10 | if (icon) {
11 | customerInfo.append(icon);
12 | }
13 |
14 | const titles = detailContainer.querySelectorAll('h5');
15 | if (titles) {
16 | let titlesHTML = '';
17 | titles.forEach((title) => {
18 | titlesHTML += ` ${title.innerHTML}
`;
19 | });
20 | const titlesDiv = createTag('div', {
21 | class: 'titles',
22 | }, titlesHTML);
23 | customerInfo.append(titlesDiv);
24 | }
25 |
26 | const cta = detailContainer.querySelector('a');
27 | if (cta) {
28 | cta.classList.add('button', 'secondary');
29 | cta.setAttribute('target', returnLinkTarget(cta.href));
30 | customerInfo.append(cta);
31 |
32 | const titleContainer = customerInfo.querySelector('.titles');
33 | if (!titleContainer || titleContainer.innerHTML.trim() === '') {
34 | cta.classList.add('align-desktop-right');
35 | }
36 | }
37 |
38 | return customerInfo;
39 | };
40 |
41 | export default function decorate(block) {
42 | [...block.children].forEach((row) => {
43 | [...row.children].forEach((div) => {
44 | const picture = div.querySelector('picture');
45 | const hasTitle = div.querySelector('h1,h2,h3,h4,h5,h6');
46 |
47 | if (hasTitle) {
48 | div.classList.add('info-side');
49 | // restyle for marquee.testimonial
50 | if (block.classList.contains('testimonial')) {
51 | const description = div.querySelectorAll('h4');
52 | const intro = description[0];
53 | const quote = description[1];
54 |
55 | const customerInfo = extractCustomerInfo(div);
56 | const statistics = div.querySelector('ul');
57 |
58 | const testimonial = createTag('div', {
59 | class: 'testimonial-info',
60 | }, '');
61 | if (intro) { testimonial.append(intro); }
62 | if (quote) { testimonial.append(quote); }
63 | if (customerInfo) { testimonial.append(customerInfo); }
64 | if (statistics) { testimonial.append(statistics); }
65 | div.replaceWith(testimonial);
66 | }
67 | } else if (picture) {
68 | div.classList.add('image-side');
69 | }
70 | });
71 | });
72 | }
73 |
--------------------------------------------------------------------------------
/blocks/pagination/README.md:
--------------------------------------------------------------------------------
1 | ## Pagination
2 |
3 | Notes: The Pagination is a block meant to be used on the documentation pages below the main content. It provides the opportunity to link users directly to the next or previous piece of documentation.
4 |
5 |
6 | ##### Custom Classes
7 | | Class | Function |
8 | |--------|------------|
9 | | - | - |
10 |
11 |
12 | #### Example:
13 |
14 | See Live output (Link TBD)
15 |
16 | #### Content Structure:
17 |
18 | [See Content in Document](https://docs.google.com/document/d/1nf9oSOtMKsjk8m6zwQeqbU3mTC7XGIC6z0THkd9GykI/edit)
19 |
20 | #### Code:
21 | Side Navigation is styled in the block CSS code.
22 |
23 | There is Javascript code for decoration purposes.
24 |
25 | [Decoration Code](pagination.js)
26 |
27 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
28 |
29 | [Styling Code](pagination.css)
--------------------------------------------------------------------------------
/blocks/pagination/pagination.css:
--------------------------------------------------------------------------------
1 | .pagination {
2 | max-width: 800px;
3 | margin: var(--spacing-xl) auto 0;
4 | }
5 |
6 | .block.pagination {
7 | max-width: none;
8 | }
9 |
10 | .pagination > div {
11 | display: grid;
12 | grid-template-columns: 1fr 1fr;
13 | column-gap: 8px;
14 | }
15 |
16 | .pagination .column {
17 | background-color: var(--bg-color-lightgrey);
18 | border-radius: 16px;
19 | border: solid 1px var(--bg-color-grey);
20 | padding: 12px 16px;
21 | transition: all 0.3s ease-in-out;
22 | }
23 |
24 | .pagination .column:hover {
25 | cursor: pointer;
26 | box-shadow: 0 0 15px var(--bg-color-grey);
27 | }
28 |
29 | .pagination .column svg {
30 | color: var(--spectrum-blue);
31 | }
32 |
33 | .pagination .column.left p > span {
34 | margin-right: 8px;
35 | }
36 |
37 | .pagination .column.right p > span {
38 | margin-left: 8px;
39 | }
40 |
41 | .pagination .column.right p > span svg {
42 | transform: rotate(180deg);
43 | }
44 |
45 | .pagination .column p {
46 | font-size: var(--type-body-xs-size);
47 | margin: 0 0 var(--spacing-xxs);
48 | }
49 |
50 | .pagination .column p:first-of-type {
51 | display: flex;
52 | align-items: center;
53 | }
54 |
55 | .pagination .column p:first-of-type span {
56 | display: flex;
57 | }
58 |
59 | .pagination .column p:last-of-type span {
60 | display: flex;
61 | margin-left: 8px;
62 | }
63 |
64 | .pagination .column h3 {
65 | margin: 0;
66 | line-height: 1;
67 | }
68 |
69 | .pagination .column h3 a {
70 | font-size: var(--type-heading-s-size);
71 | font-weight: 900;
72 | line-height: 1;
73 | letter-spacing: -0.04em;
74 | color: black;
75 | text-decoration: none;
76 | }
77 |
78 | @media (width >= 600px) {
79 | .block.pagination {
80 | max-width: none;
81 | }
82 |
83 | .pagination .column p {
84 | font-size: var(--type-body-s-size);
85 | margin: 0 0 var(--spacing-xs);
86 | }
87 |
88 | .pagination > div {
89 | column-gap: 16px;
90 | }
91 |
92 | .pagination .column h3 a {
93 | font-size: var(--type-heading-m-size);
94 | }
95 | }
96 |
97 | @media (width >= 900px) {
98 | .pagination {
99 | margin: var(--spacing-xxxl) auto 0;
100 | }
101 |
102 | .block.pagination {
103 | max-width: none;
104 | }
105 |
106 | .pagination > div {
107 | column-gap: var(--spacing-m);
108 | }
109 |
110 | .pagination .column {
111 | padding: var(--spacing-s) var(--spacing-m);
112 | }
113 |
114 | .pagination .column p a {
115 | font-size: var(--type-body-l-size);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/blocks/pagination/pagination.js:
--------------------------------------------------------------------------------
1 | export default function decorate(block) {
2 | block.querySelectorAll(':scope > div > div').forEach((column, i) => {
3 | column.classList.add('column');
4 | column.classList.add(i % 2 ? 'right' : 'left');
5 | const linkElement = column.querySelector('a');
6 |
7 | if (linkElement) {
8 | column.classList.add('link-highlight-colorful-effect-hover-wrapper');
9 | linkElement.classList.add('link-highlight-colorful-effect');
10 |
11 | column.addEventListener('click', () => {
12 | linkElement.click();
13 | });
14 | }
15 | });
16 | }
17 |
--------------------------------------------------------------------------------
/blocks/publication-time/publication-time.css:
--------------------------------------------------------------------------------
1 | .highlight {
2 | margin-left: 1em;
3 | width: max-content;
4 | background-color: yellow;
5 | }
6 |
--------------------------------------------------------------------------------
/blocks/publication-time/publication-time.js:
--------------------------------------------------------------------------------
1 | import '../../web-components/relative-time.js';
2 |
3 | export default function decorate($block) {
4 | // First row of the block defines the text prefix
5 | const prefix = $block.querySelector(':scope > div > div ')?.textContent?.trim();
6 |
7 | // Output the element as specified,
8 | // https://github.com/github/relative-time-element
9 | if (!document.lastModified) {
10 | // eslint-disable-next-line no-console
11 | console.error('document.lastModified returns null');
12 | } else {
13 | const lastMod = new Date(document.lastModified);
14 | const p = document.createElement('p');
15 | const rt = document.createElement('relative-time');
16 | rt.setAttribute('datetime', lastMod.toISOString());
17 | rt.textContent = lastMod.toLocaleDateString();
18 | p.textContent = `${prefix} `;
19 | p.appendChild(rt);
20 | $block.replaceChildren(p);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/blocks/releases/releases.css:
--------------------------------------------------------------------------------
1 | .releases .releases-controls input[type="checkbox"] {
2 | pointer-events: none;
3 | }
4 |
5 | .releases .release-aem-certificate-rotation {
6 | background-color: #d4e8ff;
7 | }
8 |
9 | .releases .release-helix-admin {
10 | background-color: #ffd4f1;
11 | }
12 |
13 | .releases .release-helix-pipeline-service {
14 | background-color: #d1ffed;
15 | }
16 |
17 | .releases .release-helix-importer-ui {
18 | background-color: #e8e8e8;
19 | }
20 |
21 | .releases .release-aem-sidekick,
22 | .releases .release-helix-sidekick-extension {
23 | background-color: #e4e8bf;
24 | }
25 |
26 | .releases .release-franklin-sidekick-library {
27 | background-color: #f4f8cf;
28 | }
29 |
30 | .releases .release-helix-cli {
31 | background-color: #ffebd4;
32 | }
33 |
34 | .releases .release-aem-lib {
35 | background-color: #cfebf8;
36 | }
37 |
38 | .releases .release-helix-config-service {
39 | background-color: #edd1ff;
40 | }
41 |
42 | .releases .release {
43 | padding: 16px 32px 32px;
44 | margin: 16px 0;
45 | border-radius: 16px;
46 | display: none;
47 | }
48 |
49 | .releases .releases-controls {
50 | display: flex;
51 | gap: 8px;
52 | flex-wrap: wrap;
53 | }
54 |
55 | .releases .releases-control {
56 | padding: 4px 16px;
57 | border-radius: 8px;
58 | font-size: 14px;
59 | display: flex;
60 | align-items: start;
61 | }
62 |
63 | .releases-date {
64 | font-weight: 700;
65 | margin-bottom: 0;
66 | }
67 |
68 | .releases .release h2 {
69 | margin-top: 0;
70 | }
71 |
72 | .releases .releases-results[aria-hidden="true"] {
73 | display: none;
74 | }
75 |
76 | .releases .helix-admin .release-helix-admin,
77 | .releases .helix-pipeline-service .release-helix-pipeline-service,
78 | .releases .helix-importer-ui .release-helix-importer-ui,
79 | .releases .helix-sidekick-extension .release-helix-sidekick-extension,
80 | .releases .aem-sidekick .release-aem-sidekick,
81 | .releases .helix-cli .release-helix-cli,
82 | .releases .helix-config-service .release-helix-config-service,
83 | .releases .aem-lib .release-aem-lib,
84 | .releases .franklin-sidekick-library .release-franklin-sidekick-library,
85 | .releases .aem-certificate-rotation .release-aem-certificate-rotation {
86 | display: block;
87 | }
88 |
--------------------------------------------------------------------------------
/blocks/roi-calculator/README.md:
--------------------------------------------------------------------------------
1 | ## ROI Calculator
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | Default ROI Calculator Layout |
9 |
10 | #### Example:
11 |
12 | See Live output (Link):
13 | https://redesign-polish--helix-website--adobe.hlx.page/drafts/redesign/blocks/roi-calculator
14 | TBC: https://website-redesign--helix-website--adobe.hlx.page/drafts/redesign/blocks/roi-calculator
15 |
16 | #### Content Structure:
17 |
18 | See Content in Document (Link):
19 | https://docs.google.com/document/d/1LADlPd4SPHwKKuK61WHMYvfTwFdVEg-JuNFiXZTgToo/edit
20 |
21 | #### Code:
22 | Default uses colorful background (block.colorful-bg + div.checker-board-guide)
23 | First Row Content
24 | - Heading: h2
25 | - CTA Button: link
26 |
27 | Second Row Content
28 | - can be used to input info for ROI form & calulator
29 | - TBC development from Adobe team
30 |
31 | [Decoration Code](roi-calculator.js)
32 |
33 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
34 |
35 | [Styling Code](roi-calculator.css)
--------------------------------------------------------------------------------
/blocks/roi-calculator/roi-calculator.css:
--------------------------------------------------------------------------------
1 | .section.roi-calulator-outer-wrapper {
2 | padding: 0;
3 | }
4 |
5 | .roi-calculator {
6 | width: 100%;
7 | position: relative;
8 | overflow: hidden;
9 | padding: var(--spacing-xxl) 0;
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | }
14 |
15 | .roi-calculator .inner-content {
16 | position: relative;
17 | margin: 0 auto;
18 | width: 100%;
19 | text-align: center;
20 | z-index: 2;
21 | }
22 |
23 | .roi-calculator .upper-content {
24 | margin: 0 auto;
25 | width: 100%;
26 | max-width: 600px;
27 | text-align: center;
28 | z-index: 2;
29 | }
30 |
31 | .roi-calculator .upper-content h2 {
32 | font-size: var(--type-heading-xxl-size);
33 | line-height: var(--type-heading-xxxl-lh);
34 | text-align: center;
35 | font-weight: 700;
36 | margin: 0 auto var(--spacing-s);
37 | max-width: 300px;
38 | }
39 |
40 | .roi-calculator .inner-content a.button:any-link {
41 | margin-bottom: 0;
42 | margin-top: 0;
43 | }
44 |
45 | @media screen and (width >= 900px) {
46 | .section.roi-calulator-outer-wrapper {
47 | padding-top: var(--spacing-ml);
48 | }
49 |
50 | .roi-calculator {
51 | min-height: 480px;
52 | }
53 |
54 | .roi-calculator .upper-content h2 {
55 | font-size: var(--type-heading-xxxl-size);
56 | margin-bottom: var(--spacing-ml);
57 | max-width: 560px;
58 | }
59 |
60 | .roi-calculator .inner-content a.button:any-link {
61 | margin-bottom: 0;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/blocks/roi-calculator/roi-calculator.js:
--------------------------------------------------------------------------------
1 | import createTag from '../../utils/tag.js';
2 |
3 | export default function decorate(block) {
4 | const outerSectionWrapper = block.closest('.section');
5 | outerSectionWrapper.classList.add('roi-calulator-outer-wrapper');
6 |
7 | const upperContent = block.children[0].querySelector('div');
8 | upperContent.setAttribute('class', 'upper-content');
9 | const ctaButton = upperContent.querySelector('a');
10 | if (ctaButton) {
11 | ctaButton.classList.add('button', 'large');
12 | ctaButton.closest('p').replaceWith(ctaButton);
13 | }
14 |
15 | const innerContent = createTag('div', { class: 'inner-content contained-wrapper' });
16 | innerContent.appendChild(upperContent);
17 |
18 | block.innerHTML = '';
19 |
20 | // colorful background setting
21 | block.classList.add('colorful-bg');
22 | const checkerBoardGuide = createTag('div', {
23 | class: 'checker-board-guide',
24 | });
25 | block.append(checkerBoardGuide);
26 |
27 | // TODO: append more content like form & ROI calculator here
28 | // const calculatorContent = block.children[1].querySelector('div');
29 |
30 | block.append(innerContent);
31 | }
32 |
--------------------------------------------------------------------------------
/blocks/service-status/service-status.css:
--------------------------------------------------------------------------------
1 | .service-status {
2 | background: aliceblue;
3 | padding: var(--spacing-xl) 0;
4 | text-align: center;
5 | }
6 |
7 | .service-status statuspage-widget {
8 | margin: var(--spacing-s) 0;
9 | }
10 |
--------------------------------------------------------------------------------
/blocks/service-status/service-status.js:
--------------------------------------------------------------------------------
1 | import { loadScript } from '../../scripts/lib-franklin.js';
2 | import createTag from '../../utils/tag.js';
3 |
4 | async function loadWidget(el) {
5 | await loadScript('https://unpkg.com/@statuspage/status-widget/dist/index.js');
6 | const status = createTag('statuspage-widget', { src: 'https://status.project-helix.io', appearance: 'badge' });
7 | el.append(status);
8 | }
9 |
10 | export default async function init(el) {
11 | setTimeout(() => {
12 | loadWidget(el);
13 | }, 3000);
14 | }
15 |
--------------------------------------------------------------------------------
/blocks/side-navigation/README.md:
--------------------------------------------------------------------------------
1 | ## Side Navigation
2 |
3 | Notes: The Side Navigation block is built automatically into all pages with the Page metadata template set to `guides`. The search functionality queiries the dopages-index sheet in the root of the project content folder.
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | - | - |
9 |
10 |
11 | #### Example:
12 |
13 | See Live output (Link TBD)
14 |
15 | #### Content Structure:
16 |
17 | [See Content in Document](https://docs.google.com/document/d/1ST15vxa7XD9vVYJM-CH3fgJwtn8ERogEjZdSwcMkrLI/edit)
18 |
19 | #### Code:
20 | Side Navigation is styled in the block CSS code.
21 |
22 | There is Javascript code for decoration purposes and to set up a target click handler for list items that require a toggle to reveal additional list items.
23 |
24 | [Decoration Code](side-navigation.js)
25 |
26 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
27 |
28 | [Styling Code](side-navigation.css)
--------------------------------------------------------------------------------
/blocks/sidekick-generator/sidekick-generator.css:
--------------------------------------------------------------------------------
1 | .sidekick-generator form {
2 | display: flex;
3 | flex-wrap: wrap;
4 | }
5 |
6 | .sidekick-generator form label {
7 | width: 100%;
8 | font-weight: 700;
9 | }
10 |
11 | .sidekick-generator a.back-link::before {
12 | content: "< Back to ";
13 | padding-right: 8px;
14 | display: inline-block;
15 | }
16 |
17 | .sidekick-generator input[type="text"],
18 | .sidekick-generator input[type="password"] {
19 | display: block;
20 | width: 100%;
21 | line-height: 30px;
22 | border: 1px solid;
23 | border-color: rgb(202 202 202);
24 | border-radius: 4px;
25 | padding: 0 8px;
26 | font-size: 14px;
27 | font-family: adobe-clean, sans-serif;
28 | outline: none !important;
29 | appearance: none;
30 | box-sizing: border-box;
31 | transition: border-color 130ms ease-in-out, box-shadow 130ms ease-in-out;
32 | flex: 1 1 0%;
33 | }
34 |
35 | .sidekick-generator input[type="checkbox"] {
36 | border: 1px solid;
37 | border-color: rgb(202 202 202);
38 | border-radius: 4px;
39 | transition: border-color 130ms ease-in-out, box-shadow 130ms ease-in-out;
40 | }
41 |
42 | .sidekick-generator input[type="text"]::placeholder {
43 | font-weight: 400;
44 | color: #8e8e8e;
45 | transition: color 130ms ease-in-out;
46 | }
47 |
48 | .sidekick-generator input[type="text"]:focus::placeholder,
49 | .sidekick-generator input[type="text"]:hover::placeholder {
50 | color: #2c2c2c;
51 | }
52 |
53 | .sidekick-generator input[type="text"]:focus,
54 | .sidekick-generator input[type="password"]:focus {
55 | border-color: rgb(42 124 223);
56 | }
57 |
58 | .sidekick-generator button {
59 | line-height: 32px;
60 | background: rgb(20 115 230);
61 | color: white;
62 | border-radius: 16px;
63 | font-size: 14px;
64 | font-weight: 600;
65 | padding: 0 16px;
66 | border: none;
67 | font-family: adobe-clean, "Trebuchet MS", sans-serif;
68 | flex: 0 1 auto;
69 | }
70 |
71 | .sidekick-generator p > .icon svg {
72 | box-sizing: border-box;
73 | height: 24px;
74 | margin-bottom: -5px;
75 | margin-right: 4px;
76 | }
77 |
--------------------------------------------------------------------------------
/blocks/sidekick-library/sidekick-library.css:
--------------------------------------------------------------------------------
1 | body.docs-template main .section:has(.sidekick-library-wrapper) {
2 | padding: 0;
3 | max-width: 100%;
4 | }
5 |
--------------------------------------------------------------------------------
/blocks/sidekick-library/sidekick-library.js:
--------------------------------------------------------------------------------
1 | import '../../tools/sidekick/library/index.js';
2 |
3 | export default function decorate(block) {
4 | const library = document.createElement('sidekick-library');
5 | block.replaceChildren(library);
6 | }
7 |
--------------------------------------------------------------------------------
/blocks/table/table.css:
--------------------------------------------------------------------------------
1 | .table {
2 | max-width: 100%;
3 | overflow-x: auto;
4 | width: fit-content;
5 | }
6 |
7 | /* temp fix for table rendering weirdly */
8 | .content > .table-wrapper > .table {
9 | border: 1px solid var(--bg-color-grey);
10 | padding: var(--spacing-xxs) var(--spacing-xxs) var(--spacing-xs);
11 | margin-bottom: var(--spacing-s);
12 | }
13 |
14 | .table tbody {
15 | width: 100%;
16 | }
17 |
18 | .table td,
19 | body main .table td p {
20 | font-size: var(--type-body-s-size);
21 | line-height: var(--type-body-s-lh);
22 | color: var(--color-light-grey-600);
23 | }
24 |
25 | .table td {
26 | padding: var(--spacing-xxs) var(--spacing-xxs) 0 var(--spacing-xxs);
27 | min-width: 180px;
28 | }
29 |
30 | body main .table td p {
31 | padding-left: 0;
32 | }
33 |
34 | .table tr:first-of-type {
35 | border-bottom: 1px solid var(--bg-color-grey);
36 | }
37 |
38 | .table td:first-child {
39 | color: inherit;
40 | }
41 |
42 | .table tr:first-of-type td {
43 | text-transform: uppercase;
44 | padding-top: var(--spacing-xxs);
45 | padding-bottom: var(--spacing-xxs);
46 | color: var(--color-black);
47 | }
48 |
49 | .table thead + tbody tr:first-of-type td {
50 | text-transform: none;
51 | }
52 |
--------------------------------------------------------------------------------
/blocks/table/table.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Table Block
3 | * Recreate a table
4 | * https://www.hlx.live/developer/block-collection/table
5 | */
6 |
7 | function buildCell(rowIndex) {
8 | const cell = rowIndex ? document.createElement('td') : document.createElement('th');
9 | if (!rowIndex) cell.setAttribute('scope', 'col');
10 | return cell;
11 | }
12 |
13 | export default async function decorate(block) {
14 | if (block.querySelector('table')) return;
15 | const table = document.createElement('table');
16 | const thead = document.createElement('thead');
17 | const tbody = document.createElement('tbody');
18 |
19 | const header = !block.classList.contains('no-header');
20 | if (header) table.append(thead);
21 | table.append(tbody);
22 |
23 | [...block.children].forEach((child, i) => {
24 | const row = document.createElement('tr');
25 | if (header && i === 0) thead.append(row);
26 | else tbody.append(row);
27 | [...child.children].forEach((col) => {
28 | const cell = buildCell(header ? i : i + 1);
29 | cell.innerHTML = col.innerHTML;
30 | row.append(cell);
31 | });
32 | });
33 | block.innerHTML = '';
34 | block.append(table);
35 | }
36 |
--------------------------------------------------------------------------------
/blocks/tabs/README.md:
--------------------------------------------------------------------------------
1 | ## Tabs
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | Default tabs |
9 | | image-based | Support image in top tablist |
10 |
11 | #### Example:
12 |
13 | See Live output:
14 | Tabs (.image-based) Link:
15 | https://redesign-tabs-and-marquee--helix-website--adobe.hlx.page/drafts/redesign/blocks/customer-stories-tabs-marquee
16 | TBC: https://website-redesign--helix-website--adobe.hlx.page/drafts/redesign/blocks/customer-stories-tabs-marquee
17 |
18 | #### Content Structure:
19 |
20 | See Content in Document (Link):
21 | https://docs.google.com/document/d/1_I1mwiaSk0CG-wikoknyjKPqEIjGmJMWINdJ8omKQBE/edit#heading=h.9p0k02gwynj6
22 |
23 | #### Code For Marquee in Testimonial format:
24 | - Image in unordered list in google doc -> Image in tab list
25 | - Tabs content are grouped using `---` and `section-metadata` block where `style` in `section-metadata` will need to be `Tab ${index}`
26 |
27 | [Decoration Code](tabs.js)
28 |
29 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
30 |
31 | [Styling Code](tabs.css)
--------------------------------------------------------------------------------
/blocks/testimonials/README.md:
--------------------------------------------------------------------------------
1 | ## Testimonials
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | N/A | Default testimonials |
9 |
10 | #### Example:
11 |
12 | See Live output:
13 | Testimonials Link:
14 | https://redesign-testimonial-simplify--helix-website--adobe.hlx.page/drafts/redesign/blocks/testimonials
15 |
16 | #### Content Structure:
17 |
18 | See Content in Document (Link):
19 | https://docs.google.com/document/d/10gnxPdUyIMT2Du_gimFjL-6VMX7yojCyb881ZQSb1SU/edit
20 |
21 | #### Code For Testimonials format:
22 | - first column: image + text for tabs rendering
23 | - second column: image in tabcontent testimonial section
24 | - third column: text content for testimonial
25 | - start with " ' " will add quote class
26 | - img -> icon
27 | - text after image -> customer titles
28 | - link -> cta
29 | - ul list -> stats info, will separate string into 2 part, 1st part with stat, 2nd part with info
30 |
31 | [Decoration Code](testimonials.js)
32 |
33 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
34 |
35 | [Styling Code](testimonials.css)
--------------------------------------------------------------------------------
/blocks/video/video.css:
--------------------------------------------------------------------------------
1 | .video-wrapper {
2 | width: 100%;
3 | overflow: hidden;
4 | }
5 |
6 | .video {
7 | margin: 32px 0;
8 | }
9 |
10 | .video video {
11 | width: 100%;
12 | }
13 |
--------------------------------------------------------------------------------
/blocks/video/video.js:
--------------------------------------------------------------------------------
1 | import { sampleRUM } from '../../scripts/lib-franklin.js';
2 |
3 | function decorateVideoBlock($block, videoURL) {
4 | if (videoURL.endsWith('.mp4')) {
5 | let attrs = '';
6 | attrs = 'playsinline controls';
7 | if ($block.classList.contains('autoplay')) attrs = 'playsinline controls muted autoplay loop';
8 | $block.innerHTML = /* html */`
9 |
10 |
11 |
12 | `;
13 | $block.querySelector('video').addEventListener('play', (e) => {
14 | sampleRUM('play', {
15 | source: e.target.currentSrc,
16 | });
17 | });
18 | }
19 | }
20 |
21 | export default function decorate($block) {
22 | const $a = $block.querySelector('a');
23 | const videoURL = $a.href;
24 | const observer = new IntersectionObserver((entries) => {
25 | entries.forEach((entry) => {
26 | if (entry.isIntersecting) {
27 | decorateVideoBlock($block, videoURL);
28 | }
29 | });
30 | });
31 | observer.observe($block);
32 | }
33 |
--------------------------------------------------------------------------------
/blocks/videotext/videotext.css:
--------------------------------------------------------------------------------
1 | /* default rule for mobile */
2 | .videotext > div {
3 | display: grid;
4 | padding-top: var(--spacing-xxs);
5 | }
6 |
7 | .videocontent video {
8 | width: 100%;
9 | }
10 |
11 | .videotext .text {
12 | font-size: var(--type-body-xs-size);
13 | padding-top: var(--spacing-xxs);
14 | }
15 |
--------------------------------------------------------------------------------
/blocks/videotext/videotext.js:
--------------------------------------------------------------------------------
1 | export default function decorate(block) {
2 | [...block.children].forEach((row) => {
3 | const videoEl = [...row.children][0];
4 | videoEl.classList.add('videocontent');
5 | const videoURL = videoEl.innerHTML;
6 | videoEl.innerHTML = ``;
7 | const textEl = [...row.children][1];
8 | textEl.classList.add('text');
9 | textEl.querySelectorAll('a').forEach((a) => {
10 | a.target = '_blank';
11 | });
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/blocks/z-pattern/README.md:
--------------------------------------------------------------------------------
1 | ## Z Pattern
2 |
3 | Notes:
4 |
5 | ##### Custom Classes
6 | | Class | Function |
7 | |--------|------------|
8 | | wide-text | Used for a more prominent text split. |
9 | | spacing-lg | Used to increase the spacing between rows. |
10 | | value-props | Used to add global classes `.icon-eyebrow` and `.main-headline`, and `colored-tag` with color class for props styling. |
11 | | right-first | Default is left-side content first, with this class applied the pattern should begin with right-side content first |
12 |
13 | #### Example:
14 |
15 | See Live output (Link)
16 |
17 | #### Content Structure:
18 |
19 | See Content in Document (Link)
20 |
21 | #### Code:
22 | Z Patten is styled in the block CSS code.
23 |
24 | There is Javascript code for decoration purposes primarily to alternate the layout of even and odd rows.
25 |
26 | - Left Side: Image
27 | - Right Side: Content
28 |
29 | z-pattern.value-props:
30 | - Left Side: Image
31 | - Right Side: Content
32 | - p (first p tag) -> .icon-eyebrow
33 | - h3 -> .main-headline
34 | - ul -> li
35 | - bolded text -> .colored-tag with colors
36 | - other text in li -> .colored-tag-description
37 |
38 | [Decoration Code](z-pattern.js)
39 |
40 | The CSS Styling is very project specific and gets adjusted as needed for a project or block by block.
41 |
42 | [Styling Code](z-pattern.css)
43 |
--------------------------------------------------------------------------------
/default-meta-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/default-meta-image.jpg
--------------------------------------------------------------------------------
/default-meta-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/default-meta-image.png
--------------------------------------------------------------------------------
/fonts/adobe-clean-300-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/adobe-clean-300-italic.woff2
--------------------------------------------------------------------------------
/fonts/adobe-clean-300.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/adobe-clean-300.woff2
--------------------------------------------------------------------------------
/fonts/adobe-clean-400-italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/adobe-clean-400-italic.woff2
--------------------------------------------------------------------------------
/fonts/adobe-clean-400.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/adobe-clean-400.woff2
--------------------------------------------------------------------------------
/fonts/adobe-clean-700.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/adobe-clean-700.woff2
--------------------------------------------------------------------------------
/fonts/adobe-clean-900.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/adobe-clean-900.woff2
--------------------------------------------------------------------------------
/fonts/fonts.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: adobe-clean;
3 | src:url("/fonts/adobe-clean-400.woff2") format("woff2");
4 | font-display:optional;font-style:normal;font-weight:400;
5 | }
6 |
7 | @font-face {
8 | font-family: adobe-clean;
9 | src:url("/fonts/adobe-clean-400-italic.woff2") format("woff2");
10 | font-display:optional;font-style:italic;font-weight:400;
11 | }
12 |
13 | @font-face {
14 | font-family: adobe-clean;
15 | src:url("/fonts/adobe-clean-700.woff2") format("woff2");
16 | font-display:optional;font-style:normal;font-weight:700;
17 | }
18 |
19 | @font-face {
20 | font-family: adobe-clean;
21 | src:url("/fonts/adobe-clean-900.woff2") format("woff2");
22 | font-display:optional;font-style:normal;font-weight:900;
23 | }
24 |
--------------------------------------------------------------------------------
/fonts/source-code-pro-300-italic.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/source-code-pro-300-italic.otf.woff2
--------------------------------------------------------------------------------
/fonts/source-code-pro-300.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/source-code-pro-300.otf.woff2
--------------------------------------------------------------------------------
/fonts/source-code-pro-400-italic.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/source-code-pro-400-italic.otf.woff2
--------------------------------------------------------------------------------
/fonts/source-code-pro-400.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/source-code-pro-400.otf.woff2
--------------------------------------------------------------------------------
/fonts/source-code-pro-700.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/source-code-pro-700.otf.woff2
--------------------------------------------------------------------------------
/fonts/source-code-pro-900.otf.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/fonts/source-code-pro-900.otf.woff2
--------------------------------------------------------------------------------
/head.html:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/helix-query.yaml:
--------------------------------------------------------------------------------
1 | version: 1
2 |
3 | indices:
4 | default:
5 | target: /query-index
6 | properties:
7 | title:
8 | select: head > meta[property="og:title"]
9 | value: |
10 | attribute(el, 'content')
11 | image:
12 | select: head > meta[property="og:image"]
13 | value: |
14 | match(attribute(el, 'content'), 'https:\/\/[^/]+(\/.*)')
15 | description:
16 | select: head > meta[name="description"]
17 | value: |
18 | attribute(el, 'content')
19 | lastModified:
20 | select: none
21 | value: |
22 | parseTimestamp(headers['last-modified'], 'ddd, DD MMM YYYY hh:mm:ss GMT')
23 | robots:
24 | select: head > meta[name="robots"]
25 | value: |
26 | attribute(el, 'content')
27 | publicationDate:
28 | select: head > meta[name="publication-date"]
29 | value: |
30 | attribute(el, 'content')
31 | deprecation:
32 | select: head > meta[name="deprecation"]
33 | value: |
34 | attribute(el, 'content')
35 | labs:
36 | select: head > meta[name="labs"]
37 | value: |
38 | attribute(el, 'content')
39 | docpages:
40 | target: /docpages-index
41 | include:
42 | - /docs/**
43 | - /developer/**
44 | properties:
45 | title:
46 | select: head > meta[property="og:title"]
47 | value: |
48 | attribute(el, 'content')
49 | image:
50 | select: head > meta[property="og:image"]
51 | value: |
52 | match(attribute(el, 'content'), 'https:\/\/[^/]+(\/.*)')
53 | description:
54 | select: head > meta[name="description"]
55 | value: |
56 | attribute(el, 'content')
57 | labs:
58 | select: head > meta[name="labs"]
59 | value: |
60 | attribute(el, 'content')
61 | lastModified:
62 | select: none
63 | value: |
64 | parseTimestamp(headers['last-modified'], 'ddd, DD MMM YYYY hh:mm:ss GMT')
65 | robots:
66 | select: head > meta[name="robots"]
67 | value: |
68 | attribute(el, 'content')
69 | content:
70 | select: main
71 | value: |
72 | textContent(el)
73 |
--------------------------------------------------------------------------------
/icons/icon-aec.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/icons/icon-arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-author.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/icon-back-arrow.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-bin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-browser.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/icons/icon-build-colored.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-build.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-caret-down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-caret-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/icon-caret-up.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-code.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/icon-composability.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-developer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/icon-document.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-fast-deliver.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/icons/icon-fast-publish.svg:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/icons/icon-github.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/icons/icon-heart.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-lamp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/icon-linkedin.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/icons/icon-people.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-person.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-scale.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-speed.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-star.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-work-colored.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-work.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-world-colored.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/icons/icon-world.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/img/bg-docs-1080.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/bg-docs-1080.png
--------------------------------------------------------------------------------
/img/bg-docs-assets/bg-docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/bg-docs-assets/bg-docs.png
--------------------------------------------------------------------------------
/img/bg-docs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/bg-docs.png
--------------------------------------------------------------------------------
/img/chrome-web-store.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/img/colorful-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/colorful-bg.jpg
--------------------------------------------------------------------------------
/img/colorful-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/colorful-bg.png
--------------------------------------------------------------------------------
/img/franklin-favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/franklin-favicon.png
--------------------------------------------------------------------------------
/img/icon-admin-html.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-admin-html.png
--------------------------------------------------------------------------------
/img/icon-aec.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-aec.png
--------------------------------------------------------------------------------
/img/icon-build-colored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-build-colored.png
--------------------------------------------------------------------------------
/img/icon-faq.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-faq.png
--------------------------------------------------------------------------------
/img/icon-sidekick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-sidekick.png
--------------------------------------------------------------------------------
/img/icon-status-live.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-status-live.png
--------------------------------------------------------------------------------
/img/icon-work-colored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-work-colored.png
--------------------------------------------------------------------------------
/img/icon-world-colored.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/img/icon-world-colored.png
--------------------------------------------------------------------------------
/libs/highlight/atom-one-dark.min.css:
--------------------------------------------------------------------------------
1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{color:#abb2bf;background:#282c34}.hljs-comment,.hljs-quote{color:#5c6370;font-style:italic}.hljs-doctag,.hljs-formula,.hljs-keyword{color:#c678dd}.hljs-deletion,.hljs-name,.hljs-section,.hljs-selector-tag,.hljs-subst{color:#e06c75}.hljs-literal{color:#56b6c2}.hljs-addition,.hljs-attribute,.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#98c379}.hljs-attr,.hljs-number,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-pseudo,.hljs-template-variable,.hljs-type,.hljs-variable{color:#d19a66}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-selector-id,.hljs-symbol,.hljs-title{color:#61aeee}.hljs-built_in,.hljs-class .hljs-title,.hljs-title.class_{color:#e6c07b}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-link{text-decoration:underline}
--------------------------------------------------------------------------------
/libs/highlight/rainbow.min.css:
--------------------------------------------------------------------------------
1 | pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#474949;color:#d1d9e1}.hljs-comment,.hljs-quote{color:#969896;font-style:italic}.hljs-addition,.hljs-keyword,.hljs-literal,.hljs-selector-tag,.hljs-type{color:#c9c}.hljs-number,.hljs-selector-attr,.hljs-selector-pseudo{color:#f99157}.hljs-doctag,.hljs-regexp,.hljs-string{color:#8abeb7}.hljs-built_in,.hljs-name,.hljs-section,.hljs-title{color:#b5bd68}.hljs-class .hljs-title,.hljs-selector-id,.hljs-template-variable,.hljs-title.class_,.hljs-variable{color:#fc6}.hljs-name,.hljs-section,.hljs-strong{font-weight:700}.hljs-bullet,.hljs-link,.hljs-meta,.hljs-subst,.hljs-symbol{color:#f99157}.hljs-deletion{color:#dc322f}.hljs-formula{background:#eee8d5}.hljs-attr,.hljs-attribute{color:#81a2be}.hljs-emphasis{font-style:italic}
--------------------------------------------------------------------------------
/llms.txt:
--------------------------------------------------------------------------------
1 | # Adobe Experience Manager
2 |
3 | > Edge Delivery Services for Adobe Experience Manager Sites
4 |
5 | Build blazingly fast websites using tools content creators and developers already know.
6 |
7 | ## Docs
8 |
9 | - [Getting Started - Developer Tutorial](https://www.aem.live/developer/tutorial.md)
10 | - [Anatomy of a Project](https://www.aem.live/developer/anatomy-of-a-project.md)
11 | - [Block Collection](https://www.aem.live/developer/block-collection.md)
12 | - [Spreadsheets](https://www.aem.live/developer/spreadsheets.md)
13 | - [Indexing](https://www.aem.live/developer/indexing.md)
14 | - [Web Performance](https://www.aem.live/developer/keeping-it-100.md)
15 | - [Markup - Sections](https://www.aem.live/developer/markup-sections-blocks.md)
16 | - [Favicon](https://www.aem.live/developer/favicon.md)
17 | - [Custom Headers](https://www.aem.live/docs/custom-headers.md)
18 | - [Best Practices for Developers](https://www.aem.live/docs/dev-collab-and-good-practices.md)
19 | - [Where to Author](https://www.aem.live/docs/authoring-guide.md)
20 | - [Authoring](https://www.aem.live/docs/authoring.md)
21 | - [Authoring with AEM](https://www.aem.live/docs/aem-authoring.md)
22 | - [Bulk Metadata](https://www.aem.live/docs/bulk-metadata.md)
23 | - [Placeholders](https://www.aem.live/docs/placeholders.md)
24 | - [Sitemap](https://www.aem.live/developer/sitemap.md)
25 | - [SharePoint Setup](https://www.aem.live/docs/setup-customer-sharepoint.md)
26 | - [Go Live Checklist](https://www.aem.live/docs/go-live-checklist.md)
27 | - [Push Invalidation](https://www.aem.live/docs/setup-byo-cdn-push-invalidation.md)
28 | - [Cloudflare Worker Setup](https://www.aem.live/docs/byo-cdn-cloudflare-worker-setup.md)
29 | - [Akamai Setup](https://www.aem.live/docs/byo-cdn-akamai-setup.md)
30 | - [Fastly Setup](https://www.aem.live/docs/byo-cdn-fastly-setup.md)
31 | - [Redirects](https://www.aem.live/docs/redirects.md)
32 | - [Audit Log](https://www.aem.live/docs/auditlog.md)
33 | - [Using Sidekick](https://www.aem.live/docs/sidekick.md)
34 | - [Sidekick Security](https://www.aem.live/docs/sidekick-security.md)
35 | - [Sidekick for Developers](https://www.aem.live/developer/sidekick-development.md)
36 | - [Sidekick Library](https://www.aem.live/docs/sidekick-library.md)
37 | - [FAQ](https://www.aem.live/docs/faq.md)
38 |
39 | ## API
40 |
41 | - [Admin API](https://www.aem.live/docs/admin.html)
42 | - [aem CLI Reference](https://www.aem.live/developer/cli-reference.md)
43 |
44 | ## Optional
45 |
46 | - [Sidekick Chrome Extension](https://chromewebstore.google.com/detail/aem-sidekick/igkmdomcgoebiipaifhmpfjhbjccggml)
47 | - [Github Bot](https://github.com/apps/aem-code-sync)
48 | - [AEM Tools](https://tools.aem.live/)
49 | - [AEM Labs](https://labs.aem.live/)
50 | - [AEM Status](https://status.adobe.com/products/503489)
51 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "helix-website",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "scripts.js",
6 | "scripts": {
7 | "test": "wtr \"./tests/**/*.test.js\" --node-resolve --port=2000 --coverage",
8 | "test:watch": "npm test -- --watch",
9 | "lint:js": "eslint .",
10 | "lint:css": "stylelint 'blocks/**/*.css' 'styles/*.css'",
11 | "lint": "npm run lint:js && npm run lint:css",
12 | "lint:fix": "eslint --fix --ext .js,.jsx . && npm run lint -- --fix"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/adobe/helix-website.git"
17 | },
18 | "author": "",
19 | "license": "Apache-2.0",
20 | "bugs": {
21 | "url": "https://github.com/adobe/helix-website/issues"
22 | },
23 | "homepage": "https://github.com/adobe/helix-website#readme",
24 | "devDependencies": {
25 | "@adobe/rum-distiller": "1.16.3",
26 | "@babel/core": "7.27.4",
27 | "@babel/eslint-parser": "7.27.5",
28 | "@esm-bundle/chai": "4.3.4-fix.0",
29 | "@web/test-runner": "0.20.2",
30 | "@web/test-runner-commands": "0.9.0",
31 | "eslint": "8.57.1",
32 | "eslint-config-airbnb-base": "15.0.0",
33 | "eslint-plugin-import": "2.31.0",
34 | "husky": "9.1.7",
35 | "lint-staged": "16.1.0",
36 | "sinon": "20.0.0",
37 | "stylelint": "16.20.0",
38 | "stylelint-config-standard": "38.0.0"
39 | },
40 | "lint-staged": {
41 | "*.js": "eslint",
42 | "*.cjs": "eslint",
43 | "*.css": "stylelint"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/plugins/experimentation/.eslintignore:
--------------------------------------------------------------------------------
1 | src/ued.js
--------------------------------------------------------------------------------
/plugins/experimentation/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: 'airbnb-base',
4 | env: {
5 | browser: true,
6 | },
7 | parser: '@babel/eslint-parser',
8 | parserOptions: {
9 | allowImportExportEverywhere: true,
10 | sourceType: 'module',
11 | requireConfigFile: false,
12 | },
13 | rules: {
14 | // allow reassigning param
15 | 'no-param-reassign': [2, { props: false }],
16 | 'linebreak-style': ['error', 'unix'],
17 | 'import/extensions': ['error', {
18 | js: 'always',
19 | }],
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/plugins/experimentation/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-standard"
4 | ],
5 | "rules": {
6 | "declaration-block-no-redundant-longhand-properties": null
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/plugins/experimentation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@adobe/aem-experimentation",
3 | "version": "1.0.0",
4 | "main": "src/index.js",
5 | "scripts": {
6 | "lint:js": "eslint src",
7 | "lint:css": "stylelint src/**/*.css",
8 | "lint": "npm run lint:js && npm run lint:css"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+ssh://git@github.com/adobe/aem-experimentation.git"
13 | },
14 | "author": "Adobe Inc.",
15 | "license": "Apache-2.0",
16 | "keywords": [
17 | "aem",
18 | "experimentation",
19 | "experience",
20 | "decisioning",
21 | "plugin",
22 | "campaigns",
23 | "audiences"
24 | ],
25 | "bugs": {
26 | "url": "https://github.com/adobe/aem-experimentation/issues"
27 | },
28 | "homepage": "https://github.com/adobe/aem-experimentation#readme",
29 | "devDependencies": {
30 | "@babel/eslint-parser": "7.27.5",
31 | "eslint": "8.57.1",
32 | "eslint-config-airbnb-base": "15.0.0",
33 | "eslint-plugin-import": "2.31.0",
34 | "stylelint": "16.20.0",
35 | "stylelint-config-standard": "38.0.0"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/plugins/performance.js:
--------------------------------------------------------------------------------
1 | /*
2 | * lighthouse performance instrumentation helper
3 | * (needs a refactor)
4 | */
5 |
6 | function stamp(message, time = new Date() - performance.timing.navigationStart, type = '') {
7 | if (window.name.includes('performance')) {
8 | // eslint-disable-next-line no-console
9 | const colors = {
10 | general: '#888',
11 | cls: '#c50',
12 | lcp: 'green',
13 | tbt: 'red',
14 | };
15 | const color = colors[type] || '#888';
16 | // eslint-disable-next-line no-console
17 | console.log(
18 | `%c${Math.round(time).toString().padStart(5, ' ')}%c %c${type}%c ${message}`,
19 | 'background-color: #444; padding: 3px; border-radius: 3px;',
20 | '',
21 | `background-color: ${color}; padding: 3px 5px; border-radius: 3px;`,
22 | '',
23 | );
24 | }
25 | }
26 |
27 | function registerPerformanceLogger() {
28 | try {
29 | const polcp = new PerformanceObserver((entryList) => {
30 | const entries = entryList.getEntries();
31 | entries.forEach((entry) => {
32 | stamp(JSON.stringify(entry), entry.startTime, 'lcp');
33 | // eslint-disable-next-line no-console
34 | console.log(entry.element);
35 | });
36 | });
37 | polcp.observe({ type: 'largest-contentful-paint', buffered: true });
38 |
39 | const pols = new PerformanceObserver((entryList) => {
40 | const entries = entryList.getEntries();
41 | entries.forEach((entry) => {
42 | const to = entry.sources[0].currentRect;
43 | const from = entry.sources[0].previousRect;
44 | stamp(`${Math.round(entry.value * 100000) / 100000}
45 | from: ${from.top} ${from.right} ${from.bottom} ${from.left}
46 | to: ${to.top} ${to.right} ${to.bottom} ${to.left}`, entry.startTime, 'cls');
47 | // eslint-disable-next-line no-console
48 | console.log(entry.sources[0].node);
49 | });
50 | });
51 | pols.observe({ type: 'layout-shift', buffered: true });
52 |
53 | const polt = new PerformanceObserver((list) => {
54 | list.getEntries().forEach((entry) => {
55 | // Log the entry and all associated details.
56 | stamp(JSON.stringify(entry), 'tbt', entry.startTime);
57 | });
58 | });
59 |
60 | // Start listening for `longtask` entries to be dispatched.
61 | polt.observe({ type: 'longtask', buffered: true });
62 |
63 | const pores = new PerformanceObserver((entryList) => {
64 | const entries = entryList.getEntries();
65 | entries.forEach((entry) => {
66 | stamp(`${entry.name} loaded`, Math.round(entry.startTime + entry.duration));
67 | });
68 | });
69 |
70 | pores.observe({ type: 'resource', buffered: true });
71 | } catch (e) {
72 | // no output
73 | }
74 | }
75 |
76 | export default function performanceLogger() {
77 | stamp('helix performance logging started');
78 | registerPerformanceLogger();
79 | }
80 |
--------------------------------------------------------------------------------
/scripts/delayed.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/no-cycle
2 | import { sampleRUM } from './lib-franklin.js';
3 |
4 | // Core Web Vitals RUM collection
5 | sampleRUM('cwv');
6 |
7 | // add more delayed functionality here
8 |
9 | const preloaded = new Set();
10 |
11 | async function preloadPage(href) {
12 | const hrefURL = new URL(href);
13 | if (hrefURL.origin === window.location.origin) {
14 | // use speculation rules to preload the page
15 | const script = document.createElement('script');
16 | script.type = 'speculationrules';
17 | script.textContent = JSON.stringify({
18 | prerender: [
19 | {
20 | urls: [`${hrefURL.pathname}${hrefURL.search}`],
21 | },
22 | ],
23 | });
24 | document.head.appendChild(script);
25 | }
26 | }
27 |
28 | const HPADDING = 50; // extend horizontal trigger area by 50px around the link
29 | const VPADDING = 2; // extend vertical trigger area by 10px around the link
30 |
31 | function getIsMouseOverForElement(el) {
32 | const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
33 | const scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
34 |
35 | const rect = el.getBoundingClientRect();
36 |
37 | const top = rect.top + scrollTop - VPADDING;
38 | const left = rect.left + scrollLeft - HPADDING;
39 | const bottom = rect.bottom + scrollTop + VPADDING;
40 | const right = rect.right + HPADDING;
41 |
42 | return (x, y) => {
43 | const st = document.documentElement.scrollTop || document.body.scrollTop;
44 | const sl = document.documentElement.scrollLeft || document.body.scrollLeft;
45 |
46 | const xs = x + sl;
47 | const ys = y + st;
48 |
49 | if (xs >= left
50 | && xs <= right
51 | && ys >= top
52 | && ys <= bottom) {
53 | return el.href;
54 | }
55 | return null;
56 | };
57 | }
58 |
59 | const linksIsMouseOver = [];
60 |
61 | document.querySelectorAll('.side-navigation a[href]').forEach((a) => {
62 | const isMouseOver = getIsMouseOverForElement(a);
63 | linksIsMouseOver.push(isMouseOver);
64 | });
65 |
66 | document.addEventListener('mousemove', (e) => {
67 | const x = e.clientX;
68 | const y = e.clientY;
69 |
70 | linksIsMouseOver.forEach((isMouseOver) => {
71 | const href = isMouseOver(x, y);
72 | if (href && !preloaded.has(href)) {
73 | preloadPage(href);
74 | preloaded.add(href);
75 | }
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/scripts/indexing-test.js:
--------------------------------------------------------------------------------
1 | document.documentElement.classList.add('indexing-test4');
2 | const meta = document.createElement('meta');
3 | meta.name = 'indexing-test';
4 | meta.content = 'indexing-test4';
5 | document.head.appendChild(meta);
6 |
--------------------------------------------------------------------------------
/scripts/redirects.js:
--------------------------------------------------------------------------------
1 | function globToRegex(glob) {
2 | return new RegExp(`^${glob.replace(/\*/g, '(.*)').replace(/\?/g, '(.)').replace(/\//g, '\\/')}$`);
3 | }
4 |
5 | export function activateRedirects(data) {
6 | return data.map((o) => Object.entries(o)
7 | .reduce((acc, [k, v]) => {
8 | if (k.toLowerCase() === 'from') {
9 | acc.from = globToRegex(v);
10 | } else if (k.toLowerCase() === 'to') {
11 | acc.to = (...replacements) => {
12 | replacements.shift();
13 | const result = v.replace(/(\$\d+|\*)/g, (matched) => {
14 | if (matched.startsWith('$')) {
15 | return replacements[matched.slice(1) - 1];
16 | }
17 | if (matched === '*') {
18 | return replacements.shift();
19 | }
20 | return matched;
21 | });
22 | return result;
23 | };
24 | } else if (k.toLowerCase() === 'start') {
25 | acc.start = new Date(
26 | Date.UTC(1899, 11, 30, 0, 0, 0)
27 | + (v - Math.floor(v)) * 86400000 + Math.floor(v) * 86400000,
28 | );
29 | }
30 | return acc;
31 | }, {}));
32 | }
33 | export async function fetchRedirects(path = '/smart-redirects.json') {
34 | const response = await fetch(path);
35 | const redirects = await response.json();
36 | if (redirects.data) {
37 | return activateRedirects(redirects.data);
38 | }
39 | return [];
40 | }
41 |
42 | export async function getRedirect(redirects, path, currentURL) {
43 | const redirect = (await redirects)
44 | .filter((r) => typeof r.start === 'undefined' || r.start.getTime() <= Date.now())
45 | .find((r) => r.from.test(path));
46 | if (redirect) {
47 | const target = redirect.to(path, ...redirect.from.exec(path).slice(1));
48 | const targetURL = new URL(target, currentURL);
49 | // Copy all URL parameters from currentURL to targetURL
50 | currentURL.searchParams.forEach((value, key) => {
51 | targetURL.searchParams.set(key, value);
52 | });
53 |
54 | targetURL.searchParams.set('redirect_from', path);
55 | return targetURL.toString();
56 | }
57 | return null;
58 | }
59 |
60 | export async function applyRedirects(
61 | redirects = fetchRedirects(),
62 | path = window.location.pathname,
63 | ) {
64 | const redirect = await getRedirect(redirects, path, new URL(window.location.href));
65 | if (redirect) {
66 | window.location.replace(redirect);
67 | }
68 | return path;
69 | }
70 |
--------------------------------------------------------------------------------
/styles/lazy-styles.css:
--------------------------------------------------------------------------------
1 | /* adobe clean */
2 | @font-face {
3 | font-family: adobe-clean;
4 | src: url("/fonts/adobe-clean-400.woff2") format("woff2");
5 | font-display: swap;
6 | font-style: normal;
7 | font-weight: 400;
8 | }
9 |
10 | @font-face {
11 | font-family: adobe-clean;
12 | src: url("/fonts/adobe-clean-400-italic.woff2") format("woff2");
13 | font-display: swap;
14 | font-style: italic;
15 | font-weight: 400;
16 | }
17 |
18 | @font-face {
19 | font-family: adobe-clean;
20 | src: url("/fonts/adobe-clean-700.woff2") format("woff2");
21 | font-display: swap;
22 | font-style: normal;
23 | font-weight: 700;
24 | }
25 |
26 | @font-face {
27 | font-family: adobe-clean;
28 | src: url("/fonts/adobe-clean-900.woff2") format("woff2");
29 | font-display: swap;
30 | font-style: normal;
31 | font-weight: 900;
32 | }
33 |
34 | /* source code pro */
35 | @font-face {
36 | font-family: source-code-pro;
37 | src: url("/fonts/source-code-pro-900.otf.woff2") format("woff2");
38 | font-display: swap;
39 | font-style: normal;
40 | font-weight: 900;
41 | }
42 |
43 | @font-face {
44 | font-family: source-code-pro;
45 | src: url("/fonts/source-code-pro-400.otf.woff2") format("woff2");
46 | font-display: swap;
47 | font-style: normal;
48 | font-weight: 400;
49 | }
50 |
51 | @font-face {
52 | font-family: source-code-pro;
53 | src: url("/fonts/source-code-pro-400-italic.otf.woff2") format("woff2");
54 | font-display: swap;
55 | font-style: italic;
56 | font-weight: 400;
57 | }
58 |
59 | @font-face {
60 | font-family: source-code-pro;
61 | src: url("/fonts/source-code-pro-700.otf.woff2") format("woff2");
62 | font-display: swap;
63 | font-style: normal;
64 | font-weight: 700;
65 | }
66 |
67 | @font-face {
68 | font-family: source-code-pro;
69 | src: url("/fonts/source-code-pro-900.otf.woff2") format("woff2");
70 | font-display: swap;
71 | font-style: normal;
72 | font-weight: 900;
73 | }
74 |
--------------------------------------------------------------------------------
/tests/blocks/fragment/fragment.mock.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/tests/blocks/fragment/fragment.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* global describe it */
3 |
4 | import { readFile } from '@web/test-runner-commands';
5 | import { expect } from '@esm-bundle/chai';
6 | import setFragment from '../../../blocks/fragment/fragment.js';
7 |
8 | const mock = await readFile({ path: './fragment.mock.html' });
9 | document.body.innerHTML = mock;
10 |
11 | describe('Fragment loading', async () => {
12 | it('fragment is loaded', async () => {
13 | const fragment = document.querySelector('.fragment');
14 | await setFragment(fragment);
15 | const heading = document.querySelector('.h1-from-fragment');
16 | expect(heading).to.exist;
17 | });
18 |
19 | it('fragment is not loaded', async () => {
20 | const el = document.querySelector('.fragment.nope');
21 | await setFragment(el);
22 | const a = document.querySelector('.nope a');
23 | // fragment not found, not replaced
24 | expect(a).to.exist;
25 | });
26 | });
27 |
--------------------------------------------------------------------------------
/tests/blocks/publication-time/publication-time.mock.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
prefix for relative time
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tests/blocks/publication-time/publication-time.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* global describe it */
3 |
4 | import { readFile } from '@web/test-runner-commands';
5 | import { expect } from '@esm-bundle/chai';
6 | import decorate from '../../../blocks/publication-time/publication-time.js';
7 |
8 | const mock = await readFile({ path: './publication-time.mock.html' });
9 | document.body.innerHTML = mock;
10 |
11 | describe('Publication time block', async () => {
12 | it('has a relative-time element', async () => {
13 | const block = document.querySelector('.block');
14 | decorate(block);
15 | const rt = document.querySelector('relative-time');
16 | const lastMod = new Date(document.lastModified);
17 | const shortDate = lastMod.toLocaleDateString();
18 | expect(rt.getAttribute('datetime')).to.equal(lastMod.toISOString());
19 | expect(rt.textContent).to.equal(shortDate);
20 | expect(block.textContent).to.equal(`prefix for relative time ${shortDate}`);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/tests/blocks/sidekick-generator/sidekick-generator.mock.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Sidekick Configurator
5 |
6 | Form: /tests/blocks/sidekick-generator/sidekick-generator.mock
7 |
8 |
9 | Go
10 |
11 |
12 |
13 |
14 |
15 |
Installation
16 |
Drag the button below to your browser's bookmark bar.
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/tests/blocks/sidekick-generator/sidekick-generator.mock.json:
--------------------------------------------------------------------------------
1 | {
2 | "total": 3,
3 | "offset": 0,
4 | "limit": 3,
5 | "data": [
6 | {
7 | "label": "Repository URL",
8 | "type": "text",
9 | "required": "x",
10 | "name": "giturl",
11 | "placeholder": "https://github.com/..."
12 | },
13 | {
14 | "label": "Project Name",
15 | "type": "text",
16 | "required": "",
17 | "name": "project"
18 | },
19 | {
20 | "label": "Shared Key",
21 | "type": "password",
22 | "required": "",
23 | "name": "token"
24 | },
25 | {
26 | "label": "Helix 3",
27 | "type": "checkbox",
28 | "required": "",
29 | "name": "hlx3",
30 | "placeholder": "",
31 | "checked": "x"
32 | }
33 | ],
34 | ":type": "sheet"
35 | }
--------------------------------------------------------------------------------
/tests/blocks/z-pattern/z-pattern.mock.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
How it works
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
Create content in Microsoft Word or Google Docs.
14 |
15 |
Create a new folder in Google Drive or Microsoft SharePoint. Then create a new document and put some
16 | content in it.
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
Share your folder with Project Helix.
25 |
After you create a page, head back to your folder and share it with Helix.
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Give your site some features.
34 |
Create a new repository in Github. Add styles and scripts to make your site completely bespoke to your
35 | own needs.
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
See your site in action.
44 |
After you have added some content, shared with Project Helix, built a repo, and tied your repo to your
45 | conent, you can see your site in action.
46 |
47 |
48 |
52 |
--------------------------------------------------------------------------------
/tests/blocks/z-pattern/z-pattern.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-expressions */
2 | /* global describe it */
3 |
4 | import { readFile } from '@web/test-runner-commands';
5 | import { expect } from '@esm-bundle/chai';
6 | import init from '../../../blocks/z-pattern/z-pattern.js';
7 |
8 | const mock = await readFile({ path: './z-pattern.mock.html' });
9 | document.body.innerHTML = mock;
10 |
11 | describe('Z Pattern', () => {
12 | it('has a heading', () => {
13 | const zPattern = document.querySelector('.z-pattern');
14 | init(zPattern);
15 | const heading = zPattern.querySelector('.z-pattern-heading');
16 | expect(heading).to.exist;
17 | });
18 |
19 | it('has a trailing cta', () => {
20 | const zPattern = document.querySelector('.z-pattern');
21 | init(zPattern);
22 | const cta = zPattern.querySelector('.z-pattern-cta');
23 | expect(cta).to.exist;
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/tests/scripts/block.mock.plain.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Include me!
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/tests/scripts/config.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Prop 0
4 |
Plain text
5 |
6 |
10 |
11 |
Prop 2
12 |
First paragraph
Second paragraph
13 |
14 |
18 |
25 |
26 |
--------------------------------------------------------------------------------
/tests/scripts/dummy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/tests/scripts/head.html:
--------------------------------------------------------------------------------
1 | Foo
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/scripts/media_mock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adobe/helix-website/16924d81dfe58d90eebd89f670534173c13aa6e2/tests/scripts/media_mock.png
--------------------------------------------------------------------------------
/tests/scripts/mock.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/scripts/redirects.test.js:
--------------------------------------------------------------------------------
1 | import { expect } from '@esm-bundle/chai';
2 | import { readFile } from '@web/test-runner-commands';
3 | import { activateRedirects, getRedirect } from '../../scripts/redirects.js';
4 | /* eslint-env mocha */
5 |
6 | document.body.innerHTML = await readFile({ path: './dummy.html' });
7 | document.head.innerHTML = await readFile({ path: './head.html' });
8 |
9 | describe.only('Redirects', () => {
10 | it('loads redirects', async () => {
11 | const emptyRedirects = [];
12 | const redirects = await activateRedirects(emptyRedirects);
13 | expect(redirects).to.deep.equal(emptyRedirects);
14 | });
15 |
16 | it('applies applies a simple redirect', async () => {
17 | const exampleRedirects = [
18 | {
19 | from: '/foo',
20 | to: '/bar',
21 | start: 1,
22 | },
23 | ];
24 | const redirects = await activateRedirects(exampleRedirects);
25 | const currentURL = new URL('https://example.com/foo');
26 | const redirect = await getRedirect(redirects, '/foo', currentURL);
27 | expect(redirect).to.equal('https://example.com/bar?redirect_from=%2Ffoo');
28 | });
29 |
30 | it('applies applies a redirect with a parameter', async () => {
31 | const exampleRedirects = [
32 | {
33 | from: '/foo/*',
34 | to: '/bar/*',
35 | start: 1,
36 | },
37 | ];
38 | const redirects = await activateRedirects(exampleRedirects);
39 | const currentURL = new URL('https://example.com/foo/baz');
40 | const redirect = await getRedirect(redirects, '/foo/baz', currentURL);
41 | expect(redirect).to.equal('https://example.com/bar/baz?redirect_from=%2Ffoo%2Fbaz');
42 | });
43 |
44 | it('applies applies a redirect with multiple parameters', async () => {
45 | const exampleRedirects = [
46 | {
47 | from: '/foo/*/*',
48 | to: '/bar/*/baz/*',
49 | start: 1,
50 | },
51 | ];
52 | const redirects = await activateRedirects(exampleRedirects);
53 | const currentURL = new URL('https://example.com/foo/fifi/qux');
54 | const redirect = await getRedirect(redirects, '/foo/fifi/qux', currentURL);
55 | expect(redirect).to.equal('https://example.com/bar/fifi/baz/qux?redirect_from=%2Ffoo%2Ffifi%2Fqux');
56 | });
57 |
58 | it('applies applies a redirect with multiple parameters and changed order', async () => {
59 | const exampleRedirects = [
60 | {
61 | from: '/foo/*/*',
62 | to: '/bar/$2/baz/$1',
63 | start: 1,
64 | },
65 | ];
66 | const redirects = await activateRedirects(exampleRedirects);
67 | const currentURL = new URL('https://example.com/foo/fifi/qux');
68 | const redirect = await getRedirect(redirects, '/foo/fifi/qux', currentURL);
69 | expect(redirect).to.equal('https://example.com/bar/qux/baz/fifi?redirect_from=%2Ffoo%2Ffifi%2Fqux');
70 | });
71 | });
72 |
--------------------------------------------------------------------------------
/tests/scripts/test.css:
--------------------------------------------------------------------------------
1 | body {
2 | color: red;
3 | }
4 |
--------------------------------------------------------------------------------
/tools/imports.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | DEPENDENCY="@adobe/rum-distiller"
4 | VERSION_FROM_PACKAGE_JSON=$(node -e "console.log(require('./package.json').devDependencies['@adobe/rum-distiller'])")
5 |
6 | # find all import maps in the project, and replace the import named $1 with the value of $2
7 |
8 | find ./tools/oversight ./tools/rum -name "*.html" | while read file; do
9 | echo "Processing $file upgrading $DEPENDENCY to $VERSION_FROM_PACKAGE_JSON"
10 | # Use sed to replace the import map entry with the new version
11 | sed -i.bak "s|\"@adobe/rum-distiller\": \"https://esm.sh/@adobe/rum-distiller@[0-9.]*\"|\"@adobe/rum-distiller\": \"https://esm.sh/@adobe/rum-distiller@$VERSION_FROM_PACKAGE_JSON\"|g" "$file"
12 | rm "$file.bak"
13 | done
--------------------------------------------------------------------------------
/tools/oversight/charts/chart.js:
--------------------------------------------------------------------------------
1 | export default class AbstractChart {
2 | constructor(dataChunks, elems) {
3 | this.chartConfig = {};
4 | this.dataChunks = dataChunks;
5 | this.elems = elems;
6 | this.chart = {};
7 | }
8 |
9 | set config(config) {
10 | this.chartConfig = config;
11 | }
12 |
13 | get config() {
14 | return this.chartConfig || {};
15 | }
16 |
17 | render() {
18 | throw new Error('render method must be implemented', this);
19 | }
20 |
21 | async draw() {
22 | throw new Error('draw method must be implemented', this);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/tools/oversight/elements/literal-facet.js:
--------------------------------------------------------------------------------
1 | import { escapeHTML } from '../utils.js';
2 | import ListFacet from './list-facet.js';
3 |
4 | /**
5 | * A custom HTML element to display a list of facets with literal
6 | * values. If a placeholder has been provided, then the explanation
7 | * will be shown after the literal value.
8 | *
9 | *
10 | *
11 | */
12 | export default class LiteralFacet extends ListFacet {
13 | // eslint-disable-next-line class-methods-use-this
14 | createLabelHTML(labelText) {
15 | if (this.placeholders && this.placeholders[labelText]) {
16 | return `${labelText}${this.placeholders[labelText]}`;
17 | }
18 | return `${escapeHTML(labelText)}`;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tools/oversight/elements/number-format.js:
--------------------------------------------------------------------------------
1 | import { stats } from '@adobe/rum-distiller';
2 | import { findNearestVulgarFraction } from '../utils.js';
3 |
4 | const { roundToConfidenceInterval, samplingError } = stats;
5 |
6 | export default class NumberFormat extends HTMLElement {
7 | constructor() {
8 | super();
9 | this.mutationObserver = null;
10 | }
11 |
12 | connectedCallback() {
13 | this.mutationObserver = new MutationObserver(() => {
14 | const fv = this.querySelector('span.formatted-value');
15 | if (!fv) this.updateState();
16 | });
17 | this.updateState();
18 | this.mutationObserver.observe(this, { childList: true });
19 | }
20 |
21 | updateState() {
22 | const isPureNumber = !!this.textContent.trim().match(/^\d+(\.\d+)?$/);
23 | const titleValue = parseFloat((this.getAttribute('title') || '').replace(/ .*/g, ''), 10);
24 | const contentValue = parseFloat(this.textContent, 10);
25 | const number = isPureNumber
26 | ? contentValue
27 | : titleValue || contentValue;
28 |
29 | const sampleSize = parseInt(this.getAttribute('sample-size'), 10);
30 | const total = parseInt(this.getAttribute('total'), 10);
31 | const precision = parseInt(this.getAttribute('precision'), 10);
32 | const fuzzy = this.getAttribute('fuzzy') !== 'false';
33 | const fv = document.createElement('span');
34 | fv.classList.add('formatted-value');
35 | if (Number.isNaN(number)) {
36 | fv.textContent = '-';
37 | this.setAttribute('title', 'no data available');
38 | this.replaceChildren(fv);
39 | } else {
40 | fv.textContent = roundToConfidenceInterval(
41 | number,
42 | Number.isNaN(sampleSize) ? 0 : sampleSize,
43 | precision,
44 | fuzzy,
45 | );
46 | this.replaceChildren(fv);
47 |
48 | // set the title to the original number
49 | this.title = number;
50 | if (!Number.isNaN(sampleSize)) {
51 | this.title = `${number} ±${samplingError(number, sampleSize)}`;
52 | }
53 | // remove all classes ending with '-of-total'
54 | Array.from(this.classList)
55 | .filter((cls) => cls.endsWith('-of-total'))
56 | .forEach((cls) => {
57 | this.classList.remove(cls);
58 | });
59 | // if the total is given, add the percentage class
60 | if (!Number.isNaN(total) && total > 0) {
61 | const fraction = number / total;
62 | this.classList.add(`${findNearestVulgarFraction(fraction)}-of-total`);
63 | }
64 | }
65 | if (this.getAttribute('trend')) {
66 | this.title += ` and ${this.getAttribute('trend')}`;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/tools/oversight/elements/thumbnail-facet.js:
--------------------------------------------------------------------------------
1 | import { escapeHTML } from '../utils.js';
2 | import ListFacet from './list-facet.js';
3 |
4 | /**
5 | * A custom HTML element to display a list of facets with thumbnails.
6 | *
7 | *
8 | *
9 | * - desktop
10 | * - Chrome 90.0.4430.93 (Windows 10)
11 | *
12 | *
13 | */
14 | export default class ThumbnailFacet extends ListFacet {
15 | // eslint-disable-next-line class-methods-use-this
16 | createLabelHTML(labelText) {
17 | const fileName = labelText.split('/').pop().replace(/\?.*/, '');
18 | if (labelText.startsWith('https://') && labelText.includes('media_')) {
19 | return `
${escapeHTML(fileName)}`;
20 | } if (labelText.startsWith('http') && labelText.match(/\.(jpeg|jpg|gif|png|svg|webp)$/)) {
21 | return `
${escapeHTML(fileName)}`;
22 | }
23 | return escapeHTML(labelText);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tools/oversight/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@adobe/aem-rum-explorer",
3 | "version": "0.1.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "@adobe/aem-rum-explorer",
9 | "version": "0.1.0",
10 | "dependencies": {
11 | "@adobe/rum-distiller": "1.16.3"
12 | }
13 | },
14 | "node_modules/@adobe/rum-distiller": {
15 | "version": "1.16.3",
16 | "resolved": "https://registry.npmjs.org/@adobe/rum-distiller/-/rum-distiller-1.16.3.tgz",
17 | "integrity": "sha512-cV0VMcIEpveJAlGcJmFmxXwuhHIthx8lABM6e/195XLf6H7UE2V2Bdc5Cby0G32k4Jgk9o10a76gipMOY94PZQ==",
18 | "license": "Apache-2.0"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tools/oversight/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@adobe/aem-rum-explorer",
3 | "version": "0.1.0",
4 | "scripts": {
5 | "test": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=../../lcov.info --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=../../junit.xml"
6 | },
7 | "type": "module",
8 | "dependencies": {
9 | "@adobe/rum-distiller": "1.16.3"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/tools/oversight/test/utils.test.js:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'node:test';
2 | import assert from 'node:assert/strict';
3 | import {
4 | truncate, escapeHTML,
5 | } from '../utils.js';
6 |
7 | describe('truncate', () => {
8 | it('truncates to the beginning of the hour', () => {
9 | const time = new Date('2021-01-01T01:30:00Z');
10 | assert.strictEqual(truncate(time, 'hour'), '2021-01-01T02:00:00+01:00');
11 | });
12 |
13 | it('truncates to the beginning of the day', () => {
14 | const time = new Date('2021-01-01T01:30:00Z');
15 | assert.strictEqual(truncate(time, 'day'), '2021-01-01T00:00:00+01:00');
16 | });
17 |
18 | it('truncates to the beginning of the week', () => {
19 | const time = new Date('2021-01-01T01:30:00Z');
20 | assert.strictEqual(truncate(time, 'week'), '2020-12-27T00:00:00+01:00');
21 | });
22 |
23 | it('truncates to the beginning of the week (May 11th)', () => {
24 | const time = new Date('2024-05-11T00:00:00+02:00');
25 | assert.strictEqual(truncate(time, 'week'), '2024-05-05T00:00:00+02:00');
26 | });
27 |
28 | it('truncates to the beginning of the week (May 12th)', () => {
29 | const time = new Date('2024-05-12T00:00:00+02:00');
30 | assert.strictEqual(truncate(time, 'week'), '2024-05-12T00:00:00+02:00');
31 | });
32 |
33 | it('truncates to the beginning of the week (May 13th)', () => {
34 | const time = new Date('2024-05-13T00:00:00+02:00');
35 | assert.strictEqual(truncate(time, 'week'), '2024-05-12T00:00:00+02:00');
36 | });
37 |
38 | it('truncates to the beginning of the week (May 14th)', () => {
39 | const time = new Date('2024-05-14T00:00:00+02:00');
40 | assert.strictEqual(truncate(time, 'week'), '2024-05-12T00:00:00+02:00');
41 | });
42 | });
43 |
44 | describe('escapeHTML', () => {
45 | it('escapes HTML entities', () => {
46 | assert.strictEqual(escapeHTML(''), '<script>alert("xss")</script>');
47 | assert.strictEqual(escapeHTML(""), '<script>alert('xss')</script>');
48 | assert.strictEqual(escapeHTML('hello
'), '<div>hello</div>');
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/tools/oversight/website.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tools/patches.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | ## do the following to set up the list of patch files
4 |
5 | # OLDBRANCH=$1
6 | # NEWBRANCH=$2
7 |
8 | # git checkout $OLDBRANCH
9 | # git format-patch main
10 | # git checkout main
11 | # git checkout -b $NEWBRANCH
12 | # git checkout $NEWBRANCH
13 |
14 | ## you will now have a directory of files called 0001-your-commit-message.patch
15 | ## that you could, under normal circumstances just apply using `git am`. But
16 | ## we need to change the patch path from tools/rum to tools/oversight so we do
17 | ## that with `sed`
18 |
19 | for patch in 0*-*.patch; do
20 | cat $patch | sed -e "s|tools/rum/|tools/oversight/|g" | git am --3way && rm $patch
21 | done
22 |
23 | ## If any of these patches fails, the script will abort and leave your working copy
24 | ## in a slightly dirty stage. The right way to get out of this is to:
25 | ## fix the merge conflict in your favorite editor
26 | # git add tools/oversight/conflicted.js # tell git that the conflict has been resolved
27 | # git am --continue # reuse the original commit message, author, etc
28 | # rm 0003-the-patch-that-failed.patch # remove the patch file, so that we don't try this again next
29 | # sh patches.sh # apply remaining patches, hope for fewer conflicts
30 |
31 | ## The wrong way to get out of this, but that will still leave you with a clean working copy is
32 | # rm 0*-*.patch # remove all patch files
33 | # git am --abort # forget about this whole `git am` thing
--------------------------------------------------------------------------------
/tools/rum/admin/orgs.css:
--------------------------------------------------------------------------------
1 | #rumadmin {
2 | display: flex;
3 | flex-direction: column;
4 | margin: auto;
5 | max-width: 1440px;
6 | }
7 |
8 | #rumadmin h1.title {
9 | margin: 20px 0;
10 | font-size: 2em;
11 | }
12 |
13 | #rumadmin .topbar {
14 | display: flex;
15 | justify-content: space-between;
16 | }
17 |
18 | #rumadmin .topbar #org-selected {
19 | width: 250px;
20 | }
21 |
22 | #rumadmin .topbar #org-actions {
23 | display: none;
24 | }
25 |
26 | #rumadmin .topbar #org-actions .orgkey {
27 | width: 300px;
28 | }
29 |
30 | #rumadmin #org-details {
31 | margin: 20px 0;
32 | }
33 |
34 | #rumadmin #org-details .empty-message {
35 | text-align: center;
36 | font-style: italic;
37 | }
38 |
39 |
40 | #rumadmin #org-details .domain-row {
41 | display: flex;
42 | flex-direction: row;
43 | padding: 8px;
44 | }
45 |
46 | #rumadmin #org-details .domain-row:nth-child(even) {
47 | background-color: #f9f9f9
48 | }
49 |
50 | #org-details .domain-row .cell-checkbox {
51 | align-content: center;
52 | margin: 0 12px 0 0;
53 | }
54 |
55 | #org-details .domain-row .cell-domain {
56 | flex-grow: 1;
57 | }
58 |
59 | #org-details .domain-row .cell-domain-actions {
60 | align-content: center;
61 | }
62 |
63 | #rumadmin .modal {
64 | position: absolute;
65 | left: 0;
66 | top: 0;
67 | bottom: 0;
68 | right: 0;
69 | z-index: 9999;
70 | background: #00000070;
71 | }
72 |
73 | #rumadmin .modal .modal-content {
74 | display: flex;
75 | flex-direction: column;
76 | min-width: 500px;
77 | width: 30%;
78 | margin: clamp(1rem, 12%, min(15vw, 15rem)) auto;
79 | background-color: #fff;
80 | padding: 0 20px 20px;
81 | }
82 |
83 | #rumadmin .modal .modal-content span.cancel {
84 | align-self: flex-end;
85 | padding: 0 8px;
86 | margin: 8px -8px 0;
87 | cursor: pointer;
88 | }
89 |
90 | #rumadmin .modal .modal-content h2.modal-title {
91 | font-size: 1.5em;
92 | margin: -20px 0 20px;
93 | pointer-events: none;
94 | }
95 |
96 | #rumadmin .modal .modal-content .content {
97 | display: flex;
98 | flex-direction: column;
99 | gap: 8px;
100 | }
101 |
102 | #rumadmin .modal .modal-content .modal-actions {
103 | margin-top: 20px;
104 | }
--------------------------------------------------------------------------------
/tools/rum/admin/orgs.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RUM Admin | Orgs | AEM Live
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Manage Orgs
19 |
34 |
35 |
No org selected
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/tools/rum/charts/chart.js:
--------------------------------------------------------------------------------
1 | export default class AbstractChart {
2 | constructor(dataChunks, elems) {
3 | this.chartConfig = {};
4 | this.dataChunks = dataChunks;
5 | this.elems = elems;
6 | this.chart = {};
7 | }
8 |
9 | set config(config) {
10 | this.chartConfig = config;
11 | }
12 |
13 | render() {
14 | throw new Error('render method must be implemented', this);
15 | }
16 |
17 | async draw() {
18 | throw new Error('draw method must be implemented', this);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tools/rum/elements/facetsidebar.js:
--------------------------------------------------------------------------------
1 | export default class FacetSidebar extends HTMLElement {
2 | constructor() {
3 | super();
4 | this.eventListeners = {};
5 | this.elems = {};
6 | this.predefinedFacets = [];
7 | }
8 |
9 | set data(data) {
10 | this.dataChunks = data;
11 | }
12 |
13 | connectedCallback() {
14 | this.initDOM();
15 | }
16 |
17 | initDOM() {
18 | const predefinedFacets = Array.from(this.querySelectorAll(':scope > *'));
19 | //
20 | //
21 | //
22 | //
23 | //
25 | //
;
26 | const filters = document.createElement('div');
27 | filters.className = 'filters';
28 | const quickFilter = document.createElement('div');
29 | quickFilter.className = 'quick-filter';
30 | const filterInput = document.createElement('input');
31 | filterInput.type = 'text';
32 | filterInput.id = 'filter';
33 | filterInput.placeholder = 'Type to filter...';
34 | quickFilter.append(filterInput);
35 | this.append(quickFilter);
36 | const facetsElement = document.createElement('aside');
37 | facetsElement.id = 'facets';
38 | this.append(facetsElement);
39 |
40 | this.elems.filterInput = filterInput;
41 | this.elems.facetsElement = facetsElement;
42 |
43 | predefinedFacets.forEach((facet) => {
44 | this.elems.facetsElement.append(facet);
45 | });
46 | }
47 |
48 | updateFacets(mode) {
49 | const filterTags = document.querySelector('.filter-tags');
50 | filterTags.textContent = '';
51 | const addFilterTag = (name, value) => {
52 | const tag = document.createElement('span');
53 | if (value) tag.textContent = `${name}: ${value}`;
54 | else tag.textContent = `${name}`;
55 | tag.classList.add(`filter-tag-${name}`);
56 | filterTags.append(tag);
57 | };
58 |
59 | if (this.elems.filterInput.value) addFilterTag('text', this.elems.filterInput.value);
60 |
61 | const existingFacetElements = Array.from(this.elems.facetsElement.children);
62 | existingFacetElements.forEach((facet) => {
63 | facet.setAttribute('mode', 'hidden');
64 | });
65 |
66 | const keys = Object.keys(this.dataChunks.facets)
67 | // only show facets that have no decorators or are not hidden
68 | .filter((key) => key !== 'filter');
69 | keys.forEach((facetName) => {
70 | const facetEl = this.querySelector(`[facet="${facetName}"]`);
71 | // eslint-disable-next-line no-console
72 | console.assert(facetEl, `Facet ${facetName} not found in provided UI elements.`);
73 |
74 | if (facetEl) facetEl.setAttribute('mode', mode || 'default');
75 | if (facetEl) this.elems.facetsElement.append(facetEl);
76 | });
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/tools/rum/elements/file-facet.js:
--------------------------------------------------------------------------------
1 | import { escapeHTML } from '../utils.js';
2 | import ListFacet from './list-facet.js';
3 |
4 | /**
5 | * A custom HTML element to display a list of facets with literal
6 | * values. If a placeholder has been provided, then the explanation
7 | * will be shown after the literal value.
8 | *
9 | *
10 | *
11 | */
12 | export default class FileFacet extends ListFacet {
13 | // eslint-disable-next-line class-methods-use-this
14 | createLabelHTML(labelText) {
15 | return `${escapeHTML(labelText)}`;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tools/rum/elements/link-facet.js:
--------------------------------------------------------------------------------
1 | import { escapeHTML } from '../utils.js';
2 | import ListFacet from './list-facet.js';
3 |
4 | function labelURLParts(url) {
5 | const u = new URL(url);
6 | return ['protocol', 'hostname', 'port', 'pathname', 'search', 'hash']
7 | .reduce((acc, part) => `${acc}${u[part]}`, '');
8 | }
9 |
10 | /**
11 | * A custom HTML element to display a list of facets with links.
12 | *
13 | *
14 | *
15 | */
16 | export default class LinkFacet extends ListFacet {
17 | // eslint-disable-next-line class-methods-use-this
18 | createLabelHTML(labelText) {
19 | const thumbnailAtt = this.getAttribute('thumbnail') === 'true';
20 | const faviconAtt = this.getAttribute('favicon') === 'true';
21 | if (thumbnailAtt && labelText.startsWith('https://')) {
22 | const u = new URL('https://www.aem.live/tools/rum/_ogimage');
23 | u.searchParams.set('proxyurl', labelText);
24 | return `${labelURLParts(labelText)}`;
25 | }
26 | if (thumbnailAtt && (labelText.startsWith('http://') || labelText.startsWith('https://') || labelText.startsWith('android-app://'))) {
27 | const u = new URL('https://www.aem.live/tools/rum/_ogimage');
28 | u.searchParams.set('proxyurl', labelText);
29 | return `
30 |
31 | ${labelURLParts(labelText)}`;
32 | }
33 | if (labelText.startsWith('https://') || labelText.startsWith('http://')) {
34 | return `${labelText}`;
35 | }
36 | if (labelText.startsWith('referrer:')) {
37 | return `${labelText.replace('referrer:', '')}`;
38 | }
39 | const currentURL = new URL(window.location.href);
40 | const domain = currentURL.searchParams.get('domain');
41 | if (labelText.startsWith('navigate:')) {
42 | return `navigate from ${labelText.replace('navigate:', '')}`;
43 | }
44 | if (this.placeholders && this.placeholders[labelText]) {
45 | return (`${this.placeholders[labelText]} [${labelText}]`);
46 | }
47 | if (domain.endsWith(':all')) {
48 | currentURL.searchParams.set('domain', labelText);
49 | currentURL.searchParams.delete('domainkey');
50 | return `${labelText}`;
51 | }
52 | return escapeHTML(labelText);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/tools/rum/elements/literal-facet.js:
--------------------------------------------------------------------------------
1 | import { escapeHTML } from '../utils.js';
2 | import ListFacet from './list-facet.js';
3 |
4 | /**
5 | * A custom HTML element to display a list of facets with literal
6 | * values. If a placeholder has been provided, then the explanation
7 | * will be shown after the literal value.
8 | *
9 | *
10 | *
11 | */
12 | export default class LiteralFacet extends ListFacet {
13 | // eslint-disable-next-line class-methods-use-this
14 | createLabelHTML(labelText) {
15 | if (this.placeholders && this.placeholders[labelText]) {
16 | return `${labelText}${this.placeholders[labelText]}`;
17 | }
18 | return `${escapeHTML(labelText)}`;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tools/rum/elements/thumbnail-facet.js:
--------------------------------------------------------------------------------
1 | import { escapeHTML } from '../utils.js';
2 | import ListFacet from './list-facet.js';
3 |
4 | /**
5 | * A custom HTML element to display a list of facets with thumbnails.
6 | *
7 | *
8 | *
9 | * - desktop
10 | * - Chrome 90.0.4430.93 (Windows 10)
11 | *
12 | *
13 | */
14 | export default class ThumbnailFacet extends ListFacet {
15 | // eslint-disable-next-line class-methods-use-this
16 | createLabelHTML(labelText) {
17 | const fileName = labelText.split('/').pop().replace(/\?.*/, '');
18 | const isMediaStr = labelText.startsWith('media_');
19 | if ((labelText.startsWith('https://') && labelText.includes('media_')) || isMediaStr) {
20 | let src = labelText;
21 | if (isMediaStr) {
22 | const domain = new URL(window.location.href).searchParams.get('domain');
23 | // cannot work in all cases (context path...), but good enough for now
24 | src = `https://${domain}/${labelText}`;
25 | }
26 | return `
${escapeHTML(fileName)}`;
27 | } if (labelText.startsWith('http') && labelText.match(/\.(jpeg|jpg|gif|png|svg|webp)$/)) {
28 | return `
${escapeHTML(fileName)}`;
29 | }
30 | return escapeHTML(labelText);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/tools/rum/elements/vitals-facet.js:
--------------------------------------------------------------------------------
1 | import { toHumanReadable } from '../utils.js';
2 |
3 | export default class VitalsFacet extends HTMLElement {
4 | constructor() {
5 | super();
6 | this.placeholders = {};
7 | }
8 |
9 | get dataChunks() {
10 | return this.parentElement.parentElement.dataChunks;
11 | }
12 |
13 | connectedCallback() {
14 | if (this.dataChunks) this.update();
15 | }
16 |
17 | update() {
18 | const facetName = this.getAttribute('facet');
19 |
20 | const u = new URL(window.location.href);
21 | const { searchParams } = u;
22 |
23 | const facetEntries = this.dataChunks.facets[facetName];
24 |
25 | const fieldSet = this.querySelector('fieldset') || document.createElement('fieldset');
26 | const legendText = this.querySelector('legend')?.textContent || facetName;
27 |
28 | fieldSet.textContent = '';
29 |
30 | facetEntries.forEach(({ name, value, weight }) => {
31 | const div = document.createElement('div');
32 | div.classList.add('facet-entry');
33 | div.dataset.name = name;
34 | div.dataset.value = value;
35 | div.dataset.weight = weight;
36 | const label = document.createElement('label');
37 | label.textContent = `${value.replace(/(good|poor|ni)(.+)$/, (_, m, metric) => {
38 | if (m === 'ni') return `${metric} ni `;
39 | return `${metric} ${m}`;
40 | })}: ${toHumanReadable(weight)}`;
41 | label.htmlFor = `${name}=${value}`;
42 | const input = document.createElement('input');
43 | input.type = 'checkbox';
44 | input.id = `${name}=${value}`;
45 | input.value = value;
46 | input.checked = searchParams.has(facetName) && searchParams.getAll(facetName).includes(value);
47 |
48 | input.addEventListener('change', (evt) => {
49 | evt.stopPropagation();
50 | this.parentElement.parentElement.dispatchEvent(new Event('facetchange'));
51 | });
52 |
53 | div.append(input, label);
54 |
55 | fieldSet.append(div);
56 | });
57 |
58 | const legend = this.querySelector('legend') || document.createElement('legend');
59 | legend.textContent = legendText;
60 | fieldSet.append(legend);
61 | // append fieldset
62 | this.append(fieldSet);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/tools/rum/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@adobe/aem-rum-explorer",
3 | "version": "0.1.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "@adobe/aem-rum-explorer",
9 | "version": "0.1.0",
10 | "dependencies": {
11 | "@adobe/rum-distiller": "1.16.3"
12 | }
13 | },
14 | "node_modules/@adobe/rum-distiller": {
15 | "version": "1.16.3",
16 | "resolved": "https://registry.npmjs.org/@adobe/rum-distiller/-/rum-distiller-1.16.3.tgz",
17 | "integrity": "sha512-cV0VMcIEpveJAlGcJmFmxXwuhHIthx8lABM6e/195XLf6H7UE2V2Bdc5Cby0G32k4Jgk9o10a76gipMOY94PZQ==",
18 | "license": "Apache-2.0"
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/tools/rum/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@adobe/aem-rum-explorer",
3 | "version": "0.1.0",
4 | "scripts": {
5 | "test": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=../../lcov.info --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=../../junit.xml"
6 | },
7 | "type": "module",
8 | "dependencies": {
9 | "@adobe/rum-distiller": "1.16.3"
10 | }
11 | }
--------------------------------------------------------------------------------
/tools/rum/test/utils.test.js:
--------------------------------------------------------------------------------
1 | import { describe, it } from 'node:test';
2 | import assert from 'node:assert/strict';
3 | import { truncate, escapeHTML } from '../utils.js';
4 |
5 | describe('truncate', () => {
6 | it('truncates to the beginning of the hour', () => {
7 | const time = new Date('2021-01-01T01:30:00Z');
8 | assert.strictEqual(truncate(time, 'hour'), '2021-01-01T02:00:00+01:00');
9 | });
10 |
11 | it('truncates to the beginning of the day', () => {
12 | const time = new Date('2021-01-01T01:30:00Z');
13 | assert.strictEqual(truncate(time, 'day'), '2021-01-01T00:00:00+01:00');
14 | });
15 |
16 | it('truncates to the beginning of the week', () => {
17 | const time = new Date('2021-01-01T01:30:00Z');
18 | assert.strictEqual(truncate(time, 'week'), '2020-12-27T00:00:00+01:00');
19 | });
20 |
21 | it('truncates to the beginning of the week (May 11th)', () => {
22 | const time = new Date('2024-05-11T00:00:00+02:00');
23 | assert.strictEqual(truncate(time, 'week'), '2024-05-05T00:00:00+02:00');
24 | });
25 |
26 | it('truncates to the beginning of the week (May 12th)', () => {
27 | const time = new Date('2024-05-12T00:00:00+02:00');
28 | assert.strictEqual(truncate(time, 'week'), '2024-05-12T00:00:00+02:00');
29 | });
30 |
31 | it('truncates to the beginning of the week (May 13th)', () => {
32 | const time = new Date('2024-05-13T00:00:00+02:00');
33 | assert.strictEqual(truncate(time, 'week'), '2024-05-12T00:00:00+02:00');
34 | });
35 |
36 | it('truncates to the beginning of the week (May 14th)', () => {
37 | const time = new Date('2024-05-14T00:00:00+02:00');
38 | assert.strictEqual(truncate(time, 'week'), '2024-05-12T00:00:00+02:00');
39 | });
40 | });
41 |
42 | describe('escapeHTML', () => {
43 | it('escapes HTML entities', () => {
44 | assert.strictEqual(escapeHTML(''), '<script>alert("xss")</script>');
45 | assert.strictEqual(escapeHTML(""), '<script>alert('xss')</script>');
46 | assert.strictEqual(escapeHTML('hello
'), '<div>hello</div>');
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tools/rum/website.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tools/sidekick/lib/process-queue.js:
--------------------------------------------------------------------------------
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 | // Copy of: https://github.com/adobe/helix-shared/blob/main/packages/helix-shared-process-queue/src/process-queue.js
14 |
15 | /**
16 | * Simple dequeing iterator.
17 | * @param queue
18 | * @returns {Generator<*, void, *>}
19 | */
20 | function* dequeue(queue) {
21 | while (queue.length) {
22 | yield queue.shift();
23 | }
24 | }
25 |
26 | /**
27 | * Processes the given queue concurrently. The handler functions can add more items to the queue
28 | * if needed.
29 | *
30 | * @param {Iterable|Array} queue A list of tasks
31 | * @param {ProcessQueueHandler} fn A handler function `fn(task:any, queue:array, results:array)`
32 | * @param {number} [maxConcurrent = 8] Concurrency level
33 | * @returns {Promise<[]>} the results
34 | */
35 | export async function processQueue(queue, fn, maxConcurrent = 8) {
36 | if (typeof queue !== 'object') {
37 | throw Error('invalid queue argument: iterable expected');
38 | }
39 |
40 | const running = [];
41 | const results = [];
42 |
43 | const handler = (entry) => {
44 | const task = fn(entry, queue, results);
45 | if (task?.then) {
46 | running.push(task);
47 | task
48 | .then((r) => {
49 | if (r !== undefined) {
50 | results.push(r);
51 | }
52 | })
53 | .catch(() => {})
54 | .finally(() => {
55 | running.splice(running.indexOf(task), 1);
56 | });
57 | } else if (task !== undefined) {
58 | results.push(task);
59 | }
60 | };
61 |
62 | const iter = Array.isArray(queue)
63 | ? dequeue(queue)
64 | : queue;
65 | if (!iter || !('next' in iter)) {
66 | throw Error('invalid queue argument: iterable expected');
67 | }
68 |
69 | for await (const value of iter) {
70 | while (running.length >= maxConcurrent) {
71 | // eslint-disable-next-line no-await-in-loop
72 | await Promise.race(running);
73 | }
74 | handler(value);
75 | }
76 | // wait until remaining tasks have completed
77 | await Promise.all(running);
78 | return results;
79 | }
80 |
--------------------------------------------------------------------------------
/tools/sidekick/library/events/events.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 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 | export const APP_EVENTS = {
14 | TOAST: 'Toast',
15 | LIBRARY_LOADED: 'LibraryLoaded',
16 | PLUGIN_LOADED: 'PluginLoaded',
17 | PLUGIN_UNLOADED: 'PluginUnloaded',
18 | LOCALE_SET: 'LocaleSet',
19 | SEARCH_UPDATED: 'SearchUpdated',
20 | ON_ACTION: 'OnAction',
21 | };
22 |
23 | export const PLUGIN_EVENTS = {
24 | TOAST: 'Toast',
25 | SHOW_LOADER: 'ShowLoader',
26 | HIDE_LOADER: 'HideLoader',
27 | };
28 |
--------------------------------------------------------------------------------
/tools/sidekick/library/focus-visible.js:
--------------------------------------------------------------------------------
1 | var y=(s,d)=>()=>(d||s((d={exports:{}}).exports,d),d.exports);var w=y((L,b)=>{var u;u=function(){function s(o){var i=!0,a=!1,m=null,p={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function c(e){return!!(e&&e!==document&&e.nodeName!=="HTML"&&e.nodeName!=="BODY"&&"classList"in e&&"contains"in e.classList)}function v(e){e.classList.contains("focus-visible")||(e.classList.add("focus-visible"),e.setAttribute("data-focus-visible-added",""))}function r(e){i=!1}function l(){document.addEventListener("mousemove",t),document.addEventListener("mousedown",t),document.addEventListener("mouseup",t),document.addEventListener("pointermove",t),document.addEventListener("pointerdown",t),document.addEventListener("pointerup",t),document.addEventListener("touchmove",t),document.addEventListener("touchstart",t),document.addEventListener("touchend",t)}function t(e){e.target.nodeName&&e.target.nodeName.toLowerCase()==="html"||(i=!1,document.removeEventListener("mousemove",t),document.removeEventListener("mousedown",t),document.removeEventListener("mouseup",t),document.removeEventListener("pointermove",t),document.removeEventListener("pointerdown",t),document.removeEventListener("pointerup",t),document.removeEventListener("touchmove",t),document.removeEventListener("touchstart",t),document.removeEventListener("touchend",t))}document.addEventListener("keydown",function(e){e.metaKey||e.altKey||e.ctrlKey||(c(o.activeElement)&&v(o.activeElement),i=!0)},!0),document.addEventListener("mousedown",r,!0),document.addEventListener("pointerdown",r,!0),document.addEventListener("touchstart",r,!0),document.addEventListener("visibilitychange",function(e){document.visibilityState==="hidden"&&(a&&(i=!0),l())},!0),l(),o.addEventListener("focus",function(e){var n,f,E;c(e.target)&&(i||(n=e.target,f=n.type,(E=n.tagName)==="INPUT"&&p[f]&&!n.readOnly||E==="TEXTAREA"&&!n.readOnly||n.isContentEditable))&&v(e.target)},!0),o.addEventListener("blur",function(e){var n;c(e.target)&&(e.target.classList.contains("focus-visible")||e.target.hasAttribute("data-focus-visible-added"))&&(a=!0,window.clearTimeout(m),m=window.setTimeout(function(){a=!1},100),(n=e.target).hasAttribute("data-focus-visible-added")&&(n.classList.remove("focus-visible"),n.removeAttribute("data-focus-visible-added")))},!0),o.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&o.host?o.host.setAttribute("data-js-focus-visible",""):o.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window<"u"&&typeof document<"u"){var d;window.applyFocusVisiblePolyfill=s;try{d=new CustomEvent("focus-visible-polyfill-ready")}catch(o){(d=document.createEvent("CustomEvent")).initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(d)}typeof document<"u"&&s(document)},typeof L=="object"&&typeof b<"u"?u():typeof define=="function"&&define.amd?define(u):u()});export default w();
2 | //# sourceMappingURL=focus-visible.js.map
3 |
--------------------------------------------------------------------------------
/tools/sidekick/library/locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "appTitle": {
3 | "message": "AEM Sidekick Library"
4 | },
5 | "unknownPlugin": {
6 | "message": "Unknown plugin"
7 | },
8 | "invalidConfiguration": {
9 | "message": "Invalid Configuration"
10 | },
11 | "invalidConfigurationDescription": {
12 | "message": "The library is misconfigured"
13 | },
14 | "errorLoadingPlugin": {
15 | "message": "Error loading plugin"
16 | },
17 | "errorLoadingLibraryJSON": {
18 | "message": "Error loading library JSON"
19 | },
20 | "generativeTextPrompt": {
21 | "message": "What would you like to say?"
22 | },
23 | "generativeTextCTA": {
24 | "message": "Generate"
25 | },
26 | "search": {
27 | "message": "Search"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tools/sidekick/library/plugins/blocks/blocks.css:
--------------------------------------------------------------------------------
1 | .message-container {
2 | width: 100%;
3 | display: flex;
4 | justify-content: center;
5 | align-items: center;
6 | margin-top: 65px;
7 | }
8 |
9 | .block-library {
10 | height: 100%;
11 | }
12 |
13 | sp-split-view {
14 | height: 100%;
15 | }
16 |
17 | .menu {
18 | height: 100%;
19 | overflow-y: hidden;
20 | display: flex;
21 | flex-direction: column;
22 | }
23 |
24 | .menu .list-container {
25 | height: 100%;
26 | overflow-y: auto;
27 | }
28 |
29 | .search {
30 | padding: 10px;
31 | }
32 |
33 | .search sp-search {
34 | width: 100%;
35 | }
36 |
37 | .details-container {
38 | width: auto;
39 | display: flex;
40 | flex-direction: column;
41 | background-color: var(--spectrum-global-color-gray-75);
42 | overflow: hidden;
43 | }
44 |
45 | .details-container .details {
46 | height: 100%;
47 | padding: 0 16px 0 16px;
48 | overflow-y: auto;
49 | }
50 |
51 | .details-container .actions {
52 | display: flex;
53 | justify-content: flex-end;
54 | }
55 |
56 | .frame-view {
57 | outline: 1px solid transparent;
58 | margin: 0 auto;
59 | background-color: #fff;
60 | transition: 0.2s ease-in-out;
61 | width: 100%;
62 | overflow: hidden;
63 | }
64 |
65 | .frame-view block-renderer {
66 | border: 0;
67 | width: 100%;
68 | height: 100%;
69 | }
70 |
71 |
72 | .frame-view {
73 | height: 100%;
74 | }
75 |
76 | .view {
77 | display: flex;
78 | flex-direction: column;
79 | overflow-y: hidden;
80 | }
81 |
82 | .view .action-bar {
83 | display: flex;
84 | flex-direction: column;
85 | align-items: center;
86 | }
87 |
88 | .view .action-bar sp-action-group{
89 | padding: 10px;
90 | }
91 |
92 | .details-container .action-bar {
93 | display: flex;
94 | justify-content: space-between;
95 | align-items: center;
96 | background-color: var(--spectrum-global-color-gray-75);
97 | padding: 10px 16px;
98 | }
99 |
100 | .details-container .action-bar h3 {
101 | margin: 0;
102 | }
--------------------------------------------------------------------------------
/tools/sidekick/library/utils/rum.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 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 | export function sampleRUM(checkpoint, data = {}) {
14 | sampleRUM.defer = sampleRUM.defer || [];
15 | const defer = (fnname) => {
16 | sampleRUM[fnname] = sampleRUM[fnname]
17 | || ((...args) => sampleRUM.defer.push({ fnname, args }));
18 | };
19 | sampleRUM.drain = sampleRUM.drain
20 | || ((dfnname, fn) => {
21 | sampleRUM[dfnname] = fn;
22 | sampleRUM.defer
23 | .filter(({ fnname }) => dfnname === fnname)
24 | .forEach(({ fnname, args }) => sampleRUM[fnname](...args));
25 | });
26 | sampleRUM.on = (chkpnt, fn) => { sampleRUM.cases[chkpnt] = fn; };
27 | defer('observe');
28 | defer('cwv');
29 | try {
30 | window.hlx = window.hlx || {};
31 | if (!window.hlx.rum) {
32 | const usp = new URLSearchParams(window.location.search);
33 | const weight = (usp.get('rum') === 'on') ? 1 : 100; // with parameter, weight is 1. Defaults to 100.
34 | // eslint-disable-next-line no-bitwise
35 | const hashCode = s => s.split('').reduce((a, b) => (((a << 5) - a) + b.charCodeAt(0)) | 0, 0);
36 | const id = `${hashCode(window.location.href)}-${new Date().getTime()}-${Math.random().toString(16).substr(2, 14)}`;
37 | const random = Math.random();
38 | const isSelected = (random * weight < 1);
39 | // eslint-disable-next-line object-curly-newline
40 | window.hlx.rum = { weight, id, random, isSelected, sampleRUM };
41 | }
42 | const { weight, id } = window.hlx.rum;
43 | if (window.hlx && window.hlx.rum && window.hlx.rum.isSelected) {
44 | const sendPing = (pdata = data) => {
45 | // eslint-disable-next-line object-curly-newline, max-len, no-use-before-define
46 | const body = JSON.stringify({ weight, id, referer: window.location.href, checkpoint, ...data });
47 | const url = `https://rum.hlx.page/.rum/${weight}`;
48 | // eslint-disable-next-line no-unused-expressions
49 | navigator.sendBeacon(url, body);
50 | // eslint-disable-next-line no-console
51 | console.debug(`ping:${checkpoint}`, pdata);
52 | };
53 | sendPing(data);
54 | if (sampleRUM.cases[checkpoint]) { sampleRUM.cases[checkpoint](); }
55 | }
56 | } catch (error) {
57 | // something went wrong
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/tools/sidekick/view/json/json.css:
--------------------------------------------------------------------------------
1 | .hlx-sk-view {
2 | background-color: var(--hlx-sk-bg);
3 | color: var(--hlx-sk-color);
4 | padding: 10px 20px;
5 | margin: 0;
6 | }
7 |
8 | .hlx-sk-view h2 {
9 | margin: 10px 0;
10 | font-size: 1.25rem;
11 | min-height: 1.25rem;
12 | }
13 |
14 | .hlx-sk-view table {
15 | width: 100%;
16 | border-collapse: collapse;
17 | text-align: left;
18 | margin-bottom: 20px;
19 | }
20 |
21 | .hlx-sk-view table tr {
22 | vertical-align: top;
23 | }
24 |
25 |
26 | .hlx-sk-view table th {
27 | background-color: var(--hlx-sk-bg);
28 | padding: 5px;
29 | }
30 |
31 | .hlx-sk-view table,
32 | .hlx-sk-view table tr,
33 | .hlx-sk-view table th,
34 | .hlx-sk-view table td {
35 | border: solid 1px var(--hlx-sk-special-view-table-border);
36 | }
37 |
38 | .hlx-sk-view table td > div {
39 | padding: 5px;
40 | min-height: 0.875rem;
41 | font-size: 0.875rem;
42 | }
43 |
44 | .hlx-sk-view table td > div.image,
45 | .hlx-sk-view table td > div.video {
46 | padding: 0;
47 | }
48 |
49 | .hlx-sk-view table td > div img,
50 | .hlx-sk-view table td > div video {
51 | width: 100%;
52 | max-width: 240px;
53 | }
54 |
55 | .hlx-sk-view table td > div ul {
56 | margin: 0;
57 | padding-inline-start: 20px;
58 | }
59 |
60 | .hlx-sk-view table td > div li {
61 | padding: 0;
62 | }
63 |
64 | .hlx-sk-view table td > div a:any-link {
65 | color: var(--hlx-sk-color);
66 | text-decoration: none;
67 | }
68 |
69 | .hlx-sk-view table td > div a:hover {
70 | text-decoration: underline;
71 | }
72 |
--------------------------------------------------------------------------------
/tools/sidekick/view/json/json.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/utils/console.js:
--------------------------------------------------------------------------------
1 | export default function debug(message) {
2 | const { hostname } = window.location;
3 | const usp = new URLSearchParams(window.location.search);
4 | if (usp.has('helix-debug') || hostname === 'localhost') {
5 | // eslint-disable-next-line no-console
6 | console.log(message);
7 | return message;
8 | }
9 | return null;
10 | }
11 |
--------------------------------------------------------------------------------
/utils/env.js:
--------------------------------------------------------------------------------
1 | function setHelixEnv(name) {
2 | if (name) {
3 | sessionStorage.setItem('helix-env', name);
4 | } else {
5 | sessionStorage.removeItem('helix-env');
6 | }
7 | }
8 |
9 | export async function displayEnv() {
10 | try {
11 | const usp = new URLSearchParams(window.location.search);
12 | const env = usp.get('helix-env');
13 | if (env) { setHelixEnv(env); }
14 | } catch (e) {
15 | const { debug } = await import('./console.js');
16 | debug(`display env failed: ${e.message}`);
17 | }
18 | }
19 |
20 | /**
21 | * Get the current Helix environment
22 | * @returns {Object} the env object
23 | */
24 | export function getEnv() {
25 | let envName = sessionStorage.getItem('helix-env');
26 | if (!envName) envName = 'prod';
27 | const envs = {
28 | stage: {
29 | ims: 'stg1',
30 | adminconsole: 'stage.adminconsole.adobe.com',
31 | account: 'stage.account.adobe.com',
32 | target: false,
33 | },
34 | prod: {
35 | ims: 'prod',
36 | adminconsole: 'adminconsole.adobe.com',
37 | account: 'account.adobe.com',
38 | target: true,
39 | },
40 | };
41 | const env = envs[envName];
42 | if (env) {
43 | env.name = envName;
44 | }
45 | return env;
46 | }
47 |
--------------------------------------------------------------------------------
/utils/property.js:
--------------------------------------------------------------------------------
1 | const getProperty = (object, objectPath) => {
2 | const pathArray = objectPath.split('.');
3 | return pathArray.reduce((acc, part) => acc && acc[part], object);
4 | };
5 |
6 | const getObjectProperty = (property, timeout) => new Promise((resolve) => {
7 | let i = 0;
8 | const interval = 10;
9 | const refreshId = setInterval(() => {
10 | const prop = getProperty(window, property);
11 | if (prop !== null && typeof prop !== 'undefined') {
12 | resolve(prop);
13 | clearInterval(refreshId);
14 | } else if (i >= timeout) {
15 | resolve(null);
16 | clearInterval(refreshId);
17 | }
18 | i += interval;
19 | }, interval);
20 | });
21 |
22 | export default getObjectProperty;
23 |
--------------------------------------------------------------------------------
/utils/tag.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Create an element with ID, class, children, and attributes
3 | * @param {String} tag the tag nav of the element
4 | * @param {Object} attributes the attributes of the tag
5 | * @param {HTMLElement} html the content of the element
6 | * @returns {HTMLElement} the element created
7 | */
8 | export default function createTag(tag, attributes, html) {
9 | const el = document.createElement(tag);
10 | if (html) {
11 | if (html instanceof HTMLElement) {
12 | el.append(html);
13 | } else {
14 | el.insertAdjacentHTML('beforeend', html);
15 | }
16 | }
17 | if (attributes) {
18 | Object.keys(attributes).forEach((key) => {
19 | el.setAttribute(key, attributes[key]);
20 | });
21 | }
22 | return el;
23 | }
24 |
--------------------------------------------------------------------------------