diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2aa098362a..874cbf56c2 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,10 +1,8 @@ name: Publish on: - push: - branches: - - main - - test-release + release: + types: [created] jobs: publish: runs-on: ubuntu-latest diff --git a/.github/workflows/studio.yml b/.github/workflows/studio.yml new file mode 100644 index 0000000000..cfe1524300 --- /dev/null +++ b/.github/workflows/studio.yml @@ -0,0 +1,81 @@ +name: studio-nuxt-build +run-name: studio nuxt build + +on: + # Runs on pushes targeting the default branch + push: + branches: + - 'develop' + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Add write workflow permissions +permissions: + contents: write + +# Allow one concurrent deployment +concurrency: + group: 'pages' + cancel-in-progress: true + +jobs: + # Build job + build-and-deploy: + runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: docs + + strategy: + matrix: + os: [ubuntu-latest] + node: [18] + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Identify package manager + id: pkgman + run: | + cache=`[ -f "docs/pnpm-lock.yaml" ] && echo "pnpm" || ([ -f "docs/package-lock.json" ] && echo "npm" || ([ -f "docs/yarn.lock" ] && echo "yarn" || echo ""))` + package_manager=`[ ! -z "$cache" ] && echo "$cache" || echo "pnpm"` + echo "cache=$cache" >> $GITHUB_OUTPUT + echo "package_manager=$package_manager" >> $GITHUB_OUTPUT + + - uses: pnpm/action-setup@v2.4.0 + if: ${{ steps.pkgman.outputs.package_manager == 'pnpm' }} + name: Install pnpm + id: pnpm-install + with: + version: 8.6.2 + + - uses: actions/setup-node@v3 + with: + version: ${{ matrix.node }} + cache: ${{ steps.pkgman.outputs.cache }} + + - name: Install dependencies + run: ${{ steps.pkgman.outputs.package_manager }} install + + - name: Install @nuxthq/studio + run: ${{ steps.pkgman.outputs.package_manager }} add -D @nuxthq/studio + + - name: Create .nuxtrc + run: echo '\nautoImport=true\nmodules[]=@nuxthq/studio' >> .nuxtrc + + - name: Generate + run: npx nuxi generate + env: + NUXT_PUBLIC_STUDIO_API_URL: https://api.nuxt.studio + NUXT_PUBLIC_STUDIO_TOKENS: 7ae2515146ef474b6daa2184d4cdf98a00e724791e002e791a928ee41fe5e267 + + - name: Add .nojekyll file + run: touch .output/public/.nojekyll + + # Deployment job + - name: Deploy ๐Ÿš€ + uses: JamesIves/github-pages-deploy-action@v4 + with: + folder: docs/.output/public diff --git a/.vscode/settings.json b/.vscode/settings.json index 3b1c906757..12f813e584 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,7 +27,8 @@ "nuxt-ripple", "@dpc-sdp/nuxt-ripple-cli", "@dpc-sdp/ripple-test-utils", - "@dpc-sdp/nuxt-ripple-analytics" + "@dpc-sdp/nuxt-ripple-analytics", + "eslint-config-ripple" ], "cSpell.words": [ "colour", diff --git a/CHANGELOG.md b/CHANGELOG.md index 84bd2e85ab..fb9ef2f650 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,347 @@ # Changelog +## v2.4.2 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.4.1...v2.4.2) + + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/ripple-tide-landing-page:** ๐Ÿ› handle case when no items in carousel ([e9a1af40](https://github.com/dpc-sdp/ripple-framework/commit/e9a1af40)) + +### โค๏ธ Contributors + +- Dylan Kelly + +## v2.4.1 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.4.0...v2.4.1) + + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/nuxt-ripple:** Ensure that redirect code runs even on (nuxt) cached pages ([6f095499](https://github.com/dpc-sdp/ripple-framework/commit/6f095499)) + - **@dpc-sdp/nuxt-ripple:** Added null check for redirect code ([4410cef5](https://github.com/dpc-sdp/ripple-framework/commit/4410cef5)) + +### ๐Ÿก Chore + + - Updated babel / @babel/traverse ([f8796a39](https://github.com/dpc-sdp/ripple-framework/commit/f8796a39)) + - ๐Ÿ”– release ripple 2.4.1 ([a015f463](https://github.com/dpc-sdp/ripple-framework/commit/a015f463)) + +### โค๏ธ Contributors + +- Jeffrey Dowdle + + +## v2.4.0 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.3.5...v2.4.0) + + +### ๐Ÿš€ Enhancements + + - **@dpc-sdp/nuxt-ripple-cli:** โœจ generate favicon ([1f1c21e7](https://github.com/dpc-sdp/ripple-framework/commit/1f1c21e7)) + - **@dpc-sdp/nuxt-ripple:** ๐Ÿšง wip on adding favicon generation as module ([34279a16](https://github.com/dpc-sdp/ripple-framework/commit/34279a16)) + - **nuxt-ripple:** โœจ implement site fetch, favicon generate ([37a29489](https://github.com/dpc-sdp/ripple-framework/commit/37a29489)) + - **@dpc-sdp/ripple-ui-core:** Added css var for heading font ([5dbd7044](https://github.com/dpc-sdp/ripple-framework/commit/5dbd7044)) + - **@dpc-sdp/ripple-tide-search:** โœจ add secondary campaign to mapping, template ([ac537928](https://github.com/dpc-sdp/ripple-framework/commit/ac537928)) + - **nuxt-ripple:** โœจ add mapping for new footer logo field ([9b8217ba](https://github.com/dpc-sdp/ripple-framework/commit/9b8217ba)) + - โœจ replace twitter with x ([76d32333](https://github.com/dpc-sdp/ripple-framework/commit/76d32333)) + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/nuxt-ripple:** Ensured that language specific fonts override all other fonts ([80c2d1aa](https://github.com/dpc-sdp/ripple-framework/commit/80c2d1aa)) + - **@dpc-sdp/ripple-ui-core:** ๐Ÿ’„ prevent hanging icon ([f31659fe](https://github.com/dpc-sdp/ripple-framework/commit/f31659fe)) + - **@dpc-sdp/nuxt-ripple:** ๐Ÿ› fix redirects adding to history state ([60dbea98](https://github.com/dpc-sdp/ripple-framework/commit/60dbea98)) + - **@dpc-sdp/ripple-ui-core:** ๐Ÿ› only render icon if item has text or url ([af34dce5](https://github.com/dpc-sdp/ripple-framework/commit/af34dce5)) + - **@dpc-sdp/ripple-ui-core:** ๐Ÿ› add check for secondary link in header ([efe022b5](https://github.com/dpc-sdp/ripple-framework/commit/efe022b5)) + - **nuxt-ripple:** ๐Ÿ› filter out alerts with missing type relation ([682328bd](https://github.com/dpc-sdp/ripple-framework/commit/682328bd)) + - **@dpc-sdp/ripple-ui-core:** ๐Ÿ› fix carousel links ([5b4e5b5b](https://github.com/dpc-sdp/ripple-framework/commit/5b4e5b5b)) + +### ๐Ÿ’… Refactors + + - **nuxt-ripple:** โ™ป๏ธ use site data in favicon generator ([46552d07](https://github.com/dpc-sdp/ripple-framework/commit/46552d07)) + - **nuxt-ripple:** ๐Ÿท๏ธ update appconfig interface ([2b178f33](https://github.com/dpc-sdp/ripple-framework/commit/2b178f33)) + - **@dpc-sdp/ripple-tide-search:** โ™ป๏ธ move secondary campaign to right slot ([cae72da6](https://github.com/dpc-sdp/ripple-framework/commit/cae72da6)) + - **@dpc-sdp/ripple-tide-publication:** โ™ป๏ธ change page link props at mapping, add test ([5d7bbc30](https://github.com/dpc-sdp/ripple-framework/commit/5d7bbc30)) + - **@dpc-sdp/ripple-ui-core:** โ™ป๏ธ check for url or text ([e0e89fd5](https://github.com/dpc-sdp/ripple-framework/commit/e0e89fd5)) + - โ™ป๏ธ replace icon at mapping level ([b2eb2fd3](https://github.com/dpc-sdp/ripple-framework/commit/b2eb2fd3)) + - **@dpc-sdp/ripple-ui-core:** โช๏ธ revert hyphenation, add separate util class ([8614c759](https://github.com/dpc-sdp/ripple-framework/commit/8614c759)) + +### ๐Ÿ“– Documentation + + - ๐Ÿ“ update badges, add tools and frameworks ([ee110382](https://github.com/dpc-sdp/ripple-framework/commit/ee110382)) + - ๐Ÿ“ add more detail to readme ([690c239b](https://github.com/dpc-sdp/ripple-framework/commit/690c239b)) + - **docs:** ๐Ÿ“ add personal access token instructions ([db3997f1](https://github.com/dpc-sdp/ripple-framework/commit/db3997f1)) + - ๐Ÿ“ simplify badge labels ([779d9d8a](https://github.com/dpc-sdp/ripple-framework/commit/779d9d8a)) + - **docs:** ๐Ÿ“ split usage into separate docs, add nuxt load order fix ([2e41717e](https://github.com/dpc-sdp/ripple-framework/commit/2e41717e)) + +### ๐Ÿก Chore + + - **deployment:** Add workflow file ([f5a1740b](https://github.com/dpc-sdp/ripple-framework/commit/f5a1740b)) + - ๐Ÿ‘ท update pnpm ([872ff090](https://github.com/dpc-sdp/ripple-framework/commit/872ff090)) + - Fix studio pnpm ver and allow greater ([5ebc4ae8](https://github.com/dpc-sdp/ripple-framework/commit/5ebc4ae8)) + - **docs:** โž• add nuxt studio ([7b1cd87d](https://github.com/dpc-sdp/ripple-framework/commit/7b1cd87d)) + +### โœ… Tests + + - โœ… fix linter issues ([30420455](https://github.com/dpc-sdp/ripple-framework/commit/30420455)) + - **nuxt-ripple:** โœ… fix tests ([4dc9358a](https://github.com/dpc-sdp/ripple-framework/commit/4dc9358a)) + +### โค๏ธ Contributors + +- Dylan Kelly +- Jason Smith +- Dylankelly +- Jeffrey Dowdle + +## v2.3.5 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.3.4...v2.3.5) + + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/ripple-tide-api:** Passed headers to route, page and publication menu endpoints ([e760940d](https://github.com/dpc-sdp/ripple-framework/commit/e760940d)) + +### โค๏ธ Contributors + +- Jeffrey Dowdle + +## v2.3.4 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.3.3...v2.3.4) + + +### ๐Ÿš€ Enhancements + + - **@dpc-sdp/ripple-tide-publication:** Added proper publication header mapping ([aa657deb](https://github.com/dpc-sdp/ripple-framework/commit/aa657deb)) + +### ๐Ÿฉน Fixes + + - **nuxt-ripple:** ๐Ÿ› enable external redirects ([6c0e4894](https://github.com/dpc-sdp/ripple-framework/commit/6c0e4894)) + - **@dpc-sdp/nuxt-ripple:** Fixed console error when request event was being fetched client side ([b8f94314](https://github.com/dpc-sdp/ripple-framework/commit/b8f94314)) + - **@dpc-sdp/ripple-tide-publication:** Fixed duplication side nav links ([a08742e4](https://github.com/dpc-sdp/ripple-framework/commit/a08742e4)) + +### ๐Ÿก Chore + + - **@dpc-sdp/ripple-tide-publication:** ๐Ÿ› [R20-1574] fix publication preview menu issue ([b737e32b](https://github.com/dpc-sdp/ripple-framework/commit/b737e32b)) + - **@dpc-sdp/ripple-tide-publication:** ๐Ÿšจ fix unused var ([acd09f34](https://github.com/dpc-sdp/ripple-framework/commit/acd09f34)) + +### โค๏ธ Contributors + +- Dylan Kelly +- Jeffrey Dowdle +- Jason Smith + +## v2.3.3 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.3.2...v2.3.3) + + +### ๐Ÿก Chore + + - **@dpc-sdp/nuxt-ripple:** ๐Ÿ› fix redirect headers issue ([a5556f1c](https://github.com/dpc-sdp/ripple-framework/commit/a5556f1c)) + +### โค๏ธ Contributors + +- Dylan Kelly + +## v2.3.2 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.3.1...v2.3.2) + + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/ripple-tide-api:** Fixed broken redirects after cache tags refactor ([26f67b7d](https://github.com/dpc-sdp/ripple-framework/commit/26f67b7d)) + +### โค๏ธ Contributors + +- Jeffrey Dowdle + +## v2.3.1 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.3.0...v2.3.1) + + +### ๐Ÿš€ Enhancements + + - **nuxt-ripple:** Pass through section-cache-tags response header ([cf3d6477](https://github.com/dpc-sdp/ripple-framework/commit/cf3d6477)) + - **@dpc-sdp/ripple-tide-api:** Merged site+alert cache tags with page cache tags ([98c2c956](https://github.com/dpc-sdp/ripple-framework/commit/98c2c956)) + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/ripple-tide-api:** Increase menu depth for breadcrumbs ([43ea5efa](https://github.com/dpc-sdp/ripple-framework/commit/43ea5efa)) + - **@dpc-sdp/ripple-tide-publication:** Add missed optional chaining ([23e6807f](https://github.com/dpc-sdp/ripple-framework/commit/23e6807f)) + - **@dpc-sdp/ripple-tide-api:** Add optional chaining ([0f84540a](https://github.com/dpc-sdp/ripple-framework/commit/0f84540a)) + +### ๐Ÿ’… Refactors + + - **@dpc-sdp/ripple-tide-api:** Refactored http client to not swallow up headers ([da09ec47](https://github.com/dpc-sdp/ripple-framework/commit/da09ec47)) + +### โœ… Tests + + - **@dpc-sdp/ripple-tide-api:** Fixed unit tests ([57c92cd4](https://github.com/dpc-sdp/ripple-framework/commit/57c92cd4)) + +### โค๏ธ Contributors + +- Jeffrey Dowdle +- David Featherston + +## v2.3.0 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.2.1...v2.3.0) + + +### ๐Ÿš€ Enhancements + + - **@dpc-sdp/ripple-tide-search:** Add support for fallback values on filters ([8d518305](https://github.com/dpc-sdp/ripple-framework/commit/8d518305)) + - **@dpc-sdp/nuxt-ripple:** Make content rating choice required ([b5542f18](https://github.com/dpc-sdp/ripple-framework/commit/b5542f18)) + - **@dpc-sdp/nuxt-ripple:** Conditionally render rating submit action ([8f253fec](https://github.com/dpc-sdp/ripple-framework/commit/8f253fec)) + - **@dpc-sdp/ripple-tide-search:** Added sort dropdown to search listing page ([0d6b36ba](https://github.com/dpc-sdp/ripple-framework/commit/0d6b36ba)) + - **@dpc-sdp/ripple-tide-landing-page:** Updated data table component mapping and tests ([cbbf716a](https://github.com/dpc-sdp/ripple-framework/commit/cbbf716a)) + - **@dpc-sdp/ripple-ui-core:** โœจ add aria annotations on tabs ([daad51f5](https://github.com/dpc-sdp/ripple-framework/commit/daad51f5)) + - **@dpc-sdp/ripple-ui-core:** Allow supplying custom rpl icons ([2bdc083c](https://github.com/dpc-sdp/ripple-framework/commit/2bdc083c)) + - **@dpc-sdp/ripple-ui-core:** Add support for icons in description list component ([847c0e7f](https://github.com/dpc-sdp/ripple-framework/commit/847c0e7f)) + - **@dpc-sdp/ripple-ui-core:** Re-jig to remove nested div ([8855723e](https://github.com/dpc-sdp/ripple-framework/commit/8855723e)) + - **@dpc-sdp/ripple-tide-search:** Added sort dropdown to custom collection ([5ed78686](https://github.com/dpc-sdp/ripple-framework/commit/5ed78686)) + - **@dpc-sdp/ripple-tide-grant:** Use description list for grant overview ([736bcd43](https://github.com/dpc-sdp/ripple-framework/commit/736bcd43)) + - **@dpc-sdp/nuxt-ripple:** Add karenni font ([8fbb6a00](https://github.com/dpc-sdp/ripple-framework/commit/8fbb6a00)) + - **@dpc-sdp/ripple-tide-grant:** Remove un-needed props. in template ([2861b16b](https://github.com/dpc-sdp/ripple-framework/commit/2861b16b)) + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/ripple-tide-search:** Merge conflicts ([0665db19](https://github.com/dpc-sdp/ripple-framework/commit/0665db19)) + - **@dpc-sdp/ripple-ui-core:** Make only title and url 'links' in search results ([de12cb9c](https://github.com/dpc-sdp/ripple-framework/commit/de12cb9c)) + - **@dpc-sdp/ripple-ui-core:** Add aria-hidden to url ([7ad8873b](https://github.com/dpc-sdp/ripple-framework/commit/7ad8873b)) + - **@dpc-sdp/ripple-ui-core:** Fix ts error, update readme ([51d6c216](https://github.com/dpc-sdp/ripple-framework/commit/51d6c216)) + - **@dpc-sdp/ripple-tide-search:** Fixed global filters not being applied to aggregations query ([8f7a2801](https://github.com/dpc-sdp/ripple-framework/commit/8f7a2801)) + - **docs:** Fixed prerender 404s from broken links ([f6171c4c](https://github.com/dpc-sdp/ripple-framework/commit/f6171c4c)) + - **docs:** Don't try to prerender storybook routes ([20df1cb1](https://github.com/dpc-sdp/ripple-framework/commit/20df1cb1)) + - **nuxt-ripple:** ๐Ÿ› exclude metatag canonical ([3b7733c5](https://github.com/dpc-sdp/ripple-framework/commit/3b7733c5)) + - **@dpc-sdp/ripple-tide-search:** Handled search result fields coming back as either string or array ([7398b3fb](https://github.com/dpc-sdp/ripple-framework/commit/7398b3fb)) + - **@dpc-sdp/ripple-tide-search:** Fix custom collection scroll issue ([53011c9a](https://github.com/dpc-sdp/ripple-framework/commit/53011c9a)) + - **@dpc-sdp/ripple-tide-search:** Fixed 'undefined' class name for results table ([6bd90b2c](https://github.com/dpc-sdp/ripple-framework/commit/6bd90b2c)) + - **@dpc-sdp/ripple-tide-search:** Added missing aria-expanded to search filter toggle ([d6601e81](https://github.com/dpc-sdp/ripple-framework/commit/d6601e81)) + +### ๐Ÿ“– Documentation + + - **docs:** ๐Ÿ“ use different theme for card highlight as vic gov is wrong ([d46a742a](https://github.com/dpc-sdp/ripple-framework/commit/d46a742a)) + - **docs:** Fixed card examples display ([58df9085](https://github.com/dpc-sdp/ripple-framework/commit/58df9085)) + +### ๐Ÿก Chore + + - **@dpc-sdp/nuxt-ripple-analytics:** Change route name to page_title ([16a0eba9](https://github.com/dpc-sdp/ripple-framework/commit/16a0eba9)) + +### โœ… Tests + + - **@dpc-sdp/ripple-tide-search:** Added tests for sort dropdown feature of search listing ([4bdf8e4e](https://github.com/dpc-sdp/ripple-framework/commit/4bdf8e4e)) + - **@dpc-sdp/ripple-ui-core:** Fixed data table storybook tests ([68767ebc](https://github.com/dpc-sdp/ripple-framework/commit/68767ebc)) + - **@dpc-sdp/ripple-ui-core:** Updated data table stories to match chromatic snapshots ([eaffc069](https://github.com/dpc-sdp/ripple-framework/commit/eaffc069)) + - **@dpc-sdp/ripple-ui-core:** โœ… fix aria issues ([785de3f3](https://github.com/dpc-sdp/ripple-framework/commit/785de3f3)) + - **@dpc-sdp/ripple-tide-search:** Fixed up custom collection test table results ([bc8dc3b5](https://github.com/dpc-sdp/ripple-framework/commit/bc8dc3b5)) + +### โค๏ธ Contributors + +- David Featherston +- Jeffrey Dowdle +- Jason Smith +- Dylan Kelly + +## v2.2.1 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.2.0...v2.2.1) + + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/ripple-tide-search:** Fixed site search filters not appearing ([720d9b0b](https://github.com/dpc-sdp/ripple-framework/commit/720d9b0b)) + +### โค๏ธ Contributors + +- Jeffrey Dowdle + +## v2.2.0 + +[compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.1.9...v2.2.0) + + +### ๐Ÿš€ Enhancements + + - **@dpc-sdp/ripple-tide-search:** โœจ add custom collection component ([8ecb6017](https://github.com/dpc-sdp/ripple-framework/commit/8ecb6017)) + - โœจ add accessible label for links opening in a new window ([7866c3ee](https://github.com/dpc-sdp/ripple-framework/commit/7866c3ee)) + - **@dpc-sdp/ripple-ui-core:** Made search result date label customisable ([9fba5885](https://github.com/dpc-sdp/ripple-framework/commit/9fba5885)) + - **@dpc-sdp/ripple-ui-core:** โœจ hyphenate headings ([ab440a1b](https://github.com/dpc-sdp/ripple-framework/commit/ab440a1b)) + - **@dpc-sdp/nuxt-ripple-analytics:** Move measurement ids and production flag ([e3ece92f](https://github.com/dpc-sdp/ripple-framework/commit/e3ece92f)) + - **@dpc-sdp/nuxt-ripple-analytics:** Add dataLayer event for error pages i.e. 400, 500, etc ([205a2da1](https://github.com/dpc-sdp/ripple-framework/commit/205a2da1)) + - **@dpc-sdp/ripple-ui-core:** โœจ add optiional noresults message to search bar ([d5905243](https://github.com/dpc-sdp/ripple-framework/commit/d5905243)) + - **nuxt-ripple:** Added social links to footer ([a656d684](https://github.com/dpc-sdp/ripple-framework/commit/a656d684)) + - **@dpc-sdp/ripple-ui-core:** A11y slider feedback, fix key-dates link ([cb259a17](https://github.com/dpc-sdp/ripple-framework/commit/cb259a17)) + - **@dpc-sdp/ripple-tide-publication:** Support singular authors ([8161837d](https://github.com/dpc-sdp/ripple-framework/commit/8161837d)) + - **@dpc-sdp/ripple-ui-core:** Improved custom date label in RplSearchResult ([9282b3dd](https://github.com/dpc-sdp/ripple-framework/commit/9282b3dd)) + - **@dpc-sdp/nuxt-ripple:** Added table of contents to sitemap page ([ef969b51](https://github.com/dpc-sdp/ripple-framework/commit/ef969b51)) + - **@dpc-sdp/nuxt-ripple:** Made sitemap toc configurable via cms ([c3d36b12](https://github.com/dpc-sdp/ripple-framework/commit/c3d36b12)) + +### ๐Ÿฉน Fixes + + - **@dpc-sdp/ripple-tide-search:** Fix cc mapping ([7de48e11](https://github.com/dpc-sdp/ripple-framework/commit/7de48e11)) + - **@dpc-sdp/ripple-tide-search:** ๐Ÿ’„ add margin between error message ([9844062f](https://github.com/dpc-sdp/ripple-framework/commit/9844062f)) + - **eslint-config-ripple:** โž• add required eslint dependencies ([e0c44b95](https://github.com/dpc-sdp/ripple-framework/commit/e0c44b95)) + - **nuxt-ripple:** ๐Ÿ› fix title undefined in search page ([60988fe5](https://github.com/dpc-sdp/ripple-framework/commit/60988fe5)) + - **@dpc-sdp/ripple-tide-search:** Fix search request proxy config ([2c0da907](https://github.com/dpc-sdp/ripple-framework/commit/2c0da907)) + - **@dpc-sdp/ripple-tide-search:** ๐Ÿ› disable autocomplete on custom collection ([7eb80638](https://github.com/dpc-sdp/ripple-framework/commit/7eb80638)) + - **@dpc-sdp/nuxt-ripple-analytics:** Fix undefined error ([f75f3d9d](https://github.com/dpc-sdp/ripple-framework/commit/f75f3d9d)) + - **@dpc-sdp/ripple-tide-search:** ๐Ÿ› fix autocomplete end point ([2304ad5d](https://github.com/dpc-sdp/ripple-framework/commit/2304ad5d)) + - **@dpc-sdp/ripple-ui-core:** Add max width to scrollable table story ([9083049e](https://github.com/dpc-sdp/ripple-framework/commit/9083049e)) + - **@dpc-sdp/nuxt-ripple-analytics:** Use new tide hook for dataLayer events ([e447970e](https://github.com/dpc-sdp/ripple-framework/commit/e447970e)) + - **@dpc-sdp/ripple-tide-publication:** Matched page api by merging configs ([cb4bbca6](https://github.com/dpc-sdp/ripple-framework/commit/cb4bbca6)) + - **@dpc-sdp/nuxt-ripple:** Fixed typo ([05825921](https://github.com/dpc-sdp/ripple-framework/commit/05825921)) + +### ๐Ÿ’… Refactors + + - **@dpc-sdp/ripple-tide-search:** โœจ use backend json field ([52970c23](https://github.com/dpc-sdp/ripple-framework/commit/52970c23)) + - **@dpc-sdp/ripple-tide-search:** โ™ป๏ธ rename proxy handlers to match purpose ([cc3fc595](https://github.com/dpc-sdp/ripple-framework/commit/cc3fc595)) + - **@dpc-sdp/ripple-ui-core:** โ™ป๏ธ use src if printSrc is not supplied ([6df87bd9](https://github.com/dpc-sdp/ripple-framework/commit/6df87bd9)) + - **@dpc-sdp/ripple-ui-core:** โ™ป๏ธ restore print logo to default ([e1150cec](https://github.com/dpc-sdp/ripple-framework/commit/e1150cec)) + +### ๐Ÿ“ฆ Build + + - Update lockfile ([dc8c026c](https://github.com/dpc-sdp/ripple-framework/commit/dc8c026c)) + - ๐Ÿ’š fix storybook, jest, axe incompat ([d6ce5b48](https://github.com/dpc-sdp/ripple-framework/commit/d6ce5b48)) + +### ๐Ÿก Chore + + - **eslint-config-ripple:** Add ripple eslint to cli templates ([b54dd78f](https://github.com/dpc-sdp/ripple-framework/commit/b54dd78f)) + - **@dpc-sdp/nuxt-ripple-cli:** Updated new layer cli with correct publish action ([2e3fcc81](https://github.com/dpc-sdp/ripple-framework/commit/2e3fcc81)) + - **@dpc-sdp/nuxt-ripple-cli:** Added missing npmrc to site generator ([3201dad9](https://github.com/dpc-sdp/ripple-framework/commit/3201dad9)) + - **@dpc-sdp/nuxt-ripple-cli:** Removed caret from layer package.json template ([15d1b5d2](https://github.com/dpc-sdp/ripple-framework/commit/15d1b5d2)) + - **@dpc-sdp/ripple-ui-forms:** Remove counter from form inputs ([d834ba31](https://github.com/dpc-sdp/ripple-framework/commit/d834ba31)) + - **@dpc-sdp/ripple-tide-landing-page:** Remove counter from textareas ([991bd454](https://github.com/dpc-sdp/ripple-framework/commit/991bd454)) + - **@dpc-sdp/ripple-tide-landing-page:** Infer counter type as api doesn't always return it ([1ef7549e](https://github.com/dpc-sdp/ripple-framework/commit/1ef7549e)) + - ๐Ÿš€ change publish process to be on release ([ab027fe2](https://github.com/dpc-sdp/ripple-framework/commit/ab027fe2)) + +### โœ… Tests + + - **@dpc-sdp/ripple-tide-search:** โœ… ignore resize observer exception in expander ([d3a1ea85](https://github.com/dpc-sdp/ripple-framework/commit/d3a1ea85)) + - **@dpc-sdp/ripple-tide-search:** ๐Ÿงช remove unimplemented test ([8f96db79](https://github.com/dpc-sdp/ripple-framework/commit/8f96db79)) + - **@dpc-sdp/ripple-tide-search:** Skip custom collection tests ([06fff8c1](https://github.com/dpc-sdp/ripple-framework/commit/06fff8c1)) + - **@dpc-sdp/ripple-tide-search:** ๐Ÿ’š fix custom collection tests ([c1a9acbc](https://github.com/dpc-sdp/ripple-framework/commit/c1a9acbc)) + - **nuxt-ripple:** Fixed footer tests and social links ([e047c198](https://github.com/dpc-sdp/ripple-framework/commit/e047c198)) + - **nuxt-app:** Remove old counter test ([f7d95822](https://github.com/dpc-sdp/ripple-framework/commit/f7d95822)) + +### ๐ŸŽจ Styles + + - ๐Ÿšš rename flowchart ([4a25952e](https://github.com/dpc-sdp/ripple-framework/commit/4a25952e)) + - **@dpc-sdp/ripple-ui-core:** ๐Ÿ’„ add bg on hyphenated example ([cea33a86](https://github.com/dpc-sdp/ripple-framework/commit/cea33a86)) + +### โค๏ธ Contributors + +- Dylan Kelly +- David Featherston +- Jeffrey Dowdle +- Jason Smith + ## v2.1.9 [compare changes](https://github.com/dpc-sdp/ripple-framework/compare/v2.1.8...v2.1.9) diff --git a/README.md b/README.md index ddd3440d3c..eb427830b0 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,10 @@ ripple storybook - - ripple ui core npm + + build status - - ripple develop branch build status - # Ripple 2.0 @@ -25,6 +22,8 @@ **Table of Contents** - [About the project](#about-the-project) - [Ripple Framework](#ripple-framework) + - [Usage](#usage) + - [Availability](#availability) - [Documentation](#documentation) - [Contributing](#contributing) - [License](#license) @@ -42,11 +41,27 @@ Ripple is a system of reusable styles, components, patterns and tools for buildi Over 50 government websites use Ripple to date, including our main vic.gov.au platform. These sites attract the visitation of millions of views per month. - ### Ripple Framework -The Ripple design system consists of the design elements and components used to build websites using the Victorian government brand and Ripple _framework_, a collection of [Nuxt 3](https://www.ripple.sdp.vic.gov.au/framework/key-concepts/nuxt/) modules and [layers](https://www.ripple.sdp.vic.gov.au/framework/key-concepts/nuxt-layers) primarily used to create headless SDP websites using the [Tide Drupal CMS](https://github.com/dpc-sdp/tide). +The Ripple design system consists of the design elements and components used to build websites using the Victorian government brand and Ripple _framework_, a collection of [Nuxt](https://www.ripple.sdp.vic.gov.au/framework/key-concepts/nuxt/) modules and [layers](https://www.ripple.sdp.vic.gov.au/framework/key-concepts/nuxt-layers) primarily used to create headless SDP websites using the [Tide Drupal CMS](https://github.com/dpc-sdp/tide). + +Ripple components are built using Vue 3 and [TypeScript](https://github.com/dpc-sdp/ripple-framework/blob/develop/tsconfig.json). + +The monorepo is managed with pnpm [workspaces](https://github.com/dpc-sdp/ripple-framework/blob/develop/pnpm-workspace.yaml), using [Node.js](.nvmrc). + +Tests use [Jest](https://github.com/dpc-sdp/ripple-framework/blob/develop/jest.config.js), [Axe-core](https://github.com/dpc-sdp/ripple-framework/blob/develop/packages/ripple-ui-core/stories/interactions.js) and [Cypress](packages/ripple-test-utils). + +We use custom rules for [ESLint](https://github.com/dpc-sdp/ripple-framework/blob/develop/packages/eslint-config-ripple/index.js) and [Stylelint](https://github.com/dpc-sdp/ripple-framework/blob/develop/packages/stylelint-config-ripple/index.js). +### Usage + +Ripple was built as a whole to implement front end sites for SDP using a framework of Vue 3 and Nuxt 3, but parts of the modular architecture can be used independently: `ripple-ui-core` and `ripple-ui-forms` can be used as UI component libraries for any Vue 3 project. + +There is also an experimental web components implementation, and a standalone export of all Ripple design system CSS. See the [relevant section](https://ripple.sdp.vic.gov.au/design-system/develop/usage/) on the Ripple documentation site for more details. + +### Availability + +Note: Ripple 2 will only be hosted on Github Packages, any packages still published to npm are either pre-release or deprecated, and should not be used. Please see the section [Access to Github Packages repos](https://www.ripple.vic.gov.au/design-system/develop/usage#access-to-github-packages-repos) for instructions on how to set up a personal access token, and where to use it. ## Documentation @@ -54,7 +69,6 @@ See https://www.ripple.sdp.vic.gov.au/ for more information about the Ripple des For information about using Ripple in SDP websites see the [Ripple Framework](https://www.ripple.sdp.vic.gov.au/framework) section. - ## Contributing Please see [CONTRIBUTING.md](CONTRIBUTING.md) as well as https://www.ripple.sdp.vic.gov.au/design-system/develop/contributing/ for information about how to submit changes to Ripple. diff --git a/docs/content/design-system/3.develop/3.usage.md b/docs/content/design-system/3.develop/3.usage.md index 70fd8ee599..d75fbb0fa0 100644 --- a/docs/content/design-system/3.develop/3.usage.md +++ b/docs/content/design-system/3.develop/3.usage.md @@ -16,101 +16,21 @@ Whilst Ripple is built to implement sites using a framework built on Vue JS and ![ripple is made up of Figma design, CSS styles, Vue JS components and Nuxt JS Sites](/assets/img/modules/rpl-modules.png) - Ripple UI libraries are usable in the following contexts: -| Library | CSS styles | Vue Components | Ripple Framework (Nuxt) | Web components | +| Library | CSS styles | Vue Components | Ripple Framework (Nuxt) | Web components | | --------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | ------------------------- | | Ripple UI Core | supported | supported |supported | Partial support | | Ripple UI Forms | supported | supported |supported | not supported | - - - ### Using in an SDP website > If you are using Ripple 2 to build a site on the SDP platform, the UI libraries will be included automatically when scaffolding your project. > > For documentation on using Ripple with Nuxt in SDP sites, visit the [Ripple Framework documentation](/framework) -### Using in a Vue app - -To install Ripple UI in your project, run: - -`npm install @dpc-sdp/ripple-ui-core --save` - -In order for the styles to appear correctly, you will need to import them. Do this at the root of your project (usually in your app.vue or index.js file): - -```js -import '@dpc-sdp/ripple-ui-core/style'; -import '@dpc-sdp/ripple-ui-core/style/components'; -``` - -To use a component, import it from `@dpc-sdp/ripple-ui-core/vue`, note the addition of `/vue`. - -```js - - - -``` - -### Using in a Nuxt app - -To install Ripple UI in your project, run: - -`npm install @dpc-sdp/ripple-ui-core --save` - -Ripple UI exports a nuxt module that you can add to your nuxt config, note the addition of `/nuxt`: - -```js -export default defineNuxtConfig({ - modules: [ - '@dpc-sdp/ripple-ui-core/nuxt' - ] -}) -``` - -In order for the styles to appear correctly, you will need to import them. Do this at the root of your project (usually in your app.vue file): - -```js -import '@dpc-sdp/ripple-ui-core/style'; -import '@dpc-sdp/ripple-ui-core/style/components'; -``` - -There is no need to import the components as they will be registered globally by the nuxt module - -```js - -``` - -### Webcomponents - -> Web components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps. Custom components and widgets build on the Web Component standards, will work across modern browsers, and can be used with any JavaScript library or framework that works with HTML. -> -> Web components are based on existing web standards. Features to support web components are currently being added to the HTML and DOM specs, letting web developers easily extend HTML with new elements with encapsulated styling and custom behavior. -> https://www.webcomponents.org/ - -Ripple exports a limited set of components as standard browser native webcomponents. The advantage of using Webcomponents is they can be dropped straight into traditional webpages and CMS's that do not have javascript build pipeline such as Squiz Matrix and Salesforce. - -Support for this output target is currently experimental, if you think you have a use case for using Webcomponents in your project please add a comment [here](https://github.com/dpc-sdp/ripple-framework/discussions/658). - -### Using Ripple CSS styles only - -Ripple UI core exports it's CSS stylesheets directly. If you have a use case where you can't use any of the other outputs, you can use our styles directly and provide your own markup based upon the rendered examples in Storybook. - -To import CSS styles only you can import them from the ripple-ui-core package. - -```js -import '@dpc-sdp/ripple-ui-core/style'; -import '@dpc-sdp/ripple-ui-core/style/components'; -``` +### Github Packages -We recommend that you lock the version of `@dpc-sdp/ripple-ui-core` so that any future changes of styles without changes to markup do not break your application. +Ripple 2 is hosted publicly on Github Packages (ghcr) under the **@dpc/sdp** namespace, please read these [setup instructions for accessing Github Packages](/design-system/develop/usage/access-to-github-packages). diff --git a/docs/content/design-system/3.develop/4.usage/1.access-to-github-packages.md b/docs/content/design-system/3.develop/4.usage/1.access-to-github-packages.md new file mode 100644 index 0000000000..d206afdf0b --- /dev/null +++ b/docs/content/design-system/3.develop/4.usage/1.access-to-github-packages.md @@ -0,0 +1,48 @@ +--- +title: Access to Github Packages +description: How to access and use repos hosted on Github Packages. +layout: page +--- + +NPM allows the use of multiple repo hosts, as long as they can be identified by namespace. + +Ripple 2 is hosted publicly on Github Packages (ghcr) under the **@dpc/sdp** namespace, so a few steps need to be followed access these packages, while also co-existing with the npm ecosystem. + +First, create a Personal Access Token on Github: + +1. Visit https://github.com/settings/tokens and select **Generate new token** > **Generate new token (classic)** + +::DocsImageExample +--- +src: /img/generate.jpg +alt: "Screenshot of a clicked button (title Generate new token) that has opened a drop-down menu with two options: fine-grained and classic" +style: "width:360px" +--- +:: + +2. Fill in the **Note** and choose an **Expiration** - the 30 day default is generally fine, you'll receive a reminder to regenerate the token just before expiry + +::DocsImageExample +--- +src: /img/token.jpg +alt: "Screenshot of personal access token setup form, with 'Note' filled in as 'public ghcr access' and 'Expiration' set to default of 30 days" +style: "width:505px" +--- +:: + +3. Set the scope to only allow `read:packages`, and **Generate** token + +::DocsImageExample +--- +src: /img/permissions.jpg +alt: "Screenshot of scope, with only read:packages selected" +style: "width:827px" +--- +:: + +4. Store the resulting token somewhere secure, and add it to either a user `.npmrc` (recommended) or the project-level `.npmrc` (the url on line 2 below is protocol-less, not a comment): + +``` +@dpc-sdp:registry=https://npm.pkg.github.com +//npm.pkg.github.com/:_authToken= +``` diff --git a/docs/content/design-system/3.develop/4.usage/2.vue.md b/docs/content/design-system/3.develop/4.usage/2.vue.md new file mode 100644 index 0000000000..3a15c56986 --- /dev/null +++ b/docs/content/design-system/3.develop/4.usage/2.vue.md @@ -0,0 +1,32 @@ +--- +title: Vue +description: How to use Ripple components with Vue. +layout: page +--- + +First, set up [access to Github Packages](access-to-github-packages). + +Next, install Ripple UI in your project: + +`npm install @dpc-sdp/ripple-ui-core --save` + +In order for the styles to appear correctly, you will need to import them. Do this at the root of your project (usually in your app.vue or index.js file): + +```js +import '@dpc-sdp/ripple-ui-core/style'; +import '@dpc-sdp/ripple-ui-core/style/components'; +``` + +To use a component, import it from `@dpc-sdp/ripple-ui-core/vue`, note the addition of `/vue`. + +```js + + + +``` + +Please see this [example app](https://github.com/dpc-sdp/ripple-vue-example) for a basic demonstration of how to use Ripple components in a Vue app. diff --git a/docs/content/design-system/3.develop/4.usage/3.nuxt.md b/docs/content/design-system/3.develop/4.usage/3.nuxt.md new file mode 100644 index 0000000000..69b2335f65 --- /dev/null +++ b/docs/content/design-system/3.develop/4.usage/3.nuxt.md @@ -0,0 +1,38 @@ +--- +title: Nuxt +description: How to use Ripple components with Nuxt in a non-SDP context. +layout: page +--- + +First, set up [access to Github Packages](access-to-github-packages). + +Next, install Ripple UI in your project: + +`npm install @dpc-sdp/ripple-ui-core --save` + +Ripple UI exports a nuxt module that you can add to your nuxt config, note the addition of `/nuxt`: + +```js +export default defineNuxtConfig({ + modules: [ + '@dpc-sdp/ripple-ui-core/nuxt' + ] +}) +``` + +In order for the styles to appear correctly, you will need to import them. Do this at the root of your project (usually in your app.vue file): + +```js +import '@dpc-sdp/ripple-ui-core/style'; +import '@dpc-sdp/ripple-ui-core/style/components'; +``` + +There is no need to import the components as they will be registered globally by the nuxt module + +```js + +``` + +Even if you are not using SDP, the [Ripple Framework documentation](/framework) is a good starting point for any Nuxt development using Ripple. diff --git a/docs/content/design-system/3.develop/4.usage/4.webcomponents.md b/docs/content/design-system/3.develop/4.usage/4.webcomponents.md new file mode 100644 index 0000000000..4cdc5d8189 --- /dev/null +++ b/docs/content/design-system/3.develop/4.usage/4.webcomponents.md @@ -0,0 +1,23 @@ +--- +title: Web components +description: How to use the web component exports with other front-end systems. +layout: page +--- + +> Web components are a set of web platform APIs that allow you to create new custom, reusable, encapsulated HTML tags to use in web pages and web apps. Custom components and widgets build on the Web Component standards, will work across modern browsers, and can be used with any JavaScript library or framework that works with HTML. +> +> Web components are based on existing web standards. Features to support web components are currently being added to the HTML and DOM specs, letting web developers easily extend HTML with new elements with encapsulated styling and custom behavior. +> https://www.webcomponents.org/ + + + + +Ripple exports a limited set of components as standard browser native web components. The advantage of using Web components is they can be dropped straight into conventional webpages and any CMS without a javascript build pipeline, such as Squiz Matrix and Salesforce. + +Support for this output target is currently experimental, if you think you have a use case for using Web components in your project please add a comment [here](https://github.com/dpc-sdp/ripple-framework/discussions/658). + +As with the other methods, set up [access to Github Packages](access-to-github-packages) and then install Ripple UI in your project: + +`npm install @dpc-sdp/ripple-ui-core --save` + +A basic example of how to use the web component exports can be found under [examples/webcomponents](https://github.com/dpc-sdp/ripple-framework/tree/develop/examples/webcomponents) in the ripple-framework monorepo. diff --git a/docs/content/design-system/3.develop/4.usage/5.css-only.md b/docs/content/design-system/3.develop/4.usage/5.css-only.md new file mode 100644 index 0000000000..1a8a8c501f --- /dev/null +++ b/docs/content/design-system/3.develop/4.usage/5.css-only.md @@ -0,0 +1,22 @@ +--- +title: Styles only +description: Using the design system styles without a front-end framework. +layout: page +--- + +Ripple UI core exports it's CSS stylesheets directly. If you have a use case where you can't use any of the other outputs, you can use our styles directly and provide your own markup based upon the rendered examples in Storybook. + +First, set up [access to Github Packages](access-to-github-packages). + +Next, install Ripple UI in your project: + +`npm install @dpc-sdp/ripple-ui-core --save` + +You can import CSS styles from the ripple-ui-core package. + +```js +import '@dpc-sdp/ripple-ui-core/style'; +import '@dpc-sdp/ripple-ui-core/style/components'; +``` + +We recommend that you lock the version of `@dpc-sdp/ripple-ui-core` so that any future changes of styles without changes to markup do not break your application. diff --git a/docs/content/design-system/5.components/card.md b/docs/content/design-system/5.components/card.md index 543383241a..480cd0abc3 100644 --- a/docs/content/design-system/5.components/card.md +++ b/docs/content/design-system/5.components/card.md @@ -17,7 +17,8 @@ Ensure headings are direct and summaries are concise. ::DocsExample --- -id: core-navigation-card--promo&args=graphicElement:None +id: core-navigation-card--promo +argsString: 'graphicElement:None' --- :: @@ -96,6 +97,7 @@ The highlight variant gives the card more visual prominence. Use this to guide u ::DocsExample --- id: core-navigation-card--promo +theme: 'docsTheme2' argsString: 'graphicElement:Highlight' --- :: diff --git a/docs/content/framework/1.index.md b/docs/content/framework/1.index.md index 3d1c3bbf7e..8c8f6953a9 100644 --- a/docs/content/framework/1.index.md +++ b/docs/content/framework/1.index.md @@ -9,7 +9,7 @@ layout: page ## What is it? -The Ripple design system consists of the design elements and components used to build _any_ web application using the Victorian government brand. Ripple _framework_ is a collection of [Nuxt 3](2.key-concepts/1.nuxt.md) modules and [layers](2.key-concepts/2.nuxt-layers.md) primarily used to create headless SDP websites using the Tide Drupal CMS. +The Ripple design system consists of the design elements and components used to build _any_ web application using the Victorian government brand. Ripple _framework_ is a collection of [Nuxt 3](/framework/key-concepts/nuxt) modules and [layers](/framework/key-concepts/nuxt-layers) primarily used to create headless SDP websites using the Tide Drupal CMS. ![ripple is made up of Figma design, CSS styles, Vue JS components and Nuxt JS Sites](/assets/img/modules/rpl-modules.png){.rpl-u-padding-t-4} diff --git a/docs/content/framework/2.key-concepts/4.content-types.md b/docs/content/framework/2.key-concepts/4.content-types.md index 33562a8a7d..e6c8a9ca18 100644 --- a/docs/content/framework/2.key-concepts/4.content-types.md +++ b/docs/content/framework/2.key-concepts/4.content-types.md @@ -17,7 +17,7 @@ Ripple content types consist of the following parts: ### API Mapping -See [API Endpoints](/framework/key-concepts/API-endpoints) for more information the Tide API translation layer. For each content type we supply a function that maps the Tide Drupal API response into more concise fields that match the data needed for +See [API Endpoints](/framework/key-concepts/api-endpoints) for more information the Tide API translation layer. For each content type we supply a function that maps the Tide Drupal API response into more concise fields that match the data needed for ### Template diff --git a/docs/content/framework/3.guides/1.migrating.md b/docs/content/framework/3.guides/1.migrating.md index b57c57f276..09137e992c 100644 --- a/docs/content/framework/3.guides/1.migrating.md +++ b/docs/content/framework/3.guides/1.migrating.md @@ -39,7 +39,7 @@ The migration of the following will be out of scope for sites without an MoU for Please review the following concepts before proceeding: - [Content types](/framework/key-concepts/content-types) -- [API mapping](/framework/key-concepts/API-endpoints) +- [API mapping](/framework/key-concepts/api-endpoints) - [Dynamic components](/framework/key-concepts/dynamic-components) ### Content types diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index a5b551a60e..ffd93218f6 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -8,6 +8,7 @@ export default defineNuxtConfig({ '@dpc-sdp/ripple-ui-core/nuxt', '@dpc-sdp/ripple-ui-forms/nuxt', '@nuxt/content', + '@nuxthq/studio', '@nuxtjs/tailwindcss', '@nuxtlabs/github-module' ], @@ -29,5 +30,13 @@ export default defineNuxtConfig({ }, vite: { plugins: [ViteYaml()] + }, + experimental: { + inlineSSRStyles: (id) => !id?.includes('entry') + }, + nitro: { + prerender: { + ignore: ['/storybook'] + } } }) diff --git a/docs/package.json b/docs/package.json index 05ecefb2e1..acd2e366ef 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,6 +14,7 @@ "@iconify/vue": "^4.1.0", "@nuxt/content": "^2.7.0", "@nuxt/kit": "^3.3.2", + "@nuxthq/studio": "^1.0.0", "@nuxtjs/color-mode": "^3.2.0", "@nuxtjs/tailwindcss": "^6.6.4", "@nuxtlabs/github-module": "^1.6.2", diff --git a/docs/public/img/generate.jpg b/docs/public/img/generate.jpg new file mode 100644 index 0000000000..73efedb3f4 Binary files /dev/null and b/docs/public/img/generate.jpg differ diff --git a/docs/public/img/permissions.jpg b/docs/public/img/permissions.jpg new file mode 100644 index 0000000000..c122203d5b Binary files /dev/null and b/docs/public/img/permissions.jpg differ diff --git a/docs/public/img/token.jpg b/docs/public/img/token.jpg new file mode 100644 index 0000000000..4a8737f5b1 Binary files /dev/null and b/docs/public/img/token.jpg differ diff --git a/examples/nuxt-app/app.config.ts b/examples/nuxt-app/app.config.ts index bcf718943d..6d3defb6eb 100644 --- a/examples/nuxt-app/app.config.ts +++ b/examples/nuxt-app/app.config.ts @@ -22,6 +22,10 @@ export default defineAppConfig({ 'linear-gradient(180deg, #382484 0%, #5A0099 20%, #7623B0 35%, #2E7478 50%, #2FA26F 70%, #2FCE6A 80%)' }, search: { + fallbackValues: { + // `dynamicValue` is used in a cypress test to ensure fallbackValues function are called + dynamicValue: () => ['blue'] + }, filterFunctions: { // `dummyFunctionFilter` is used in a cypress test to check that the correct parameters are passed to custom filter functions dummyFunctionFilter: (filterConfig, values) => { diff --git a/examples/nuxt-app/test/features/landingpage/forms.feature b/examples/nuxt-app/test/features/landingpage/forms.feature index b6b97bcae1..ca25b9d880 100644 --- a/examples/nuxt-app/test/features/landingpage/forms.feature +++ b/examples/nuxt-app/test/features/landingpage/forms.feature @@ -124,20 +124,3 @@ Feature: Forms Then a server message should be displayed above the form | status | title | description | | success | Server success | Test success message | - - @mockserver - Scenario: Field counter - Given the mock server has started - And the page endpoint for path "/kitchen-sink" returns fixture "/landingpage/full-form" with status 200 - And the site endpoint returns fixture "/site/reference" with status 200 - Given I visit the page "/kitchen-sink" - Then the landing page component "TideLandingPageWebForm" should exist - And the form with ID "full_form" should exist - Then 7 "words" in the field "role" on "full_form" should display a counter of "You have 2 words too many" - And 0 "characters" in the field "message" on "full_form" should display a counter of "You have 0 characters" - And 5 "characters" in the field "message" on "full_form" should display a counter of "You have 5 characters" - And 9 "characters" in the field "message" on "full_form" should display a counter of "You have 9 characters" - And 10 "characters" in the field "message" on "full_form" should display a counter of "You have 10 characters" - And 50 "characters" in the field "message" on "full_form" should display a counter of "You have 50 characters" - And 51 "characters" in the field "message" on "full_form" should display a counter of "You have 1 character too many" - And 55 "characters" in the field "message" on "full_form" should display a counter of "You have 5 characters too many" diff --git a/examples/nuxt-app/test/features/landingpage/page-components.feature b/examples/nuxt-app/test/features/landingpage/page-components.feature index 19a3637078..a532e2631c 100644 --- a/examples/nuxt-app/test/features/landingpage/page-components.feature +++ b/examples/nuxt-app/test/features/landingpage/page-components.feature @@ -91,7 +91,7 @@ Feature: Home page | title | date | content | url | image | | Sample title | 1 Dec to 31 Dec | Sample Card Summary | /sample-page | https://develop.content.reference.sdp.vic.gov.au/sites/default/files/tide_demo_content/Aerial-shot-of-new-housing-development.jpg | | Promotion title | 3 Nov 2022 | Promotion Card summary | /promo-page | https://develop.content.reference.sdp.vic.gov.au/sites/default/files/tide_demo_content/Engage-Vic-photo-hero.jpeg | - And the card carousel with ID "1155" should contain a key dates card with the title "Key calendar dates" and the following entries + And the card carousel with ID "1155" should contain a key dates card with the title "Key calendar dates", link "/dates" and the following entries | title | subtitle | content | | April 16th | Key subtitle 1 | Key content 1 | | December 1st | Key subtitle 2 | Key content 2 | diff --git a/examples/nuxt-app/test/features/publication-page/publication-page.feature b/examples/nuxt-app/test/features/publication-page/publication-page.feature index ee148defc4..870b223d02 100644 --- a/examples/nuxt-app/test/features/publication-page/publication-page.feature +++ b/examples/nuxt-app/test/features/publication-page/publication-page.feature @@ -11,3 +11,5 @@ Feature: Publication page page Example: On load When I visit the page "/demo-publication/demo-publication-chapter-1" Then the title should be "Demo Publication - Chapter 1" + And there should be a page link with a title of "Previous" and description text of "Demo Publication" + And there should be a page link with a title of "Next" and description text of "Demo Publication - Chapter 1 - Page 1" diff --git a/examples/nuxt-app/test/features/search-listing/custom-collection.feature b/examples/nuxt-app/test/features/search-listing/custom-collection.feature new file mode 100644 index 0000000000..ed7dd53922 --- /dev/null +++ b/examples/nuxt-app/test/features/search-listing/custom-collection.feature @@ -0,0 +1,36 @@ +Feature: Custom Collection + + As an editor I want to be able to add a view of results in a search index to a landing page. + + Background: + Given the page endpoint for path "/custom-collection" returns fixture "/landingpage/custom-collection" with status 200 + Given the site endpoint returns fixture "/site/reference" with status 200 + And the search autocomplete request is stubbed with "/search-listing/suggestions/none" fixture + Given I am using a "macbook-16" device + + @mockserver + Scenario: Custom collection + Given the "/api/tide/elasticsearch/sdp_data_pipelines_scl/_search" network request is stubbed with fixture "/landingpage/custom-collection/response" and status 200 as alias "cslReq" + Given I visit the page "/custom-collection" + Then the landing page component "TideCustomCollection" should exist + And the custom collection component should have a search input bar + And the custom collection component results count should read "Displaying 1-10 of 282 results" + And the "cslReq" network request should be made to the elasticsearch endpoint + And the search listing layout should be "table" + + @mockserver + Scenario: Error + Given the "/api/tide/elasticsearch/sdp_data_pipelines_scl/_search" network request is stubbed with fixture "/landingpage/custom-collection/response" and status 400 as alias "cslReq" + Given I visit the page "/custom-collection" + Then the landing page component "TideCustomCollection" should exist + And the custom collection component should display the error "Sorry! Something went wrong. Please try again later." + + @mockserver + Scenario: No results + Given the "/api/tide/elasticsearch/sdp_data_pipelines_scl/_search" network request is stubbed with fixture "/landingpage/custom-collection/response-no-items" and status 200 as alias "cslReq" + Given I visit the page "/custom-collection" + Then the landing page component "TideCustomCollection" should exist + And the custom collection component should display the error "Sorry! We couldn't find any matches for ''." + + + diff --git a/examples/nuxt-app/test/features/search-listing/filters.feature b/examples/nuxt-app/test/features/search-listing/filters.feature index f81b0a7ef0..1480f00385 100644 --- a/examples/nuxt-app/test/features/search-listing/filters.feature +++ b/examples/nuxt-app/test/features/search-listing/filters.feature @@ -98,6 +98,15 @@ Feature: Search listing - Filter When I toggle the search listing filters section Then the search listing dropdown field labelled "Custom function filter example" should have the value "Open, Closed" + @mockserver + Example: Custom fallback values - Should be included in the search query when not set + Given the page endpoint for path "/filters" returns fixture "/search-listing/filters/page-fallback-values" with status 200 + And the search network request is stubbed with fixture "/search-listing/filters/response" and status 200 + + When I visit the page "/filters?valueSet=orange" + Then the search listing page should have 2 results + And the search network request should be called with the "/search-listing/filters/request-fallback-values" fixture + @mockserver Example: Clear filters Given the page endpoint for path "/filters" returns fixture "/search-listing/filters/page" with status 200 diff --git a/examples/nuxt-app/test/features/search-listing/sort.feature b/examples/nuxt-app/test/features/search-listing/sort.feature index 8bea36d5d0..68c3fdcadd 100644 --- a/examples/nuxt-app/test/features/search-listing/sort.feature +++ b/examples/nuxt-app/test/features/search-listing/sort.feature @@ -9,10 +9,62 @@ Feature: Search listing - Filter @mockserver Example: Custom sort from config - Given the page endpoint for path "/sort" returns fixture "/search-listing/sort/page" with status 200 + Given the page endpoint for path "/sort" returns fixture "/search-listing/sort/page-custom-sort" with status 200 And the search network request is stubbed with fixture "/search-listing/sort/response" and status 200 And the current date is "Fri, 02 Feb 2050 03:04:05 GMT" When I visit the page "/sort" Then the search listing page should have 2 results - And the search network request should be called with the "/search-listing/sort/request" fixture + And the search network request should be called with the "/search-listing/sort/request-custom-sort" fixture + + @mockserver + Example: Sort dropdown - no dropdown + Given the page endpoint for path "/sort" returns fixture "/search-listing/sort/page-custom-sort" with status 200 + And the search network request is stubbed with fixture "/search-listing/sort/response" and status 200 + And the current date is "Fri, 02 Feb 2050 03:04:05 GMT" + + When I visit the page "/sort" + Then the search listing page should have 2 results + And the sort dropdown should not be visible + + @mockserver + Example: Sort dropdown - default + Given the page endpoint for path "/sort" returns fixture "/search-listing/sort/page-sort-dropdown" with status 200 + And the search network request is stubbed with fixture "/search-listing/sort/response" and status 200 + And the current date is "Fri, 02 Feb 2050 03:04:05 GMT" + + When I visit the page "/sort" + Then the search listing page should have 2 results + And the search network request should be called with the "/search-listing/sort/request-a-z" fixture + + Then the sort dropdown should be visible + And the sort dropdown should have the "A to Z" option selected + + When I click "Date last updated" from the select field with label "Sort by" + + Then the search network request should be called with the "/search-listing/sort/request-last-updated" fixture + And the URL should reflect that the current sort option is "last-updated" + And the sort dropdown should have the "Date last updated" option selected + + @mockserver + Example: Sort dropdown - populating from URL + Given the page endpoint for path "/sort" returns fixture "/search-listing/sort/page-sort-dropdown" with status 200 + And the search network request is stubbed with fixture "/search-listing/sort/response" and status 200 + And the current date is "Fri, 02 Feb 2050 03:04:05 GMT" + + When I visit the page "/sort?sort=last-updated&page=3" + Then the search listing page should have 2 results + And the search network request should be called with the "/search-listing/sort/request-last-updated-url" fixture + + Then the sort dropdown should be visible + And the sort dropdown should have the "Date last updated" option selected + And the URL should reflect that the current sort option is "last-updated" + And the URL should reflect that the current page number is 3 + + + When I click "A to Z" from the select field with label "Sort by" + + Then the search network request should be called with the "/search-listing/sort/request-a-z" fixture + And the URL should reflect that the current sort option is "a-to-z" + And the URL should reflect that the current page number is 1 + And the sort dropdown should have the "A to Z" option selected diff --git a/examples/nuxt-app/test/features/site/shared-elements.feature b/examples/nuxt-app/test/features/site/shared-elements.feature index 3d8493ab7a..7e82d6594d 100644 --- a/examples/nuxt-app/test/features/site/shared-elements.feature +++ b/examples/nuxt-app/test/features/site/shared-elements.feature @@ -73,6 +73,10 @@ Feature: Shared site elements Then the footer nav section with title "Level 1 - Item 2" should have the following links | text | url | | Level 1 - Item 2 | /level-1-item-2 | + Then the footer nav section with title "Connect with us" should have the following links + | text | url | + | Facebook | https://www.facebook.com/ | + | LinkedIn | https://www.linkedin.com/ | Then the footer should have the following links | text | url | | Footer link 1 | /footer-link-1 | diff --git a/examples/nuxt-app/test/fixtures/landingpage/custom-collection.json b/examples/nuxt-app/test/fixtures/landingpage/custom-collection.json new file mode 100644 index 0000000000..69693b8f43 --- /dev/null +++ b/examples/nuxt-app/test/fixtures/landingpage/custom-collection.json @@ -0,0 +1,187 @@ +{ + "title": "Custom collection", + "changed": "2022-11-02T12:47:29+11:00", + "created": "2022-11-02T12:47:29+11:00", + "type": "landing_page", + "nid": "11dede11-10c0-111e1-1100-000000000330", + "summary": "Page summary", + "showInPageNav": true, + "inPageNavHeadingLevel": "h3", + "background": "default", + "header": { + "title": "Custom collection test", + "summary": "Test landing page title introduction text", + "theme": "default", + "backgroundImage": null + }, + "sidebar": { + "contacts": [ + { + "id": "26146cba-f307-449e-885c-7446efb3f315", + "contactTitle": "Victorian Government", + "contactName": "Victorian Government", + "department": "Department of Premier and Cabinet", + "email": "no-reply@vic.gov.au", + "locationAddress": { + "countryCode": "AU", + "administrativeArea": "VIC", + "locality": "Melbourne", + "postalCode": "3001", + "addressLine1": "Department of Premier and Cabinet", + "addressLine2": "GPO Box 4509" + }, + "postalAddress": { + "countryCode": "AU", + "administrativeArea": "VIC", + "locality": "Melbourne", + "postalCode": "3001", + "addressLine1": "Department of Premier and Cabinet", + "addressLine2": "GPO Box 4509" + }, + "phones": [ + { + "id": "8a6d0e28-3d34-40b7-a97b-ef980f0f6f49", + "title": "Calls in Australia", + "number": "1300 366 356" + }, + { + "id": "798566cf-2e8c-48eb-bc89-38a6486cb06a", + "title": "Calls from overseas", + "number": "+61 3 9603 8804" + } + ], + "socialMedia": [ + { + "id": "0b58c974-05bd-4379-8947-12c4959b992c", + "type": "twitter", + "text": "Twitter", + "url": "https://twitter.com/VicGovAu" + } + ] + } + ], + "relatedLinks": [ + { + "id": "33133902-1f57-4283-9f6b-48dd76297c69", + "text": "State Government of Victoria", + "url": "https://www.vic.gov.au" + }, + { + "id": "254c49f8-b14a-44a1-bad0-729bfb40e425", + "text": "Department of Premier and Cabinet", + "url": "https://www.vic.gov.au/department-premier-and-cabinet" + } + ], + "socialShareNetworks": ["Facebook", "Twitter", "LinkedIn"], + "siteSectionNav": { + "title": "Site-section Navigation", + "items": [ + { + "text": "Demo Landing Page", + "url": "route:entity.node.canonical;node=65", + "id": "683e718c-f024-41f5-aea3-3036155ef4c8", + "parent": null, + "weight": 0 + } + ] + } + }, + "headerComponents": [], + "bodyComponents": [ + { + "uuid": "a99aa287-7fac-430b-864e-3a1b044460b1", + "component": "TideLandingPageContent", + "id": "969", + "props": { + "html": "

