Compare commits

...
Author SHA1 Message Date
Ken 82470ba669 🐛 Synket dropdown med Figma 2022-11-01 10:18:40 +01:00
github-actions[bot]andgithub-actions[bot] 6286f0e8e6 Version Packages (#1705)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2022-10-31 13:34:55 +01:00
Ken 3c08651df2 ✨ Changeset 2022-10-31 13:26:02 +01:00
Ken e6d55fdcd6 changeset 2022-10-31 13:24:57 +01:00
Ken 8c445f020c 💚 Flere workers for e2e testing 2022-10-31 12:07:11 +01:00
Ken 78a76bac5d 🎉 Migrert aksel-website repo (#1704) 2022-10-31 11:44:38 +01:00
Ken 75b334af61 Merge branch 'master' of https://github.com/navikt/Designsystemet 2022-10-28 17:29:58 +02:00
Ken 5d98c50b80 🐛 Fikset eksempel 2022-10-28 17:28:30 +02:00
512 changed files with 40467 additions and 1736 deletions

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@
"access": "public",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": ["nextjs-demo", "shadow-dom"],
"ignore": ["nextjs-demo", "shadow-dom", "aksel.nav.no"],
"___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
"updateInternalDependents": "always"
+71
View File
@@ -0,0 +1,71 @@
name: Deploy aksel.dev.nav.no
on:
workflow_dispatch:
push:
branches:
- next
env:
IMAGE: ghcr.io/navikt/designsystemet
jobs:
build_and_deploy:
name: Build and deploy to dev-gcp
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-node@v1
with:
node-version: "14"
- uses: actions/checkout@v2
with:
ref: "next"
- name: add tokens to .env
run: |
echo "SANITY_WRITE_KEY=${{ secrets.SANITY_FEEDBACK_WRITE_KEY }}" >> aksel.nav.no/website/.env
echo "ALGOLIA_ADMIN=${{ secrets.ALGOLIA_ADMIN }}" >> aksel.nav.no/website/.env
echo "ALGOLIA_ID=${{ secrets.ALGOLIA_ID}}" >> aksel.nav.no/website/.env
echo "TRELLO_TOKEN=${{ secrets.TRELLO_TOKEN }}" >> aksel.nav.no/website/.env
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> aksel.nav.no/website/.env
echo "SANITY_PREVIEW_TOKEN=${{ secrets.SANITY_PREVIEW_TOKEN }}" >> aksel.nav.no/website/.env
echo "SANITY_PRIVATE_NO_DRAFTS=${{ secrets.SANITY_PRIVATE_NO_DRAFTS }}" >> aksel.nav.no/website/.env
- name: Restore cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-dev-${{ github.sha }}
restore-keys: |
${{ runner.os }}-dev-
- name: Install dependencies
run: yarn
- name: Build application
run: |
yarn boot
yarn docgen
yarn workspace aksel.nav.no build:next
- name: Login to GitHub Container Registry
uses: docker/login-action@ab80d026d4753220c4243394c07c7d80f9638d06 # Use commit-sha1 instead of tag for security concerns
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get complete tag
run: echo "TAG=DEV-$( date +%s )" >> $GITHUB_ENV
- name: Create docker image tags
run: echo "DOCKER_TAG=${{ env.IMAGE }}:${{ env.TAG }}" >> $GITHUB_ENV
- name: Build docker image
run: docker build -t ${{ env.DOCKER_TAG }} . -f aksel.nav.no/Dockerfile
- name: Push docker image
run: |
docker push ${{ env.DOCKER_TAG }}
- name: Deploy to dev-gcp
uses: nais/deploy/actions/deploy@master
env:
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
RESOURCE: aksel.nav.no/nais-dev.yaml
CLUSTER: dev-gcp
IMAGE: ${{ env.DOCKER_TAG }}
+81
View File
@@ -0,0 +1,81 @@
name: Deploy aksel.nav.no
on:
push:
branches:
- master
paths:
- "aksel.nav.no/**"
env:
IMAGE: ghcr.io/navikt/designsystemet
jobs:
build_and_deploy:
name: Build and deploy to prod-gcp
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-node@v1
with:
node-version: "14"
- uses: actions/checkout@v2
- name: add tokens to .env
run: |
echo "SANITY_WRITE_KEY=${{ secrets.SANITY_FEEDBACK_WRITE_KEY }}" >> aksel.nav.no/website/.env
echo "ALGOLIA_ADMIN=${{ secrets.ALGOLIA_ADMIN }}" >> aksel.nav.no/website/.env
echo "ALGOLIA_ID=${{ secrets.ALGOLIA_ID}}" >> aksel.nav.no/website/.env
echo "TRELLO_TOKEN=${{ secrets.TRELLO_TOKEN }}" >> aksel.nav.no/website/.env
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> aksel.nav.no/website/.env
echo "SANITY_PREVIEW_TOKEN=${{ secrets.SANITY_PREVIEW_TOKEN }}" >> aksel.nav.no/website/.env
echo "SANITY_PRIVATE_NO_DRAFTS=${{ secrets.SANITY_PRIVATE_NO_DRAFTS }}" >> aksel.nav.no/website/.env
- name: Restore cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-prod-${{ github.sha }}
restore-keys: |
${{ runner.os }}-prod-
- name: Install dependencies
run: yarn
- name: build packages
run: |
yarn boot
yarn docgen
- name: Tests
run: yarn workspace aksel.nav.no test
- name: E2E
run: NEXT_PUBLIC_TEST=true yarn workspace website e2e
- name: Update sanity sync
run: yarn workspace aksel.nav.no sanity:update
- name: Build application
run: yarn workspace aksel.nav.no build:next
- name: Login to GitHub Container Registry
uses: docker/login-action@ab80d026d4753220c4243394c07c7d80f9638d06 # Use commit-sha1 instead of tag for security concerns
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Get complete tag
run: echo "TAG=$( date +%s )" >> $GITHUB_ENV
- name: Create docker image tags
run: echo "DOCKER_TAG=${{ env.IMAGE }}:${{ env.TAG }}" >> $GITHUB_ENV
- name: Build docker image
run: docker build -t ${{ env.DOCKER_TAG }} . -f aksel.nav.no/Dockerfile
- name: Push docker image
run: |
docker push ${{ env.DOCKER_TAG }}
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@master
env:
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
RESOURCE: aksel.nav.no/nais-prod.yaml
CLUSTER: prod-gcp
IMAGE: ${{ env.DOCKER_TAG }}
+41
View File
@@ -0,0 +1,41 @@
name: Redeploy image to for aksel.nav.no
on:
workflow_dispatch:
inputs:
tag:
description: "Docker image tag"
required: true
default: ""
dry:
description: "Dry-run"
required: true
default: "false"
env:
IMAGE: ghcr.io/navikt/designsystemet
jobs:
redeploy:
name: Redeploy image
runs-on: ubuntu-20.04
steps:
- uses: actions/setup-node@v1
with:
node-version: "14"
- uses: actions/checkout@v2
- name: Login to GitHub Container Registry
uses: docker/login-action@ab80d026d4753220c4243394c07c7d80f9638d06 # Use commit-sha1 instead of tag for security concerns
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Create docker image tag
run: echo "DOCKER_TAG=${{ env.IMAGE }}:${{ github.event.inputs.tag }}" >> $GITHUB_ENV
- name: Deploy to prod-gcp
uses: nais/deploy/actions/deploy@master
env:
APIKEY: ${{ secrets.NAIS_DEPLOY_APIKEY }}
RESOURCE: aksel.nav.no/nais-prod.yaml
CLUSTER: prod-gcp
IMAGE: ${{ env.DOCKER_TAG }}
DRY_RUN: ${{ github.event.inputs.dry }}
+39
View File
@@ -0,0 +1,39 @@
name: Backup sanity dateset to gcp
on:
workflow_dispatch:
schedule:
# 12.00 every 3 days
- cron: "0 0 */3 * *"
jobs:
execute:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Required to retrieve git history
- uses: actions/setup-node@v1
with:
node-version: 14
- name: Restore cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-build-${{ github.sha }}
restore-keys: |
${{ runner.os }}-build-
- name: Install dependencies
run: yarn
- name: Export dataset
run: SANITY_TOKEN=${{secrets.SANITY_DATASET_EDITOR_TOKEN}} yarn workspace aksel.nav.no sanity:backup
- name: Upload GCP bucket
uses: "google-github-actions/auth@v0"
with:
credentials_json: "${{ secrets.gcp_credentials_prod }}"
- uses: "google-github-actions/upload-cloud-storage@v0"
with:
path: ./aksel.nav.no/sanity
destination: "aksel-website-prod/backups"
glob: "**/backup-*.tar.gz"
+1 -2
View File
@@ -4,11 +4,10 @@ on:
push:
branches:
- "master"
- "next"
paths:
- "@navikt/**"
pull_request:
branches: [master]
branches: [master, next]
paths:
- "@navikt/**"
+71
View File
@@ -0,0 +1,71 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [master, next]
pull_request:
# The branches below must be a subset of the branches above
branches: [master]
schedule:
- cron: "41 8 * * 6"
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: ["javascript"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
+56
View File
@@ -0,0 +1,56 @@
name: E2E / Playwright Tests
on:
push:
branches: [master, next]
paths:
- "aksel.nav.no/**"
pull_request:
branches: [master, next]
paths:
- "aksel.nav.no/**"
jobs:
playwright:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: add tokens to .env
run: |
echo "SANITY_WRITE_KEY=${{ secrets.SANITY_FEEDBACK_WRITE_KEY }}" >> aksel.nav.no/website/.env
echo "ALGOLIA_ADMIN=${{ secrets.ALGOLIA_ADMIN }}" >> aksel.nav.no/website/.env
echo "ALGOLIA_ID=${{ secrets.ALGOLIA_ID}}" >> aksel.nav.no/website/.env
echo "TRELLO_TOKEN=${{ secrets.TRELLO_TOKEN }}" >> aksel.nav.no/website/.env
echo "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" >> aksel.nav.no/website/.env
echo "SANITY_PREVIEW_TOKEN=${{ secrets.SANITY_PREVIEW_TOKEN }}" >> aksel.nav.no/website/.env
echo "SANITY_PRIVATE_NO_DRAFTS=${{ secrets.SANITY_PRIVATE_NO_DRAFTS }}" >> aksel.nav.no/website/.env
- name: Restore cache
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-e2e-${{ github.sha }}
restore-keys: |
${{ runner.os }}-e2e-
- name: Install dependencies
run: yarn
- name: build packages
run: |
yarn boot
yarn docgen
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: yarn workspace website e2e
- uses: actions/upload-artifact@v3
if: always()
with:
name: playwright-report
path: aksel.nav.no/website/playwright-report/
retention-days: 10
+2 -2
View File
@@ -27,9 +27,9 @@ jobs:
uses: actions/cache@v3
with:
path: "**/node_modules"
key: ${{ runner.os }}-test-${{ github.sha }}
key: ${{ runner.os }}-prod-${{ github.sha }}
restore-keys: |
${{ runner.os }}-test
${{ runner.os }}-prod
- name: Install dependencies
run: yarn
+25
View File
@@ -0,0 +1,25 @@
name: Indexing search
on:
workflow_dispatch:
schedule:
# 01.00 & 12.00 every day
- cron: "00 01,12 * * *"
jobs:
searchcrawl:
name: Search-indexing aksel.nav.no
runs-on: ubuntu-latest
env:
APPLICATION_ID: ${{ secrets.ALGOLIA_ID}}
API_KEY: ${{ secrets.ALGOLIA_ADMIN }}
steps:
- uses: actions/checkout@v2
- name: Read crawler.json
id: algolia_config
run: echo "::set-output name=config::$(cat ./aksel.nav.no/crawler.json | jq -r tostring)"
- name: Push indices to Algolia
uses: signcl/docsearch-scraper-action@master
env:
APPLICATION_ID: ${{ secrets.ALGOLIA_ID}}
API_KEY: ${{ secrets.ALGOLIA_ADMIN }}
CONFIG: ${{ steps.algolia_config.outputs.config }}
+19 -1
View File
@@ -129,4 +129,22 @@ _docs.json
*/icons/src
*/icons/png
*/core/icons
/storybook-static
/storybook-static
# Aksel.nav.no
.next
!*/website/lib/
!*/website/public/
!aksel.nav.no/**/*.d.ts
!*/sanity/lib/
!*/sanity/static/
out
test-urls.json
videos
sanity/scripts/renameField.js
tmp.json
/test-results/
/playwright-report/
/playwright/.cache/
playwright-report/
/test-results/
+2
View File
@@ -0,0 +1,2 @@
**/nais-dev.yaml
**/nais-prod.yaml
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-codemod
## 1.4.2
## 1.4.1
## 1.4.0
## 1.3.39
+5 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-codemod",
"version": "1.4.0",
"version": "1.4.2",
"description": "NAV designsystem codemods",
"author": "NAV Designsystem team",
"license": "MIT",
@@ -36,7 +36,7 @@
"globby": "11.0.1",
"inquirer": "7.3.3",
"is-git-clean": "1.1.0",
"jest": "^27.5.0",
"jest": "^29.0.0",
"jscodeshift": "0.13.1",
"jscodeshift-add-imports": "1.0.10",
"lodash": "4.17.21",
@@ -44,10 +44,12 @@
"prettier": "2.7.1"
},
"devDependencies": {
"@babel/preset-env": "^7.0.0",
"@types/jest": "^29.0.0",
"@types/jscodeshift": "0.11.5",
"@types/react": "^18.0.0",
"del": "7.0.0",
"typescript": "^4.7.2"
"typescript": "^4.8.0"
},
"sideEffects": false,
"engines": {
+8
View File
@@ -1,5 +1,13 @@
# @navikt/ds-css
## 1.4.2
## 1.4.1
### Patch Changes
- [`3c08651df`](https://github.com/navikt/Designsystemet/commit/3c08651df28c3e19dd8c8a7a1d0032200bec473d) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Date-komponenter følger språkrådets dato-formatering for måneder.
## 1.4.0
## 1.3.39
-5
View File
@@ -13,10 +13,6 @@
gap: var(--navds-spacing-5);
}
.navds-date__caption-label {
text-transform: capitalize;
}
.navds-date .rdp-head_cell {
text-transform: capitalize;
font-size: var(--navds-font-size-small);
@@ -90,7 +86,6 @@
text-align: center;
width: 3rem;
height: 2.75rem;
text-transform: capitalize;
border-radius: var(--navds-border-radius-medium);
cursor: pointer;
}
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css",
"version": "1.4.0",
"version": "1.4.2",
"description": "Css for NAV Designsystem components",
"author": "NAV Designsystem team",
"keywords": [
@@ -20,11 +20,11 @@
"watch": "postcss --watch --use postcss-import -o dist/index.css index.css"
},
"devDependencies": {
"@navikt/ds-tokens": "^1.4.0",
"@navikt/ds-tokens": "^1.4.2",
"normalize.css": "^8.0.1",
"postcss": "^8.3.6",
"postcss-cli": "^8.3.1",
"postcss": "^8.4.0",
"postcss-cli": "^9.0.0",
"postcss-combine-duplicated-selectors": "10.0.3",
"postcss-import": "^14.0.2"
"postcss-import": "^14.1.0"
}
}
+16
View File
@@ -1,5 +1,21 @@
# @navikt/ds-react
## 1.4.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-icons@1.4.2
## 1.4.1
### Patch Changes
- [`3c08651df`](https://github.com/navikt/Designsystemet/commit/3c08651df28c3e19dd8c8a7a1d0032200bec473d) Thanks [@KenAJoh](https://github.com/KenAJoh)! - Date-komponenter følger språkrådets dato-formatering for måneder.
- Updated dependencies []:
- @navikt/ds-icons@1.4.1
## 1.4.0
### Minor Changes
+11 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react",
"version": "1.4.0",
"version": "1.4.2",
"description": "NAV designsystem react components",
"author": "NAV Designsystem team",
"license": "MIT",
@@ -24,19 +24,20 @@
"/cjs",
"/esm",
"/src",
"_docs.json"
"_docs.json",
"!**/*.tsbuildinfo"
],
"scripts": {
"docgen": "yarn ts-node ../../../scripts/docgen.ts",
"create-package-json-pointers-to-esm": "node ../../../scripts/createPackageJsonsWithESMPointers.js",
"clean": "rimraf cjs esm",
"build": "yarn run clean && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
"build": "concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
"watch": "tsc --watch -p tsconfig.esm.json",
"test": "jest"
},
"dependencies": {
"@floating-ui/react-dom-interactions": "0.9.2",
"@navikt/ds-icons": "^1.4.0",
"@navikt/ds-icons": "^1.4.2",
"@radix-ui/react-tabs": "1.0.0",
"@radix-ui/react-toggle-group": "1.0.0",
"clsx": "^1.2.1",
@@ -50,21 +51,22 @@
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.0",
"@types/faker": "5.5.8",
"@types/jest": "^27.0.1",
"@types/jest": "^29.0.0",
"@types/node": "^17.0.35",
"@types/react-modal": "^3.13.1",
"concurrently": "7.2.1",
"copyfiles": "^2.4.1",
"faker": "5.5.3",
"fast-glob": "3.2.11",
"jest": "^27.5.0",
"jest": "^29.0.0",
"jest-environment-jsdom": "^29.0.0",
"react-dom": "^18.0.0",
"react-router-dom": "^6.3.0",
"rimraf": "3.0.2",
"swr": "^1.1.2",
"ts-jest": "^27.1.0",
"ts-node": "10.7.0",
"typescript": "^4.7.2"
"ts-jest": "^29.0.0",
"ts-node": "10.9.0",
"typescript": "^4.8.0"
},
"peerDependencies": {
"@types/react": "^17.0.30 || ^18.0.0",
@@ -114,7 +114,7 @@ describe("Popover", () => {
expect(fn).toHaveBeenCalledTimes(0);
cleanup();
});
}, 20000);
it("keep open on popover-click", async () => {
const fn = jest.fn();
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-tailwind
## 1.4.2
## 1.4.1
## 1.4.0
## 1.3.39
+7 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tailwind",
"version": "1.4.0",
"version": "1.4.2",
"description": "Auto-generated tailwind config based on @navikt/ds-tokens",
"author": "NAV Designsystem team",
"keywords": [
@@ -22,13 +22,13 @@
"directory": "@navikt/core/tailwind"
},
"devDependencies": {
"@navikt/ds-tokens": "^1.4.0",
"@types/jest": "^27.0.1",
"@navikt/ds-tokens": "^1.4.2",
"@types/jest": "^29.0.0",
"@types/node": "^17.0.35",
"jest": "^27.2.0",
"jest": "^29.0.0",
"lodash": "^4.17.21",
"ts-jest": "^27.0.5",
"ts-node": "^10.3.0",
"typescript": "^4.7.2"
"ts-jest": "^29.0.0",
"ts-node": "10.9.0",
"typescript": "^4.8.0"
}
}
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-tokens
## 1.4.2
## 1.4.1
## 1.4.0
## 1.3.39
+6 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-tokens",
"version": "1.4.0",
"version": "1.4.2",
"description": "Auto-generated design-tokens for NAV design-systems framework",
"author": "NAV Designsystem team",
"keywords": [
@@ -29,18 +29,18 @@
"directory": "@navikt/core/tokens"
},
"devDependencies": {
"@types/jest": "^27.0.1",
"@types/jest": "^29.0.0",
"@types/node": "^17.0.35",
"axios": "^0.21.3",
"color": "4.2.3",
"dotenv": "^10.0.0",
"figma-api": "^1.7.0",
"jest": "^27.2.0",
"jest": "^29.0.0",
"lodash": "^4.17.21",
"nodemon": "^2.0.12",
"style-dictionary": "^3.0.2",
"ts-jest": "^27.0.5",
"ts-node": "^10.3.0",
"typescript": "^4.7.2"
"ts-jest": "^29.0.0",
"ts-node": "10.9.0",
"typescript": "^4.8.0"
}
}
+4
View File
@@ -1,5 +1,9 @@
# @navikt/ds-icons
## 1.4.2
## 1.4.1
## 1.4.0
## 1.3.39
+10 -9
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-icons",
"version": "1.4.0",
"version": "1.4.2",
"description": "Aksel icons",
"repository": {
"type": "git",
@@ -25,13 +25,14 @@
"/esm",
"/src",
"/svg",
"/meta.json"
"/meta.json",
"!**/*.tsbuildinfo"
],
"scripts": {
"create-package-json-pointers-to-esm": "node ../../scripts/createPackageJsonsWithESMPointers.js",
"create-icons": "svgr --out-dir src svg",
"create-icons": "svgr --silent --out-dir src svg",
"clean": "rimraf src cjs esm",
"build": "yarn clean && copyfiles util/* src && yarn create-icons && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
"build": "copyfiles util/* src && yarn create-icons && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
"update": "ts-node figma/index.ts dotenv/config",
"test": "jest"
},
@@ -46,7 +47,7 @@
},
"devDependencies": {
"@svgr/cli": "6.2.1",
"@types/jest": "^27.0.1",
"@types/jest": "^29.0.0",
"@types/lodash.startcase": "^4.4.6",
"@types/node": "^17.0.35",
"@types/rimraf": "3.0.2",
@@ -54,13 +55,13 @@
"concurrently": "7.2.1",
"copyfiles": "^2.4.1",
"dotenv": "^10.0.0",
"jest": "^27.5.0",
"jest": "^29.0.0",
"lodash.kebabcase": "^4.1.1",
"lodash.startcase": "4.4.0",
"react": "^18.0.0",
"rimraf": "3.0.2",
"ts-jest": "^27.1.0",
"ts-node": "^10.4.0",
"typescript": "^4.7.2"
"ts-jest": "^29.0.0",
"ts-node": "10.9.0",
"typescript": "^4.8.0"
}
}
+2 -1
View File
@@ -4,5 +4,6 @@
"outDir": "./cjs",
"module": "commonjs"
},
"exclude": ["./figma", "./util", "./tests", "./scripts", "**/*.stories.*"]
"exclude": ["./figma", "./util", "./tests", "./scripts", "**/*.stories.*"],
"include": ["./src"]
}
+8
View File
@@ -1,5 +1,13 @@
# @navikt/ds-css-internal
## 1.4.2
### Patch Changes
- :bug: Dropdown-link ikke i synk med Figma
## 1.4.1
## 1.4.0
## 1.3.39
+3 -2
View File
@@ -3,7 +3,7 @@
--navds-dropdown-menu-color: var(--navds-semantic-color-text);
--navdsi-dropdown-item-color-text: var(--navds-semantic-color-link);
--navdsi-dropdown-item-color-background-hover: var(--navds-semantic-color-interaction-primary-hover-subtle);
--navdsi-dropdown-item-color-background-active: var(--navds-global-color-blue-200);
--navdsi-dropdown-item-color-background-active: var(--navds-global-color-deepblue-500);
--navds-dropdown-item-color-focus: var(--navds-semantic-color-focus);
}
@@ -64,9 +64,10 @@
.navdsi-dropdown__item:active {
background-color: var(--navdsi-dropdown-item-color-background-active);
color: var(--navds-global-color-white);
}
.navdsi-dropdown__item:focus {
.navdsi-dropdown__item:focus-visible {
outline: none;
box-shadow: inset 0 0 0 2px var(--navds-dropdown-item-color-focus);
}
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-css-internal",
"version": "1.4.0",
"version": "1.4.2",
"description": "Css for NAV internal Designsystem components",
"author": "NAV Designsystem team",
"keywords": [
@@ -20,9 +20,9 @@
"watch": "yarn build --watch"
},
"devDependencies": {
"postcss": "^8.3.8",
"postcss": "^8.4.0",
"postcss-cli": "^9.0.0",
"postcss-combine-duplicated-selectors": "10.0.3",
"postcss-import": "^14.0.2"
"postcss-import": "^14.1.0"
}
}
+16
View File
@@ -1,5 +1,21 @@
# @navikt/ds-react-internal
## 1.4.2
### Patch Changes
- Updated dependencies []:
- @navikt/ds-react@1.4.2
- @navikt/ds-icons@1.4.2
## 1.4.1
### Patch Changes
- Updated dependencies [[`3c08651df`](https://github.com/navikt/Designsystemet/commit/3c08651df28c3e19dd8c8a7a1d0032200bec473d)]:
- @navikt/ds-react@1.4.1
- @navikt/ds-icons@1.4.1
## 1.4.0
### Patch Changes
+8 -7
View File
@@ -1,6 +1,6 @@
{
"name": "@navikt/ds-react-internal",
"version": "1.4.0",
"version": "1.4.2",
"description": "NAV internal designsystem react components",
"author": "NAV Designsystem team",
"license": "MIT",
@@ -24,18 +24,19 @@
"/cjs",
"/esm",
"/src",
"_docs.json"
"_docs.json",
"!**/*.tsbuildinfo"
],
"scripts": {
"docgen": "yarn ts-node ../../../scripts/docgen.ts",
"create-package-json-pointers-to-esm": "node ../../../scripts/createPackageJsonsWithESMPointers.js",
"clean": "rimraf cjs esm",
"build": "yarn run clean && concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
"build": "concurrently \"tsc\" \"tsc -p tsconfig.esm.json\" && yarn create-package-json-pointers-to-esm",
"watch": "tsc --watch -p tsconfig.esm.json"
},
"dependencies": {
"@navikt/ds-icons": "^1.4.0",
"@navikt/ds-react": "^1.4.0",
"@navikt/ds-icons": "^1.4.2",
"@navikt/ds-react": "^1.4.2",
"clsx": "^1.1.1",
"copy-to-clipboard": "^3.3.1"
},
@@ -43,8 +44,8 @@
"@types/node": "^17.0.35",
"concurrently": "7.2.1",
"rimraf": "^3.0.2",
"ts-node": "10.7.0",
"typescript": "^4.7.2"
"ts-node": "10.9.0",
"typescript": "^4.8.0"
},
"peerDependencies": {
"@types/react": "^17.0.30 || ^18.0.0",
+2 -1
View File
@@ -3,5 +3,6 @@
"compilerOptions": {
"outDir": "./cjs",
"module": "commonjs"
}
},
"include": ["./src"]
}
+2 -8
View File
@@ -1,16 +1,10 @@
Font License
The Source Sans Pro font files in packages/node_modules/nav-frontend-typografi-style/assets are a subset of Source Sans Pro, licensed under the SIL Open Font License, and copyright Adobe Systems Incorporated, with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
Icon License
This project uses Streamline Icons. If you use nav-frontend-moduler in your project please adhere to the Streamline Icons license agreement found here: https://streamlineicons.com/ux/extended-license.html
The rest of the codebase (excluding 3rd party dependencies):
SIL Open Font License (OFL)
# The MIT License
Copyright 2019 NAV (Arbeids- og velferdsdirektoratet) - The Norwegian Labour and Welfare Administration
Copyright 2022 NAV (Arbeids- og velferdsdirektoratet) - The Norwegian Labour and Welfare Administration
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the "Software"),
+4 -6
View File
@@ -36,6 +36,10 @@
├── internal/css # @navikt/ds-css-internal, css interne flater
├── icons # @navikt/ds-icons, ikonpakke
└── codemod # @navikt/ds-codemod, migrerings-scripts
aksel.nav.no/
├── /website # NEXT.JS app (v12)
└── /sanity # Sanity CMS-app (v2)
```
## Commands
@@ -54,12 +58,6 @@ yarn && yarn boot
| `yarn clean` | Fjerner genererte filer |
| `yarn docgen` | Generer ts-docs |
## Dokumentasjon
[Dokumentasjon forvaltes i navikt/aksel-website](https://github.com/navikt/aksel-website)
[Migreringer](https://aksel.nav.no/designsystem/side/migrering#beta-til-v1)
## Bidra
Vi tar gjerne i mot PR-er [CONTRIBUTING.md](https://github.com/navikt/Designsystemet/blob/master/CONTRIBUTING.md)
+17
View File
@@ -0,0 +1,17 @@
FROM node:16.4-alpine3.14
RUN apk add --no-cache libc6-compat
ENV NODE_ENV production
WORKDIR /app
COPY aksel.nav.no/website/package.json .
COPY aksel.nav.no/website/.next/ .next/
COPY aksel.nav.no/website/public/ public
COPY aksel.nav.no/website/.env .
COPY aksel.nav.no/website/redirects.json .
COPY aksel.nav.no/website/next.config.js .
COPY node_modules/ node_modules/
COPY @navikt/ @navikt/
EXPOSE 3000
CMD yarn start
+92
View File
@@ -0,0 +1,92 @@
CMS og nettside for [aksel.nav.no](https://aksel.nav.no/)
```sh
├── /website # NEXT.JS app (v12)
└── /sanity # Sanity CMS-app (v2)
```
### /website
[Nextjs](https://nextjs.org/) applikasjon som henter data fra et [sanity-datasett](https://www.sanity.io/).
Siden leverer to løsninger:
- Innhold på toppnivå i Aksel: artikler, prinsipper og blogger
- Designsystem-dokumentasjon under https://aksel.nav.no/designsystem
### /sanity
Det publiserte studioet finner man på [aksel.nav.no/studio](https://aksel.nav.no/studio)(krever NAV SSO eller invite).
`production`-datasettet er privat, så man må ha riktige tilganger for å kunne lese/jobbe med dataen der.
## Kom i gang
### Bidrag
Branch `master` blir pusher rett til prod -> aksel.nav.no. For å utvikle nye features brukes branch `next` som ved push publiseres til devmiljø aksel.dev.nav.no (krever naisdevice).
### Nettside (localhost:3000)
```
yarn install
yarn workspace aksel.nav.no dev
```
**Rettigheter**
Sanity-datasettet er privat, noe som betyr du må ha tilgang til sanity applikasjonen + følgende token for å få data lokalt:
```
SANITY_PREVIEW_TOKEN
SANITY_PRIVATE_NO_DRAFTS
```
### Sanity (localhost:3333)
> !! Må være developer i https://verktoykasse.sanity.studio, Ta kontakt med team Aksel om du trenger tilgang !!
Husk at alle endringer i datasettet lokalt også gjøres i prod. Bytt derfor til `development`-dataset hvis man vil teste destruktive handlinger. Dette kan endres i `/sanity/schema.json`
```
yarn install
yarn workspace aksel.nav.no sanity
```
**Deploy av ny sanity-oppdatering**
```
yarn workspace aksel.nav.no sanity:deploy
```
### .env
Blir brukt flere keys i .env under `./website`, men kun `SANITY_PRIVATE_NO_DRAFTS` trengs for å teste lokalt
- SANITY_WRITE_KEY:
For å sende dokumenter til sanity, brukt til å oppdatere sandboxes/examples/farger. Trengs bare hvis kode skal synkes fra lokal branch
- ALGOLIA_ADMIN:
For å oppdatere records i søkemotoren. Trengs bare hvis man oppdaterer søkeindexen lokalt.
- ALGOLIA_ID:
Søkemotor index-id for å redigere riktig Index. Trengs bare for at søk skal fungere lokalt
- TRELLO_TOKEN: Sender feedback til et egent trello-board. Ikke nødvendig lokalt hvis man ikke vil teste feedback-modulen.
- SANITY_PREVIEW_TOKEN: Gir appen tilgang til å lese draft innhold fra sanity i "preview"-mode. Trengs for å teste preview-sider
- SANITY_PRIVATE_NO_DRAFTS: Gir appen tilgang til å lese innhold fra Sanity da datasettet er privat
### Søk
Søk-index blir generert av [algolia-scraper](https://github.com/algolia/docsearch-scraper). Kjøres daglig i en github-action, men kan kjøres lokalt også med docker.
```
docker run -it --env-file=.env -e "CONFIG=$(cat crawler.json | jq -r tostring)" algolia/docsearch-scraper
```
## Backups
Kjøres backups hver 3 dag til GCP-bucket under `designsystem-prod`
## Henvendelser
Spørsmål og kontakt kan rettes til team Aksel
Aksel har også en egen slack-kanal #aksel
+48
View File
@@ -0,0 +1,48 @@
{
"index_name": "aksel_docsearch",
"start_urls": [
"https://aksel.nav.no/",
{
"url": "https://aksel.nav.no/designsystem",
"tags": ["designsystem", "docs"]
},
{
"url": "https://aksel.nav.no/artikkel/",
"tags": ["aksel"],
"selectors_key": "aksel"
},
{
"url": "https://aksel.nav.no/blogg/",
"tags": ["aksel"],
"selectors_key": "aksel"
}
],
"selectors": {
"default": {
"lvl0": {
"selector": "h1.algolia-index-lvl1",
"global": true
},
"lvl1": ".algolia-index-tabs--active",
"lvl2": "h2.algolia-index-lvl2",
"lvl3": "h3.algolia-index-lvl3",
"text": "p.algolia-index-body, p.algolia-index-ingress"
},
"aksel": {
"lvl0": {
"selector": "h1.algolia-index-lvl1",
"global": true
},
"lvl1": "h2.algolia-index-lvl2",
"lvl2": "h3.algolia-index-lvl3",
"text": "p.algolia-index-body, p.algolia-index-ingress"
}
},
"selectors_exclude": [".algolia-ignore-index"],
"stop_urls": [
"https://aksel.nav.no/preview",
"https://old-design-nav.vercel.app"
],
"sitemap_urls": ["https://aksel.nav.no/api/sitemap"],
"js_render": true
}
+6
View File
@@ -0,0 +1,6 @@
AZURE_APP_CLIENT_ID=notfound
AZURE_OPENID_CONFIG_TOKEN_ENDPOINT=https://fakedings.dev-gcp.nais.io/fake/aad?NAVIdent=A123456&aud=testbruker-aksel" >> website/.env
AZURE_APP_CLIENT_SECRET=secret
AZURE_OPENID_CONFIG_ISSUER=https://fakedings.dev-gcp.nais.io/fake
AZURE_OPENID_CONFIG_JWKS_URI=https://fakedings.dev-gcp.nais.io/fake/jwks
AZURE_APP_JWK=https://fakedings.dev-gcp.nais.io/fake/jwk
+49
View File
@@ -0,0 +1,49 @@
apiVersion: "nais.io/v1alpha1"
kind: "Application"
metadata:
name: verktoykasse
namespace: designsystem
labels:
team: designsystem
spec:
image: {{ image }}
port: 3000
ingresses:
- "https://aksel.dev.nav.no"
liveness:
path: /api/isAlive
initialDelay: 20
timeout: 1
periodSeconds: 5
failureThreshold: 10
readiness:
path: /api/isAlive
initialDelay: 20
timeout: 1
replicas:
min: 1
max: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
accessPolicy:
outbound:
external:
- host: api.sanity.io
- host: hnbe3yhs.api.sanity.io
- host: apicdn.sanity.io
- host: hnbe3yhs.apicdn.sanity.io
- host: cdn.sanity.io
rules: []
env:
azure:
application:
enabled: true
allowAllUsers: true
tenant: nav.no
sidecar:
enabled: true
+55
View File
@@ -0,0 +1,55 @@
apiVersion: "nais.io/v1alpha1"
kind: "Application"
metadata:
name: verktoykasse
namespace: designsystem
labels:
team: designsystem
spec:
image: {{ image }}
port: 3000
ingresses:
- "https://aksel.nav.no"
- "https://design.nav.no"
liveness:
path: /api/isAlive
initialDelay: 20
timeout: 1
periodSeconds: 5
failureThreshold: 10
readiness:
path: /api/isAlive
initialDelay: 20
timeout: 1
resources:
limits:
cpu: 500m
memory: 512Mi
requests:
cpu: 200m
memory: 256Mi
accessPolicy:
outbound:
external:
- host: api.sanity.io
- host: hnbe3yhs.api.sanity.io
- host: apicdn.sanity.io
- host: hnbe3yhs.apicdn.sanity.io
- host: cdn.sanity.io
rules: []
env:
azure:
application:
enabled: true
allowAllUsers: true
tenant: nav.no
sidecar:
enabled: true
gcp:
buckets:
- name: aksel-website-prod
retentionPeriodDays: 30
lifecycleCondition:
age: 7
numNewerVersions: 3
withState: ANY
+33
View File
@@ -0,0 +1,33 @@
{
"name": "aksel.nav.no",
"version": "1.0.0",
"private": true,
"description": "Nettside for Aksel",
"scripts": {
"dev": "yarn workspace website dev",
"build:next": "yarn workspace website build",
"serve:next": "yarn workspace website start",
"sanity": "yarn workspace sanity start",
"sanity:deploy": "yarn workspace sanity deploy",
"sanity:backup": "yarn workspace sanity backup",
"sanity:update": "yarn workspace website update:sanity",
"test": "yarn workspace website test"
},
"workspaces": [
"sanity",
"website"
],
"dependencies": {
"@navikt/ds-codemod": "^1.4.2",
"@navikt/ds-css": "^1.4.2",
"@navikt/ds-css-internal": "^1.4.2",
"@navikt/ds-icons": "^1.4.2",
"@navikt/ds-react": "^1.4.2",
"@navikt/ds-react-internal": "^1.4.2",
"@navikt/ds-tailwind": "^1.4.2",
"@navikt/ds-tokens": "^1.4.2"
},
"devDependencies": {
"prettier-plugin-tailwindcss": "^0.1.13"
}
}
@@ -0,0 +1,48 @@
// https://www.sanity.io/schemas/make-a-field-read-only-after-publishing-once-d87bf5f6
import { useState, useEffect } from "react";
import { useDocumentOperation, useValidationStatus } from "@sanity/react-hooks";
import PublishIcon from "part:@sanity/base/publish-icon";
export default function SetAndPublishAction(props) {
const { patch, publish }: any = useDocumentOperation(props.id, props.type);
const { isValidating, markers }: any = useValidationStatus(
props.id,
props.type
);
const [isPublishing, setIsPublishing] = useState(false);
useEffect(() => {
// if the isPublishing state was set to true and the draft has changed
// to become `null` the document has been published
if (isPublishing && !props.draft) {
setIsPublishing(false);
}
}, [props.draft]);
console.log(markers);
return {
disabled:
publish.disabled ||
isValidating ||
markers.some((x) => x?.level === "error"),
label: isPublishing ? "Publishing…" : "Publish",
color: "success",
icon: PublishIcon,
onHandle: () => {
// This will update the button text
setIsPublishing(true);
// Set publishedAt to current date and time
!props.published &&
patch.execute([{ set: { publishedAt: new Date().toISOString() } }]);
// Perform the publish
publish.execute();
// Signal that the action is completed
props.onComplete();
},
};
}
@@ -0,0 +1,139 @@
import { EyeScreened } from "@navikt/ds-icons";
import { BodyLong, Button } from "@navikt/ds-react";
import React from "react";
const sanityClient = require("@sanity/client");
const SanityConfig = require("../../sanity.json");
const client = sanityClient({
projectId: SanityConfig.api.projectId,
dataset: SanityConfig.api.dataset,
apiVersion: "2021-08-21",
withCredentials: true,
});
export default function SetPrivateAction(props) {
const [isDialogOpen, setDialogOpen] = React.useState(false);
const isPrivate =
props?.published?.tilgang?.privat ?? props?.draft?.tilgang?.privat;
const handlePatch = async (dir: boolean) => {
props?.published &&
(await client
.patch(props.id)
.set({ tilgang: { privat: dir } })
.commit()
.catch((err) => {
console.error(err.message);
}));
props?.draft &&
(await client
.patch(props.draft._id)
.set({ tilgang: { privat: dir } })
.commit()
.catch((err) => {
console.error(err.message);
}));
props.onComplete();
};
return {
label: isPrivate ? "Åpne side for alle" : "Skjul side bak innlogging",
/* color: "success",
icon: PublishIcon, */
icon: EyeScreened,
onHandle: async () => {
setDialogOpen(true);
},
dialog: isDialogOpen && {
type: "modal",
onClose: props.onComplete,
header: isPrivate
? "Åpne innhold for alle"
: "Gjemme innhold bak innlogging",
content: (
<div>
{isPrivate ? (
<BodyLong spacing>
Innholdet gjøres nå tilgjengelig for alle utenfor NAV! Dette kan
medføre konsekvenser siden innholdet i artikkelen blir
tilgjengelig for alle. Innhold som NAV potensielt ikke ønsker å
dele offentlig kan medføre eks omdømmetap og uønsket oppmerksomhet
utenfra. Dobbelsjekk med temaansvarlig og andre som har skrevet
artikkelen før du velger å åpne innholdet for alle. Er du usikker
er det bare å sende en melding på #aksel eller #aksel-redaktører
på slack.
</BodyLong>
) : (
<>
<BodyLong spacing>
Innholdet gjøres nå bare tilgjengelig for NAV-ansatte. Alt
utenom "innhold" vil fortsatt være tilgjengelig for alle. Dette
inkluderer alle bilder som er lastet opp til sanity også! Husk
at ingen "sikker" data skal skrives i Aksel, og at
innloggings-barrieren er ment for å kunne dele innhold som vi
ikke er helt trygg på å dele med alle.
</BodyLong>
Dette vil fortsatt være åpent for alle
<ul>
<li>Sidetittel</li>
<li>Ingress</li>
<li>Redaktører</li>
<li>Bilder/filer</li>
<li>SEO</li>
</ul>
Dette vil bare være tilgjengelig for NAV-ansatte
<ul>
<li>"Innhold"-blokken</li>
</ul>
</>
)}
<div style={{ display: "flex", gap: "6px", marginTop: 8 }}>
{isPrivate ? (
<>
<Button
onClick={() => {
setDialogOpen(false);
handlePatch(false);
}}
>
Åpne innholdet for alle
</Button>
<Button
variant="secondary"
onClick={() => {
setDialogOpen(false);
props.onComplete();
}}
>
Avbryt
</Button>
</>
) : (
<>
<Button
onClick={() => {
setDialogOpen(false);
handlePatch(true);
}}
>
Skjul innholdet bak innlogging
</Button>
<Button
variant="secondary"
onClick={() => {
setDialogOpen(false);
props.onComplete();
}}
>
Avbryt
</Button>
</>
)}
</div>
</div>
),
},
};
}
@@ -0,0 +1,3 @@
import MediaAssetSource from "part:sanity-plugin-media/asset-source";
import Unsplash from "part:sanity-plugin-asset-source-unsplash/image-asset-source";
export default [MediaAssetSource, Unsplash];
@@ -0,0 +1,52 @@
import { useEffect } from "react";
import config from "config:sanity";
const BUNDLE_CHECK_INTERVAL = 60 * 1000;
const BUNDLE_CHECK_INTERVAL_DECLINED = 60 * 10000;
const CHANGES_AVAILABLE_MESSAGE =
"Studioet er har nye endringer! For å ta i bruk disse vil siden nå oppdateres for den nyeste versjonen.";
async function getCurrentHash() {
const basePath = (config.project && config.project.basePath) || "/";
const html = await window.fetch(basePath).then((res) => res.text());
const [, hash] = html.match(/app\.bundle\.js\?(\w+)/) || [];
return hash;
}
let hash = null;
let interval = null;
const BundleChecker = () => {
useEffect(() => {
getCurrentHash().then((newHash) => {
hash = newHash;
});
interval = createInterval();
return () => clearInterval(interval);
}, []);
// We're a react component, in theory, so return null to not render anything
return null;
};
export default BundleChecker;
const createInterval = (declined?: boolean) =>
setInterval(
async () => {
const newHash = await getCurrentHash();
if (hash && newHash !== hash) {
clearInterval(interval);
if (window.confirm(CHANGES_AVAILABLE_MESSAGE)) {
window.location.reload();
} else {
interval = createInterval(true);
}
}
},
declined ? BUNDLE_CHECK_INTERVAL_DECLINED : BUNDLE_CHECK_INTERVAL
);
@@ -0,0 +1,44 @@
import { allDocumentTypes } from "../config";
export default {
widgets: [
{
name: "document-list",
options: {
title: "Drafts for God Praksis",
query: '*[_type in $types && (_id in path("drafts.**"))]',
queryParams: {
types: allDocumentTypes.filter((x) => x.startsWith("gp")),
},
},
},
{
name: "document-list",
options: {
title: "Drafts for Designsystemet",
query: '*[_type in $types && (_id in path("drafts.**"))]',
queryParams: {
types: allDocumentTypes.filter((x) => x.startsWith("ds")),
},
},
},
{
name: "outdated-documents-widget",
options: {
type: "warning",
},
},
{
name: "outdated-documents-widget",
options: {
type: "error",
},
},
{
name: "project-info",
},
{
name: "project-users",
},
],
};
@@ -0,0 +1,23 @@
import { Picture } from "@navikt/ds-icons";
import S from "@sanity/desk-tool/structure-builder";
import React from "react";
export const adminPanel = async (roles) =>
roles.find((x) => x.name === "administrator") &&
S.listItem()
.title("Admin")
.child(
S.list()
.title("Admin")
.items([
S.documentListItem()
.title(`Forside Aksel`)
.schemaType(`vk_frontpage`)
.icon(() => <Picture />)
.id(`frontpage_vk_praksis`),
S.listItem()
.title("Standalone-sider")
.child(S.documentTypeList("aksel_standalone")),
])
);
@@ -0,0 +1,102 @@
import { prinsipper } from "@/lib";
import { Facilitet } from "@navikt/ds-icons";
import S from "@sanity/desk-tool/structure-builder";
import documentStore from "part:@sanity/base/datastore/document";
import React from "react";
import { map } from "rxjs/operators";
const temaQuery = `*[_type == "aksel_tema" && count(*[references(^._id)]) > 0]`;
const AkselLogo = () => (
<svg
width="192px"
height="192px"
viewBox="0 0 192 192"
fill="none"
xmlns="http://www.w3.org/2000/svg"
focusable="false"
aria-hidden="true"
strokeLinecap="round"
strokeWidth="10"
stroke="currentColor"
>
<path d="m41.63 150.3 47.39-47.38" />
<path d="M40.92 80.3 77.7 43.53c4.25-4.24 9.9-4.24 14.15 0l56.56 56.57c4.25 4.25 4.25 9.9 0 14.14l-36.77 36.77" />
<path d="m40.92 80.3 38.19-38.18c3.53-3.54 6.72-3.18 13.79-3.18h48.79s0 0 0 0c6.36 0 11.31 4.95 11.31 11.31 0 0 0 0 0 0v48.8c0 7.06.35 10.24-3.18 13.78l-38.19 38.18" />
</svg>
);
export const akselInnhold = async () => {
return S.listItem()
.title("Aksel")
.icon(() => <AkselLogo />)
.child(
S.list()
.title("Innhold")
.items([
S.listItem()
.title("Artikler")
.child(() =>
documentStore.listenQuery(temaQuery).pipe(
map((tema: any) => {
return S.list()
.title("Artikler")
.items([
S.listItem()
.title("Alle artikler")
.child(S.documentTypeList("aksel_artikkel")),
S.listItem()
.title("Uten tema")
.child(
S.documentTypeList("aksel_artikkel").filter(
`_type == "aksel_artikkel" && !defined(tema)`
)
),
...tema.map((tag) => {
return S.listItem()
.title(tag.title)
.child(
S.documentList()
.title(tag.title)
.filter(
`_type == 'aksel_artikkel' && $tag in tema[]->title`
)
.params({ tag: tag.title })
);
}),
]);
})
)
),
S.listItem().title("Blogg").child(S.documentTypeList("aksel_blogg")),
S.listItem()
.title("Prinsipper")
.child(
S.list()
.title("Prinsipper")
.items([
S.listItem()
.title("Alle artikler")
.child(S.documentTypeList("aksel_prinsipp")),
...prinsipper.map(({ value, title }) =>
S.listItem()
.title(title)
.child(
S.documentList()
.title(title)
.filter(
`_type == 'aksel_prinsipp' && $value == prinsipp.prinsippvalg`
)
.params({ value })
)
),
])
),
S.divider(),
S.listItem()
.title("Temasider")
.icon(() => <Facilitet />)
.child(S.documentTypeList("aksel_tema")),
])
);
};
@@ -0,0 +1,92 @@
import { CoApplicant, Facilitet, Information, People } from "@navikt/ds-icons";
import S from "@sanity/desk-tool/structure-builder";
import userStore from "part:@sanity/base/user";
import React from "react";
import { createSuperPane } from "sanity-super-pane";
import { ComponentPageWebPreview } from "../../web-previews/ComponentWebPreview";
import { KomponentPreview } from "../../web-previews/KomponentPreview";
import { PageWebPreview } from "../../web-previews/PageWebPreview";
import { adminPanel } from "./admin";
import { akselInnhold } from "./aksel";
import { dsPanel } from "./ds";
import { profilePanel } from "./profile";
export const getDefaultDocumentNode = ({ schemaType }) => {
switch (schemaType) {
case "komponent_artikkel":
return S.document().views([
S.view.form(),
S.view.component(ComponentPageWebPreview).title("Preview"),
S.view.component(KomponentPreview).title("Preview-dev"),
]);
case "ds_artikkel":
return S.document().views([
S.view.form(),
S.view.component(PageWebPreview).title("Preview"),
S.view.component(KomponentPreview).title("Preview-dev"),
]);
case "aksel_artikkel":
return S.document().views([
S.view.form(),
S.view.component(PageWebPreview).title("Preview"),
S.view.component(KomponentPreview).title("Preview-dev"),
]);
case "aksel_blogg":
return S.document().views([
S.view.form(),
S.view.component(PageWebPreview).title("Preview"),
S.view.component(KomponentPreview).title("Preview-dev"),
]);
case "aksel_prinsipp":
return S.document().views([
S.view.form(),
S.view.component(PageWebPreview).title("Preview"),
S.view.component(KomponentPreview).title("Preview-dev"),
]);
case "aksel_standalone":
return S.document().views([
S.view.form(),
S.view.component(PageWebPreview).title("Preview"),
S.view.component(KomponentPreview).title("Preview-dev"),
]);
}
};
const items = [
S.divider(),
S.listItem()
.title("Redaktører")
.icon(() => <People />)
.child(createSuperPane("editor")),
S.divider(),
];
export default () => {
return userStore.getCurrentUser().then(async ({ roles, id }) => {
const panels = [];
const admin = await adminPanel(roles);
const profile = await profilePanel(id);
const ds = await dsPanel(roles);
const aksel = await akselInnhold();
const intro = [];
profile && intro.push(profile);
profile && intro.push(S.divider());
let struct = items;
if (ds) {
struct = [ds, ...struct];
}
struct = [aksel, ...struct];
if (admin) {
struct = [...struct, admin];
}
return S.list()
.title("Aksel")
.items([...intro, ...struct, ...panels]);
});
};
@@ -0,0 +1,119 @@
import {
Facilitet,
FileContent,
Findout,
Folder,
Picture,
Place,
Ruler,
Task,
} from "@navikt/ds-icons";
import S from "@sanity/desk-tool/structure-builder";
import client from "part:@sanity/base/client";
import React from "react";
import { createSuperPane } from "sanity-super-pane";
export const dsPanel = async (roles) => {
const role = roles.some(({ name }) => "aksel-editor" === name);
if (role) {
return null;
} else {
return S.listItem()
.title("Designsystemet")
.icon(() => <Facilitet />)
.child(
S.list()
.title("Designsystem")
.items([
S.listItem()
.title("Komponentsider")
.icon(() => <Facilitet />)
.child(createSuperPane("komponent_artikkel")),
S.listItem()
.title("Artikler")
.icon(() => <FileContent />)
.child(createSuperPane("ds_artikkel")),
S.divider(),
S.documentListItem()
.title(`Navigasjon`)
.schemaType(`ds_navigation`)
.icon(() => <Place />)
.id(`ds_navigationid`),
S.listItem()
.title("Publiserte sider ikke i navigasjon")
.child(async (): Promise<any> => {
const doc = await client.fetch(
`*[_id == 'ds_navigationid'][0]{
headings[]{
title,
menu
}
}`
);
if (!doc.headings) {
return [];
}
let allIds = "";
try {
allIds = doc.headings
.map((heading) =>
heading.menu
.filter((item) => item._type === "item")
.map((item) => item.link._ref)
.map((x) => `"${x}"`)
.join(",")
)
.join(",");
} catch (e) {
console.log(e);
}
return S.documentList()
.title("Sider ikke i navigasjon")
.filter(
`!(_id in [${allIds}]) && !(_id in path('drafts.**')) && _type in ["komponent_artikkel", "ds_artikkel"]`
);
}),
S.documentListItem()
.title(`Forside`)
.schemaType(`ds_frontpage`)
.icon(() => <Picture />)
.id(`frontpage_designsystem`),
S.divider(),
S.listItem()
.title("Autogenerert data")
.child(
S.list()
.title("Autogenerert data")
.items([
S.listItem()
.title("Eksempler")
.icon(() => <span>{`EX`}</span>)
.child(S.documentTypeList("kode_eksempler_fil")),
S.listItem()
.title("Fargekategorier")
.icon(() => <Folder />)
.child(S.documentTypeList("ds_color_categories")),
S.listItem()
.title("Props")
.icon(() => <Folder />)
.child(S.documentTypeList("ds_props")),
S.listItem()
.title("Tokens")
.icon(() => <Folder />)
.child(S.documentTypeList("ds_tokens")),
])
),
S.divider(),
S.documentListItem()
.title(`Komponent-template`)
.schemaType(`ds_component_template`)
.icon(() => <Task />)
.id(`ds_component_templateid`),
])
);
}
};
@@ -0,0 +1,26 @@
import { Picture } from "@navikt/ds-icons";
import S from "@sanity/desk-tool/structure-builder";
import userStore from "part:@sanity/base/user";
import client from "part:@sanity/base/client";
import React from "react";
export const profilePanel = async (id) => {
const ids = await client.withConfig({ apiVersion: "2021-05-31" })
.fetch(`*[_type == "editor"]{
_id,
user_id
}`);
const editor = ids.find(({ user_id }) => user_id?.current === id);
if (editor) {
return S.documentListItem()
.title(`Profil`)
.schemaType(`editor`)
.icon(() => <Picture />)
.id(editor._id);
} else {
return null;
}
};
@@ -0,0 +1,53 @@
import React from "react";
import { People } from "@navikt/ds-icons";
import T from "@sanity/base/initial-value-template-builder";
import userStore from "part:@sanity/base/user";
const sanityClient = require("@sanity/client");
const sanityToken = process.env.SANITY_TOKEN;
const SanityConfig = require("../sanity.json");
const client = sanityClient({
projectId: SanityConfig.api.projectId,
dataset: SanityConfig.api.dataset,
apiVersion: "2020-06-19",
/* Token er safe og har bare tillatelse til å lese template doc */
token:
"skt6kqEcXmRxUTGDzdEcw11g7HJi9DaHX2fyn8Z0Dearr1k67ykW2N64KTP9GxAPNN6P8TlzPjUdkfGsvbJkfmwfq0UNudKfMYOfyz9OeA666y15pPLRr6D7xnrJGEEBYwSvG6baLLiJI478YowWpjqp1Mz2U1PbAPnIMW4W5btTpdQbwPSD",
useCdn: false,
});
export default [
...T.defaults().filter((x) => !["komponent_artikkel"].includes(x.spec.id)),
T.template({
id: "komponent_artikkel_template",
title: "Mal for Komponentsider",
schemaType: "komponent_artikkel",
value: async () => {
const doc = await client.fetch(`*[_type == "ds_component_template"][0]`);
return doc
? Object.entries(doc).reduce((old, [key, val]) => {
return key.startsWith("_") ? { ...old } : { ...old, [key]: val };
}, {})
: {};
},
}),
T.template({
id: "newEditor",
title: "Editor",
schemaType: "editor",
icon: () => <People />,
value: async () => {
const { id, displayName } = await userStore.getUser("me");
return {
title: displayName,
user_id: {
current: id,
},
_type: "editor",
};
},
}),
];
@@ -0,0 +1,38 @@
import S from "@sanity/base/structure-builder";
const docTypes = [
{
name: "aksel_artikkel",
title: "Aksel Artikkel",
},
{
name: "newEditor",
title: "Bruker/redaktør",
desc: "Lager en psuedo-bruker for deg som kan koble artikler til brukeren",
},
{
name: "aksel_blogg",
title: "Bloggpost",
},
{
name: "ds_artikkel",
title: "Designsystem Artikkel",
desc: "Atikkel som legges under designsystemet",
},
{
name: "komponent_artikkel_template",
title: "Designsystem Komponentside",
desc: "Atikkel som legges under designsystemet",
},
];
export default [
...docTypes.map((doct) =>
S.initialValueTemplateItem(doct.name)
.id(`doc-${doct.name}`)
.title(doct.title)
.templateId(doct.name)
.description(doct?.desc ?? "")
),
/* ...S.defaultInitialValueTemplateItems() */
];
@@ -0,0 +1,15 @@
import defaultResolve, {
PublishAction,
} from "part:@sanity/base/document-actions";
import SetAndPublishAction from "./actions/publishedAt";
/* import SetPrivateAction from "./actions/set-private"; */
import { allDocumentTypes } from "../config";
export default function resolveDocumentActions(props) {
const [publish, ...rest] = defaultResolve(props).map((Action) => {
return allDocumentTypes.includes(props.type) && Action === PublishAction
? SetAndPublishAction
: Action;
});
return [publish /* SetPrivateAction */, ...rest];
}
@@ -0,0 +1,53 @@
import { Button, Card, Inline, Stack, Text } from "@sanity/ui";
import moment from "moment";
import PatchEvent, { set } from "part:@sanity/form-builder/patch-event";
import React from "react";
const CustomDisplay = React.forwardRef((props, ref) => {
const click = () => {
props.onChange &&
props.onChange(
PatchEvent.from(set(new Date().toISOString().split("T")[0]))
);
};
const lastUpdate = moment(props.value);
const daysSince = moment().diff(lastUpdate, "days");
return (
<Stack tabIndex={0} space={4} ref={ref}>
{daysSince > 90 && (
<Card
padding={[3, 3, 4]}
radius={2}
shadow={1}
tone={daysSince > 180 ? "critical" : "caution"}
>
<Text align="center" size={[2, 2, 3]}>
{daysSince > 180
? "Innholdet er utdatert!"
: "Innholdet er stagnert!"}
</Text>
</Card>
)}
<Stack space={4}>
<Text size={1} weight="semibold">
Sist oppdatert
</Text>
<Text size={2}>{props.value?.split("-").reverse().join("/")}</Text>
</Stack>
<Inline space={[3, 3, 4]}>
<Button
onClick={() => click()}
fontSize={[2]}
mode="ghost"
padding={[2, 2]}
text="Oppdater"
/>
</Inline>
</Stack>
);
});
export default CustomDisplay;
@@ -0,0 +1,30 @@
import React, { useState, useEffect, useRef } from "react";
import { withDocument } from "part:@sanity/form-builder";
import {
TextInput,
Stack,
Label,
Checkbox,
Radio,
Flex,
Text,
Box,
Card,
} from "@sanity/ui";
import styled from "styled-components";
/* TODO: Implement this. Issue now is that document returns null in props */
const CustomDisplay = React.forwardRef((props, ref) => {
const [doc, setDoc] = useState(null);
useEffect(() => {
doc && setDoc(props.document);
}, [props]);
return (
<Stack space={0} ref={ref}>
<Card padding={0}>T</Card>
</Stack>
);
});
export default withDocument(CustomDisplay);
@@ -0,0 +1,49 @@
import React from "react";
import Preview from "part:@sanity/base/preview";
import { IntentLink } from "part:@sanity/base/router";
import schema from "part:@sanity/base/schema";
import {
Item as DefaultItem,
List as DefaultList,
} from "part:@sanity/components/lists/default";
import styles from "./profile.css";
const ReferringDocumentsList = (props) => {
const { documents } = props;
return (
<DefaultList className={styles.root}>
{documents
.sort(
(a, b) =>
new Date(b._updatedAt).getTime() - new Date(a._updatedAt).getTime()
)
.map((document) => {
const schemaType = schema.get(document._type);
return (
<DefaultItem key={document._id} className={styles.item}>
{schemaType ? (
<IntentLink
className={styles.link}
intent="edit"
params={{ id: document._id, type: document._type }}
>
<Preview value={document} type={schemaType} />
{document._updatedAt
.split("T")[0]
.split("-")
.reverse()
.join("-")}
</IntentLink>
) : (
<div>
A document of the unknown type <em>{document._type}</em>
</div>
)}
</DefaultItem>
);
})}
</DefaultList>
);
};
export default ReferringDocumentsList;
@@ -0,0 +1,18 @@
export const getUnpublished = (docs: Record<string, any>[]) =>
docs.filter(
(doc, i, arr) =>
doc._id.startsWith("drafts.") &&
!arr.find((x, y) => i !== y && doc._id.includes(x._id))
);
export const getDrafts = (docs: Record<string, any>[]) =>
docs.filter((doc, i, arr) =>
arr.find((x, y) => i !== y && x._id.includes(doc._id))
);
export const getPublished = (docs: Record<string, any>[]) =>
docs
.filter(
(doc, i, arr) => !arr.find((x, y) => i !== y && x._id.includes(doc._id))
)
.filter((doc) => !doc._id.includes("drafts"));
@@ -0,0 +1,171 @@
import { Caseworker, Sandglass, Upload, Wrench } from "@navikt/ds-icons";
import {
BodyShort,
Detail,
Heading,
HelpText,
Label,
Link,
ReadMore,
} from "@navikt/ds-react";
import { SanityDocument } from "@sanity/client";
import { Stack } from "@sanity/ui";
import userStore from "part:@sanity/base/user";
import { WithReferringDocuments } from "part:@sanity/base/with-referring-documents";
import Spinner from "part:@sanity/components/loading/spinner";
import { withDocument } from "part:@sanity/form-builder";
import React, { useEffect, useState } from "react";
import ReferringDocumentsList from "./document-list";
import styles from "../styles.css";
import { getDrafts, getPublished, getUnpublished } from "./filter-docs";
type Props = {
referringDocuments: Record<string, any>[];
isLoading: boolean;
published?: SanityDocument | null;
type: any;
document: any;
};
const IntroPage = React.forwardRef<HTMLDivElement, Props>((props, ref) => {
const { document } = props;
const [user, setUser] = useState<any>({});
useEffect(() => {
userStore.getCurrentUser().then((user) => setUser(user));
}, []);
const EmptyState = () => (
<Detail className={styles.detailsContent} size="small">
Ingen sider her...
</Detail>
);
if (
!document._id ||
(document?.user_id?.current !== user.id &&
!user?.roles?.find((x) => x.name === "administrator"))
)
return null;
return (
<Stack ref={ref} space={[3, 1, 2, 4]}>
<Heading level="2" size="medium" spacing style={{ display: "flex" }}>
Profil <Caseworker style={{ marginLeft: "0.5rem", fontSize: "2rem" }} />
</Heading>
{user?.roles && (
<div>
<Heading level="3" size="small" spacing>
Roller/tilganger:
</Heading>
{user?.roles.map((x, i) => (
<div key={i}>
<Label size="small" spacing as="p">
{x?.title}
</Label>
<BodyShort size="small">{x?.description}</BodyShort>
</div>
))}
</div>
)}
<BodyShort
size="small"
spacing
as="span"
style={{ display: "flex", gap: "0.5rem" }}
>
<ReadMore header="Har du ikke de rollene du trenger?">
Send en melding til oss på{" "}
<Link href="https://nav-it.slack.com/archives/C0370ADS0HX">
#Aksel
</Link>{" "}
slack så fikser vi det.
</ReadMore>
</BodyShort>
<div style={{ borderTop: "1px solid #bbb" }} />
<WithReferringDocuments id={document._id?.replace("drafts.", "")}>
{({ referringDocuments, isLoading }) => {
if (isLoading) {
return <Spinner message="Looking for referring documents…" />;
}
if (!referringDocuments.length) return null;
const drafts = getDrafts(referringDocuments);
const published = getPublished(referringDocuments);
const unpublished = getUnpublished(referringDocuments);
return (
<Stack space={2} className={styles.profileDetails}>
<Heading level="3" size="xsmall" style={{ display: "flex" }}>
<Upload />
Publiserte sider
</Heading>
<details>
<Label size="small" as="summary">
<BodyShort size="small">
Oppdaterte: {published.length}
</BodyShort>
</Label>
{published ? (
<ReferringDocumentsList documents={published} />
) : (
<EmptyState />
)}
</details>
<details>
<Label size="small" as="summary">
<BodyShort size="small">
Med upubliserte endringer: {drafts.length}
</BodyShort>
</Label>
{drafts ? (
<ReferringDocumentsList documents={drafts} />
) : (
<EmptyState />
)}
</details>
<Heading level="3" size="xsmall" className={styles.headings}>
<Wrench />
Under arbeid
</Heading>
<details>
<Label size="small" as="summary">
<BodyShort size="small">
Sider: {unpublished.length}
</BodyShort>
</Label>
{unpublished ? (
<ReferringDocumentsList documents={unpublished} />
) : (
<EmptyState />
)}
</details>
<Heading level="3" size="xsmall" className={styles.headings}>
<Sandglass />
Utdaterte sider
</Heading>
<details>
<Label size="small" as="summary">
<BodyShort size="small">
Ikke oppdatert på over 90 dager: 0
</BodyShort>
</Label>
<EmptyState />
</details>
<details>
<Label size="small" as="summary">
<BodyShort size="small">
Ikke oppdatert på over 180 dager: 0
</BodyShort>
</Label>
<EmptyState />
</details>
</Stack>
);
}}
</WithReferringDocuments>
</Stack>
);
});
export default withDocument(IntroPage);
@@ -0,0 +1,35 @@
/* ./cms/styles/ReferringDocumentsList.css */
@import "part:@sanity/base/theme/variables-style";
.root {
composes: root from "part:@sanity/components/lists/default-style";
}
.item {
composes: lineBetween from "part:@sanity/components/lists/default-item-style";
background: var(--white);
border-radius: var(--border-radius-base);
color: inherit;
margin-bottom: var(--small-padding);
border: 1px solid var(--list-border-color);
}
.item:hover {
background-color: var(--navds-semantic-color-interaction-primary-hover-subtle, rgba(230, 240, 255, 1));
}
.link {
padding: var(--small-padding);
color: inherit;
text-decoration: none;
display: flex;
align-items: center;
justify-content: space-between;
}
.link:focus {
border-radius: 2px;
outline: none;
box-shadow: var(--navds-shadow-focus, var(--navds-shadow-focus, 0 0 0 3px rgba(0, 52, 125, 1)));
}
+83
View File
@@ -0,0 +1,83 @@
@import "part:@sanity/base/theme/variables-style";
.link {
composes: item from "part:@sanity/base/theme/layout/selectable-style";
display: block;
color: inherit;
text-decoration: inherit;
outline: none;
padding: 0.5rem 0;
height: 100%;
}
.profileDetails details {
border-radius: 1px;
}
.profileDetails details summary {
cursor: pointer;
padding: 0.5rem;
padding-left: 0.66rem;
margin-bottom: 0;
border-radius: 1px;
position: relative;
list-style: none;
}
.profileDetails details summary > p {
margin-left: 1.35rem;
}
.profileDetails > * > svg {
margin-right: 0.5rem;
font-size: 1.5rem;
}
.profileDetails details summary:hover {
background-color: var(--navds-semantic-color-interaction-primary-hover-subtle, rgba(230, 240, 255, 1));
}
.profileDetails details summary:focus {
outline: none;
}
.profileDetails details summary:focus-visible {
box-shadow: var(--navds-shadow-focus, 0 0 0 3px rgba(0, 52, 125, 1));
}
.profileDetails details summary > * {
display: inline;
}
.profileDetails details summary::marker {
display: none;
content: "";
}
.profileDetails details summary::before {
color: #3b3b3b;
content: "+";
font-size: 1.5rem;
display: block;
position: absolute;
left: 0.27rem;
top: 6px;
}
.profileDetails details[open] summary::before {
content: "-";
}
.profileDetails details[open] summary {
margin-bottom: 0.75rem;
}
.headings {
margin-top: 1rem !important;
display: flex;
}
p.detailsContent {
margin-left: 2rem;
margin-bottom: 1.5rem;
}
@@ -0,0 +1,130 @@
import { Heading, Stack, Text } from "@sanity/ui";
import { withDocument } from "part:@sanity/form-builder";
import { getPublishedId } from "part:@sanity/base/util/draft-utils";
import schema from "part:@sanity/base/schema";
import React, { useEffect, useCallback, useState } from "react";
import moment from "moment";
import { IntentLink } from "part:@sanity/base/router";
import { Item, List } from "part:@sanity/components/lists/default";
import SanityPreview from "part:@sanity/base/preview";
import styles from "./styles.css";
const sanityClient = require("@sanity/client");
const SanityConfig = require("../sanity.json");
const client = sanityClient({
projectId: SanityConfig.api.projectId,
dataset: SanityConfig.api.dataset,
apiVersion: "2020-06-19",
useCdn: false,
});
const RelatedArticles = React.forwardRef((props, ref) => {
const [userDocuments, setUserDocuments] = useState([]);
const getUserDocs = useCallback(async () => {
const userDocs = await client
.fetch(`*["${props.document._id}" == contact._ref]`)
.catch((e) => console.error(e.message));
userDocs && setUserDocuments(userDocs);
}, [props.document]);
useEffect(() => {
if (!props.document) {
return;
}
getUserDocs();
}, [getUserDocs]);
const outdated = userDocuments.filter((doc) => {
if (!doc?.metadata?.last_update) return false;
const lastUpdate = moment(doc._updatedAt);
const daysSince = moment().diff(lastUpdate, "days");
return daysSince > 180;
});
const stagnated = userDocuments.filter((doc) => {
if (!doc?.metadata?.last_update) return false;
const lastUpdate = moment(doc._updatedAt);
const daysSince = moment().diff(lastUpdate, "days");
return daysSince > 90 && daysSince <= 180;
});
return (
<Stack space={0} ref={ref} space={[3, 1, 2, 4]}>
<details>
<summary>Utdaterte sider: {outdated.length}</summary>
<List>
{outdated.map((doc) => (
<Item key={doc._id}>
<IntentLink
intent="edit"
params={{
type: doc._type,
id: getPublishedId(doc._id),
}}
className={styles.link}
>
<SanityPreview
layout="default"
type={schema.get(doc._type)}
value={doc}
key={doc._id}
/>
</IntentLink>
</Item>
))}
</List>
</details>
<details>
<summary>Stagnerte sider: {stagnated.length}</summary>
<List>
{stagnated.map((doc) => (
<Item key={doc._id}>
<IntentLink
intent="edit"
params={{
type: doc._type,
id: getPublishedId(doc._id),
}}
className={styles.link}
>
<SanityPreview
layout="default"
type={schema.get(doc._type)}
value={doc}
key={doc._id}
/>
</IntentLink>
</Item>
))}
</List>
</details>
<details>
<summary>Alle sider: {userDocuments.length}</summary>
<List>
{userDocuments.map((doc) => (
<Item key={doc._id}>
<IntentLink
intent="edit"
params={{
type: doc._type,
id: getPublishedId(doc._id),
}}
className={styles.link}
>
<SanityPreview
layout="default"
type={schema.get(doc._type)}
value={doc}
key={doc._id}
/>
</IntentLink>
</Item>
))}
</List>
</details>
</Stack>
);
});
export default withDocument(RelatedArticles);
+41
View File
@@ -0,0 +1,41 @@
const teams = [
{
name: "designsystem",
prefix: "ds",
documents: ["komponent_artikkel", "ds_artikkel"],
navigation: `navigation_designsystem`,
},
{
name: "god_praksis",
prefix: "gp",
documents: ["aksel_artikkel", "aksel_blogg", "aksel_prinsipp"],
},
];
/* Collection of all document-pages to account for */
const allDocumentTypes = teams.reduce(
(docs, team) => [...docs, ...team.documents],
[]
);
/* Collection of all navigation-documents */
const allNavDocumentIds = teams.map((x) => x?.navigation).filter((x) => x);
/**
* Defines when a document of a spesific type is set to stagnant or expired
* @returns [stagnant Date, expired Date]
*/
function getExpireDates() {
const stagnantDate = new Date();
const expiredDate = new Date();
stagnantDate.setDate(stagnantDate.getDate() + 120);
expiredDate.setDate(expiredDate.getDate() + 180);
return [stagnantDate, expiredDate];
}
module.exports = {
teams,
allDocumentTypes,
allNavDocumentIds,
getExpireDates,
};
+9
View File
@@ -0,0 +1,9 @@
{
"#": "Used by Sanity to keep track of configuration file checksums, do not delete or modify!",
"@sanity/default-layout": "bb034f391ba508a6ca8cd971967cbedeb131c4d19b17b28a0895f32db5d568ea",
"@sanity/default-login": "6fb6d3800aa71346e1b84d95bbcaa287879456f2922372bb0294e30b968cd37f",
"@sanity/form-builder": "b38478227ba5e22c91981da4b53436df22e48ff25238a55a973ed620be5068aa",
"@sanity/data-aspects": "d199e2c199b3e26cd28b68dc84d7fc01c9186bf5089580f2e2446994d36b3cb6",
"@sanity/vision": "da5b6ed712703ecd04bf4df560570c668aa95252c6bc1c41d6df1bda9b8b8f60",
"power-table": "922902575995e62af8f628e6abe652ecc5f3b1e1d2dec5859392e79e47707088"
}
@@ -0,0 +1,3 @@
{
"listOptions": {}
}
@@ -0,0 +1,6 @@
{
"toolSwitcher": {
"order": [],
"hidden": []
}
}
@@ -0,0 +1,14 @@
{
"providers": {
"mode": "append",
"redirectOnSingle": false,
"entries": [
{
"name": "saml",
"title": "NAV SSO",
"url": "https://api.sanity.io/v2021-10-01/auth/saml/login/f3270b37",
"logo": "/static/navlogo.svg"
}
]
}
}
@@ -0,0 +1,5 @@
{
"images": {
"directUploads": true
}
}
@@ -0,0 +1,3 @@
{
"defaultApiVersion": "2021-10-21"
}
@@ -0,0 +1,3 @@
{
"add-config": "here"
}
+129
View File
@@ -0,0 +1,129 @@
import { allDocumentTypes } from "../config";
export const editorField = {
title: "Redaktører",
description: "Legg til alle som har bidratt med denne siden!",
name: "contributors",
type: "array",
of: [{ type: "reference", to: [{ type: "editor" }] }],
group: "settings",
validation: (Rule) => Rule.required(),
};
export const privateField = {
title: "Sidetilganger",
name: "tilgang",
type: "object",
description: "Styrer tilgang for side-innhold",
hidden: true,
fields: [
{
title: "Privat",
name: "privat",
type: "boolean",
},
],
initialValue: {
privat: true,
},
};
export const publishedAtField = {
title: "Publiseringsdato",
name: "publishedAt",
type: "datetime",
group: "settings",
description: "Synlig bare for admins. Setter publiseringsdato for dokument",
hidden: ({ currentUser }) =>
!currentUser.roles.find((x) => x.name === "administrator"),
};
export const migratedField = {
title: "Migrert riktekst",
name: "isMigrated",
type: "boolean",
hidden: true,
initialValue: true,
};
export const titleField = {
title: "Sidetittel",
name: "heading",
type: "string",
group: "innhold",
description:
"Bruk en kort og konsis tittel om mulig. Blir satt som `<H1 />` på toppen av siden i URL.",
validation: (Rule) =>
Rule.required()
.max(60)
.error("Siden bør ha en kort og konsis heading (<h1>)"),
};
export const ingressField = {
title: "Ingress",
name: "ingress",
description: "Side, innganger og seo description-tag",
type: "text",
group: "innhold",
rows: 3,
validation: (Rule) =>
Rule.required().max(210).error("Ingress kan ikke være på over 210 tegn"),
};
export const innholdFieldNew = {
title: "Innhold",
name: "content",
type: "riktekst_aksel",
group: "innhold",
};
export const innholdFieldPrinsipp = {
title: "Innhold",
name: "content",
type: "riktekst_prinsipp",
group: "innhold",
};
export const relevanteArtiklerField = {
title: "Relevante artikler",
description:
"Legg til relaterte artikler som du tenker er relevant å lese (maks 3)",
name: "relevante_artikler",
type: "array",
group: "innhold",
validation: (Rule) =>
Rule.max(3).error("Kan ikke ha mer enn 3 relevante artikler lagt til"),
of: [
{
type: "reference",
weak: true,
to: [...allDocumentTypes.map((x) => ({ type: x }))],
},
],
};
export const innholdFieldNewNested = (name?: string, type?: string) => {
const fields = {
...innholdFieldNew,
type: type ?? "riktekst_ds_artikkel",
name: name ?? "content",
};
delete fields.group;
return fields;
};
export const hidePageFeedback = {
title: "Tilbakemeldinger",
name: "metadata_feedback",
type: "object",
group: "settings",
fields: [
{
title: "Skjul artikkel feedback modul",
description: "Gjemmer <<Var denne artikkelen til hjelp?>> modulen.",
name: "hide_feedback",
type: "boolean",
initialValue: false,
},
],
};
+116
View File
@@ -0,0 +1,116 @@
export * from "./fields";
export * from "./preview";
export * from "./seo";
import { Settings } from "@navikt/ds-icons";
import sanityClient from "part:@sanity/base/client";
const client = sanityClient.withConfig({ apiVersion: "2020-06-19" });
export const prinsipper = [
{ title: "Brukeropplevelse", value: "brukeropplevelse" },
];
export function toPlainText(blocks = []) {
return blocks
.filter((block) => !(block._type !== "block" || !block.children))
.map((block) => {
return block.children.map((child) => child.text).join("");
})
.join("\n");
}
export const sanitySlug = (prefix: string, depth: number, source?: string) => ({
title: "url",
name: "slug",
type: "slug",
validation: (Rule) => validateSlug(Rule, prefix, depth ?? 3),
group: "settings",
options: {
isUnique: isSlugUnique,
source: source ?? "heading",
slugify: (input) =>
`${prefix}${input}`
.toLowerCase()
.trim()
.slice(0, 80)
.trim()
.replace(/\s+/g, "-")
.replace("--", "-")
.replace("(", "")
.replace(")", "")
.replace("æ", "a")
.replace("ø", "o")
.replace("å", "a")
.replace(".", "-"),
},
});
/* Checks that slug starts with prefix and is nested to x depth */
export const validateSlug = (Rule, prefix, nesting) =>
Rule.required().custom((slug) => {
if (!slug.current.startsWith(prefix)) {
return `Slug må starte med prefiks: ${prefix}`;
}
if ((slug.current.match(/\//g) || []).length > nesting - 1) {
return `Siden kan bare være på ${nesting} nivå`;
}
return true;
});
/* Checks every document for matching slug */
export const isSlugUnique = (slug, options) => {
const { document } = options;
const id = document._id.replace(/^drafts\./, "");
const params = {
draft: `drafts.${id}`,
published: id,
slug,
};
const query = `!defined(*[!(_id in [$draft, $published]) && slug.current == $slug][0]._id)`;
return client.fetch(query, params);
};
export const isEditorUnique = (slug, options) => {
const { document } = options;
const id = document._id.replace(/^drafts\./, "");
const params = {
draft: `drafts.${id}`,
published: id,
slug,
};
const query = `!defined(*[!(_id in [$draft, $published]) && _type == "editor" && user_id.current == $slug][0]._id)`;
return client.fetch(query, params);
};
export const groups = [
{
name: "innhold",
title: "Innhold",
default: true,
},
{
name: "metadata",
title: "Metadata",
},
{
name: "settings",
title: "Innstillinger",
icon: Settings,
},
{
name: "lenker",
title: "Linking",
},
{
name: "beta",
title: "Beta",
},
{
name: "seo",
title: "SEO",
},
];
+38
View File
@@ -0,0 +1,38 @@
import { Edit, Task } from "@navikt/ds-icons";
import React from "react";
export const defaultDocPreview = {
preview: {
select: {
heading: "heading",
status: "status",
metadata: "metadata",
id: "_id",
type: "_type",
tema: "tema.0.title",
prinsipp: "prinsipp",
},
prepare(selection) {
const { id, heading, type, tema, prinsipp } = selection;
return {
title: heading,
subtitle: !!prinsipp
? !!prinsipp?.hovedside
? "Hovedside"
: "Artikkel"
: !!tema
? `${tema}`
: type.includes("_component_")
? "Komponentside"
: type.includes("_tabbed_")
? "Artikkel m/tabs"
: "Artikkel",
media: id.includes("drafts.") ? (
<Edit aria-hidden />
) : (
<Task aria-hidden />
),
};
},
},
};
+48
View File
@@ -0,0 +1,48 @@
const pattern = /^image-([a-f\d]+)-(\d+x\d+)-(\w+)$/;
const decodeAssetId = (id) => {
const [, assetId, dimensions, format] = pattern.exec(id);
const [width, height] = dimensions.split("x").map((v) => parseInt(v, 10));
return {
assetId,
dimensions: { width, height },
format,
};
};
export const SEOFields = {
name: "seo",
type: "object",
title: "SEO",
group: "seo",
fields: [
{
name: "meta",
type: "text",
title: "Meta/:og description",
description:
"Anbefalt maks 150-160 bokstaver. Erstatter ingress som <meta /> description",
rows: 3,
},
{
title: "og:Image",
name: "image",
type: "image",
description: "Anbefalt størrelse er 1200:630px",
options: {
accept: "image/*",
},
validation: (Rule) =>
Rule.custom((image) => {
if (!image) return true;
const { dimensions } = decodeAssetId(image.asset._ref);
const aspectR = dimensions.width / dimensions.height;
if (aspectR < 1.5 || aspectR > 2.4) {
return `Forventet aspect-ration rundt 1.91:1. Er nå: ${aspectR}:1.`;
}
return true;
}),
},
],
};
+53
View File
@@ -0,0 +1,53 @@
{
"name": "sanity",
"private": true,
"version": "0.1.0",
"description": "Sanity løsning for Aksel",
"license": "MIT",
"scripts": {
"start": "sanity start",
"build": "sanity build",
"deploy": "yarn typegen && sanity deploy",
"backup": "node scripts/backup.js",
"typegen": "sanity exec typegen.ts"
},
"main": "package.json",
"keywords": [
"sanity"
],
"dependencies": {
"@sanity/base": "^2.35.0",
"@sanity/cli": "^2.35.0",
"@sanity/code-input": "^2.35.0",
"@sanity/components": "^2.14.0",
"@sanity/core": "^2.35.0",
"@sanity/dashboard": "^2.35.0",
"@sanity/default-layout": "^2.35.0",
"@sanity/default-login": "^2.35.0",
"@sanity/desk-tool": "^2.35.0",
"@sanity/export": "^2.30.3",
"@sanity/production-preview": "^2.29.3",
"@sanity/react-hooks": "^2.35.0",
"@sanity/vision": "^2.35.0",
"moment": "2.29.4",
"sanity-codegen": "0.9.8",
"sanity-plugin-asset-source-unsplash": "^0.1.5",
"sanity-plugin-dashboard-widget-document-list": "^0.0.13",
"sanity-plugin-media": "^1.4.4",
"sanity-plugin-power-table": "^0.0.14",
"sanity-super-pane": "^1.0.0-alpha.11",
"styled-components": "^5.3.1"
},
"devDependencies": {
"@babel/core": "^7.19.0",
"@babel/preset-env": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/preset-typescript": "^7.0.0",
"@sanity-codegen/schema-codegen": "1.0.0-alpha.13",
"babel-plugin-mock-imports": "1.2.0"
},
"repository": {
"type": "git",
"url": "https://github.com/navikt/aksel-website.git"
}
}
+1
View File
@@ -0,0 +1 @@
User-specific packages can be placed here
@@ -0,0 +1,137 @@
/* https://github.com/sanity-io/dashboard-widget-document-list/blob/master/src/DocumentList.js */
import client from "@sanity/client";
import { Button, Flex } from "@sanity/ui";
import { DashboardWidget } from "@sanity/dashboard";
import React, { useEffect, useState, useRef, useCallback } from "react";
import config from "../../config";
import SanityConfig from "../../sanity.json";
import moment from "moment";
import styles from "./widget.css";
import { Item, List } from "part:@sanity/components/lists/default";
import { IntentLink } from "part:@sanity/base/router";
import { getPublishedId } from "part:@sanity/base/util/draft-utils";
import { Tag } from "@navikt/ds-react";
const sanityClient = client({
projectId: SanityConfig.api.projectId,
dataset: SanityConfig.api.dataset,
apiVersion: "2020-06-19",
useCdn: false,
});
const OutDatedComponents = ({ type }) => {
const query = `*[_type in [${config.allDocumentTypes.map((x) => `"${x}"`)}]]`;
const [docs, setDocs] = useState([]);
const [loading, setloading] = useState(true);
const [timer, setTimer] = useState(new Date());
const [fromTime, setFromTime] = useState(moment(new Date()).fromNow());
const success = (v) => {
setDocs(
v
.filter((x) => {
if (!x?._updatedAt) return false;
const lastUpdate = moment(x._updatedAt);
const daysSince = moment().diff(lastUpdate, "days");
return type === "error"
? daysSince > 180
: daysSince >= 90 && daysSince < 180;
})
.sort((a, b) => {
const lastUpdateA = moment(a._updatedAt);
const lastUpdateB = moment(b._updatedAt);
return lastUpdateA.diff(lastUpdateB, "days");
})
);
setloading(false);
setTimer(new Date());
};
const load = useCallback(() => {
setloading(true);
sanityClient.fetch(query).then(success, (e) => console.error(e));
}, []);
useEffect(() => {
load();
}, []);
/* Refreshes every 5 minutes */
useEffect(() => {
const interval = setInterval(() => {
load();
}, 300000);
return () => clearInterval(interval);
}, []);
useEffect(() => {
const interval = setInterval(() => {
setFromTime(moment(timer).fromNow());
}, 10000);
return () => clearInterval(interval);
}, []);
const refresh = () => {
load();
};
const footer = (
<Flex direction="column" align="stretch">
<Button
fontSize={[2]}
tone="primary"
paddingX={2}
paddingY={4}
text={`Updated ${fromTime}`}
onClick={() => refresh()}
mode="bleed"
loading={loading}
disabled={loading}
/>
</Flex>
);
return (
<DashboardWidget
header={type === "error" ? "Utdaterte sider" : "Stagnerte sider"}
footer={footer}
>
<List>
{docs &&
docs.map((doc) => {
const lastUpdate = moment(doc._updatedAt);
const daysSince = moment().diff(lastUpdate, "days");
return (
<Item key={doc._id}>
<IntentLink
intent="edit"
params={{
type: doc._type,
id: getPublishedId(doc._id),
}}
className={styles.link}
>
<span className={styles.spacing}>
<Tag size="small" variant={type}>
{daysSince}d
</Tag>
{doc?.heading}
</span>
</IntentLink>
</Item>
);
})}
</List>
</DashboardWidget>
);
};
export default {
name: "outdated-documents-widget",
component: OutDatedComponents,
};
@@ -0,0 +1,9 @@
{
"parts": [
{
"name": "part:@sanity/dashboard/widget/outdated-documents-widget",
"implements": "part:@sanity/dashboard/widget",
"path": "index.js"
}
]
}
@@ -0,0 +1,47 @@
@import "part:@sanity/base/theme/variables-style";
.container {
composes: container from "part:@sanity/dashboard/widget-styles";
}
.header {
composes: header from "part:@sanity/dashboard/widget-styles";
}
.title {
composes: title from "part:@sanity/dashboard/widget-styles";
}
.content {
composes: content from "part:@sanity/dashboard/widget-styles";
border-top: 1px solid var(--hairline-color);
padding: var(--small-padding) 0;
display: flex;
justify-content: center;
}
.link {
composes: item from "part:@sanity/base/theme/layout/selectable-style";
display: block;
color: inherit;
text-decoration: inherit;
outline: none;
padding: var(--small-padding) var(--medium-padding);
height: 100%;
}
.footer {
composes: footer from "part:@sanity/dashboard/widget-styles";
border-top: 1px solid var(--hairline-color);
}
.img {
display: block;
width: 100%;
}
.spacing {
display: inline-flex;
gap: 1rem;
align-items: center;
}
+70
View File
@@ -0,0 +1,70 @@
{
"root": true,
"project": {
"name": "Aksel"
},
"api": {
"projectId": "hnbe3yhs",
"dataset": "production"
},
"env": {
"development": {
"api": {
"dataset": "production"
},
"plugins": ["@sanity/vision"]
}
},
"plugins": [
"@sanity/base",
"@sanity/components",
"@sanity/default-layout",
"@sanity/default-login",
"@sanity/desk-tool",
"@sanity/code-input",
"@sanity/dashboard",
"dashboard-widget-document-list",
"outdated-documents-widget",
"media",
"power-table",
"asset-source-unsplash"
],
"parts": [
{
"name": "part:@sanity/base/schema",
"path": "./schemas/schema"
},
{
"name": "part:@sanity/desk-tool/structure",
"path": "./client/desk-structure/deskStructure.tsx"
},
{
"implements": "part:@sanity/dashboard/config",
"path": "./client/dashboardConfig.tsx"
},
{
"name": "part:@sanity/base/initial-value-templates",
"path": "./client/initialValueTemplates.tsx"
},
{
"name": "part:@sanity/base/new-document-structure",
"path": "./client/newDocumentStructure.tsx"
},
{
"implements": "part:@sanity/base/absolutes",
"path": "./client/bundleCheck.tsx"
},
{
"implements": "part:@sanity/form-builder/input/image/asset-sources",
"path": "./client/assetSources.ts"
},
{
"implements": "part:@sanity/form-builder/input/file/asset-sources",
"path": "./client/assetSources.ts"
},
{
"implements": "part:@sanity/base/document-actions/resolver",
"path": "./client/resolveDocumentActions.tsx"
}
]
}
+1
View File
@@ -0,0 +1 @@
export default null;
@@ -0,0 +1,44 @@
import {
defaultDocPreview,
editorField,
groups,
ingressField,
innholdFieldNew,
migratedField,
publishedAtField,
relevanteArtiklerField,
sanitySlug,
SEOFields,
titleField,
} from "@/lib";
const prefix = "artikkel/";
export default {
title: "Aksel Artikkel",
name: "aksel_artikkel",
type: "document",
groups,
...defaultDocPreview,
fields: [
publishedAtField,
editorField,
titleField,
sanitySlug(prefix, 2),
{
title: "Tema",
description: "Legg til de viktigeste temaene",
name: "tema",
type: "array",
of: [{ type: "reference", to: [{ type: "aksel_tema" }] }],
group: "innhold",
},
ingressField,
innholdFieldNew,
relevanteArtiklerField,
SEOFields,
migratedField,
],
};
@@ -0,0 +1,33 @@
import {
defaultDocPreview,
editorField,
groups,
ingressField,
innholdFieldNew,
migratedField,
publishedAtField,
sanitySlug,
SEOFields,
titleField,
} from "@/lib";
const prefix = "blogg/";
export default {
title: "Blogg",
name: "aksel_blogg",
type: "document",
groups,
...defaultDocPreview,
fields: [
publishedAtField,
editorField,
titleField,
sanitySlug(prefix, 2),
ingressField,
innholdFieldNew,
SEOFields,
migratedField,
],
};
@@ -0,0 +1,143 @@
import {
defaultDocPreview,
editorField,
groups,
ingressField,
innholdFieldPrinsipp,
isSlugUnique,
migratedField,
prinsipper,
publishedAtField,
SEOFields,
titleField,
} from "@/lib";
import sanityClient from "part:@sanity/base/client";
const client = sanityClient.withConfig({ apiVersion: "2020-06-19" });
const prefix = "prinsipper/";
export default {
title: "Aksel Prinsipp",
name: "aksel_prinsipp",
type: "document",
groups,
...defaultDocPreview,
fields: [
publishedAtField,
editorField,
titleField,
{
title: "url",
name: "slug",
type: "slug",
validation: (Rule) =>
Rule.required().custom((slug, { document }) => {
if (!slug || !slug.current)
return `Må ha noe innhold. Har du husket å velget et prinsipp først?`;
const maxLength = document?.prinsipp?.hovedside ? 2 : 3;
const newPrefix = `${prefix}${document?.prinsipp?.prinsippvalg}`;
if (!document?.prinsipp?.prinsippvalg)
return `Siden må ha valgt prinsipp for å sette opp url`;
if (document?.prinsipp?.hovedside && slug.current !== newPrefix) {
return `Siden er en hovedside for prinsippet og må derfor være på url ${newPrefix}`;
}
if (
!document?.prinsipp?.hovedside &&
!slug.current.startsWith(newPrefix)
) {
return `Slug må starte med: ${newPrefix}`;
}
if (
(slug.current.split("/") || []).filter((x) => !!x).length !==
maxLength
) {
return `Siden må være på ${maxLength} nivå`;
}
return true;
}),
group: "settings",
options: {
isUnique: isSlugUnique,
source: ({ prinsipp = null, heading = null }) => ({
prinsipp,
heading,
}),
slugify: ({ prinsipp, heading }) => {
if (!prinsipp || !heading) return "";
const rest = prinsipp.hovedside ? "" : `/${heading}`;
return `${prefix}${prinsipp.prinsippvalg}${rest}`
.toLowerCase()
.trim()
.slice(0, 80)
.trim()
.replace(/\s+/g, "-")
.replace("æ", "a")
.replace("ø", "o")
.replace("å", "a");
},
},
},
{
title: "Prinsipp",
description: "Velg prinsippet siden omhandler",
name: "prinsipp",
group: "innhold",
type: "object",
fields: [
{
title: "Velg prinsipp",
name: "prinsippvalg",
type: "string",
options: {
list: prinsipper,
layout: "radio",
},
validation: (Rule) =>
Rule.required().error("Prinsippsider må ha valgt et prinsipp"),
},
{
title: "Er denne siden hovedsiden til Prinsippet?",
name: "hovedside",
type: "boolean",
initialValue: false,
validation: (Rule) =>
Rule.required().custom(async (_, { document, parent }) => {
const id = document._id.replace(/^drafts\./, "");
const params = {
draft: `drafts.${id}`,
published: id,
hoved: parent?.hovedside,
prinsipp: parent?.prinsippvalg,
};
const query = `*[!(_id in [$draft, $published]) && _type == "aksel_prinsipp" && prinsipp.prinsippvalg == $prinsipp && prinsipp.hovedside == true][0]{heading,_id}`;
const res = await client.fetch(query, params);
if (parent?.hovedside && !!res?._id) {
return `Kan bare ha en hovedside for hvert prinsipp. Hovedsiden er nå: ${
res?.heading ?? "Siden har ikke en heading"
}`;
} else if (!parent?.hovedside && !res?._id) {
return `Hvert prinsipp må ha minst en hovedside. Velg en hovedside før du fortsetter`;
}
return true;
}),
},
],
},
{
title: "Hero bilde",
name: "hero_bilde",
type: "herobilde",
group: "innhold",
hidden: ({ parent }) => !parent.prinsipp.hovedside,
},
ingressField,
innholdFieldPrinsipp,
SEOFields,
migratedField,
],
};
@@ -0,0 +1,26 @@
import {
defaultDocPreview,
groups,
innholdFieldNew,
migratedField,
publishedAtField,
sanitySlug,
titleField,
} from "@/lib";
const prefix = "side/";
export default {
title: "Standalone-sider",
name: "aksel_standalone",
type: "document",
groups,
...defaultDocPreview,
fields: [
publishedAtField,
titleField,
sanitySlug(prefix, 2),
innholdFieldNew,
migratedField,
],
};
@@ -0,0 +1,5 @@
export { default as Artikkel } from "./articles/artikkel";
export { default as Blogg } from "./articles/blogg";
export { default as Tema } from "./tema";
export { default as Prinsipp } from "./articles/prinsipp";
export { default as Standalone } from "./articles/standalone";
@@ -0,0 +1,111 @@
import { groups } from "@/lib";
export default {
title: "Aksel Tema",
name: "aksel_tema",
type: "document",
groups,
fields: [
{
title: "Navn",
name: "title",
type: "string",
group: "innhold",
validation: (Rule) => Rule.required().error("Temaet må ha et navn"),
},
{
title: "Kort Intro/Oppsummering",
description: "Brukes i kort og innganger",
name: "oppsummering",
type: "string",
group: "innhold",
validation: (Rule) =>
Rule.required()
.max(65)
.error("Temaet burde ha en kort oppsummering/intro på max 65tegn"),
},
{
title: "Beskrivelse",
name: "beskrivelse",
type: "riktekst_enkel",
group: "innhold",
},
{
title: "Ansvarlig for tema",
description: "Legg til redaktør som har forvaltningsansvaret for temaet",
name: "ansvarlig",
type: "reference",
to: [{ type: "editor" }],
validation: (Rule) => Rule.required(),
group: "innhold",
},
{
title: "Bruk seksjonsinndeling",
description:
"Vil ikke vise artikler som ikke er lagt til i seksjoner hvis valgt!",
name: "bruk_seksjoner",
type: "boolean",
group: "innhold",
initialValue: false,
},
{
title: "Seksjonering",
description:
"Del inn artiklene i flere seksjoner (vises ikke i preview før publisering desverre)",
name: "seksjoner",
type: "array",
group: "innhold",
hidden: ({ parent }) => !parent?.bruk_seksjoner,
of: [
{
type: "object",
title: "Seksjon",
name: "seksjon",
fields: [
{
type: "string",
name: "title",
title: "Tittel",
validation: (Rule) =>
Rule.required().error("Seksjonen må ha et navn"),
},
{
title: "Beskrivelse",
name: "beskrivelse",
type: "riktekst_enkel",
},
{
name: "sider",
type: "array",
title: "Sider",
of: [
{
type: "reference",
name: "ref",
title: "Side",
validation: (Rule) =>
Rule.required().error("Seksjonen må ha minst en referanse"),
to: [{ type: "aksel_artikkel" }],
options: {
filter: ({ document, parent }) => {
const selected = parent
.filter((x) => !!x._ref)
.map((x) => x._ref);
return {
filter: "references($refId) && !(_id in $docIds)",
params: {
refId: document._id.replace("drafts.", ""),
docIds: selected,
},
};
},
},
},
],
},
],
},
],
},
],
};
@@ -0,0 +1,83 @@
import {
defaultDocPreview,
editorField,
groups,
hidePageFeedback,
innholdFieldNew,
innholdFieldNewNested,
migratedField,
publishedAtField,
sanitySlug,
titleField,
} from "@/lib";
const prefix = "designsystem/side/";
export default {
title: "Artikkel",
name: "ds_artikkel",
type: "document",
groups,
...defaultDocPreview,
fields: [
migratedField,
publishedAtField,
editorField,
titleField,
sanitySlug(prefix, 3),
{
title: "Layout",
description:
"'Full' bruker hele bredden, men fjerner table of content (bruk bare for eks ikonside eller komponentvisning)",
name: "layout",
type: "string",
group: "settings",
options: {
list: [
{ title: "Full", value: "full" },
{ title: "Default", value: "default" },
],
layout: "radio",
},
initialValue: "default",
},
{
type: "boolean",
name: "artikkel_type",
title: "Bruk Tabs",
group: "innhold",
initialValue: false,
},
{
...innholdFieldNew,
type: "riktekst_ds_artikkel",
hidden: ({ document }) => !!document?.artikkel_type,
},
{
name: "content_tabs",
title: "Innhold i Tabs",
type: "array",
group: "innhold",
hidden: ({ document }) => !document?.artikkel_type,
of: [
{
name: "tab",
title: "Tab",
type: "object",
fields: [
{
title: "Tittel",
description: "Innhold vil da legges under url/tab-tittel",
name: "title",
type: "string",
validation: (Rule) =>
Rule.required().error("Tabben må ha en enkel tittel"),
},
innholdFieldNewNested(),
],
},
],
},
hidePageFeedback,
],
};
@@ -0,0 +1,93 @@
import {
defaultDocPreview,
editorField,
groups,
innholdFieldNew,
migratedField,
publishedAtField,
sanitySlug,
titleField,
} from "@/lib";
const prefix = "designsystem/komponenter/";
export default {
title: "Komponentartikkel",
name: "komponent_artikkel",
type: "document",
groups,
...defaultDocPreview,
fields: [
publishedAtField,
editorField,
titleField,
sanitySlug(prefix, 3),
{
title: "Metadata",
name: "status",
group: "innhold",
type: "object",
fields: [
{
title: "Status",
name: "tag",
type: "string",
initialValue: "new",
options: {
list: [
{ title: "Beta", value: "beta" },
{ title: "New", value: "new" },
{ title: "Ready", value: "ready" },
{ title: "Deprecated", value: "deprecated" },
],
layout: "radio",
},
validation: (Rule) => Rule.required(),
},
{
name: "bilde",
title: "Thumbnail/og-bilde",
type: "image",
},
],
options: {
collapsible: true,
collapsed: true,
},
},
{
name: "intro",
type: "intro_komponent",
group: "innhold",
},
{
...innholdFieldNew,
type: "riktekst_komponent",
name: "bruk_tab",
title: "Innhold",
},
{
title: "Kodepakker",
name: "kodepakker",
type: "array",
of: [{ type: "string" }],
options: {
list: [
{ title: "ds-react", value: "ds-react" },
{ title: "ds-css", value: "ds-css" },
{ title: "ds-react-internal", value: "ds-react-internal" },
{ title: "ds-css-internal", value: "ds-css-internal" },
{ title: "ds-icons", value: "ds-icons" },
{ title: "ds-tokens", value: "ds-tokens" },
{ title: "ds-tailwind", value: "ds-tailwind" },
],
},
},
{
title: "Figma lenke (optional)",
name: "figma_link",
type: "url",
group: "lenker",
},
],
};
@@ -0,0 +1,93 @@
export default {
title: "Fargekategori",
name: "ds_color_categories",
type: "document",
fields: [
{
title: "Tittel",
name: "title",
type: "string",
validation: (Rule) => Rule.required(),
},
{
title: "Beskrivelse",
name: "description",
type: "riktekst_enkel",
},
{
type: "array",
name: "colors",
title: "Farger",
of: [{ type: "ds_color" }],
options: {
sortable: false,
},
},
],
};
export const Color = {
name: "ds_color",
title: "Farge",
type: "object",
fields: [
{
name: "title",
title: "Navn",
type: "string",
validation: (Rule) => Rule.required(),
readOnly: true,
},
{
name: "full_title",
title: "CSS variabelnavn",
type: "string",
validation: (Rule) => Rule.required(),
readOnly: true,
},
{
title: "Fargetype/nivå",
name: "color_type",
type: "string",
validation: (Rule) => Rule.required(),
initialValue: "global",
readOnly: true,
options: {
layout: "radio",
list: [
{ value: "global", title: "Global" },
{ value: "semantic", title: "Semantic" },
],
},
},
{
name: "color_name",
title: "Brukt fargenavn",
type: "string",
readOnly: true,
hidden: ({ parent }) => parent?.color_type === "global",
},
{
name: "color_roles",
title: "Fargeroller",
type: "array",
hidden: ({ parent }) => parent?.color_type === "global",
of: [{ type: "string", name: "role" }],
},
{
name: "color_value",
title: "Brukt farge",
type: "string",
readOnly: true,
validation: (Rule) => Rule.required(),
},
{
name: "color_index",
title: "Fargeindeks",
type: "number",
description:
"Brukes for å endre rekkefølgen på listen. Farger med indeks 0 vil være først, større tall sorteres under der igjen",
},
],
__experimental_omnisearch_visibility: false,
};
@@ -0,0 +1,57 @@
/* import { ExampleKeys } from "website/component-examples"; */
export default {
title: "Dir/filnavn for kode eksempler",
name: "kode_eksempler_fil",
type: "document",
fields: [
{
title: "Tittel",
name: "title",
type: "string",
validation: (Rule) => Rule.required(),
readOnly: true,
},
{
title: "Toggle om eksemplet er et dir eller filnavn",
name: "dir",
type: "boolean",
validation: (Rule) => Rule.required(),
initialValue: false,
readOnly: true,
},
{
title: "Filer",
name: "filer",
type: "array",
readOnly: true,
of: [
{
title: "Data i filer",
name: "fil",
type: "object",
fields: [
{ title: "Filnavn", name: "navn", type: "string" },
{ title: "Innhold", name: "innhold", type: "string" },
{ title: "Beskrivelse", name: "description", type: "text" },
],
},
],
},
],
preview: {
select: {
title: "title",
dir: "dir",
},
prepare(selection) {
const { title, dir } = selection;
return {
title: title,
subtitle: dir ? "Alle kode-eksempler" : "Spesifikt kode-eksempel",
media: () => "</>",
};
},
},
__experimental_omnisearch_visibility: false,
};
@@ -0,0 +1,88 @@
/* import { ExampleKeys } from "website/component-examples"; */
export default {
title: "Autogenerert Propdata",
name: "ds_props",
type: "document",
fields: [
{
title: "Tittel",
name: "title",
type: "string",
readOnly: true,
},
{
title: "Displayname",
name: "displayname",
type: "string",
readOnly: true,
},
{
title: "Filepath",
name: "filepath",
type: "string",
readOnly: true,
},
{
title: "props",
name: "proplist",
type: "array",
readOnly: true,
of: [
{
title: "Prop",
name: "prop",
type: "object",
fields: [
{
title: "Name",
name: "name",
type: "string",
},
{
title: "DefaultValue",
name: "defaultValue",
type: "string",
},
{
title: "Description",
name: "description",
type: "string",
},
{
title: "Required",
name: "required",
type: "boolean",
},
{
title: "Type",
name: "type",
type: "string",
},
{
title: "isRef",
name: "ref",
type: "boolean",
},
],
},
],
},
],
preview: {
select: {
title: "title",
filepath: "filepath",
id: "_id",
},
prepare(selection) {
const { title, filepath, id } = selection;
const str = id.includes("core") ? "ds-react" : "ds-internal";
return {
title,
subtitle: `${str}: ${filepath}`,
};
},
},
__experimental_omnisearch_visibility: false,
};
@@ -0,0 +1,45 @@
export default {
title: "Tokens",
name: "ds_tokens",
type: "document",
fields: [
{
title: "Navn",
name: "title",
type: "string",
validation: (Rule) => Rule.required(),
readOnly: true,
},
{
title: "Verdi",
name: "token",
type: "string",
validation: (Rule) => Rule.required(),
readOnly: true,
},
{
title: "Faktisk verdi",
name: "raw",
type: "string",
readOnly: true,
},
{
title: "Parent",
name: "parent",
type: "string",
readOnly: true,
},
{
title: "Beskrivelse",
name: "beskrivelse",
type: "string",
},
{
title: "Autogenerert",
name: "autogenerated",
type: "boolean",
readOnly: true,
},
],
__experimental_omnisearch_visibility: false,
};
@@ -0,0 +1,7 @@
import { KomponentArtikkel } from "./index";
export default {
...KomponentArtikkel,
title: "Komponentartikkel-template",
name: "ds_component_template",
};
@@ -0,0 +1,77 @@
export default {
title: "Forside",
name: "ds_frontpage",
type: "document",
fields: [
{
name: "body",
type: "riktekst_enkel",
title: "Innhold",
validation: (Rule) => Rule.required(),
},
{
name: "cards",
type: "array",
title: "Cards",
of: [
{
title: "Card",
name: "card",
type: "object",
fields: [
{
title: "Lenke",
name: "link_ref",
type: "reference",
to: [{ type: "komponent_artikkel" }, { type: "ds_artikkel" }],
validation: (Rule) => Rule.required(),
},
{
title: "Tittel",
name: "title",
type: "string",
},
{
title: "Innhold",
name: "content",
type: "string",
},
{
title: "Pictogram",
name: "picture",
type: "image",
options: {
hotspot: true,
},
fields: [
{
name: "title",
title: "Alt-tekst",
type: "string",
validation: (Rule) =>
Rule.required().error("Bilde må ha en alt-tekst"),
description: "Beskriv bildet for skjermlesere",
options: {
isHighlighted: true,
},
},
],
validation: (Rule) =>
Rule.required().error("Må legge til et pictogram"),
},
],
options: {
modal: {
type: "dialog",
width: "medium", // 'small' | 'medium' | 'large' | 'full'
},
},
},
],
validation: (Rule) => Rule.required(),
},
],
preview: {
prepare: () => ({ title: "Forside designsystemet" }),
},
};
@@ -0,0 +1,13 @@
export { default as KomponentArtikkel } from "./articles/komponent";
export { default as Artikkel } from "./articles/artikkel";
export { default as ComponentTemplate } from "./component-template";
export { default as Props } from "./automated/props";
export { default as Tokens } from "./automated/tokens";
export { default as KodeEksempler } from "./automated/kode-eksempler";
export {
Color,
default as ColorCategories,
} from "./automated/color-categories";
export { default as Frontpage } from "./frontpage";
export { default as Navigation, ds_header_heading } from "./navigation";
@@ -0,0 +1,129 @@
export default {
name: "ds_navigation",
title: "Navigation",
type: "document",
fields: [
{
title: "Designsystem navigajsons-struktur",
name: "title",
type: "string",
readOnly: true,
hidden: true,
initialValue: "Designsystem navigajsons-struktur",
},
{
name: "headings",
title: "Header linker",
type: "array",
of: [
{
type: "ds_navigation_heading",
name: "heading",
title: "Header link",
},
],
validation: (Rule) =>
Rule.required()
.min(1)
.error("Headingmenyen må ha minst en koblet lenke i sidemenyen"),
},
],
};
export const ds_header_heading = {
name: "ds_navigation_heading",
title: "Header link",
type: "object",
fields: [
{
name: "title",
title: "Heading tittel",
type: "string",
validation: (Rule) =>
Rule.required().error("Header lenken må ha en tittel"),
},
{
title: "Side selve headingen linker til",
description:
"Husk å legge denne til i menyen også, hvis ikke blir den bare tilgjengelig via headern",
name: "link_ref",
type: "reference",
to: [{ type: "komponent_artikkel" }, { type: "ds_artikkel" }],
validation: (Rule) =>
Rule.required().error("Header lenken må linke til en startside"),
},
{
title: "Meny for denne headingen",
name: "menu",
type: "array",
validation: (Rule) =>
Rule.required().error("Sidemeny må ha misnt en lenke"),
of: [
{
title: "Menypunkt",
name: "item",
type: "object",
fields: [
{
title: "Menypunkt tittel",
name: "title",
type: "string",
validation: (Rule) =>
Rule.required().error("Sidemeny-lenken må ha en tittel"),
},
{
title: "Link til side",
name: "link",
type: "reference",
to: [{ type: "komponent_artikkel" }, { type: "ds_artikkel" }],
options: {
modal: {
type: "dialog",
width: "medium", // 'small' | 'medium' | 'large' | 'full'
},
},
validation: (Rule) =>
Rule.required().error("Sidemeny-lenken må lenke til en side"),
},
],
},
{
title: "Subheading",
name: "subheading",
type: "object",
fields: [
{
title: "Subheading",
name: "title",
type: "string",
validation: (Rule) =>
Rule.required().error("Subheading må være fylt ut"),
},
],
preview: {
select: {
title: "title",
},
prepare(selection) {
const { title } = selection;
return {
title: title,
subtitle: "Subheading",
};
},
},
},
],
},
],
preview: {
select: {
title: "title",
},
prepare({ title }) {
return {
title: `${title}`,
};
},
},
};
@@ -0,0 +1,68 @@
import { People } from "@navikt/ds-icons";
import React from "react";
import profilePage from "../../components/profile/profile-page";
import { isEditorUnique } from "@/lib";
import userStore from "part:@sanity/base/user";
export default {
title: "Redaktører",
name: "editor",
type: "document",
fields: [
{
title: "Navn",
name: "title",
type: "string",
validation: (Rule) => Rule.required().error("Må legge til navn"),
},
{
title: "Sanity bruker-id",
name: "user_id",
type: "slug",
validation: (Rule) => Rule.required().error("Må ha Id"),
options: {
isUnique: isEditorUnique,
source: async () => {
const { id } = await userStore.getUser("me");
return id;
},
slugify: (input) => input,
},
},
{
title: "Roller",
description: "eks: Utvikler, Webanalytiker, uu-spesialist",
name: "roller",
type: "array",
of: [{ type: "string" }],
options: {
layout: "tags",
},
},
{
name: "profile_page",
type: "string",
title: "Profil",
inputComponent: profilePage,
hidden: ({ currentUser, parent }) => {
const { id, roles } = currentUser;
return (
!roles.find(({ name }) => name === "administrator") &&
parent?.user_id?.current !== id
);
},
},
],
preview: {
select: {
title: "title",
},
prepare(selection) {
const { title } = selection;
return {
title,
media: () => <People />,
};
},
},
};
@@ -0,0 +1,58 @@
import { groups, SEOFields } from "@/lib";
export default {
title: "Forside",
name: "vk_frontpage",
type: "document",
groups,
fields: [
{
title: "Tittel",
name: "title",
type: "string",
validation: (Rule) => Rule.required(),
},
{
title: "Beskrivelse",
name: "beskrivelse",
type: "riktekst_enkel",
validation: (Rule) => Rule.required(),
},
{
title: "Brukeropplevelse",
name: "prinsipp_1",
type: "object",
fields: [
{
title: "Vis på forside",
name: "vis",
type: "boolean",
validation: (Rule) => Rule.required(),
},
{
title: "Beskrivelse",
name: "beskrivelse",
type: "riktekst_enkel",
validation: (Rule) => Rule.required(),
},
{
title: "Hovedside",
name: "hovedside",
type: "reference",
weak: true,
to: [{ type: "aksel_prinsipp" }],
},
{
title: "Undersider",
description: "Rekkefølge bestemmer rekkefølgen på forsiden!",
name: "undersider",
type: "array",
of: [
{ type: "reference", weak: true, to: [{ type: "aksel_prinsipp" }] },
],
},
],
},
SEOFields,
],
};
Loaded 100 of 512 files, more files were not shown because too many files have changed in this diff. Show more