This is a collection of grants which have some content above them

" + } + }, + { + "uuid": "55555555-5555-5555-5555-555555555555", + "component": "TideCustomCollection", + "id": "123", + "title": "Cameras save lives", + "props": { + "searchListingConfig": { + "searchProvider": "elasticsearch", + "index": "sdp_data_pipelines_scl", + "resultsPerPage": 10, + "labels": { + "submit": "Search", + "placeholder": "Enter suburb, postcode, streetname or offence location" + }, + "customSort": [ + { + "suburb": "asc" + } + ] + }, + "queryConfig": { + "multi_match": { + "query": "{{query}}", + "fields": ["suburb^3", "street^2", "offence_location"] + } + }, + "globalFilters": [], + "resultsConfig": { + "layout": { + "component": "TideSearchResultsTable", + "props": { + "columns": [ + { + "label": "Suburb", + "objectKey": "suburb" + }, + { + "label": "Location", + "objectKey": "street" + }, + { + "label": "Last annual test", + "objectKey": "last_annual_test" + } + ] + } + } + } + } + } + ], + "meta": { + "url": "/demo-landing-page", + "langcode": "en", + "description": "Nulla ultricies dignissim leo, posuere vestibulum erat cursus vitae", + "additional": [ + { + "tag": "link", + "attributes": { + "rel": "canonical", + "href": "https://develop.content.reference.sdp.vic.gov.au/demo-landing-page" + } + }, + { + "tag": "meta", + "attributes": { + "name": "title", + "content": "Demo Landing Page | Single Digital Presence Content Management System" + } + }, + { + "tag": "meta", + "attributes": { + "property": "og:image", + "content": "https://develop.content.reference.sdp.vic.gov.au/sites/default/files/tide_demo_content/Melbourne-tram.jpg" + } + } + ], + "keywords": "", + "image": { + "src": "https://develop.content.reference.sdp.vic.gov.au/sites/default/files/tide_demo_content/Melbourne-tram.jpg", + "alt": "Demo: Melbourne tram", + "title": "Demo: Melbourne tram", + "width": 1413, + "height": 785, + "drupal_internal__target_id": 46 + } + } +} diff --git a/examples/nuxt-app/test/fixtures/landingpage/custom-collection/request.json b/examples/nuxt-app/test/fixtures/landingpage/custom-collection/request.json new file mode 100644 index 0000000000..00531ea5b1 --- /dev/null +++ b/examples/nuxt-app/test/fixtures/landingpage/custom-collection/request.json @@ -0,0 +1,6 @@ +{ + "query": { "match_all": {} }, + "size": 1, + "from": 0, + "sort": [{ "suburb": "asc" }] +} diff --git a/examples/nuxt-app/test/fixtures/landingpage/custom-collection/response-no-items.json b/examples/nuxt-app/test/fixtures/landingpage/custom-collection/response-no-items.json new file mode 100644 index 0000000000..759bf60b7b --- /dev/null +++ b/examples/nuxt-app/test/fixtures/landingpage/custom-collection/response-no-items.json @@ -0,0 +1,18 @@ +{ + "took": 1, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 0, + "relation": "eq" + }, + "max_score": 1, + "hits": [] + } +} diff --git a/examples/nuxt-app/test/fixtures/landingpage/custom-collection/response.json b/examples/nuxt-app/test/fixtures/landingpage/custom-collection/response.json new file mode 100644 index 0000000000..b45397144d --- /dev/null +++ b/examples/nuxt-app/test/fixtures/landingpage/custom-collection/response.json @@ -0,0 +1,269 @@ +{ + "took": 1, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 282, + "relation": "eq" + }, + "max_score": 1, + "hits": [ + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:0", + "_score": 1, + "_source": { + "camera_id": "A00", + "suburb": "Fitzroy North", + "street": "Alexandra Parade", + "offence_location": "Alexandra Parade, at the intersection of Alexandra Parade and Smith Street, Fitzroy North", + "postcode": "3068", + "last_annual_test": "18/01/2023", + "last_annual_test_sortable": "2023/01/18", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/A00_Fitzroy_North_intersection-of-alexandra-parade-and-smith-street_camera_cert_0.pdf ", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.793722", + "longitude": "144.985021", + "intersects_with": "Smith Street", + "expiry_date": "18/01/2024", + "location": "-37.793722,144.985021" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:3", + "_score": 1, + "_source": { + "camera_id": "A06", + "suburb": "Wantirna", + "street": "Boronia Road", + "offence_location": "Boronia Road, at the intersection of Boronia Road and Wantirna Road, Wantirna", + "postcode": "3152", + "last_annual_test": "30/01/2023", + "last_annual_test_sortable": "2023/01/30", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/A06_Wantirna_intersection-of-boronia-road-and-wantirna-road_camera_cert.pdf ", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.847742", + "longitude": "145.227402", + "intersects_with": "Wantirna Road", + "expiry_date": "30/01/2024", + "location": "-37.847742,145.227402" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:15", + "_score": 1, + "_source": { + "camera_id": "A25", + "suburb": "Melbourne", + "street": "Elizabeth Street", + "offence_location": "Elizabeth Street, at the intersection of Elizabeth Street and Victoria Street, Melbourne", + "postcode": "3000", + "last_annual_test": "11/11/2022", + "last_annual_test_sortable": "2022/11/11", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/A25_Melbourne_intersection-of-elizabeth-street-and-victoria-street_camera_cert.pdf", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.8060786", + "longitude": "144.9595705", + "intersects_with": "Victoria Street", + "expiry_date": "11/11/2023", + "location": "-37.8060786,144.9595705" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:18", + "_score": 1, + "_source": { + "camera_id": "A39", + "suburb": "Clayton South", + "street": "Centre Road", + "offence_location": "Centre Road, at the intersection of Centre Road and Springs Road, Clayton South", + "postcode": "3169", + "last_annual_test": "7/12/2022", + "last_annual_test_sortable": "2022/12/07", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/A39_Clayton_South_intersection-of-centre-road-and-springs-road_camera_cert.pdf ", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.927488", + "longitude": "145.1100484", + "intersects_with": "Springs Road", + "expiry_date": "7/12/2023", + "location": "-37.927488,145.1100484" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:27", + "_score": 1, + "_source": { + "camera_id": "A51", + "suburb": "Templestowe Lower", + "street": "Manningham Road", + "offence_location": "Manningham Road, at the intersection of Manningham Road and Macedon Road, Templestowe Lower", + "postcode": "3107", + "last_annual_test": "14/06/2022", + "last_annual_test_sortable": "2022/06/14", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/A51_Templestowe_Lower_manningham-road-and-macedon-road_camer_cert.pdf", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.77336", + "longitude": "145.112057", + "intersects_with": "Macedon Road", + "expiry_date": "14/06/2023", + "location": "-37.77336,145.112057" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:29", + "_score": 1, + "_source": { + "camera_id": "A56", + "suburb": "Surrey Hills", + "street": "Mont Albert Road", + "offence_location": "Mont Albert Road, at the intersection of Mont Albert Road and Union Road, Surrey Hills", + "postcode": "3127", + "last_annual_test": "31/03/2023", + "last_annual_test_sortable": "2023/03/31", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-04/A56_Surrey_Hills_intersection-of-mont-albert-road-and-union-road_camera_cert_310323.pdf", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.8204019", + "longitude": "145.0981877", + "intersects_with": "Union Road", + "expiry_date": "31/03/2024", + "location": "-37.8204019,145.0981877" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:31", + "_score": 1, + "_source": { + "camera_id": "A59", + "suburb": "Echuca", + "street": "Ogilvie Avenue", + "offence_location": "Ogilvie Avenue, at the intersection of Ogilvie Avenue and High Street, Echuca", + "postcode": "3564", + "last_annual_test": "9/05/2023", + "last_annual_test_sortable": "2023/05/09", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-05/A59_Echuca-intersection-of-Ogilvie-Ave-and-High-St-camera-cert_090523.pdf", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-36.140432", + "longitude": "144.750963", + "intersects_with": "High Street", + "expiry_date": "9/05/2024", + "location": "-36.140432,144.750963" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:33", + "_score": 1, + "_source": { + "camera_id": "A66", + "suburb": "Newtown", + "street": "Shannon Avenue", + "offence_location": "Shannon Avenue, at the intersection of Shannon Avenue and Noble Street, Newtown", + "postcode": "3220", + "last_annual_test": "17/06/2022", + "last_annual_test_sortable": "2022/06/17", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/A66_Newtown_intersection-of-shannon-avenue-and-noble-street_camera_cert.pdf", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-38.152505", + "longitude": "144.3341973", + "intersects_with": "Noble Street", + "expiry_date": "17/06/2023", + "location": "-38.152505,144.3341973" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:45", + "_score": 1, + "_source": { + "camera_id": "B02", + "suburb": "Parkville", + "street": "Flemington Road", + "offence_location": "Flemington Road, at the intersection of Flemington Road and Gatehouse Street, Parkville", + "postcode": "3052", + "last_annual_test": "14/12/2022", + "last_annual_test_sortable": "2022/12/14", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/B02_Parkville_Intersection-of-Flemington-Road-and-Gatehouse-Street_camera_cert.pdf ", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.796335", + "longitude": "144.951148", + "intersects_with": "Gatehouse Street", + "expiry_date": "14/12/2023", + "location": "-37.796335,144.951148" + } + }, + { + "_index": "a83890f7a31dea14e1ae83c6f0afacca--sdp_data_pipelines_scl", + "_type": "_doc", + "_id": "scl:47", + "_score": 1, + "_source": { + "camera_id": "B12", + "suburb": "Bayswater North", + "street": "Canterbury Road", + "offence_location": "Canterbury Road, at the intersection of Canterbury Road and Bayswater Road, Bayswater North", + "postcode": "3153", + "last_annual_test": "29/06/2022", + "last_annual_test_sortable": "2022/06/29", + "camera_type": "Fixed Camera", + "certificate": "https://content.vic.gov.au/sites/default/files/2023-03/B12_Bayswater_North_intersection-of-canterbury-road-and-bayswater-road_camera_cert.pdf", + "site_type": "Intersection", + "reason": "Demonstrated accident risk", + "how_cameras_work": "https://www.vic.gov.au/camera-accuracy", + "latitude": "-37.832238", + "longitude": "145.269109", + "intersects_with": "Bayswater Road", + "expiry_date": "29/06/2023", + "location": "-37.832238,145.269109" + } + } + ] + } +} diff --git a/examples/nuxt-app/test/fixtures/landingpage/home.json b/examples/nuxt-app/test/fixtures/landingpage/home.json index 9771b0b57b..9948ed5760 100644 --- a/examples/nuxt-app/test/fixtures/landingpage/home.json +++ b/examples/nuxt-app/test/fixtures/landingpage/home.json @@ -235,7 +235,7 @@ } ], "props": { - "html": "

Here is some sample rich text content

" + "html": "

Here is some sample rich text content, with a random link(opens in a new window) thrown in.

" } }, { @@ -624,7 +624,7 @@ }, { "type": "keydates", - "url": "/another-keydates-page", + "url": "/dates", "title": "Key dates heading", "keyDates": [ { @@ -683,17 +683,21 @@ }, "orientation": "row", "columns": [ - "Row One Column One", - "Row One Column Two", - "Row One Column Three" + { "label": "Row One Column One", "objectKey": "col0" }, + { "label": "Row One Column Two", "objectKey": "col1" }, + { "label": "Row One Column Three", "objectKey": "col2" } ], "items": [ - ["Row Two Column One", "Row Two Column Two", "Row Two Column Three"], - [ - "Row Three Column One", - "Row Three Column Two", - "Row Three Column Three" - ] + { + "col0": "Row Two Column One", + "col1": "Row Two Column Two", + "col2": "Row Two Column Three" + }, + { + "col0": "Row Three Column One", + "col1": "Row Three Column Two", + "col2": "Row Three Column Three" + } ] } }, @@ -707,14 +711,22 @@ "vertical": false }, "orientation": "row", - "columns": [], + "columns": [ + { "objectKey": "col0" }, + { "objectKey": "col1" }, + { "objectKey": "col2" } + ], "items": [ - ["Row Two Column One", "Row Two Column Two", "Row Two Column Three"], - [ - "Row Three Column One", - "Row Three Column Two", - "Row Three Column Three" - ] + { + "col0": "Row Two Column One", + "col1": "Row Two Column Two", + "col2": "Row Two Column Three" + }, + { + "col0": "Row Three Column One", + "col1": "Row Three Column Two", + "col2": "Row Three Column Three" + } ] } }, diff --git a/examples/nuxt-app/test/fixtures/publication-page/sample-publication-page.json b/examples/nuxt-app/test/fixtures/publication-page/sample-publication-page.json index 28b8597fe2..8b4bebd8c5 100644 --- a/examples/nuxt-app/test/fixtures/publication-page/sample-publication-page.json +++ b/examples/nuxt-app/test/fixtures/publication-page/sample-publication-page.json @@ -190,14 +190,14 @@ ], "pagination": { "prev": { - "text": "Demo Publication", + "text": "Previous", "url": "/demo-publication", - "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam tincidunt sit amet ligula sit amet lacinia. In a leo nec tortor aliquet faucibus." + "description": "Demo Publication" }, "next": { - "text": "Demo Publication - Chapter 1 - Page 1", + "text": "Next", "url": "/demo-publication/demo-publication-chapter-1/demo-publication-chapter-1-page-1", - "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam tincidunt sit amet ligula sit amet lacinia. In a leo nec tortor aliquet faucibus." + "description": "Demo Publication - Chapter 1 - Page 1" } } }, diff --git a/examples/nuxt-app/test/fixtures/search-listing/filters/page-fallback-values.json b/examples/nuxt-app/test/fixtures/search-listing/filters/page-fallback-values.json new file mode 100644 index 0000000000..3c6e432a6a --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/filters/page-fallback-values.json @@ -0,0 +1,120 @@ +{ + "title": "Grants and programs", + "changed": "2022-11-02T12:47:29+11:00", + "created": "2022-11-02T12:47:29+11:00", + "type": "tide_search_listing", + "nid": "11dede11-10c0-111e1-1100-000000000332", + "showTopicTags": true, + "summary": "", + "config": { + "searchListingConfig": { + "resultsPerPage": 10 + }, + "queryConfig": { + "multi_match": { + "query": "{{query}}", + "fields": [ + "title^3", + "field_landing_page_summary^2", + "body", + "field_paragraph_body", + "summary_processed" + ] + } + }, + "results": { + "layout": { + "component": "TideSearchResultsList" + }, + "item": { + "grant": { + "component": "TideGrantSearchResult" + } + } + }, + "globalFilters": [ + { "terms": { "type": ["grant"] } }, + { "terms": { "field_node_site": [8888] } } + ], + "userFilters": [ + { + "id": "fallbackValuesFunction", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "fallbackValue": "dynamicValue", + "value": "fallbackValuesFunction" + }, + "props": { + "id": "fallbackValuesFunction", + "label": "Filter fallback value function", + "multiple": true, + "options": [ + { + "id": "1", + "label": "Blue", + "value": "blue" + }, + { + "id": "2", + "label": "Green", + "value": "green" + } + ] + } + }, + { + "id": "fallbackValuesText", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "fallbackValue": ["red"], + "value": "fallbackValuesText" + }, + "props": { + "id": "fallbackValuesText", + "label": "Filter fallback value function", + "multiple": true, + "options": [ + { + "id": "1", + "label": "Blue", + "value": "blue" + }, + { + "id": "2", + "label": "Red", + "value": "red" + } + ] + } + }, + { + "id": "valueSet", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "terms", + "fallbackValue": ["gold"], + "value": "valueSet" + }, + "props": { + "id": "valueSet", + "label": "Filter fallback value function", + "multiple": true, + "options": [ + { + "id": "1", + "label": "Gold", + "value": "gold" + }, + { + "id": "2", + "label": "Orange", + "value": "orange" + } + ] + } + } + ] + } +} diff --git a/examples/nuxt-app/test/fixtures/search-listing/filters/request-fallback-values.json b/examples/nuxt-app/test/fixtures/search-listing/filters/request-fallback-values.json new file mode 100644 index 0000000000..de96f9a750 --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/filters/request-fallback-values.json @@ -0,0 +1,54 @@ +{ + "query": { + "bool": { + "must": [ + { + "match_all": {} + } + ], + "filter": [ + { + "terms": { + "type": ["grant"] + } + }, + { + "terms": { + "field_node_site": [8888] + } + }, + { + "terms": { + "valueSet": [ + "orange" + ] + } + }, + { + "terms": { + "fallbackValuesFunction": [ + "blue" + ] + } + }, + { + "terms": { + "fallbackValuesText": [ + "red" + ] + } + } + ] + } + }, + "size": 10, + "from": 0, + "sort": [ + { + "_score": "desc" + }, + { + "_doc": "desc" + } + ] +} diff --git a/examples/nuxt-app/test/fixtures/search-listing/sort/page.json b/examples/nuxt-app/test/fixtures/search-listing/sort/page-custom-sort.json similarity index 100% rename from examples/nuxt-app/test/fixtures/search-listing/sort/page.json rename to examples/nuxt-app/test/fixtures/search-listing/sort/page-custom-sort.json diff --git a/examples/nuxt-app/test/fixtures/search-listing/sort/page-sort-dropdown.json b/examples/nuxt-app/test/fixtures/search-listing/sort/page-sort-dropdown.json new file mode 100644 index 0000000000..d346a5b015 --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/sort/page-sort-dropdown.json @@ -0,0 +1,102 @@ +{ + "title": "Grants and programs", + "changed": "2022-11-02T12:47:29+11:00", + "created": "2022-11-02T12:47:29+11:00", + "type": "tide_search_listing", + "nid": "11dede11-10c0-111e1-1100-000000000330", + "showTopicTags": true, + "summary": "", + "config": { + "searchListingConfig": { + "resultsPerPage": 10, + "customSort": [ + { + "some.test.field": "asc" + } + ] + }, + "sortOptions": [ + { + "id": "a-to-z", + "label": "A to Z", + "clause": [ + { + "title.keyword": "asc" + } + ] + }, + { + "id": "last-updated", + "label": "Date last updated", + "clause": [ + { + "field_date_last_updated": "desc" + } + ] + } + ], + "queryConfig": { + "multi_match": { + "query": "{{query}}", + "fields": [ + "title^3", + "field_landing_page_summary^2", + "body", + "field_paragraph_body", + "summary_processed" + ] + } + }, + "results": { + "layout": { + "component": "TideSearchResultsList" + }, + "item": { + "grant": { + "component": "TideGrantSearchResult" + } + } + }, + "globalFilters": [ + { "terms": { "type": ["grant"] } }, + { "terms": { "field_node_site": [8888] } } + ], + "userFilters": [ + { + "id": "termFilter", + "component": "TideSearchFilterDropdown", + "filter": { + "type": "term", + "value": "termFilter.keyword" + }, + "aggregations": { + "field": "termFilter", + "source": "taxonomy" + }, + "props": { + "id": "termFilter", + "label": "Term filter example", + "placeholder": "Select a colour", + "multiple": true, + "options": [ + { + "id": "1", + "label": "Red", + "value": "Red" + }, + { + "id": "2", + "label": "Green", + "value": "Green" + }, + { + "id": "3", + "label": "Blue", + "value": "Blue" + } + ] + } + } + ] + } +} diff --git a/examples/nuxt-app/test/fixtures/search-listing/sort/request-a-z.json b/examples/nuxt-app/test/fixtures/search-listing/sort/request-a-z.json new file mode 100644 index 0000000000..e397e499db --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/sort/request-a-z.json @@ -0,0 +1,30 @@ +{ + "query": { + "bool": { + "must": [ + { + "match_all": {} + } + ], + "filter": [ + { + "terms": { + "type": ["grant"] + } + }, + { + "terms": { + "field_node_site": [8888] + } + } + ] + } + }, + "size": 10, + "from": 0, + "sort": [ + { + "title.keyword": "asc" + } + ] +} diff --git a/examples/nuxt-app/test/fixtures/search-listing/sort/request.json b/examples/nuxt-app/test/fixtures/search-listing/sort/request-custom-sort.json similarity index 100% rename from examples/nuxt-app/test/fixtures/search-listing/sort/request.json rename to examples/nuxt-app/test/fixtures/search-listing/sort/request-custom-sort.json diff --git a/examples/nuxt-app/test/fixtures/search-listing/sort/request-last-updated-url.json b/examples/nuxt-app/test/fixtures/search-listing/sort/request-last-updated-url.json new file mode 100644 index 0000000000..8264e896cd --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/sort/request-last-updated-url.json @@ -0,0 +1,30 @@ +{ + "query": { + "bool": { + "must": [ + { + "match_all": {} + } + ], + "filter": [ + { + "terms": { + "type": ["grant"] + } + }, + { + "terms": { + "field_node_site": [8888] + } + } + ] + } + }, + "size": 10, + "from": 20, + "sort": [ + { + "field_date_last_updated": "desc" + } + ] +} diff --git a/examples/nuxt-app/test/fixtures/search-listing/sort/request-last-updated.json b/examples/nuxt-app/test/fixtures/search-listing/sort/request-last-updated.json new file mode 100644 index 0000000000..ca3eb944cf --- /dev/null +++ b/examples/nuxt-app/test/fixtures/search-listing/sort/request-last-updated.json @@ -0,0 +1,30 @@ +{ + "query": { + "bool": { + "must": [ + { + "match_all": {} + } + ], + "filter": [ + { + "terms": { + "type": ["grant"] + } + }, + { + "terms": { + "field_node_site": [8888] + } + } + ] + } + }, + "size": 10, + "from": 0, + "sort": [ + { + "field_date_last_updated": "desc" + } + ] +} diff --git a/examples/nuxt-app/test/fixtures/site/shared-elements.json b/examples/nuxt-app/test/fixtures/site/shared-elements.json index df511c1e48..97d61a9a19 100644 --- a/examples/nuxt-app/test/fixtures/site/shared-elements.json +++ b/examples/nuxt-app/test/fixtures/site/shared-elements.json @@ -41,6 +41,22 @@ } } }, + "socialLinks": [ + { + "id": "social_link-0", + "text": "Facebook", + "url": "https://www.facebook.com/", + "icon": "icon-facebook", + "iconColour": "currentColor" + }, + { + "id": "social_link-1", + "text": "LinkedIn", + "url": "https://www.linkedin.com/", + "icon": "icon-linkedin", + "iconColour": "currentColor" + } + ], "menus": { "menuMain": [ { diff --git a/examples/nuxt-app/test/support/step_definitions/index.ts b/examples/nuxt-app/test/support/step_definitions/index.ts index b1fa0b7287..acb1b0286d 100644 --- a/examples/nuxt-app/test/support/step_definitions/index.ts +++ b/examples/nuxt-app/test/support/step_definitions/index.ts @@ -1 +1,8 @@ import '@dpc-sdp/ripple-test-utils/step_definitions' +Cypress.on('uncaught:exception', (err) => { + // https://stackoverflow.com/a/50387233 + // Ignore Resize observer loop issue in expand search filters for now + if (err.message.includes('ResizeObserver loop')) { + return false + } +}) diff --git a/lerna.json b/lerna.json index edc0778bc3..1827522d09 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.1.9", + "version": "2.4.2", "npmClient": "pnpm", "exact": true, "command": { diff --git a/package.json b/package.json index 08f04c0e3a..483e74eda0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "release:changelog": "changelogen --output --no-commit -r $(cat lerna.json | jq -r '.version')", "release:publish-alpha": "lerna publish --canary minor --preid alpha --dist-tag alpha --force-publish", "release:publish-next": "lerna publish --canary --preid next.$(git rev-parse --short HEAD) --dist-tag next --force-publish", - "release:publish": "changelogen gh release && lerna publish from-package", + "release:publish": "lerna publish from-package", "lint": "eslint . --ext .ts,.vue && stylelint 'packages/ripple-ui-core/**/*.css'", "test:unit": "jest --colors --runInBand", "test:components-core": "pnpm -F @dpc-sdp/ripple-ui-core test:components", @@ -43,9 +43,9 @@ "generate:component": "nuxt-ripple add component ./packages/ripple-ui-core/src/components" }, "devDependencies": { - "@babel/core": "^7.21.3", - "@babel/plugin-transform-runtime": "^7.21.0", - "@babel/preset-env": "^7.20.2", + "@babel/core": "^7.23.2", + "@babel/plugin-transform-runtime": "^7.23.2", + "@babel/preset-env": "^7.23.2", "@bahmutov/cypress-esbuild-preprocessor": "^2.2.0", "@commitlint/cli": "^17.4.4", "@commitlint/config-conventional": "^17.4.4", @@ -88,7 +88,7 @@ }, "engines": { "node": "^16.11.0 || ^18.15.0", - "pnpm": "8.6.2" + "pnpm": "^8.6.2" }, "dependencies": { "@vue/vue3-jest": "^29.2.3", @@ -97,7 +97,6 @@ }, "pnpm": { "overrides": { - "@storybook/test-runner>jest": "29.5.0", "@types/react": "file:./stub/types__react", "jest-matcher-utils@29.2.2>chalk": "5.2.0", "cypress": "12.8.1", diff --git a/packages/eslint-config-ripple/index.js b/packages/eslint-config-ripple/index.js index d5d29640d6..d7b421cdb0 100644 --- a/packages/eslint-config-ripple/index.js +++ b/packages/eslint-config-ripple/index.js @@ -17,6 +17,7 @@ module.exports = { 'vue/max-attributes-per-line': 'off', 'vue/multi-word-component-names': 'off', 'vue/no-v-html': 'off', + 'vue/v-on-event-hyphenation': 'off', '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-explicit-any': 'off', // allow explicit any types for now '@typescript-eslint/no-unused-vars': [ diff --git a/packages/eslint-config-ripple/package.json b/packages/eslint-config-ripple/package.json index b568421caa..3e0a0136ab 100644 --- a/packages/eslint-config-ripple/package.json +++ b/packages/eslint-config-ripple/package.json @@ -2,7 +2,7 @@ "packageManager": "pnpm@8.6.2", "name": "@dpc-sdp/eslint-config-ripple", "description": "ESLint config for Ripple projects", - "version": "2.1.9", + "version": "2.4.2", "license": "Apache-2.0", "repository": "https://github.com/dpc-sdp/ripple-framework", "main": "index.js", @@ -13,15 +13,15 @@ "index.js" ], "dependencies": { - "@nuxt/eslint-config": "0.1.1" - }, - "devDependencies": { + "@nuxt/eslint-config": "0.1.1", "@typescript-eslint/eslint-plugin": "^6.2.0", "@typescript-eslint/parser": "^6.2.0", - "eslint": "^8.45.0", - "eslint-junit": "^1.0.1", "vue-eslint-parser": "^9.3.1" }, + "devDependencies": { + "eslint": "^8.45.0", + "eslint-junit": "^1.0.1" + }, "peerDependencies": { "eslint": "^8.45.0" } diff --git a/packages/nuxt-ripple-analytics/lib/routeChange.ts b/packages/nuxt-ripple-analytics/lib/routeChange.ts index 83dbc6abc8..3ad9a6f547 100644 --- a/packages/nuxt-ripple-analytics/lib/routeChange.ts +++ b/packages/nuxt-ripple-analytics/lib/routeChange.ts @@ -1,22 +1,19 @@ import { IRplAnalyticsEventPayload } from './tracker' import { getBreadcrumbs } from '#imports' -import { useRuntimeConfig } from '#app' const trimValue = (value: any) => typeof value === 'string' ? value.trim() : value export default function ({ route, site, page }): IRplAnalyticsEventPayload { - const production = useRuntimeConfig()?.public?.isProduction - const payload: IRplAnalyticsEventPayload = { - production, event: 'routeChange', - name: page?.title, + page_title: page?.title, page_url: route.fullPath, content_type: page?.type, publication_name: page?.publication?.text, search_term: trimValue(route.query?.q), site_section: page?.siteSection?.name, + status_code: page?.statusCode || 200, platform_event: 'page/routeChange' } @@ -30,14 +27,5 @@ export default function ({ route, site, page }): IRplAnalyticsEventPayload { .map((crumb) => crumb.text) } - const measurementIds = { - uat_measurement_id: site?.featureFlags?.uatMeasurementID, - prod_measurement_id: site?.featureFlags?.prodMeasurementID - } - - if (Object.values(measurementIds).filter(Boolean).length) { - payload.google_analytics = measurementIds - } - return payload } diff --git a/packages/nuxt-ripple-analytics/lib/tracker.ts b/packages/nuxt-ripple-analytics/lib/tracker.ts index 7f74b8ac93..10937d1bfa 100644 --- a/packages/nuxt-ripple-analytics/lib/tracker.ts +++ b/packages/nuxt-ripple-analytics/lib/tracker.ts @@ -3,6 +3,7 @@ export interface IRplAnalyticsEventPayload { event: string name?: string page_url?: string + page_title?: string platform_event: string // Component properties label?: string @@ -26,6 +27,7 @@ export interface IRplAnalyticsEventPayload { component?: string component_options?: string // Route properties + status_code?: number content_type?: string search_term?: string site_section?: string diff --git a/packages/nuxt-ripple-analytics/package.json b/packages/nuxt-ripple-analytics/package.json index 29fbda0ff4..2a2ac8d5d2 100644 --- a/packages/nuxt-ripple-analytics/package.json +++ b/packages/nuxt-ripple-analytics/package.json @@ -2,7 +2,7 @@ "packageManager": "pnpm@8.6.2", "name": "@dpc-sdp/nuxt-ripple-analytics", "description": "Nuxt module for handling event tracking.", - "version": "2.1.9", + "version": "2.4.2", "license": "Apache-2.0", "main": "./nuxt.config.ts", "repository": "https://github.com/dpc-sdp/ripple-framework", diff --git a/packages/nuxt-ripple-analytics/plugins/analytics.ts b/packages/nuxt-ripple-analytics/plugins/analytics.ts index 5e9674d134..f0d40a7482 100644 --- a/packages/nuxt-ripple-analytics/plugins/analytics.ts +++ b/packages/nuxt-ripple-analytics/plugins/analytics.ts @@ -2,6 +2,7 @@ import { defineNuxtPlugin, useAppConfig, useRuntimeConfig } from '#app' import { loadScript } from '@gtm-support/core' import { trackEvent } from '../lib/tracker' import routeChange from '../lib/routeChange' +import type { IRplFeatureFlags } from '@dpc-sdp/ripple-tide-api/types' declare global { interface Window { @@ -9,10 +10,22 @@ declare global { } } -const setupDataLayer = () => { +const setupDataLayer = (featureFlags: IRplFeatureFlags) => { + const production = useRuntimeConfig()?.public?.isProduction + /*eslint-disable no-prototype-builtins */ - if (typeof window !== undefined && !window.hasOwnProperty('dataLayer')) { - window.dataLayer = [] + if (typeof window !== undefined) { + if (!window.hasOwnProperty('dataLayer')) { + window.dataLayer = [] + } + + window.dataLayer.push({ + production, + google_analytics: { + prod_measurement_id: featureFlags?.prodMeasurementID, + uat_measurement_id: featureFlags?.uatMeasurementID + } + }) } } @@ -31,10 +44,8 @@ export default defineNuxtPlugin((nuxtApp) => { /* @ts-ignore process is extended by webpack */ if (process.client) { - nuxtApp.hook('page:finish', () => { + nuxtApp.hook('tide:page', ({ page, site }) => { const route = useRoute() - const site = nuxtApp.payload.data?.[`site-${runtimeConfig.site}`] - const page = nuxtApp.payload.data?.[`page-${route.fullPath}`] if (appConfig?.analytics?.routeChange === false) return @@ -55,10 +66,10 @@ export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.use({ install(app: any) { const rplEventBus = app._context?.provides?.$rplEvent - setupDataLayer() + const site = nuxtApp?.payload.data?.[`site-${runtimeConfig.site}`] + setupDataLayer(site?.featureFlags) setupGTM(runtimeConfig?.analytics?.GTM) // Check for site-specific GTM container - const site = nuxtApp?.payload.data?.[`site-${runtimeConfig.site}`] if (site?.featureFlags?.gtmContainerID) { setupGTM(site?.featureFlags?.gtmContainerID) } diff --git a/packages/nuxt-ripple-cli/package.json b/packages/nuxt-ripple-cli/package.json index cbad421db8..91525a4370 100644 --- a/packages/nuxt-ripple-cli/package.json +++ b/packages/nuxt-ripple-cli/package.json @@ -2,7 +2,7 @@ "packageManager": "pnpm@8.6.2", "name": "@dpc-sdp/nuxt-ripple-cli", "description": "A CLI for simplifying common setup and scaffolding tasks", - "version": "2.1.9", + "version": "2.4.2", "license": "Apache-2.0", "repository": "https://github.com/dpc-sdp/ripple-framework", "main": "./dist/index.js", diff --git a/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/eslint.rc.t b/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/eslint.rc.t index b757f03b84..59ce3eca26 100644 --- a/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/eslint.rc.t +++ b/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/eslint.rc.t @@ -3,5 +3,5 @@ to: .eslintrc --- { "root": true, - "extends": ["@nuxtjs/eslint-config-typescript"] + "extends": ["@dpc-sdp/eslint-config-ripple"] } diff --git a/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/github/publish.yml.t b/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/github/publish.yml.t index 5e798266e2..09161713b9 100644 --- a/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/github/publish.yml.t +++ b/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/github/publish.yml.t @@ -1,28 +1,34 @@ --- to: "<%= gitHubActions ? `.github/workflows/publish.yml` : null %>" --- -name: Publish package -author: SDP -description: Publishes the package to GitHub Package registry on GitHub release creation. See https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages#publishing-packages-to-github-packages. +name: Publish Package + on: release: types: [created] jobs: - Publish: - needs: - - Build-test - - Integration + publish-gpr: runs-on: ubuntu-latest permissions: packages: write - contents: read + contents: write steps: - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - name: Install Node + uses: actions/setup-node@v3 with: + registry-url: 'https://npm.pkg.github.com/' node-version: 18 - - run: npm ci - - run: npm config set @dpc-sdp:https://npm.pkg.github.com/ &&ย npm publish + - name: Set Git credentials + run: | + git config --global user.email "sdp.devs@dpc.vic.gov.au" + git config --global user.name "SDP Deploy" + - name: Bump version from release number + run: npm version ${{ github.event.release.name }} --allow-same-version + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Publish to GH Package registry + run: npm publish env: - NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/package.json.t b/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/package.json.t index 130271e20c..9bcdddc93c 100644 --- a/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/package.json.t +++ b/packages/nuxt-ripple-cli/src/commands/init/_templates/layer/latest/package.json.t @@ -2,9 +2,10 @@ to: package.json --- { - "name": "<%= h.changeCase.kebabCase(name) %>", + "name": "@dpc-sdp/<%= h.changeCase.kebabCase(name) %>", "type": "module", "version": "0.0.0", + "repository": "https://github.com/dpc-sdp/<%= h.changeCase.kebabCase(name) %>", "main": "./nuxt.config.ts", "exports": { ".": "./nuxt.config.ts", @@ -30,6 +31,7 @@ to: package.json "@babel/plugin-transform-runtime": "^7.22.4", "@babel/preset-env": "^7.22.4", "@babel/preset-typescript": "^7.21.5", + "@dpc-sdp/eslint-config-ripple": "<%= rplVersion %>", "@dpc-sdp/nuxt-ripple": "<%= rplVersion %>", "@dpc-sdp/nuxt-ripple-analytics": "<%= rplVersion %>", "@dpc-sdp/nuxt-ripple-preview": "<%= rplVersion %>", @@ -43,7 +45,6 @@ to: package.json "@dpc-sdp/ripple-tide-news": "<%= rplVersion %>", "@dpc-sdp/ripple-tide-publication": "<%= rplVersion %>", "@dpc-sdp/ripple-tide-search": "<%= rplVersion %>", - "@nuxtjs/eslint-config-typescript": "^12.0.0", "cypress": "^12.5.1", "eslint": "^8.28.0", "jest-environment-jsdom": "^29.5.0", @@ -55,5 +56,8 @@ to: package.json "engines": { "node": "^18.12.1", "npm": "^9.5.1" + }, + "publishConfig": { + "registry": "https://npm.pkg.github.com/" } } diff --git a/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/eslint.rc.t b/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/eslint.rc.t index b757f03b84..59ce3eca26 100644 --- a/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/eslint.rc.t +++ b/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/eslint.rc.t @@ -3,5 +3,5 @@ to: .eslintrc --- { "root": true, - "extends": ["@nuxtjs/eslint-config-typescript"] + "extends": ["@dpc-sdp/eslint-config-ripple"] } diff --git a/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/npmrc.t b/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/npmrc.t new file mode 100644 index 0000000000..44de85d2f7 --- /dev/null +++ b/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/npmrc.t @@ -0,0 +1,4 @@ +--- +to: .npmrc +--- +@dpc-sdp:registry=https://npm.pkg.github.com diff --git a/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/package.json.t b/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/package.json.t index 8523105f3e..6211081c39 100644 --- a/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/package.json.t +++ b/packages/nuxt-ripple-cli/src/commands/init/_templates/site/latest/package.json.t @@ -24,11 +24,11 @@ to: package.json "@dpc-sdp/ripple-tide-media": "<%= rplVersion %>", "@dpc-sdp/ripple-tide-news": "<%= rplVersion %>", "@dpc-sdp/ripple-tide-publication": "<%= rplVersion %>", - "@dpc-sdp/ripple-tide-search": "^<%= rplVersion %>", - "@dpc-sdp/ripple-tide-topic": "^<%= rplVersion %>" + "@dpc-sdp/ripple-tide-search": "<%= rplVersion %>", + "@dpc-sdp/ripple-tide-topic": "<%= rplVersion %>" }, "devDependencies": { - "@nuxtjs/eslint-config-typescript": "^12.0.0", + "@dpc-sdp/eslint-config-ripple": "<%= rplVersion %>", "nuxt": "3.6.5", "eslint": "^8.28.0" }, diff --git a/packages/nuxt-ripple-preview/package.json b/packages/nuxt-ripple-preview/package.json index 25b6b823c7..48d4957ca1 100644 --- a/packages/nuxt-ripple-preview/package.json +++ b/packages/nuxt-ripple-preview/package.json @@ -2,7 +2,7 @@ "packageManager": "pnpm@8.6.2", "name": "@dpc-sdp/nuxt-ripple-preview", "description": "Adds support for drupal preview links in Ripple frontend sites", - "version": "2.1.9", + "version": "2.4.2", "license": "Apache-2.0", "main": "./nuxt.config.ts", "repository": "https://github.com/dpc-sdp/ripple-framework", diff --git a/packages/nuxt-ripple/app.config.ts b/packages/nuxt-ripple/app.config.ts index a50738dabd..a55b1c00d2 100644 --- a/packages/nuxt-ripple/app.config.ts +++ b/packages/nuxt-ripple/app.config.ts @@ -6,20 +6,34 @@ declare module '@nuxt/schema' { featureFlags?: IRplFeatureFlags theme?: { ['rpl-clr-primary']?: string + ['rpl-clr-primary-alpha']?: string + ['rpl-clr-footer']?: string + ['rpl-clr-footer-alt']?: string ['rpl-clr-primary-alt']?: string + ['rpl-clr-type-primary-accessible']?: string + ['rpl-clr-type-primary-alt-accessible']?: string + ['rpl-clr-type-footer-accessible']?: string ['rpl-clr-accent']?: string ['rpl-clr-accent-alt']?: string ['rpl-clr-link']?: string ['rpl-clr-focus']?: string ['rpl-clr-type-focus-contrast']?: string + ['rpl-clr-gradient-horizontal']?: string + ['rpl-clr-gradient-vertical']?: string } languages?: { - name?: string - url?: string - rtl?: boolean + [key: string]: { + name: string + url: string + rtl?: boolean + } } search?: { - contentTypes: string[] + contentTypes?: string[] + fallbackValues?: Record< + string, + (filterConfig: any, values: string[]) => void + > filterFunctions?: Record< string, (filterConfig: any, values: string[]) => void @@ -100,6 +114,10 @@ export default defineAppConfig({ name: 'Noto Sans KR', url: 'https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&display=swap' }, + kyu: { + name: 'Noto Sans Kayah Li', + url: 'https://fonts.googleapis.com/css2?family=Noto+Sans+Kayah+Li:wght@400;700&display=swap' + }, mk: { name: 'Noto Sans', url: 'https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&display=swap' diff --git a/packages/nuxt-ripple/components/TideBaseLayout.vue b/packages/nuxt-ripple/components/TideBaseLayout.vue index 16c382b871..87439df571 100644 --- a/packages/nuxt-ripple/components/TideBaseLayout.vue +++ b/packages/nuxt-ripple/components/TideBaseLayout.vue @@ -74,7 +74,7 @@ diff --git a/packages/nuxt-ripple/components/TideDynamicComponents.vue b/packages/nuxt-ripple/components/TideDynamicComponents.vue index 2805dfcf1e..57eb37eaae 100644 --- a/packages/nuxt-ripple/components/TideDynamicComponents.vue +++ b/packages/nuxt-ripple/components/TideDynamicComponents.vue @@ -6,7 +6,7 @@ import type { import { computed } from 'vue' import groupDynamicComponents from '../utils/groupDynamicComponents' interface Props { - components: TideDynamicPageComponent[] + components: TideDynamicPageComponent[] fullWidth?: boolean hasSidebar?: boolean } diff --git a/packages/nuxt-ripple/components/TideSidebarContactUs.vue b/packages/nuxt-ripple/components/TideSidebarContactUs.vue index 6383762a54..3e38d07e0e 100644 --- a/packages/nuxt-ripple/components/TideSidebarContactUs.vue +++ b/packages/nuxt-ripple/components/TideSidebarContactUs.vue @@ -43,7 +43,10 @@ const getSocialMediaIconByType = (type: string): string => { if (type === 'youtube_channel') { return 'icon-youtube' } - if (['twitter', 'facebook', 'linkedin', 'instagram'].includes(type)) { + if (type === 'twitter') { + return 'icon-x' + } + if (['x', 'facebook', 'linkedin', 'instagram'].includes(type)) { return `icon-${type}` } else { return 'icon-browser' diff --git a/packages/nuxt-ripple/composables/use-merge-section-tags.ts b/packages/nuxt-ripple/composables/use-merge-section-tags.ts new file mode 100644 index 0000000000..f1a6e4eb40 --- /dev/null +++ b/packages/nuxt-ripple/composables/use-merge-section-tags.ts @@ -0,0 +1,35 @@ +import { setResponseHeader, getResponseHeaders } from 'h3' + +// Merge two section cache tags string and deduplicate tag +// Return a string of merged tags +const mergeTags = (existingTags: string, newTags: string): string => { + const tags1 = existingTags.split(' ') + const tags2 = newTags.split(' ') + const tags = [...new Set([...tags1, ...tags2])] + return tags.join(' ') +} + +export const useMergeSectionTags = async ( + sectionCacheTags: any +): Promise => { + // event will be undefined if the request is on the client side + const event = useRequestEvent() + + // Section.io cache tags must be set on the response header to invalidate the cache after a change in drupal + if (event && sectionCacheTags) { + const currentResponseHeaders = getResponseHeaders(event) + + const currentSectionTags: string = + currentResponseHeaders && currentResponseHeaders['section-cache-tags'] + ? (currentResponseHeaders['section-cache-tags'] as string) + : ('' as string) + + setResponseHeader( + event, + 'section-cache-tags', + mergeTags(currentSectionTags, sectionCacheTags) + ) + } +} + +export default useMergeSectionTags diff --git a/packages/nuxt-ripple/composables/use-tide-language.ts b/packages/nuxt-ripple/composables/use-tide-language.ts index a22db43ce5..042aa9c1dd 100644 --- a/packages/nuxt-ripple/composables/use-tide-language.ts +++ b/packages/nuxt-ripple/composables/use-tide-language.ts @@ -29,7 +29,7 @@ export default (page: any) => { style: [ { children: ` - .${language.value} { font-family: '${found.value?.name}', var(--rpl-type-font-family) } + .${language.value} * { font-family: '${found.value?.name}', var(--rpl-type-font-family) !important } ` } ] diff --git a/packages/nuxt-ripple/composables/use-tide-page.ts b/packages/nuxt-ripple/composables/use-tide-page.ts index 9234375cd6..22315c3a04 100644 --- a/packages/nuxt-ripple/composables/use-tide-page.ts +++ b/packages/nuxt-ripple/composables/use-tide-page.ts @@ -8,6 +8,27 @@ const isCacheTimeExpired = (date: number, expiryInMinutes = 5) => { return date < timePlusExpiry } +const checkForRedirect = async (page: TidePageBase) => { + // Redirect on the 6 codes that Drupal supplies + if (page?.type === 'redirect') { + switch (page.status_code) { + case '301': + case '302': + case '303': + case '304': + case '305': + case '307': + await navigateTo(page.redirect_url, { + replace: true, + redirectCode: page.status_code, + external: page.redirect_type === 'external' + }) + break + default: + } + } +} + export const useTidePage = async ( slug?: string, site?: number @@ -49,6 +70,8 @@ export const useTidePage = async ( headers.cookie = `${AuthCookieNames.ACCESS_TOKEN}=${accessTokenCookie.value};` } + let sectionCacheTags + if (!pageData.value) { const { data, error } = await useFetch('/api/tide/page', { key: `page-${path}`, @@ -59,35 +82,30 @@ export const useTidePage = async ( }, headers, async onResponse({ response }) { + sectionCacheTags = response.headers.get('section-cache-tags') + if (response.ok && response._data) { response._data['_fetched'] = Date.now() } } }) + + // Section.io cache tags must be set on the response header to invalidate the cache after a change in drupal + if (sectionCacheTags) { + useMergeSectionTags(sectionCacheTags) + } + if (error && error.value?.statusCode) { useTideError(error.value?.statusCode) } - // Redirect on the 6 codes that Drupal supplies - if (data.value.type === 'redirect') { - switch (data.value.status_code) { - case '301': - case '302': - case '303': - case '304': - case '305': - case '307': - await navigateTo(data.value.redirect_url, { - redirectCode: data.value.status_code - }) - break - default: - } - } + await checkForRedirect(data.value) return data.value } + await checkForRedirect(pageData.value) + return pageData.value } diff --git a/packages/nuxt-ripple/composables/use-tide-site.ts b/packages/nuxt-ripple/composables/use-tide-site.ts index a2ee9fb0cb..867cc63a46 100644 --- a/packages/nuxt-ripple/composables/use-tide-site.ts +++ b/packages/nuxt-ripple/composables/use-tide-site.ts @@ -4,12 +4,18 @@ export const useTideSite = async (id?: number): Promise => { const { public: config } = useRuntimeConfig() const siteId = id || config.tide?.site const { data: siteData } = useNuxtData(`site-${siteId}`) + + let sectionCacheTags + if (!siteData.value) { const { data, error } = await useFetch('/api/tide/site', { key: `site-${siteId}`, baseURL: config.apiUrl || '', params: { id: siteId + }, + async onResponse({ response }) { + sectionCacheTags = response.headers.get('section-cache-tags') } }) if (error && error.value?.statusCode) { @@ -17,8 +23,15 @@ export const useTideSite = async (id?: number): Promise => { console.log('API error fetching site data') useTideError(500) } + + // Section.io cache tags must be set on the response header to invalidate the cache after a change in drupal + if (sectionCacheTags) { + useMergeSectionTags(sectionCacheTags) + } + return data.value } + return siteData.value } diff --git a/packages/nuxt-ripple/error.vue b/packages/nuxt-ripple/error.vue index 26efb62593..4ba3a5fc9d 100644 --- a/packages/nuxt-ripple/error.vue +++ b/packages/nuxt-ripple/error.vue @@ -12,9 +12,9 @@ @@ -53,6 +53,10 @@ const props = defineProps() const is500 = computed(() => props.error?.statusCode === 500) const title = computed(() => (is500.value ? 'Sorry!' : 'Oops!')) const site = is500.value ? undefined : await useTideSite() +const page = computed(() => ({ + title: `${props.error?.statusCode} - ${props.error?.statusMessage}`, + statusCode: props.error?.statusCode +})) onMounted(() => { // Since the template is skipped on 500, need to tell cypress that the page is ready diff --git a/packages/nuxt-ripple/lib/generate.ts b/packages/nuxt-ripple/lib/generate.ts new file mode 100644 index 0000000000..377f87cd4c --- /dev/null +++ b/packages/nuxt-ripple/lib/generate.ts @@ -0,0 +1,91 @@ +import { init as initRfgApi } from 'rfg-api' +import fs from 'fs' +import path from 'path' + +const { generateFavicon, createRequest } = initRfgApi() + +export interface generateOpts { + masterPath: string + outputPath: string + API_KEY: string + themeColour: string + siteName: string +} + +export async function generate(opt: generateOpts): Promise { + console.info('Favicon: generating assets') + + const iosConfig = { + pictureAspect: 'noChange' + } + + const safariConfig = { + pictureAspect: 'black_and_white', + backgroundColor: '#ffffff', + threshold: 60 + } + + const androidConfig = { + pictureAspect: 'noChange', + manifest: { + name: opt.siteName, + display: 'standalone', + orientation: 'portrait', + start_url: '/' + }, + assets: { + legacyIcon: false, + lowResolutionIcons: false + }, + theme_color: opt.themeColour + } + + const windowsConfig = { + pictureAspect: 'white_silhouette', + backgroundColor: opt.themeColour, + assets: { + windows80Ie10Tile: true, + windows10Ie11EdgeTiles: { + small: true, + medium: true, + big: true, + rectangle: true + } + } + } + + const faviconDesign = { + desktopBrowser: {}, + ios: iosConfig, + androidChrome: androidConfig, + safariPinnedTab: safariConfig, + windows: windowsConfig + } + + return generateFavicon( + createRequest({ + apiKey: opt.API_KEY, + masterPicture: opt.masterPath, + iconsPath: opt.outputPath, + design: faviconDesign, + settings: { usePathAsIs: false } + // versioning? + }), + path.resolve(process.cwd(), opt.outputPath || '.'), + async (err: any) => { + if (err) { + throw err + } + + // Remove outputPath from manifest files + for (const manifest of ['browserconfig.xml', 'site.webmanifest']) { + const path = `${opt.outputPath}/${manifest}`, + original = await fs.promises.readFile(path, 'utf8'), + updated = original.replace(new RegExp(opt.outputPath, 'g'), '') + await fs.promises.writeFile(path, updated, 'utf8') + } + + console.info('Favicon: generate complete!') + } + ) +} diff --git a/packages/nuxt-ripple/lib/lib.d.ts b/packages/nuxt-ripple/lib/lib.d.ts new file mode 100644 index 0000000000..bf3ecad64c --- /dev/null +++ b/packages/nuxt-ripple/lib/lib.d.ts @@ -0,0 +1,2 @@ +declare module 'rfg-api' +declare module 'jsonapi-parse' diff --git a/packages/nuxt-ripple/mapping/base/sidebar-social-share/__test__/sidebar-social-share-mapping.test.ts b/packages/nuxt-ripple/mapping/base/sidebar-social-share/__test__/sidebar-social-share-mapping.test.ts index 21a4eea61c..0184aa5713 100644 --- a/packages/nuxt-ripple/mapping/base/sidebar-social-share/__test__/sidebar-social-share-mapping.test.ts +++ b/packages/nuxt-ripple/mapping/base/sidebar-social-share/__test__/sidebar-social-share-mapping.test.ts @@ -22,7 +22,7 @@ describe('sidebarSocialShareMapping', () => { it('should return the default list of networks when social share display is switched on', () => { expect(sidebarSocialShareMapping(parsedData)).toEqual([ 'Facebook', - 'Twitter', + 'X', 'LinkedIn' ]) }) diff --git a/packages/nuxt-ripple/mapping/base/sidebar-social-share/sidebar-social-share-mapping.ts b/packages/nuxt-ripple/mapping/base/sidebar-social-share/sidebar-social-share-mapping.ts index 8275e44370..6b1b99f5cc 100644 --- a/packages/nuxt-ripple/mapping/base/sidebar-social-share/sidebar-social-share-mapping.ts +++ b/packages/nuxt-ripple/mapping/base/sidebar-social-share/sidebar-social-share-mapping.ts @@ -5,7 +5,7 @@ export const map = (src: TideApiResponse): string[] => { return [] } - return ['Facebook', 'Twitter', 'LinkedIn'] + return ['Facebook', 'X', 'LinkedIn'] } export const includes = [] diff --git a/packages/nuxt-ripple/mapping/site/alerts/site-alerts-mapping.ts b/packages/nuxt-ripple/mapping/site/alerts/site-alerts-mapping.ts index 0dae29c9f1..27d4536383 100644 --- a/packages/nuxt-ripple/mapping/site/alerts/site-alerts-mapping.ts +++ b/packages/nuxt-ripple/mapping/site/alerts/site-alerts-mapping.ts @@ -47,19 +47,21 @@ const getAlertVariantForType = ( } export const map = (src: TideApiResponse): TideAlert[] => { - const alerts = (src.site_alerts || []).map((rawAlert): TideAlert => { - const alertType = rawAlert.field_alert_type.name - const link = getLinkFromField(rawAlert, 'field_call_to_action') + const alerts = (src.site_alerts || []) + .filter((rawAlert: any) => rawAlert.field_alert_type) + .map((rawAlert: any) => { + const alertType = rawAlert.field_alert_type.name + const link = getLinkFromField(rawAlert, 'field_call_to_action') - return { - alertId: rawAlert.id, - variant: getAlertVariantForType(alertType), - iconName: getIconForType(alertType), - message: rawAlert.title || '', - linkText: link?.text || '', - linkUrl: link?.url || '' - } - }) + return { + alertId: rawAlert.id, + variant: getAlertVariantForType(alertType), + iconName: getIconForType(alertType), + message: rawAlert.title || '', + linkText: link?.text || '', + linkUrl: link?.url || '' + } + }) return sortAlertsByPriority(alerts) } diff --git a/packages/nuxt-ripple/mapping/site/index.ts b/packages/nuxt-ripple/mapping/site/index.ts index fe0ed43f90..595cd8945a 100644 --- a/packages/nuxt-ripple/mapping/site/index.ts +++ b/packages/nuxt-ripple/mapping/site/index.ts @@ -9,6 +9,7 @@ import { map as siteAlertsMapping, includes as siteAlertsIncludes } from './alerts/site-alerts-mapping.js' +import processSiteSocialLinks from '../utils/processSiteSocialLinks.js' export default { mapping: { @@ -48,10 +49,9 @@ export default { footerLogos: (src: any) => { return src.field_site_footer_logos.map((logo) => { const link = getLinkFromField(logo, 'field_paragraph_cta') - const image = getImageFromField( - logo, - 'field_paragraph_media.field_media_image' - ) + const image = + getImageFromField(logo, 'field_paragraph_media.field_media_image') || + getImageFromField(logo, 'field_feature_image') return { alt: link?.text, @@ -90,20 +90,26 @@ export default { } return socialImages }, - menus: async function (src, tideSiteApi: TideSiteApi) { - const menuMain = await tideSiteApi.getSiteMenu( - tideSiteApi.site, - src.field_site_main_menu - ) - const menuFooter = await tideSiteApi.getSiteMenu( - tideSiteApi.site, - src.field_site_footer_menu - ) - - return { - menuMain, - menuFooter + menus: { + menuMain: async (src: any, tideSiteApi: TideSiteApi) => { + return await tideSiteApi.getSiteMenu( + tideSiteApi.site, + src.field_site_main_menu + ) + }, + menuFooter: async (src: any, tideSiteApi: TideSiteApi) => { + return await tideSiteApi.getSiteMenu( + tideSiteApi.site, + src.field_site_footer_menu + ) } + }, + socialLinks: (src: any) => { + return processSiteSocialLinks(src.field_site_social_links || []) + }, + sitemap: { + showTableOfContents: 'field_show_table_of_contents', + tableOfContentsTitle: 'field_title_of_table_of_contents' } }, includes: [ @@ -119,6 +125,7 @@ export default { 'field_top_corner_graphic', 'field_bottom_corner_graphic', 'field_site_footer_logos', - 'field_site_footer_logos.field_paragraph_media.field_media_image' + 'field_site_footer_logos.field_paragraph_media.field_media_image', + 'field_site_footer_logos.field_feature_image' ] } diff --git a/packages/nuxt-ripple/mapping/utils/index.ts b/packages/nuxt-ripple/mapping/utils/index.ts index 30f8ae1663..bda0a31ad6 100644 --- a/packages/nuxt-ripple/mapping/utils/index.ts +++ b/packages/nuxt-ripple/mapping/utils/index.ts @@ -1 +1,2 @@ export { default as formatPriceRange } from './formatPriceRange.js' +export { default as processSiteSocialLinks } from './processSiteSocialLinks.js' diff --git a/packages/nuxt-ripple/mapping/utils/processSiteSocialLinks.test.ts b/packages/nuxt-ripple/mapping/utils/processSiteSocialLinks.test.ts new file mode 100644 index 0000000000..7ca4a0a6d0 --- /dev/null +++ b/packages/nuxt-ripple/mapping/utils/processSiteSocialLinks.test.ts @@ -0,0 +1,151 @@ +import { expect, describe, it } from '@jest/globals' +import processSiteSocialLinks, { getIconForUrl } from './processSiteSocialLinks' + +// write jest unit tests for the getIconForUrl function +describe('getIconForUrl', () => { + it('returns icon-phone when passed tel: link', () => { + expect(getIconForUrl('tel:1234567890')).toBe('icon-phone') + }) + + it('returns icon-mail when passed mailto: link', () => { + expect(getIconForUrl('mailto:test@test.com')).toBe('icon-mail') + }) + + it('returns icon-facebook when passed facebook.com link', () => { + expect(getIconForUrl('https://www.facebook.com/')).toBe('icon-facebook') + expect(getIconForUrl('https://facebook.com/')).toBe('icon-facebook') + }) + + it('returns icon-instagram when passed instagram.com link', () => { + expect(getIconForUrl('https://www.instagram.com/')).toBe('icon-instagram') + expect(getIconForUrl('https://instagram.com/')).toBe('icon-instagram') + expect(getIconForUrl('https://instagr.am/')).toBe('icon-instagram') + }) + + it('returns icon-linkedin when passed linkedin.com link', () => { + expect(getIconForUrl('https://www.linkedin.com/')).toBe('icon-linkedin') + expect(getIconForUrl('https://linkedin.com/')).toBe('icon-linkedin') + }) + + it('returns icon-x when passed twitter.com link', () => { + expect(getIconForUrl('https://www.twitter.com/')).toBe('icon-x') + expect(getIconForUrl('https://twitter.com/')).toBe('icon-x') + }) + + it('returns icon-x when passed x.com link', () => { + expect(getIconForUrl('https://www.x.com/')).toBe('icon-x') + expect(getIconForUrl('https://x.com/')).toBe('icon-x') + }) + + it('returns icon-youtube when passed youtube.com link', () => { + expect(getIconForUrl('https://www.youtube.com/')).toBe('icon-youtube') + expect(getIconForUrl('https://youtube.com/')).toBe('icon-youtube') + }) + + it('returns icon-link-external-square-filled when passed any other link', () => { + expect(getIconForUrl('https://www.google.com/')).toBe( + 'icon-link-external-square-filled' + ) + }) +}) + +// write jest unit tests for the processSiteSocialLinks function +describe('processSiteSocialLinks', () => { + it('returns an array of objects with the correct properties', () => { + const rawLinks = [ + { + uri: 'tel:1234567890', + title: 'Phone' + }, + { + uri: 'mailto:test@test.com', + title: 'Email' + }, + { + uri: 'https://www.facebook.com/', + title: 'Facebook' + }, + { + uri: 'https://www.instagram.com/', + title: 'Instagram' + }, + { + uri: 'https://www.linkedin.com/', + title: 'LinkedIn' + }, + { + uri: 'https://www.twitter.com/', + title: 'Twitter' + }, + { + uri: 'https://www.youtube.com/', + title: 'YouTube' + }, + { + uri: 'https://www.google.com/', + title: 'Google' + } + ] + + const expected = [ + { + id: 'social_link-0', + text: 'Phone', + url: 'tel:1234567890', + icon: 'icon-phone', + iconColour: 'currentColor' + }, + { + id: 'social_link-1', + text: 'Email', + url: 'mailto:test@test.com', + icon: 'icon-mail', + iconColour: 'currentColor' + }, + { + id: 'social_link-2', + text: 'Facebook', + url: 'https://www.facebook.com/', + icon: 'icon-facebook', + iconColour: 'currentColor' + }, + { + id: 'social_link-3', + text: 'Instagram', + url: 'https://www.instagram.com/', + icon: 'icon-instagram', + iconColour: 'currentColor' + }, + { + id: 'social_link-4', + text: 'LinkedIn', + url: 'https://www.linkedin.com/', + icon: 'icon-linkedin', + iconColour: 'currentColor' + }, + { + id: 'social_link-5', + text: 'Twitter', + url: 'https://www.twitter.com/', + icon: 'icon-x', + iconColour: 'currentColor' + }, + { + id: 'social_link-6', + text: 'YouTube', + url: 'https://www.youtube.com/', + icon: 'icon-youtube', + iconColour: 'currentColor' + }, + { + id: 'social_link-7', + text: 'Google', + url: 'https://www.google.com/', + icon: 'icon-link-external-square-filled', + iconColour: 'currentColor' + } + ] + + expect(processSiteSocialLinks(rawLinks)).toEqual(expected) + }) +}) diff --git a/packages/nuxt-ripple/mapping/utils/processSiteSocialLinks.ts b/packages/nuxt-ripple/mapping/utils/processSiteSocialLinks.ts new file mode 100644 index 0000000000..84255647e6 --- /dev/null +++ b/packages/nuxt-ripple/mapping/utils/processSiteSocialLinks.ts @@ -0,0 +1,54 @@ +import { TideMenuItem } from '../../types' + +export const getIconForUrl = (urlString: string) => { + const trimmedUrl = (urlString || '').trim() + + if (trimmedUrl.startsWith('tel:')) { + return 'icon-phone' + } + + if (trimmedUrl.startsWith('mailto:')) { + return 'icon-mail' + } + + const url = new URL(trimmedUrl) + const hostNormalised = url.host.replace('www.', '') + + switch (hostNormalised) { + case 'facebook.com': + return 'icon-facebook' + case 'instagram.com': + case 'instagr.am': + return 'icon-instagram' + case 'linkedin.com': + return 'icon-linkedin' + case 'twitter.com': + case 'x.com': + return 'icon-x' + case 'youtube.com': + return 'icon-youtube' + default: + return 'icon-link-external-square-filled' + } +} + +interface RawLink { + uri: string + title: string +} + +const processSiteSocialLinks = (rawLinks: RawLink[]): TideMenuItem[] => { + return (rawLinks || []).map((link, i) => { + console.log(getIconForUrl(link.uri)) + + return { + id: `social_link-${i}`, + text: link.title, + url: link.uri, + icon: getIconForUrl(link.uri), + iconColour: 'currentColor' + } + }) +} + +export default processSiteSocialLinks diff --git a/packages/nuxt-ripple/modules/generate-favicon.ts b/packages/nuxt-ripple/modules/generate-favicon.ts new file mode 100644 index 0000000000..e706663834 --- /dev/null +++ b/packages/nuxt-ripple/modules/generate-favicon.ts @@ -0,0 +1,91 @@ +import { createResolver, defineNuxtModule } from 'nuxt/kit' +import { generate } from './../lib/generate' +import * as jsonapiParse from 'jsonapi-parse' +import fs from 'fs' +import path from 'path' +import { Readable } from 'stream' +import { finished } from 'stream/promises' + +export default defineNuxtModule({ + meta: { + name: 'generateFavicon' + }, + hooks: { + ready: async (nuxtApp) => { + const faviconApiKey = process.env.RFG_API_KEY + + // Exit early if API key is not set + if (faviconApiKey === undefined) { + console.info('Favicon: missing RFG_API_KEY, skipping') + return + } + + const publicFolderPath = nuxtApp.options.alias.public + + // 1. Check if asset already exists + if (fs.existsSync(`${publicFolderPath}/favicon.ico`)) { + console.info('Favicon: Assets already exist, skipping') + return + } + + // 2. Fetch theme and master asset url from site taxonomy + const siteTaxonomyRes = await fetch( + `${nuxtApp.options.runtimeConfig.public.tide.baseUrl}/api/v1/taxonomy_term/sites?filter%5Bdrupal_internal__tid%5D=${nuxtApp.options.runtimeConfig.public.tide.site}&site=${nuxtApp.options.runtimeConfig.public.tide.site}&include=field_site_favicon` + ), + siteTaxonomyData = await siteTaxonomyRes.json(), + parsedData = jsonapiParse.parse(siteTaxonomyData).data[0] + + // 3. Extract site name + const siteName = + parsedData.field_site_slogan.processed.replace(/

|<\/p>/g, '') || + parsedData.name || + 'SDP' + + // 4. Extract theme colour (use Vic primary if theme is not set) + const themeColour = + parsedData.field_site_theme_values?.filter( + (t: any) => t.key.trim() === 'rpl-clr-primary' + )[0]?.value || '#0052C2' + + // 5. Set up master asset in public + const masterAssetUrl = parsedData.field_site_favicon?.url + if (!masterAssetUrl) { + console.info('Favicon: Master asset not set in site taxonomy, skipping') + return + } + + // 6. Create public folder if it doesn't exist at the app level + if (!fs.existsSync(publicFolderPath)) { + await fs.promises.mkdir(publicFolderPath) + } + + // 7. Fetch master asset + const savedFaviconPath = `${publicFolderPath}/${path.basename( + masterAssetUrl + )}` + + if (!fs.existsSync(savedFaviconPath)) { + const masterAssetRes = await fetch(masterAssetUrl), + fileStream = fs.createWriteStream(savedFaviconPath, { flags: 'wx' }) + // @ts-ignore TS2345 + await finished(Readable.fromWeb(masterAssetRes.body).pipe(fileStream)) + } + + // 8. Generate assets + await generate({ + masterPath: savedFaviconPath, + outputPath: publicFolderPath, + API_KEY: faviconApiKey, + themeColour: themeColour, + siteName: siteName + }).then(() => { + // 9. Remove master asset + fs.unlinkSync(savedFaviconPath) + }) + } + }, + setup() { + const { resolve } = createResolver(import.meta.url) + // console.log(resolve('./../lib/generate')) + } +}) diff --git a/packages/nuxt-ripple/package.json b/packages/nuxt-ripple/package.json index ca64391489..b49f4e57c5 100644 --- a/packages/nuxt-ripple/package.json +++ b/packages/nuxt-ripple/package.json @@ -2,7 +2,7 @@ "packageManager": "pnpm@8.6.2", "name": "@dpc-sdp/nuxt-ripple", "description": "Nuxt module for integrating Ripple and Tide", - "version": "2.1.9", + "version": "2.4.2", "license": "Apache-2.0", "main": "./nuxt.config.ts", "repository": "https://github.com/dpc-sdp/ripple-framework", @@ -19,6 +19,8 @@ "@nuxtjs/robots": "^3.0.0", "@vueuse/core": "^9.13.0", "change-case": "^4.1.2", - "defu": "^6.1.2" + "defu": "^6.1.2", + "jsonapi-parse": "^2.0.1", + "rfg-api": "^0.5.3" } } diff --git a/packages/nuxt-ripple/pages/sitemap.vue b/packages/nuxt-ripple/pages/sitemap.vue index 456c85f2b3..4b67fa4766 100644 --- a/packages/nuxt-ripple/pages/sitemap.vue +++ b/packages/nuxt-ripple/pages/sitemap.vue @@ -8,6 +8,15 @@ export default { import { useTideSite } from '#imports' const site = await useTideSite() + +const toc = computed(() => { + return site.menus.menuMain.map((item) => { + return { + text: item.text, + url: `#${item.id}` + } + }) +})