Răsfoiți Sursa

Merge branch 'master' into feat/g2g-nextjs

Taichi Masuyama 3 ani în urmă
părinte
comite
26ba1dcad9
100 a modificat fișierele cu 4164 adăugiri și 2395 ștergeri
  1. 0 2
      .github/workflows/ci-app.yml
  2. 0 1
      .github/workflows/ci-slackbot-proxy.yml
  3. 53 34
      .github/workflows/release-rc.yml
  4. 63 37
      .github/workflows/release.yml
  5. 56 0
      .github/workflows/reusable-app-build-image.yml
  6. 52 0
      .github/workflows/reusable-app-create-manifests.yml
  7. 5 0
      .gitignore
  8. 1 3
      .mergify.yml
  9. 14 1
      CHANGELOG.md
  10. 1 1
      lerna.json
  11. 1 1
      package.json
  12. 0 1
      packages/app/.env.development
  13. 5 5
      packages/app/docker/Dockerfile
  14. 1 1
      packages/app/docker/README.md
  15. 65 0
      packages/app/docker/codebuild/.terraform.lock.hcl
  16. 32 0
      packages/app/docker/codebuild/buildspec.yml
  17. 25 0
      packages/app/docker/codebuild/codebuild.tf
  18. 15 0
      packages/app/docker/codebuild/main.tf
  19. 26 0
      packages/app/docker/codebuild/oidc.tf
  20. 15 0
      packages/app/docker/codebuild/secretsmanager.tf
  21. 663 0
      packages/app/docker/codebuild/terraform.tfstate
  22. 0 6
      packages/app/docker/nocdn/.env.production.local
  23. 11 10
      packages/app/package.json
  24. 3 4
      packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx
  25. 1 1
      packages/app/src/components/Admin/UserGroupDetail/UpdateParentConfirmModal.tsx
  26. 33 1
      packages/app/src/components/Fab.module.scss
  27. 50 34
      packages/app/src/components/Fab.tsx
  28. 1 1
      packages/app/src/components/Layout/BasicLayout.tsx
  29. 2 1
      packages/app/src/components/Layout/RawLayout.tsx
  30. 1 1
      packages/app/src/components/Layout/ShareLinkLayout.tsx
  31. 3 3
      packages/app/src/components/Navbar/AppearanceModeDropdown.tsx
  32. 18 20
      packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx
  33. 0 9
      packages/app/src/components/Navbar/GrowiSubNavigation.module.scss
  34. 0 141
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx
  35. 9 0
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss
  36. 103 0
      packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx
  37. 1 1
      packages/app/src/components/Page.tsx
  38. 1 1
      packages/app/src/components/PageRenameModal.tsx
  39. 2 2
      packages/app/src/components/SearchPage/SearchControl.tsx
  40. 8 0
      packages/app/src/components/SearchPage/SearchResultContent.module.scss
  41. 3 1
      packages/app/src/components/SearchPage/SearchResultContent.tsx
  42. 20 20
      packages/app/src/components/StickyStretchableScroller.tsx
  43. 1 1
      packages/app/src/interfaces/customize.ts
  44. 45 5
      packages/app/src/pages/[[...path]].page.tsx
  45. 5 2
      packages/app/src/pages/_app.page.tsx
  46. 6 51
      packages/app/src/pages/_document.page.tsx
  47. 22 5
      packages/app/src/pages/share/[[...path]].page.tsx
  48. 6 2
      packages/app/src/pages/utils/commons.ts
  49. 2 1
      packages/app/src/server/crowi/index.js
  50. 1 1
      packages/app/src/server/models/config.ts
  51. 1 0
      packages/app/src/server/routes/apiv3/customize-setting.js
  52. 0 6
      packages/app/src/server/service/config-loader.ts
  53. 41 1
      packages/app/src/server/service/customize.ts
  54. 18 7
      packages/app/src/server/service/plugin.ts
  55. 7 3
      packages/app/src/stores/admin/customize.tsx
  56. 5 1
      packages/app/src/stores/context.tsx
  57. 19 26
      packages/app/src/stores/page.tsx
  58. 2 2
      packages/app/src/stores/ui.tsx
  59. 14 19
      packages/app/src/stores/use-next-themes.tsx
  60. 0 32
      packages/app/src/styles/_layout.scss
  61. 0 1
      packages/app/src/styles/bootstrap/_variables.scss
  62. 469 429
      packages/app/src/styles/theme/_apply-colors-dark.scss
  63. 376 342
      packages/app/src/styles/theme/_apply-colors-light.scss
  64. 121 124
      packages/app/src/styles/theme/_apply-colors.scss
  65. 32 0
      packages/app/src/styles/theme/_hsl-functions.scss
  66. 112 0
      packages/app/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss
  67. 1 1
      packages/app/src/styles/theme/_reboot-bootstrap-border-colors.scss
  68. 7 6
      packages/app/src/styles/theme/_reboot-bootstrap-buttons.scss
  69. 8 8
      packages/app/src/styles/theme/_reboot-bootstrap-colors.scss
  70. 11 11
      packages/app/src/styles/theme/_reboot-bootstrap-dropdown.scss
  71. 9 9
      packages/app/src/styles/theme/_reboot-bootstrap-tables.scss
  72. 14 5
      packages/app/src/styles/theme/_reboot-bootstrap-theme-colors.scss
  73. 11 4
      packages/app/src/styles/theme/_reboot-toastr-colors.scss
  74. 21 0
      packages/app/src/styles/theme/mixins/_hsl-badge.scss
  75. 142 0
      packages/app/src/styles/theme/mixins/_hsl-button.scss
  76. 1 1
      packages/app/src/styles/theme/mixins/_list-group.scss
  77. 0 34
      packages/app/src/styles/theme/mixins/_tables.scss
  78. 6 22
      packages/app/src/utils/axios.ts
  79. 2 2
      packages/app/test/cypress/integration/20-basic-features/20-basic-features--access-to-page.spec.ts
  80. 2 2
      packages/app/test/cypress/integration/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.spec.ts
  81. 2 2
      packages/app/test/cypress/integration/60-home/60-home--home.spec.ts
  82. 1 1
      packages/codemirror-textlint/package.json
  83. 1 1
      packages/core/package.json
  84. 2 0
      packages/core/src/index.ts
  85. 5 0
      packages/core/src/interfaces/color-scheme.ts
  86. 3 2
      packages/core/src/interfaces/growi-theme-metadata.ts
  87. 8 0
      packages/core/src/utils/growi-theme-metadata.ts
  88. 1 1
      packages/hackmd/package.json
  89. 1 1
      packages/preset-themes/package.json
  90. 89 66
      packages/preset-themes/src/styles/antarctic.scss
  91. 92 64
      packages/preset-themes/src/styles/blackboard.scss
  92. 0 1
      packages/preset-themes/src/styles/bootstrap/_variables.scss
  93. 94 77
      packages/preset-themes/src/styles/christmas.scss
  94. 172 127
      packages/preset-themes/src/styles/default.scss
  95. 180 112
      packages/preset-themes/src/styles/fire-red.scss
  96. 92 58
      packages/preset-themes/src/styles/future.scss
  97. 76 63
      packages/preset-themes/src/styles/halloween.scss
  98. 188 137
      packages/preset-themes/src/styles/hufflepuff.scss
  99. 91 60
      packages/preset-themes/src/styles/island.scss
  100. 163 112
      packages/preset-themes/src/styles/jade-green.scss

+ 0 - 2
.github/workflows/ci-app.yml

@@ -5,8 +5,6 @@ on:
     branches-ignore:
       - release/**
       - rc/**
-      - chore/**
-      - support/prepare-v**
     paths:
       - .github/workflows/ci-app.yml
       - .eslint*

+ 0 - 1
.github/workflows/ci-slackbot-proxy.yml

@@ -5,7 +5,6 @@ on:
     branches-ignore:
       - release/**
       - rc/**
-      - chore/**
       - support/prepare-v**
     paths:
       - .github/workflows/ci-slackbot-proxy.yml

+ 53 - 34
.github/workflows/release-rc.yml

@@ -5,60 +5,79 @@ on:
     branches:
       - rc/**
 
-jobs:
 
-  build-rc:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+jobs:
 
+  determine-tags:
     runs-on: ubuntu-latest
 
-    strategy:
-      matrix:
-        platform: [linux/amd64, linux/arm64]
+    outputs:
+      TAGS: ${{ steps.meta.outputs.tags }}
+      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
 
     steps:
     - uses: actions/checkout@v3
-      with:
-        lfs: true
 
     - name: Retrieve information from package.json
       uses: myrotvorets/info-from-package-json-action@1.2.0
       id: package-json
 
-    - name: Docker meta
+    - name: Docker meta for docker.io
+      uses: docker/metadata-action@v4
       id: meta
+      with:
+        images: docker.io/weseek/growi
+        sep-tags: ','
+        tags: |
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}
+          type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
+
+    - name: Docker meta for ghcr.io
       uses: docker/metadata-action@v4
+      id: meta-ghcr
       with:
-        images: weseek/growi,ghcr.io/weseek/growi
+        images: ghcr.io/weseek/growi
+        sep-tags: ','
         tags: |
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}
           type=raw,value=${{ steps.package-json.outputs.packageVersion }}.{{sha}}
 
-    - name: Login to docker.io registry
-      run: |
-        echo ${{ secrets. DOCKER_REGISTRY_PASSWORD }} | docker login --username wsmoogle --password-stdin
 
-    - name: Login to GitHub Container Registry
-      uses: docker/login-action@v2
-      with:
-        registry: ghcr.io
-        username: wsmoogle
-        password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+  build-image-rc:
+    uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
+    with:
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
 
-    - name: Set up QEMU
-      if: ${{ matrix.platform == 'linux/arm64' }}
-      uses: docker/setup-qemu-action@v1
 
-    - name: Set up Docker Buildx
-      uses: docker/setup-buildx-action@v2
+  publish-image-rc:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS }}
+      registry: docker.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+
+  publish-image-rc-ghcr:
+    needs: [determine-tags, build-image-rc]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
+      registry: ghcr.io
+      image-name: weseek/growi
+      tag-temporary: latest-rc
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
 
-    - name: Build and push
-      uses: docker/build-push-action@v2
-      with:
-        context: .
-        file: ./packages/app/docker/Dockerfile
-        platforms: ${{ matrix.platform }}
-        push: true
-        builder: ${{ steps.buildx.outputs.name }}
-        cache-from: type=gha
-        cache-to: type=gha,mode=max
-        tags: ${{ steps.meta.outputs.tags }}

+ 63 - 37
.github/workflows/release.yml

@@ -121,63 +121,90 @@ jobs:
         github_token: ${{ secrets.GITHUB_TOKEN }}
 
 
-  build-image:
+  determine-tags:
     needs: create-github-release
-
     runs-on: ubuntu-latest
 
-    strategy:
-      matrix:
-        platform: [linux/amd64, linux/arm64]
+    outputs:
+      TAGS: ${{ steps.meta.outputs.tags }}
+      TAGS_GHCR: ${{ steps.meta-ghcr.outputs.tags }}
 
     steps:
     - uses: actions/checkout@v3
-      with:
-        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
-        lfs: true
 
-    - name: Docker meta
-      id: meta
+    - name: Retrieve information from package.json
+      uses: myrotvorets/info-from-package-json-action@1.2.0
+      id: package-json
+
+    - name: Docker meta for docker.io
       uses: docker/metadata-action@v4
+      id: meta
       with:
-        images: weseek/growi,ghcr.io/weseek/growi
+        images: docker.io/weseek/growi
+        sep-tags: ','
         tags: |
           type=raw,value=latest
           type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}
           type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}
           type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
 
-    - name: Login to Docker Hub
-      uses: docker/login-action@v2
+    - name: Docker meta for ghcr.io
+      uses: docker/metadata-action@v4
+      id: meta-ghcr
       with:
-        username: wsmoogle
-        password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+        images: ghcr.io/weseek/growi
+        sep-tags: ','
+        tags: |
+          type=raw,value=latest
+          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}
+          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}
+          type=semver,value=${{ needs.create-github-release.outputs.RELEASED_VERSION }},pattern={{major}}.{{minor}}.{{patch}}
 
-    - name: Login to GitHub Container Registry
-      uses: docker/login-action@v2
-      with:
-        registry: ghcr.io
-        username: wsmoogle
-        password: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
 
-    - name: Set up QEMU
-      if: ${{ matrix.platform == 'linux/arm64' }}
-      uses: docker/setup-qemu-action@v1
+  build-image:
+    needs: create-github-release
 
-    - name: Set up Docker Buildx
-      uses: docker/setup-buildx-action@v2
+    uses: weseek/growi/.github/workflows/reusable-app-build-image.yml@master
+    with:
+      source-version: refs/tags/v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
+      image-name: weseek/growi
+      tag-temporary: latest
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+
+
+  publish-image:
+    needs: [determine-tags, build-image]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+      tags: ${{ needs.determine-tags.outputs.TAGS }}
+      registry: docker.io
+      image-name: weseek/growi
+      tag-temporary: latest
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+
+  publish-image-ghcr:
+    needs: [determine-tags, build-image]
+
+    uses: weseek/growi/.github/workflows/reusable-app-create-manifests.yml@master
+    with:
+      tags: ${{ needs.determine-tags.outputs.TAGS_GHCR }}
+      registry: ghcr.io
+      image-name: weseek/growi
+      tag-temporary: latest
+    secrets:
+      DOCKER_REGISTRY_PASSWORD: ${{ secrets.DOCKER_REGISTRY_ON_GITHUB_PASSWORD }}
+
+
+  post-publish:
+    needs: [create-github-release, publish-image, publish-image-ghcr]
+    runs-on: ubuntu-latest
 
-    - name: Build and push
-      uses: docker/build-push-action@v3
+    steps:
+    - uses: actions/checkout@v3
       with:
-        context: .
-        file: ./packages/app/docker/Dockerfile
-        platforms: ${{ matrix.platform }}
-        push: true
-        builder: ${{ steps.buildx.outputs.name }}
-        cache-from: type=gha
-        cache-to: type=gha,mode=max
-        tags: ${{ steps.meta.outputs.tags }}
+        ref: v${{ needs.create-github-release.outputs.RELEASED_VERSION }}
 
     - name: Update Docker Hub Description
       uses: peter-evans/dockerhub-description@v3
@@ -193,7 +220,6 @@ jobs:
         channel: '#release'
         url: ${{ secrets.SLACK_WEBHOOK_URL }}
         created_tag: 'v${{ needs.create-github-release.outputs.RELEASED_VERSION }}'
-        message: '*Release v${{ needs.create-github-release.outputs.RELEASED_VERSION }} (${{ matrix.platform }})* Succeeded'
 
     - name: Check whether workspace is clean
       run: |

+ 56 - 0
.github/workflows/reusable-app-build-image.yml

@@ -0,0 +1,56 @@
+name: Reusable build app container image workflow
+
+on:
+  workflow_call:
+    inputs:
+      source-version:
+        type: string
+        default: ${{ github.sha }}
+      image-name:
+        type: string
+        default: weseek/growi
+      tag-temporary:
+        type: string
+        default: latest
+    secrets:
+      AWS_ROLE_TO_ASSUME_FOR_OIDC:
+        required: true
+
+
+
+jobs:
+
+  build-image:
+    runs-on: ubuntu-latest
+
+    # These permissions are needed to interact with GitHub's OIDC Token endpoint.
+    permissions:
+      id-token: write
+      contents: write
+
+    strategy:
+      matrix:
+        platform: [amd64, arm64]
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Configure AWS Credentials
+      uses: aws-actions/configure-aws-credentials@v1
+      with:
+        aws-region: ap-northeast-1
+        role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME_FOR_OIDC }}
+        role-session-name: SessionForReleaseGROWI-RC
+
+    - name: Run CodeBuild
+      uses: dark-mechanicum/aws-codebuild@v1
+      with:
+        projectName: growi-official-image-builder
+      env:
+        CODEBUILD__sourceVersion: ${{ inputs.source-version }}
+        CODEBUILD__imageOverride: ${{ (matrix.platform == 'amd64' && 'aws/codebuild/standard:6.0') || 'aws/codebuild/amazonlinux2-aarch64-standard:2.0' }}
+        CODEBUILD__environmentTypeOverride: ${{ (matrix.platform == 'amd64' && 'LINUX_CONTAINER') || 'ARM_CONTAINER' }}
+        CODEBUILD__environmentVariablesOverride: '[
+          { "name": "IMAGE_TAG", "type": "PLAINTEXT", "value": "docker.io/${{ inputs.image-name }}:${{ inputs.tag-temporary }}-${{ matrix.platform }}" },
+          { "name": "IMAGE_TAG_GHCR", "type": "PLAINTEXT", "value": "ghcr.io/${{ inputs.image-name }}:${{ inputs.tag-temporary }}-${{ matrix.platform }}" }
+        ]'

+ 52 - 0
.github/workflows/reusable-app-create-manifests.yml

@@ -0,0 +1,52 @@
+name: Reusable create app container image manifests workflow
+
+on:
+  workflow_call:
+    inputs:
+      tags:
+        type: string
+        required: true
+      registry:
+        type: string
+        default: 'docker.io'
+      image-name:
+        type: string
+        default: weseek/growi
+      tag-temporary:
+        type: string
+        default: latest
+    secrets:
+      DOCKER_REGISTRY_PASSWORD:
+        required: true
+
+
+
+jobs:
+
+  create-manifest:
+    runs-on: ubuntu-latest
+
+    steps:
+    - name: Docker meta for extra-images
+      id: meta-extra-images
+      uses: docker/metadata-action@v4
+      with:
+        images: ${{ inputs.registry }}/${{ inputs.image-name }}
+        sep-tags: ','
+        tags: |
+          type=raw,value=${{ inputs.tag-temporary }}-amd64
+          type=raw,value=${{ inputs.tag-temporary }}-arm64
+
+    - name: Login to Container Registry
+      uses: docker/login-action@v2
+      with:
+        registry: ${{ inputs.registry }}
+        username: wsmoogle
+        password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
+
+    - name: Create and push manifest images
+      uses: Noelware/docker-manifest-action@master
+      with:
+        base-image: ${{ inputs.tags }}
+        extra-images: ${{ steps.meta-extra-images.outputs.tags }}
+        push: true

+ 5 - 0
.gitignore

@@ -29,6 +29,11 @@ yarn-error.log*
 # typescript
 *.tsbuildinfo
 
+# Terraform
+**/.terraform/*
+# *.tfstate
+*.tfstate.*
+
 # IDE, dev #
 .idea
 *.orig

+ 1 - 3
.mergify.yml

@@ -16,9 +16,7 @@ pull_request_rules:
     conditions:
       - author = github-actions[bot]
       - '#approved-reviews-by >= 1'
-      - check-skipped = "test (16.x)"
-      - check-skipped = "test-prod-node14 / launch-prod"
-      - check-skipped = "test-prod-node16 / launch-prod"
+      - label = "prepare next version"
     actions:
       merge:
         method: merge

+ 14 - 1
CHANGELOG.md

@@ -1,9 +1,22 @@
 # Changelog
 
-## [Unreleased](https://github.com/weseek/growi/compare/v6.0.1...HEAD)
+## [Unreleased](https://github.com/weseek/growi/compare/v6.0.2...HEAD)
 
 *Please do not manually update this file. We've automated the process.*
 
+## [v6.0.2](https://github.com/weseek/growi/compare/v6.0.1...v6.0.2) - 2023-01-10
+
+### 🐛 Bug Fixes
+
+- fix: Attaching page title as initial header section (#7228) @yukendev
+- fix: Update PageTree data after saving page (#7227) @yukendev
+- fix: Lsx "filter" and "except" options does not work (#7226) @yuki-takei
+- fix: Omit remark-growi-directive shortcuts (#7225) @yuki-takei
+
+### 🧰 Maintenance
+
+- ci(deps-dev): bump textlint-rule-no-doubled-joshi from 4.0.0 to 4.0.1 (#7222) @dependabot
+
 ## [v6.0.1](https://github.com/weseek/growi/compare/v6.0.0...v6.0.1) - 2023-01-07
 
 ### 🚀 Improvement

+ 1 - 1
lerna.json

@@ -1,7 +1,7 @@
 {
   "npmClient": "yarn",
   "useWorkspaces": true,
-  "version": "6.0.2-RC.0",
+  "version": "6.0.3-RC.0",
   "packages": [
     "packages/*"
   ]

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "growi",
-  "version": "6.0.2-RC.0",
+  "version": "6.0.3-RC.0",
   "description": "Team collaboration software using markdown",
   "tags": [
     "wiki",

+ 0 - 1
packages/app/.env.development

@@ -7,7 +7,6 @@ MIGRATIONS_DIR=src/migrations/
 APP_SITE_URL=http://localhost:3000
 FILE_UPLOAD=mongodb
 # MONGO_GRIDFS_TOTAL_LIMIT=10485760
-# NO_CDN=true
 MONGO_URI="mongodb://mongo:27017/growi"
 # REDIS_URI="http://redis:6379"
 # NCHAN_URI="http://nchan"

+ 5 - 5
packages/app/docker/Dockerfile

@@ -95,16 +95,16 @@ WORKDIR ${optDir}
 # ignore eslint and stylelint
 COPY ["package.json", "lerna.json", "tsconfig.base.json", "./"]
 # copy all related packages
-COPY packages/app packages/app
-COPY packages/core packages/core
 COPY packages/codemirror-textlint packages/codemirror-textlint
+COPY packages/preset-themes packages/preset-themes
 COPY packages/slack packages/slack
-COPY packages/ui packages/ui
+COPY packages/hackmd packages/hackmd
 COPY packages/remark-drawio packages/remark-drawio
 COPY packages/remark-growi-directive packages/remark-growi-directive
 COPY packages/remark-lsx packages/remark-lsx
-COPY packages/hackmd packages/hackmd
-COPY packages/preset-themes packages/preset-themes
+COPY packages/ui packages/ui
+COPY packages/core packages/core
+COPY packages/app packages/app
 
 # build
 RUN yarn lerna run build

+ 1 - 1
packages/app/docker/README.md

@@ -10,7 +10,7 @@ GROWI Official docker image
 Supported tags and respective Dockerfile links
 ------------------------------------------------
 
-* [`6.0.1`, `6.0`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.1/packages/app/docker/Dockerfile)
+* [`6.0.2`, `6.0`, `6`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v6.0.2/packages/app/docker/Dockerfile)
 * [`5.1.7`, `5.1`, `5`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
 * [`5.1.7-nocdn`, `5.1-nocdn`, `5-nocdn`](https://github.com/weseek/growi/blob/v5.1.7/packages/app/docker/Dockerfile)
 * [`4.5.23`, `4.5`, `4`, `latest` (Dockerfile)](https://github.com/weseek/growi/blob/v4.5.23/packages/app/docker/Dockerfile)

+ 65 - 0
packages/app/docker/codebuild/.terraform.lock.hcl

@@ -0,0 +1,65 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+  version     = "4.49.0"
+  constraints = "~> 4.16"
+  hashes = [
+    "h1:oOwWQpvQWd1uVP1axBz/TO6xzzLWoL982AY/MQfeF7I=",
+    "zh:09803937f00fdf2873eccf685eec7854408925cbf183c9b683df7c5825be463f",
+    "zh:2af1575e538fb0b669266f8d1385b17bfdaf17c521b6b6329baa1f2971fc4a4d",
+    "zh:3f71882b438cde3108fe68cfe2637839d3eed08157a9721bd97babf3912247a8",
+    "zh:577af1b38f5da8a9f29eebe5eebec9279d26e757cd03b0c8c59311f9ce8a859b",
+    "zh:60160d39094973beefb9b10cfd6aaa5b63a2e68c32445ecffcd1b101356e6f9b",
+    "zh:762656454722548baeccf35cbaa23b887976337e1ed321682df7390419fdf22d",
+    "zh:7f6d7887821659bf3bef815949077dc91ffcdb0d911644a887b6683b264a5ca6",
+    "zh:8f16a352cc903f8951fa4619c36233b3e66e27d724817b131f2035dd8896f524",
+    "zh:8f768f65e370366c8b91c00d01c9a6264fe26ea9ae1819f14bdcd12c066272bc",
+    "zh:95ad78c689a83c08ef7c3e544c3c9aca93ed528054aa77cc968ddd9efa3a1023",
+    "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+    "zh:a47097ab6a4ca8302da82964303ffdd2310ed65e8f8524bfe4058816cf1addb7",
+    "zh:b66d820c70cd5fd628ffe882d2b97e76b969dca4e6827ac2ba0f8d3bc5d6e9c6",
+    "zh:b80f713a4f3e1355c3dd1600e9d08b9f15ed2370054ec792ad2c01f2541abe02",
+    "zh:ce065bc3962cb71fa7652562226b9d486f3d7fcb88285c1020ebe2f550e28641",
+  ]
+}
+
+provider "registry.terraform.io/hashicorp/random" {
+  version     = "3.4.3"
+  constraints = ">= 2.1.0"
+  hashes = [
+    "h1:xZGZf18JjMS06pFa4NErzANI98qi59SEcBsOcS2P2yQ=",
+    "zh:41c53ba47085d8261590990f8633c8906696fa0a3c4b384ff6a7ecbf84339752",
+    "zh:59d98081c4475f2ad77d881c4412c5129c56214892f490adf11c7e7a5a47de9b",
+    "zh:686ad1ee40b812b9e016317e7f34c0d63ef837e084dea4a1f578f64a6314ad53",
+    "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
+    "zh:84103eae7251384c0d995f5a257c72b0096605048f757b749b7b62107a5dccb3",
+    "zh:8ee974b110adb78c7cd18aae82b2729e5124d8f115d484215fd5199451053de5",
+    "zh:9dd4561e3c847e45de603f17fa0c01ae14cae8c4b7b4e6423c9ef3904b308dda",
+    "zh:bb07bb3c2c0296beba0beec629ebc6474c70732387477a65966483b5efabdbc6",
+    "zh:e891339e96c9e5a888727b45b2e1bb3fcbdfe0fd7c5b4396e4695459b38c8cb1",
+    "zh:ea4739860c24dfeaac6c100b2a2e357106a89d18751f7693f3c31ecf6a996f8d",
+    "zh:f0c76ac303fd0ab59146c39bc121c5d7d86f878e9a69294e29444d4c653786f8",
+    "zh:f143a9a5af42b38fed328a161279906759ff39ac428ebcfe55606e05e1518b93",
+  ]
+}
+
+provider "registry.terraform.io/hashicorp/tls" {
+  version     = "4.0.4"
+  constraints = ">= 3.0.0"
+  hashes = [
+    "h1:pe9vq86dZZKCm+8k1RhzARwENslF3SXb9ErHbQfgjXU=",
+    "zh:23671ed83e1fcf79745534841e10291bbf34046b27d6e68a5d0aab77206f4a55",
+    "zh:45292421211ffd9e8e3eb3655677700e3c5047f71d8f7650d2ce30242335f848",
+    "zh:59fedb519f4433c0fdb1d58b27c210b27415fddd0cd73c5312530b4309c088be",
+    "zh:5a8eec2409a9ff7cd0758a9d818c74bcba92a240e6c5e54b99df68fff312bbd5",
+    "zh:5e6a4b39f3171f53292ab88058a59e64825f2b842760a4869e64dc1dc093d1fe",
+    "zh:810547d0bf9311d21c81cc306126d3547e7bd3f194fc295836acf164b9f8424e",
+    "zh:824a5f3617624243bed0259d7dd37d76017097dc3193dac669be342b90b2ab48",
+    "zh:9361ccc7048be5dcbc2fafe2d8216939765b3160bd52734f7a9fd917a39ecbd8",
+    "zh:aa02ea625aaf672e649296bce7580f62d724268189fe9ad7c1b36bb0fa12fa60",
+    "zh:c71b4cd40d6ec7815dfeefd57d88bc592c0c42f5e5858dcc88245d371b4b8b1e",
+    "zh:dabcd52f36b43d250a3d71ad7abfa07b5622c69068d989e60b79b2bb4f220316",
+    "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
+  ]
+}

+ 32 - 0
packages/app/docker/codebuild/buildspec.yml

@@ -0,0 +1,32 @@
+version: 0.2
+
+env:
+  variables:
+    DOCKER_BUILDKIT: 1
+    IMAGE_TAG: ''
+    IMAGE_TAG_GHCR: ''
+  secrets-manager:
+    DOCKER_REGISTRY_PASSWORD: growi/official-image-builder:DOCKER_REGISTRY_PASSWORD
+    DOCKER_REGISTRY_ON_GITHUB_PAT: growi/official-image-builder:DOCKER_REGISTRY_ON_GITHUB_PAT
+
+phases:
+  pre_build:
+    commands:
+      # login to docker.io
+      - echo ${DOCKER_REGISTRY_PASSWORD} | docker login --username wsmoogle --password-stdin
+      # login to ghcr.io
+      - echo ${DOCKER_REGISTRY_ON_GITHUB_PAT} | docker login ghcr.io --username wsmoogle --password-stdin
+  build:
+    commands:
+      - docker build -t ${IMAGE_TAG} -f ./packages/app/docker/Dockerfile .
+      - docker tag ${IMAGE_TAG} ${IMAGE_TAG_GHCR}
+
+  post_build:
+    commands:
+      - docker push $IMAGE_TAG
+      - docker push $IMAGE_TAG_GHCR
+
+cache:
+  paths:
+    - node_modules/**/*
+    - packages/*/node_modules/**/*

+ 25 - 0
packages/app/docker/codebuild/codebuild.tf

@@ -0,0 +1,25 @@
+module "codebuild" {
+  source = "cloudposse/codebuild/aws"
+
+  name                = "growi-official-image-builder"
+  description         = "The CodeBuild Project for GROWI official docker image"
+
+  artifact_type       = "NO_ARTIFACTS"
+
+  source_type         = "GITHUB"
+  source_location     = "https://github.com/weseek/growi.git"
+  source_version      = "refs/heads/master"
+  git_clone_depth     = 1
+
+  buildspec           = "packages/app/docker/codebuild/buildspec.yml"
+
+  # https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-available.html
+  build_image         = "aws/codebuild/amazonlinux2-x86_64-standard:3.0"
+  build_compute_type  = "BUILD_GENERAL1_LARGE"
+
+  privileged_mode     = true
+
+  cache_type          = "LOCAL"
+  local_cache_modes   = ["LOCAL_DOCKER_LAYER_CACHE", "LOCAL_CUSTOM_CACHE"]
+
+}

+ 15 - 0
packages/app/docker/codebuild/main.tf

@@ -0,0 +1,15 @@
+terraform {
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "~> 4.16"
+    }
+  }
+
+  required_version = ">= 1.2.0"
+}
+
+provider "aws" {
+  profile = "weseek"
+  region  = "ap-northeast-1"
+}

+ 26 - 0
packages/app/docker/codebuild/oidc.tf

@@ -0,0 +1,26 @@
+module "oidc_github" {
+  source  = "unfunco/oidc-github/aws"
+
+  iam_role_name = "GitHubOIDC-for-growi"
+  iam_role_inline_policies = {
+    "inline_policy" : data.aws_iam_policy_document.policy_document.json
+  }
+
+  github_repositories = [
+    "weseek/growi",
+  ]
+}
+
+data "aws_iam_policy_document" "policy_document" {
+  statement {
+    actions   = [
+      "codebuild:StartBuild",
+      "codebuild:StopBuild",
+      "codebuild:RetryBuild",
+      "codebuild:BatchGetBuilds"
+    ]
+    resources = [
+      module.codebuild.project_arn
+    ]
+  }
+}

+ 15 - 0
packages/app/docker/codebuild/secretsmanager.tf

@@ -0,0 +1,15 @@
+resource "aws_secretsmanager_secret" "secret" {
+  name = "growi/official-image-builder"
+}
+
+resource "aws_secretsmanager_secret_version" "main" {
+  secret_id     = aws_secretsmanager_secret.secret.id
+  secret_string = jsonencode({
+    KEY1 = "CHANGE THIS"
+    KEY2 = "CHANGE THIS"
+  })
+
+  lifecycle {
+    ignore_changes = [secret_string, version_stages]
+  }
+}

Fișier diff suprimat deoarece este prea mare
+ 663 - 0
packages/app/docker/codebuild/terraform.tfstate


+ 0 - 6
packages/app/docker/nocdn/.env.production.local

@@ -1,6 +0,0 @@
-
-##
-## Handled by Next.js with dotenv or dotenv-flow
-## https://nextjs.org/docs/basic-features/environment-variables
-##
-NO_CDN=true

+ 11 - 10
packages/app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/app",
-  "version": "6.0.2-RC.0",
+  "version": "6.0.3-RC.0",
   "license": "MIT",
   "scripts": {
     "//// for production": "",
@@ -66,14 +66,14 @@
     "@elastic/elasticsearch7": "npm:@elastic/elasticsearch@^7.17.0",
     "@godaddy/terminus": "^4.9.0",
     "@google-cloud/storage": "^5.8.5",
-    "@growi/codemirror-textlint": "^6.0.2-RC.0",
-    "@growi/core": "^6.0.2-RC.0",
-    "@growi/hackmd": "^6.0.2-RC.0",
-    "@growi/preset-themes": "^6.0.2-RC.0",
-    "@growi/remark-drawio": "^6.0.2-RC.0",
-    "@growi/remark-growi-directive": "^6.0.2-RC.0",
-    "@growi/remark-lsx": "^6.0.2-RC.0",
-    "@growi/slack": "^6.0.2-RC.0",
+    "@growi/codemirror-textlint": "^6.0.3-RC.0",
+    "@growi/core": "^6.0.3-RC.0",
+    "@growi/hackmd": "^6.0.3-RC.0",
+    "@growi/preset-themes": "^6.0.3-RC.0",
+    "@growi/remark-drawio": "^6.0.3-RC.0",
+    "@growi/remark-growi-directive": "^6.0.3-RC.0",
+    "@growi/remark-lsx": "^6.0.3-RC.0",
+    "@growi/slack": "^6.0.3-RC.0",
     "@promster/express": "^7.0.6",
     "@promster/server": "^7.0.8",
     "@slack/web-api": "^6.2.4",
@@ -96,6 +96,7 @@
     "cookie-parser": "^1.4.5",
     "csurf": "^1.11.0",
     "csv-to-markdown-table": "^1.1.0",
+    "dayjs": "^1.11.7",
     "date-fns": "^2.23.0",
     "detect-indent": "^7.0.0",
     "diff": "^5.0.0",
@@ -201,7 +202,7 @@
     "handsontable": "v7.0.0 or above is no loger MIT lisence."
   },
   "devDependencies": {
-    "@growi/ui": "^6.0.2-RC.0",
+    "@growi/ui": "^6.0.3-RC.0",
     "@handsontable/react": "=2.1.0",
     "@icon/themify-icons": "1.0.1-alpha.3",
     "@next/bundle-analyzer": "^12.2.3",

+ 3 - 4
packages/app/src/components/Admin/Customize/CustomizeThemeSetting.tsx

@@ -3,7 +3,6 @@ import React, { useCallback, useEffect, useState } from 'react';
 import { PresetThemes, PresetThemesMetadatas } from '@growi/preset-themes';
 import { useTranslation } from 'next-i18next';
 
-import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError, toastWarning } from '~/client/util/toastr';
 import { useSWRxGrowiThemeSetting } from '~/stores/admin/customize';
 
@@ -20,7 +19,7 @@ type Props = {
 const CustomizeThemeSetting = (props: Props): JSX.Element => {
   const { t } = useTranslation();
 
-  const { data, error } = useSWRxGrowiThemeSetting();
+  const { data, error, update } = useSWRxGrowiThemeSetting();
   const [currentTheme, setCurrentTheme] = useState(data?.currentTheme);
 
   useEffect(() => {
@@ -38,7 +37,7 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
     }
 
     try {
-      await apiv3Put('/customize-setting/theme', {
+      await update({
         theme: currentTheme,
       });
 
@@ -47,7 +46,7 @@ const CustomizeThemeSetting = (props: Props): JSX.Element => {
     catch (err) {
       toastError(err);
     }
-  }, [currentTheme, t]);
+  }, [currentTheme, t, update]);
 
   const availableThemes = data?.pluginThemesMetadatas == null
     ? PresetThemesMetadatas

+ 1 - 1
packages/app/src/components/Admin/UserGroupDetail/UpdateParentConfirmModal.tsx

@@ -43,7 +43,7 @@ export const UpdateParentConfirmModal: FC = () => {
                 {t('admin:user_group_management.update_parent_confirm_modal.danger_message')}
               </div>
 
-              <div className="custom-control custom-checkbox custom-checkbox-primary pl-5">
+              <div className="custom-control custom-checkbox custom-checkbox-succsess pl-5">
                 <input
                   className="custom-control-input"
                   name="forceUpdateParents"

+ 33 - 1
packages/app/src/components/Fab.module.scss

@@ -1,9 +1,41 @@
+@use '~/styles/bootstrap/init' as bs;
+
 .grw-fab :global {
+  position: fixed;
+  right: 1.5rem;
+  bottom: 3rem;
+  z-index: bs.$zindex-fixed;
+
+  transition: all 200ms linear;
+
+  .btn-create-page {
+    width: 60px;
+    height: 60px;
+    font-size: 24px;
+
+    box-shadow: 2px 3px 6px #0000005d;
+    svg {
+      width: 28px;
+      height: 28px;
+    }
+  }
+
+  .btn-scroll-to-top {
+    width: 40px;
+    height: 40px;
+
+    opacity: 0.4;
+    svg {
+      width: 18px;
+      height: 18px;
+    }
+  }
+
   // workaround
   // https://stackoverflow.com/a/57667536
   .fadeInUp {
     & :local {
-      animation: fab-fadeinup 1s ease 0s;
+      animation: fab-fadeinup 0.5s ease 0s;
     }
   }
   .fadeOut {

+ 50 - 34
packages/app/src/components/Fab.tsx

@@ -1,5 +1,5 @@
 import React, {
-  useState, useCallback, useRef,
+  useState, useCallback, useRef, useEffect,
 } from 'react';
 
 import { animateScroll } from 'react-scroll';
@@ -7,9 +7,9 @@ import { useRipple } from 'react-use-ripple';
 import StickyEvents from 'sticky-events';
 
 import { DEFAULT_AUTO_SCROLL_OPTS } from '~/client/util/smooth-scroll';
-import { useCurrentUser } from '~/stores/context';
 import { usePageCreateModal } from '~/stores/modal';
 import { useCurrentPagePath } from '~/stores/page';
+import { useIsAbleToChangeEditorMode } from '~/stores/ui';
 import loggerFactory from '~/utils/logger';
 
 import { CreatePageIcon } from './Icons/CreatePageIcon';
@@ -21,45 +21,61 @@ const logger = loggerFactory('growi:cli:Fab');
 
 export const Fab = (): JSX.Element => {
 
-  const { data: currentUser } = useCurrentUser();
+  const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
   const { data: currentPath = '' } = useCurrentPagePath();
   const { open: openCreateModal } = usePageCreateModal();
 
-  const [animateClasses, setAnimateClasses] = useState('invisible');
-  const [buttonClasses, setButtonClasses] = useState('');
+  const [animateClasses, setAnimateClasses] = useState<string>('invisible');
+  const [buttonClasses, setButtonClasses] = useState<string>('');
+  const [isSticky, setIsSticky] = useState<boolean>(false);
 
   // ripple
   const createBtnRef = useRef(null);
   useRipple(createBtnRef, { rippleColor: 'rgba(255, 255, 255, 0.3)' });
 
-  /*
-  * TODO: Comment out to prevent err >>> TypeError: Cannot read properties of null (reading 'bottom')
-  *       We need add style={{ position: 'relative }} to child elements if disable StickyEvents. see: use grep = "<Fab".
-  */
-  // const stickyChangeHandler = useCallback((event) => {
-  //   logger.debug('StickyEvents.CHANGE detected');
-
-  //   const newAnimateClasses = event.detail.isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
-  //   const newButtonClasses = event.detail.isSticky ? '' : 'disabled grw-pointer-events-none';
-
-  //   setAnimateClasses(newAnimateClasses);
-  //   setButtonClasses(newButtonClasses);
-  // }, []);
-
-  // // setup effect by sticky event
-  // useEffect(() => {
-  //   // sticky
-  //   // See: https://github.com/ryanwalters/sticky-events
-  //   const stickyEvents = new StickyEvents({ stickySelector: '#grw-fav-sticky-trigger' });
-  //   const { stickySelector } = stickyEvents;
-  //   const elem = document.querySelector(stickySelector);
-  //   elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-
-  //   // return clean up handler
-  //   return () => {
-  //     elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-  //   };
-  // }, [stickyChangeHandler]);
+  /**
+   * After the fade animation is finished, fix the button display status.
+   * Prevents the fade animation occurred each time by button components rendered.
+   * Check Fab.module.scss for fade animation time.
+   */
+  useEffect(() => {
+    const timer = setTimeout(() => {
+      if (isSticky) {
+        setAnimateClasses('visible');
+        setButtonClasses('');
+      }
+      else {
+        setAnimateClasses('invisible');
+      }
+    }, 500);
+    return () => clearTimeout(timer);
+  }, [isSticky]);
+
+  const stickyChangeHandler = useCallback((event) => {
+    logger.debug('StickyEvents.CHANGE detected');
+
+    const newAnimateClasses = event.detail.isSticky ? 'animated fadeInUp faster' : 'animated fadeOut faster';
+    const newButtonClasses = event.detail.isSticky ? '' : 'disabled grw-pointer-events-none';
+
+    setAnimateClasses(newAnimateClasses);
+    setButtonClasses(newButtonClasses);
+    setIsSticky(event.detail.isSticky);
+  }, []);
+
+  // setup effect by sticky event
+  useEffect(() => {
+    // sticky
+    // See: https://github.com/ryanwalters/sticky-events
+    const stickyEvents = new StickyEvents({ stickySelector: '#grw-fav-sticky-trigger' });
+    const { stickySelector } = stickyEvents;
+    const elem = document.querySelector(stickySelector);
+    elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+
+    // return clean up handler
+    return () => {
+      elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+    };
+  }, [stickyChangeHandler]);
 
   const PageCreateButton = useCallback(() => {
     return (
@@ -102,7 +118,7 @@ export const Fab = (): JSX.Element => {
 
   return (
     <div className={`${styles['grw-fab']} grw-fab d-none d-md-block d-edit-none`} data-testid="grw-fab-container">
-      {currentUser != null && <PageCreateButton />}
+      {isAbleToChangeEditorMode && <PageCreateButton />}
       <ScrollToTopButton />
     </div>
   );

+ 1 - 1
packages/app/src/components/Layout/BasicLayout.tsx

@@ -43,7 +43,7 @@ export const BasicLayout = ({ children, className }: Props): JSX.Element => {
             <Sidebar />
           </div>
 
-          <div className="flex-fill mw-0" style={{ position: 'relative' }}>
+          <div className="flex-fill mw-0">
             <AlertSiteUrlUndefined />
             {children}
           </div>

+ 2 - 1
packages/app/src/components/Layout/RawLayout.tsx

@@ -1,10 +1,11 @@
 import React, { ReactNode, useState } from 'react';
 
+import { ColorScheme } from '@growi/core';
 import Head from 'next/head';
 import { ToastContainer } from 'react-toastify';
 import { useIsomorphicLayoutEffect } from 'usehooks-ts';
 
-import { ColorScheme, useNextThemes, NextThemesProvider } from '~/stores/use-next-themes';
+import { useNextThemes, NextThemesProvider } from '~/stores/use-next-themes';
 import loggerFactory from '~/utils/logger';
 
 

+ 1 - 1
packages/app/src/components/Layout/ShareLinkLayout.tsx

@@ -28,7 +28,7 @@ export const ShareLinkLayout = ({ children }: Props): JSX.Element => {
       <GrowiNavbar isGlobalSearchHidden={true} />
 
       <div className="page-wrapper d-flex d-print-block">
-        <div className="flex-fill mw-0" style={{ position: 'relative' }}>
+        <div className="flex-fill mw-0">
           {children}
         </div>
       </div>

+ 3 - 3
packages/app/src/components/Navbar/AppearanceModeDropdown.tsx

@@ -47,15 +47,15 @@ export const AppearanceModeDropdown:FC<AppearanceModeDropdownProps> = (props: Ap
 
   const followOsCheckboxModifiedHandler = useCallback((isChecked: boolean) => {
     if (isChecked) {
-      setTheme(Themes.system);
+      setTheme(Themes.SYSTEM);
     }
     else {
-      setTheme(resolvedTheme ?? Themes.light);
+      setTheme(resolvedTheme ?? Themes.LIGHT);
     }
   }, [resolvedTheme, setTheme]);
 
   const userPreferenceSwitchModifiedHandler = useCallback((isDarkMode: boolean) => {
-    setTheme(isDarkMode ? Themes.dark : Themes.light);
+    setTheme(isDarkMode ? Themes.DARK : Themes.LIGHT);
   }, [setTheme]);
 
   /* eslint-disable react/prop-types */

+ 18 - 20
packages/app/src/components/Navbar/GrowiContextualSubNavigation.tsx

@@ -27,7 +27,7 @@ import {
 import { useSWRxCurrentPage, useSWRxTagsInfo } from '~/stores/page';
 import {
   EditorMode, useDrawerMode, useEditorMode, useIsAbleToShowPageManagement, useIsAbleToShowTagLabel,
-  useIsAbleToShowPageEditorModeManager, useIsAbleToShowPageAuthors,
+  useIsAbleToChangeEditorMode, useIsAbleToShowPageAuthors,
 } from '~/stores/ui';
 
 import CreateTemplateModal from '../CreateTemplateModal';
@@ -184,7 +184,7 @@ const CreateTemplateMenuItems = (props: CreateTemplateMenuItemsProps): JSX.Eleme
 };
 
 type GrowiContextualSubNavigationProps = {
-  currentPage?: IPagePopulatedToShowRevision,
+  currentPage?: IPagePopulatedToShowRevision | null,
   isCompactMode?: boolean,
   isLinkSharingDisabled: boolean,
 };
@@ -216,10 +216,10 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
 
   const { data: isAbleToShowPageManagement } = useIsAbleToShowPageManagement();
   const { data: isAbleToShowTagLabel } = useIsAbleToShowTagLabel();
-  const { data: isAbleToShowPageEditorModeManager } = useIsAbleToShowPageEditorModeManager();
+  const { data: isAbleToChangeEditorMode } = useIsAbleToChangeEditorMode();
   const { data: isAbleToShowPageAuthors } = useIsAbleToShowPageAuthors();
 
-  const { mutate: mutateSWRTagsInfo, data: tagsInfoData } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
+  const { mutate: mutateSWRTagsInfo, data: tagsInfoData } = useSWRxTagsInfo(currentPage?._id);
 
   // eslint-disable-next-line max-len
   const { data: tagsForEditors, mutate: mutatePageTagsForEditors, sync: syncPageTagsForEditors } = usePageTagsForEditors(!isSharedPage ? currentPage?._id : undefined);
@@ -380,7 +380,7 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
                 ) }
               </div>
             ) }
-            {isAbleToShowPageEditorModeManager && (
+            {isAbleToChangeEditorMode && (
               <PageEditorModeManager
                 onPageEditorModeButtonClicked={viewType => mutateEditorMode(viewType)}
                 isBtnDisabled={isGuestUser}
@@ -423,21 +423,19 @@ const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps):
     : currentPage?.path;
 
   return (
-    <div data-testid="grw-contextual-sub-nav">
-      <GrowiSubNavigation
-        pagePath={pagePath}
-        pageId={currentPage?._id}
-        showDrawerToggler={isDrawerMode}
-        showTagLabel={isAbleToShowTagLabel}
-        isGuestUser={isGuestUser}
-        isDrawerMode={isDrawerMode}
-        isCompactMode={isCompactMode}
-        tags={isViewMode ? tagsInfoData?.tags : tagsForEditors}
-        tagsUpdatedHandler={isViewMode ? tagsUpdatedHandlerForViewMode : tagsUpdatedHandlerForEditMode}
-        rightComponent={RightComponent}
-        additionalClasses={['container-fluid']}
-      />
-    </div>
+    <GrowiSubNavigation
+      pagePath={pagePath}
+      pageId={currentPage?._id}
+      showDrawerToggler={isDrawerMode}
+      showTagLabel={isAbleToShowTagLabel}
+      isGuestUser={isGuestUser}
+      isDrawerMode={isDrawerMode}
+      isCompactMode={isCompactMode}
+      tags={isViewMode ? tagsInfoData?.tags : tagsForEditors}
+      tagsUpdatedHandler={isViewMode ? tagsUpdatedHandlerForViewMode : tagsUpdatedHandlerForEditMode}
+      rightComponent={RightComponent}
+      additionalClasses={['container-fluid']}
+    />
   );
 };
 

+ 0 - 9
packages/app/src/components/Navbar/GrowiSubNavigation.module.scss

@@ -172,12 +172,3 @@
     }
   }
 }
-
-/*
- * shadow
- */
-.grw-subnav-append-shadow-container {
-  .grw-subnav {
-    box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
-  }
-}

+ 0 - 141
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.jsx

@@ -1,141 +0,0 @@
-import React, {
-  useMemo, useState, useRef, useEffect, useCallback,
-} from 'react';
-
-import PropTypes from 'prop-types';
-import StickyEvents from 'sticky-events';
-import { debounce } from 'throttle-debounce';
-
-import { useSidebarCollapsed } from '~/stores/ui';
-import loggerFactory from '~/utils/logger';
-
-import { useSWRxCurrentPage } from '~/stores/page';
-
-import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
-
-import styles from './GrowiSubNavigationSwitcher.module.scss';
-
-const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
-
-
-/**
- * Subnavigation
- *
- * needs:
- *   #grw-subnav-fixed-container element
- *   #grw-subnav-sticky-trigger element
- *
- * @param {object} props
- */
-const GrowiSubNavigationSwitcher = (props) => {
-
-  const { data: currentPage } = useSWRxCurrentPage();
-  const { data: isSidebarCollapsed } = useSidebarCollapsed();
-
-  const [isVisible, setVisible] = useState(false);
-  const [width, setWidth] = useState(null);
-
-  const fixedContainerRef = useRef();
-  /*
-  * Comment out to prevent err >>> TypeError: Cannot read properties of null (reading 'bottom')
-  * The above err occurs when moving to admin page after rendering normal pages.
-  * This is because id "grw-subnav-sticky-trigger" does not exist on admin pages.
-  */
-  // const stickyEvents = useMemo(() => new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' }), []);
-
-  const initWidth = useCallback(() => {
-    const instance = fixedContainerRef.current;
-
-    if (instance == null || instance.parentNode == null) {
-      return;
-    }
-
-    // get parent width
-    const { clientWidth } = instance.parentNode;
-    // update style
-    setWidth(clientWidth);
-  }, []);
-
-  // const initVisible = useCallback(() => {
-  //   const elements = stickyEvents.stickyElements;
-
-  //   for (const elem of elements) {
-  //     const bool = stickyEvents.isSticking(elem);
-  //     if (bool) {
-  //       setVisible(bool);
-  //       break;
-  //     }
-  //   }
-
-  // }, [stickyEvents]);
-
-  // setup effect by resizing event
-  useEffect(() => {
-    const resizeHandler = debounce(100, initWidth);
-
-    window.addEventListener('resize', resizeHandler);
-
-    // return clean up handler
-    return () => {
-      window.removeEventListener('resize', resizeHandler);
-    };
-  }, [initWidth]);
-
-  const stickyChangeHandler = useCallback((event) => {
-    logger.debug('StickyEvents.CHANGE detected');
-    setVisible(event.detail.isSticky);
-  }, []);
-
-  // // setup effect by sticky event
-  // useEffect(() => {
-  //   // sticky
-  //   // See: https://github.com/ryanwalters/sticky-events
-  //   const { stickySelector } = stickyEvents;
-  //   const elem = document.querySelector(stickySelector);
-  //   elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-
-  //   // return clean up handler
-  //   return () => {
-  //     elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-  //   };
-  // }, [stickyChangeHandler, stickyEvents]);
-
-  // update width when sidebar collapsing changed
-  useEffect(() => {
-    if (isSidebarCollapsed != null) {
-      setTimeout(initWidth, 300);
-    }
-  }, [isSidebarCollapsed, initWidth]);
-
-  // // initialize
-  // useEffect(() => {
-  //   initWidth();
-
-  //   // check sticky state several times
-  //   setTimeout(initVisible, 100);
-  //   setTimeout(initVisible, 300);
-  //   setTimeout(initVisible, 2000);
-
-  // }, [initWidth, initVisible]);
-
-  // ${styles['grw-subnav-switcher']}
-
-  return (
-    <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>
-      <div
-        id="grw-subnav-fixed-container"
-        className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
-        ref={fixedContainerRef}
-        style={{ width }}
-      >
-        <GrowiContextualSubNavigation currentPage isCompactMode isLinkSharingDisabled />
-      </div>
-    </div>
-  );
-};
-
-GrowiSubNavigationSwitcher.propTypes = {
-  isLinkSharingDisabled: PropTypes.bool,
-};
-
-export default GrowiSubNavigationSwitcher;

+ 9 - 0
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.module.scss

@@ -19,6 +19,15 @@ $easeInOutCubic: cubic-bezier(0.65, 0, 0.35, 1);
     .grw-subnav-fixed-container {
       transition: transform 150ms $easeInOutCubic;
     }
+
+    /*
+    * shadow
+    */
+    .grw-subnav-append-shadow-container {
+      .grw-subnav {
+        box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
+      }
+    }
   }
 
   &:global {

+ 103 - 0
packages/app/src/components/Navbar/GrowiSubNavigationSwitcher.tsx

@@ -0,0 +1,103 @@
+import React, {
+  useState, useRef, useEffect, useCallback,
+} from 'react';
+
+import StickyEvents from 'sticky-events';
+import { debounce } from 'throttle-debounce';
+
+import { useSWRxCurrentPage } from '~/stores/page';
+import { useSidebarCollapsed } from '~/stores/ui';
+import loggerFactory from '~/utils/logger';
+
+import GrowiContextualSubNavigation from './GrowiContextualSubNavigation';
+
+import styles from './GrowiSubNavigationSwitcher.module.scss';
+
+const logger = loggerFactory('growi:cli:GrowiSubNavigationSticky');
+
+/**
+ * GrowiSubNavigation
+ *
+ * needs:
+ *   #grw-subnav-fixed-container element
+ *   #grw-subnav-sticky-trigger element
+ */
+export const GrowiSubNavigationSwitcher = (): JSX.Element => {
+
+  const { data: currentPage } = useSWRxCurrentPage();
+  const { data: isSidebarCollapsed } = useSidebarCollapsed();
+
+  const [isVisible, setIsVisible] = useState<boolean>(false);
+  const [width, setWidth] = useState<number>(0);
+
+  // use more specific type HTMLDivElement for avoid assertion error.
+  // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLDivElement
+  const fixedContainerRef = useRef<HTMLDivElement>(null);
+
+  const initWidth = useCallback(() => {
+    if (fixedContainerRef.current && fixedContainerRef.current.parentElement) {
+      // get parent elements width
+      const { clientWidth } = fixedContainerRef.current.parentElement;
+      setWidth(clientWidth);
+    }
+  }, []);
+
+  const stickyChangeHandler = useCallback((event) => {
+    logger.debug('StickyEvents.CHANGE detected');
+    setIsVisible(event.detail.isSticky);
+  }, []);
+
+  // setup effect by sticky-events
+  useEffect(() => {
+    // sticky-events
+    // See: https://github.com/ryanwalters/sticky-events
+    const { stickySelector } = new StickyEvents({ stickySelector: '#grw-subnav-sticky-trigger' });
+    const elem = document.querySelector(stickySelector);
+    elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+
+    // return clean up handler
+    return () => {
+      elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+    };
+  }, [stickyChangeHandler]);
+
+  // setup effect by resizing event
+  useEffect(() => {
+    const resizeHandler = debounce(100, initWidth);
+    window.addEventListener('resize', resizeHandler);
+
+    // return clean up handler
+    return () => {
+      window.removeEventListener('resize', resizeHandler);
+    };
+  }, [initWidth]);
+
+  // update width when sidebar collapsing changed
+  useEffect(() => {
+    if (isSidebarCollapsed != null) {
+      setTimeout(initWidth, 300);
+    }
+  }, [isSidebarCollapsed, initWidth]);
+
+  // initialize width
+  useEffect(() => {
+    initWidth();
+  }, [initWidth]);
+
+  if (currentPage == null) {
+    return <></>;
+  }
+
+  return (
+    <div className={`${styles['grw-subnav-switcher']} ${isVisible ? '' : 'grw-subnav-switcher-hidden'}`}>
+      <div
+        id="grw-subnav-fixed-container"
+        className={`grw-subnav-fixed-container ${styles['grw-subnav-fixed-container']} position-fixed grw-subnav-append-shadow-container`}
+        ref={fixedContainerRef}
+        style={{ width }}
+      >
+        <GrowiContextualSubNavigation currentPage={currentPage} isCompactMode isLinkSharingDisabled />
+      </div>
+    </div>
+  );
+};

+ 1 - 1
packages/app/src/components/Page.tsx

@@ -70,7 +70,7 @@ export const Page: FC<Props> = (props: Props) => {
   const { data: shareLinkId } = useShareLinkId();
   const { mutate: mutateCurrentPage } = useSWRxCurrentPage();
   const { mutate: mutateEditingMarkdown } = useEditingMarkdown();
-  const { data: tagsInfo } = useSWRxTagsInfo(!isSharedPage ? currentPage?._id : undefined);
+  const { data: tagsInfo } = useSWRxTagsInfo(currentPage?._id);
   const { data: isGuestUser } = useIsGuestUser();
   const { data: isMobile } = useIsMobile();
   const { data: rendererOptions, mutate: mutateRendererOptions } = useViewOptions(storeTocNodeHandler);

+ 1 - 1
packages/app/src/components/PageRenameModal.tsx

@@ -304,7 +304,7 @@ const PageRenameModal = (): JSX.Element => {
             </label>
           </div>
 
-          <div className="custom-control custom-checkbox custom-checkbox-primary">
+          <div className="custom-control custom-checkbox custom-checkbox-success">
             <input
               className="custom-control-input"
               name="remain_metadata"

+ 2 - 2
packages/app/src/components/SearchPage/SearchControl.tsx

@@ -118,7 +118,7 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
             </div>
             <div className="d-none d-lg-flex align-items-center ml-auto search-control-include-options">
               <div className="border rounded px-2 py-1 mr-3">
-                <div className="custom-control custom-checkbox custom-checkbox-primary">
+                <div className="custom-control custom-checkbox custom-checkbox-succsess">
                   <input
                     className="custom-control-input mr-2"
                     type="checkbox"
@@ -132,7 +132,7 @@ const SearchControl: FC <Props> = React.memo((props: Props) => {
                 </div>
               </div>
               <div className="border rounded px-2 py-1">
-                <div className="custom-control custom-checkbox custom-checkbox-primary">
+                <div className="custom-control custom-checkbox custom-checkbox-succsess">
                   <input
                     className="custom-control-input mr-2"
                     type="checkbox"

+ 8 - 0
packages/app/src/components/SearchPage/SearchResultContent.module.scss

@@ -0,0 +1,8 @@
+/*
+* shadow
+*/
+.grw-subnav-append-shadow-container :global {
+  .grw-subnav {
+    box-shadow: 0px 0px 6px 3px rgba(black, 0.15);
+  }
+}

+ 3 - 1
packages/app/src/components/SearchPage/SearchResultContent.tsx

@@ -29,6 +29,8 @@ import { ROOT_ELEM_ID as RevisionLoaderRoomElemId, RevisionLoaderProps } from '.
 import { ROOT_ELEM_ID as PageCommentRootElemId, PageCommentProps } from '../PageComment';
 import { PageContentFooterProps } from '../PageContentFooter';
 
+import styles from './SearchResultContent.module.scss';
+
 
 const GrowiSubNavigation = dynamic<GrowiSubNavigationProps>(() => import('../Navbar/GrowiSubNavigation').then(mod => mod.GrowiSubNavigation), { ssr: false });
 const SubNavButtons = dynamic<SubNavButtonsProps>(() => import('../Navbar/SubNavButtons').then(mod => mod.SubNavButtons), { ssr: false });
@@ -242,7 +244,7 @@ export const SearchResultContent: FC<Props> = (props: Props) => {
 
   return (
     <div key={page._id} data-testid="search-result-content" className="search-result-content grw-page-path-text-muted-container d-flex flex-column">
-      <div className="grw-subnav-append-shadow-container">
+      <div className={`${styles['grw-subnav-append-shadow-container']} grw-subnav-append-shadow-container`}>
         <GrowiSubNavigation
           pagePath={page.path}
           pageId={page._id}

+ 20 - 20
packages/app/src/components/StickyStretchableScroller.tsx

@@ -69,26 +69,26 @@ export const StickyStretchableScroller: FC<StickyStretchableScrollerProps> = (pr
 
   const resetScrollbarDebounced = useMemo(() => debounce(100, resetScrollbar), [resetScrollbar]);
 
-  // const stickyChangeHandler = useCallback(() => {
-  //   logger.debug('StickyEvents.CHANGE detected');
-  //   resetScrollbarDebounced();
-  // }, [resetScrollbarDebounced]);
-
-  // // setup effect by sticky event
-  // useEffect(() => {
-  //   // sticky
-  //   // See: https://github.com/ryanwalters/sticky-events
-  //   const stickyEvents = new StickyEvents({ stickySelector: stickyElemSelector });
-  //   stickyEvents.enableEvents();
-  //   const { stickySelector } = stickyEvents;
-  //   const elem = document.querySelector(stickySelector);
-  //   elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-
-  //   // return clean up handler
-  //   return () => {
-  //     elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
-  //   };
-  // }, [stickyElemSelector, stickyChangeHandler]);
+  const stickyChangeHandler = useCallback(() => {
+    logger.debug('StickyEvents.CHANGE detected');
+    resetScrollbarDebounced();
+  }, [resetScrollbarDebounced]);
+
+  // setup effect by sticky event
+  useEffect(() => {
+    // sticky
+    // See: https://github.com/ryanwalters/sticky-events
+    const stickyEvents = new StickyEvents({ stickySelector: stickyElemSelector });
+    stickyEvents.enableEvents();
+    const { stickySelector } = stickyEvents;
+    const elem = document.querySelector(stickySelector);
+    elem.addEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+
+    // return clean up handler
+    return () => {
+      elem.removeEventListener(StickyEvents.CHANGE, stickyChangeHandler);
+    };
+  }, [stickyElemSelector, stickyChangeHandler]);
 
   // setup effect by resizing event
   useEffect(() => {

+ 1 - 1
packages/app/src/interfaces/customize.ts

@@ -1,4 +1,4 @@
-import { GrowiThemeMetadata } from '@growi/core';
+import type { GrowiThemeMetadata } from '@growi/core';
 
 export type IResLayoutSetting = {
   isContainerFluid: boolean,

+ 45 - 5
packages/app/src/pages/[[...path]].page.tsx

@@ -32,6 +32,7 @@ import type { CrowiRequest } from '~/interfaces/crowi-request';
 // import { useRendererSettings } from '~/stores/renderer';
 // import { EditorMode, useEditorMode, useIsMobile } from '~/stores/ui';
 import type { EditorConfig } from '~/interfaces/editor-settings';
+import { IPageGrantData } from '~/interfaces/page';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
 import type { IUserUISettings } from '~/interfaces/user-ui-settings';
@@ -56,7 +57,7 @@ import loggerFactory from '~/utils/logger';
 // import GrowiSubNavigationSwitcher from '../client/js/components/Navbar/GrowiSubNavigationSwitcher';
 import { DescendantsPageListModal } from '../components/DescendantsPageListModal';
 import { BasicLayoutWithEditorMode } from '../components/Layout/BasicLayout';
-import GrowiContextualSubNavigation from '../components/Navbar/GrowiContextualSubNavigation';
+import GrowiContextualSubNavigationSubstance from '../components/Navbar/GrowiContextualSubNavigation';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
 // import { serializeUserSecurely } from '../server/models/serializers/user-serializer';
 // import PageStatusAlert from '../client/js/components/PageStatusAlert';
@@ -90,7 +91,8 @@ const NotCreatablePage = dynamic(() => import('../components/NotCreatablePage').
 const ForbiddenPage = dynamic(() => import('../components/ForbiddenPage'), { ssr: false });
 const UnsavedAlertDialog = dynamic(() => import('../components/UnsavedAlertDialog'), { ssr: false });
 const PageSideContents = dynamic<PageSideContentsProps>(() => import('../components/PageSideContents').then(mod => mod.PageSideContents), { ssr: false });
-const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher'), { ssr: false });
+const GrowiSubNavigationSwitcher = dynamic(() => import('../components/Navbar/GrowiSubNavigationSwitcher')
+  .then(mod => mod.GrowiSubNavigationSwitcher), { ssr: false });
 const UsersHomePageFooter = dynamic<UsersHomePageFooterProps>(() => import('../components/UsersHomePageFooter')
   .then(mod => mod.UsersHomePageFooter), { ssr: false });
 const DrawioModal = dynamic(() => import('../components/PageEditor/DrawioModal').then(mod => mod.DrawioModal), { ssr: false });
@@ -131,6 +133,20 @@ superjson.registerCustom<IPageToShowRevisionWithMeta, IPageToShowRevisionWithMet
   'IPageToShowRevisionWithMetaTransformer',
 );
 
+// GrowiContextualSubNavigation for NOT shared page
+type GrowiContextualSubNavigationProps = {
+  isLinkSharingDisabled: boolean,
+}
+
+const GrowiContextualSubNavigation = (props: GrowiContextualSubNavigationProps): JSX.Element => {
+  const { isLinkSharingDisabled } = props;
+  const { data: currentPage } = useSWRxCurrentPage();
+  return (
+    <div data-testid="grw-contextual-sub-nav">
+      <GrowiContextualSubNavigationSubstance currentPage={currentPage} isLinkSharingDisabled={isLinkSharingDisabled}/>
+    </div>
+  );
+};
 
 const IdenticalPathPage = (): JSX.Element => {
   const IdenticalPathPage = dynamic(() => import('../components/IdenticalPathPage').then(mod => mod.IdenticalPathPage), { ssr: false });
@@ -184,6 +200,8 @@ type Props = CommonProps & {
   isIndentSizeForced: boolean,
   disableLinkSharing: boolean,
 
+  grantData?: IPageGrantData,
+
   rendererConfig: RendererConfig,
 
   // UI
@@ -283,8 +301,9 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
 
   // sync grant data
   useEffect(() => {
-    mutateSelectedGrant(grantData?.grantData.currentPageGrant);
-  }, [grantData?.grantData.currentPageGrant, mutateSelectedGrant]);
+    const grantDataToApply = props.grantData ? props.grantData : grantData?.grantData.currentPageGrant;
+    mutateSelectedGrant(grantDataToApply);
+  }, [grantData?.grantData.currentPageGrant, mutateSelectedGrant, props.grantData]);
 
   // sync pathname by Shallow Routing https://nextjs.org/docs/routing/shallow-routing
   useEffect(() => {
@@ -328,7 +347,7 @@ const Page: NextPageWithLayout<Props> = (props: Props) => {
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
         <header className="py-0 position-relative">
           <div id="grw-subnav-container">
-            <GrowiContextualSubNavigation currentPage={pageWithMeta?.data} isLinkSharingDisabled={props.disableLinkSharing} />
+            <GrowiContextualSubNavigation isLinkSharingDisabled={props.disableLinkSharing} />
           </div>
         </header>
         <div className="d-edit-none">
@@ -392,6 +411,21 @@ class MultiplePagesHitsError extends ExtensibleCustomError {
 
 }
 
+// apply parent page grant fot creating page
+async function applyGrantToPage(props: Props, ancestor: any) {
+  await ancestor.populate('grantedGroup');
+  const grant = {
+    grant: ancestor.grant,
+  };
+  const grantedGroup = ancestor.grantedGroup ? {
+    grantedGroup: {
+      id: ancestor.grantedGroup.id,
+      name: ancestor.grantedGroup.name,
+    },
+  } : {};
+  props.grantData = Object.assign(grant, grantedGroup);
+}
+
 async function injectPageData(context: GetServerSidePropsContext, props: Props): Promise<void> {
   const { model: mongooseModel } = await import('mongoose');
 
@@ -449,6 +483,12 @@ async function injectPageData(context: GetServerSidePropsContext, props: Props):
       props.templateTagData = templateData.templateTags as string[];
       props.templateBodyData = templateData.templateBody as string;
     }
+
+    // apply pagrent page grant
+    const ancestor = await Page.findAncestorByPathAndViewer(currentPathname, user);
+    if (ancestor != null) {
+      await applyGrantToPage(props, ancestor);
+    }
   }
 
   props.pageWithMeta = pageWithMeta;

+ 5 - 2
packages/app/src/pages/_app.page.tsx

@@ -11,7 +11,7 @@ import * as nextI18nConfig from '^/config/next-i18next.config';
 import { ActivatePluginService } from '~/client/services/activate-plugin';
 import { useI18nextHMR } from '~/services/i18next-hmr';
 import {
-  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useIsDefaultLogo,
+  useAppTitle, useConfidential, useGrowiVersion, useSiteUrl, useIsDefaultLogo, useForcedColorScheme,
 } from '~/stores/context';
 import { SWRConfigValue, swrGlobalConfiguration } from '~/utils/swr-utils';
 
@@ -20,7 +20,9 @@ import { CommonProps } from './utils/commons';
 import { registerTransformerForObjectId } from './utils/objectid-transformer';
 
 import '~/styles/style-app.scss';
-
+import '~/styles/theme/_apply-colors-light.scss';
+import '~/styles/theme/_apply-colors-dark.scss';
+import '~/styles/theme/_apply-colors.scss';
 
 const isDev = process.env.NODE_ENV === 'development';
 
@@ -64,6 +66,7 @@ function GrowiApp({ Component, pageProps }: GrowiAppProps): JSX.Element {
   useConfidential(commonPageProps.confidential);
   useGrowiVersion(commonPageProps.growiVersion);
   useIsDefaultLogo(commonPageProps.isDefaultLogo);
+  useForcedColorScheme(commonPageProps.forcedColorScheme);
 
   // Use the layout defined at the page level, if available
   const getLayout = Component.getLayout ?? (page => page);

+ 6 - 51
packages/app/src/pages/_document.page.tsx

@@ -1,8 +1,6 @@
 /* eslint-disable @next/next/google-font-display */
 import React from 'react';
 
-import type { ViteManifest } from '@growi/core';
-import { DefaultThemeMetadata, PresetThemesMetadatas } from '@growi/preset-themes';
 import Document, {
   DocumentContext, DocumentInitialProps,
   Html, Head, Main, NextScript,
@@ -14,40 +12,6 @@ import loggerFactory from '~/utils/logger';
 
 const logger = loggerFactory('growi:page:_document');
 
-type HeadersForThemesProps = {
-  theme: string,
-  presetThemesManifest: ViteManifest,
-  pluginThemeHref: string | undefined,
-}
-const HeadersForThemes = (props: HeadersForThemesProps): JSX.Element => {
-  const {
-    theme, presetThemesManifest, pluginThemeHref,
-  } = props;
-
-  const elements: JSX.Element[] = [];
-
-  // when plugin theme is specified
-  if (pluginThemeHref != null) {
-    elements.push(
-      <link rel="stylesheet" key={`link_custom-themes-${theme}`} href={pluginThemeHref} />,
-    );
-  }
-  // preset theme
-  else {
-    const themeMetadata = PresetThemesMetadatas.find(p => p.name === theme);
-    const manifestKey = themeMetadata?.manifestKey ?? DefaultThemeMetadata.manifestKey;
-    if (themeMetadata == null || !(themeMetadata.manifestKey in presetThemesManifest)) {
-      logger.warn(`Use default theme because the key for '${theme} does not exist in preset-themes manifest`);
-    }
-    const href = `/static/preset-themes/${presetThemesManifest[manifestKey].file}`; // configured by express.static
-    elements.push(
-      <link rel="stylesheet" key={`link_preset-themes-${theme}`} href={href} />,
-    );
-  }
-
-  return <>{elements}</>;
-};
-
 type HeadersForGrowiPluginProps = {
   pluginResourceEntries: GrowiPluginResourceEntries;
 }
@@ -72,12 +36,10 @@ const HeadersForGrowiPlugin = (props: HeadersForGrowiPluginProps): JSX.Element =
 };
 
 interface GrowiDocumentProps {
-  theme: string,
+  themeHref: string,
   customScript: string | null,
   customCss: string | null,
   customNoscript: string | null,
-  presetThemesManifest: ViteManifest,
-  pluginThemeHref: string | undefined,
   pluginResourceEntries: GrowiPluginResourceEntries;
 }
 declare type GrowiDocumentInitialProps = DocumentInitialProps & GrowiDocumentProps;
@@ -87,28 +49,22 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
   static override async getInitialProps(ctx: DocumentContext): Promise<GrowiDocumentInitialProps> {
     const initialProps: DocumentInitialProps = await Document.getInitialProps(ctx);
     const { crowi } = ctx.req as CrowiRequest<any>;
-    const { configManager, customizeService, pluginService } = crowi;
+    const { customizeService, pluginService } = crowi;
 
-    const theme = configManager.getConfig('crowi', 'customize:theme');
+    const { themeHref } = customizeService;
     const customScript: string | null = customizeService.getCustomScript();
     const customCss: string | null = customizeService.getCustomCss();
     const customNoscript: string | null = customizeService.getCustomNoscript();
 
-    // import preset-themes manifest
-    const presetThemesManifest = await import('@growi/preset-themes/dist/themes/manifest.json').then(imported => imported.default);
-
     // retrieve plugin manifests
     const pluginResourceEntries = await (pluginService as IPluginService).retrieveAllPluginResourceEntries();
-    const pluginThemeHref = await (pluginService as IPluginService).retrieveThemeHref(theme);
 
     return {
       ...initialProps,
-      theme,
+      themeHref,
       customScript,
       customCss,
       customNoscript,
-      presetThemesManifest,
-      pluginThemeHref,
       pluginResourceEntries,
     };
   }
@@ -137,7 +93,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
   override render(): JSX.Element {
     const {
       customCss, customScript, customNoscript,
-      theme, presetThemesManifest, pluginThemeHref, pluginResourceEntries,
+      themeHref, pluginResourceEntries,
     } = this.props;
 
     return (
@@ -150,8 +106,7 @@ class GrowiDocument extends Document<GrowiDocumentInitialProps> {
           <link rel='preload' href="/static/fonts/Lato-Regular-latin-ext.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin.woff2" as="font" type="font/woff2" />
           <link rel='preload' href="/static/fonts/Lato-Bold-latin-ext.woff2" as="font" type="font/woff2" />
-          <HeadersForThemes theme={theme}
-            presetThemesManifest={presetThemesManifest} pluginThemeHref={pluginThemeHref} />
+          <link rel="stylesheet" key="link-theme" href={themeHref} />
           <HeadersForGrowiPlugin pluginResourceEntries={pluginResourceEntries} />
           {this.renderCustomCss(customCss)}
         </Head>

+ 22 - 5
packages/app/src/pages/share/[[...path]].page.tsx

@@ -12,7 +12,7 @@ import superjson from 'superjson';
 import { useCurrentGrowiLayoutFluidClassName } from '~/client/services/layout';
 import { MainPane } from '~/components/Layout/MainPane';
 import { ShareLinkLayout } from '~/components/Layout/ShareLinkLayout';
-import GrowiContextualSubNavigation from '~/components/Navbar/GrowiContextualSubNavigation';
+import GrowiContextualSubNavigationSubstance from '~/components/Navbar/GrowiContextualSubNavigation';
 import { Page } from '~/components/Page';
 import type { PageSideContentsProps } from '~/components/PageSideContents';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
@@ -22,7 +22,7 @@ import { RendererConfig } from '~/interfaces/services/renderer';
 import { IShareLinkHasId } from '~/interfaces/share-link';
 import type { PageDocument } from '~/server/models/page';
 import {
-  useCurrentUser, useCurrentPathname, useCurrentPageId, useRendererConfig, useIsSearchPage,
+  useCurrentUser, useCurrentPageId, useRendererConfig, useIsSearchPage,
   useShareLinkId, useIsSearchServiceConfigured, useIsSearchServiceReachable, useIsSearchScopeChildrenAsDefault, useDrawioUri, useIsContainerFluid,
 } from '~/stores/context';
 import loggerFactory from '~/utils/logger';
@@ -68,12 +68,28 @@ superjson.registerCustom<IShareLinkRelatedPage, string>(
   'IShareLinkRelatedPageTransformer',
 );
 
+// GrowiContextualSubNavigation for shared page
+// get page info from props not to send request 'GET /page' from client
+type GrowiContextualSubNavigationForSharedPageProps = {
+  currentPage?: IPagePopulatedToShowRevision,
+  isLinkSharingDisabled: boolean,
+}
+
+const GrowiContextualSubNavigationForSharedPage = (props: GrowiContextualSubNavigationForSharedPageProps): JSX.Element => {
+  const { currentPage, isLinkSharingDisabled } = props;
+  if (currentPage == null) { return <></> }
+  return (
+    <div data-testid="grw-contextual-sub-nav">
+      <GrowiContextualSubNavigationSubstance currentPage={currentPage} isLinkSharingDisabled={isLinkSharingDisabled}/>
+    </div>
+  );
+};
+
 const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   useIsSearchPage(false);
   useShareLinkId(props.shareLink?._id);
   useCurrentPageId(props.shareLink?.relatedPage._id);
   useCurrentUser(props.currentUser);
-  const { data: currentPathname } = useCurrentPathname(props.currentPathname);
   useRendererConfig(props.rendererConfig);
   useIsSearchServiceConfigured(props.isSearchServiceConfigured);
   useIsSearchServiceReachable(props.isSearchServiceReachable);
@@ -88,7 +104,7 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
   const isShowSharedPage = !props.disableLinkSharing && !isNotFound && !props.isExpired;
   const shareLink = props.shareLink;
 
-  const title = generateCustomTitleForPage(props, currentPathname ?? '');
+  const title = generateCustomTitleForPage(props, props.shareLinkRelatedPage?.path ?? '');
 
 
   const sideContents = shareLink != null
@@ -111,7 +127,8 @@ const SharedPage: NextPageWithLayout<Props> = (props: Props) => {
 
       <div className={`dynamic-layout-root ${growiLayoutFluidClass} h-100 d-flex flex-column justify-content-between`}>
         <header className="py-0 position-relative">
-          {isShowSharedPage && <GrowiContextualSubNavigation currentPage={props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />}
+          {isShowSharedPage
+          && <GrowiContextualSubNavigationForSharedPage currentPage={props.shareLinkRelatedPage} isLinkSharingDisabled={props.disableLinkSharing} />}
         </header>
 
         <div id="grw-fav-sticky-trigger" className="sticky-top"></div>

+ 6 - 2
packages/app/src/pages/utils/commons.ts

@@ -1,12 +1,13 @@
+import type { ColorScheme, IUser, IUserHasId } from '@growi/core';
 import {
-  DevidedPagePath, Lang, AllLang, IUser, IUserHasId,
+  DevidedPagePath, Lang, AllLang,
 } from '@growi/core';
 import { GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { SSRConfig, UserConfig } from 'next-i18next';
 
 import * as nextI18NextConfig from '^/config/next-i18next.config';
 
-import { CrowiRequest } from '~/interfaces/crowi-request';
+import type { CrowiRequest } from '~/interfaces/crowi-request';
 
 export type CommonProps = {
   namespacesRequired: string[], // i18next
@@ -22,6 +23,7 @@ export type CommonProps = {
   redirectDestination: string | null,
   isDefaultLogo: boolean,
   currentUser?: IUser,
+  forcedColorScheme?: ColorScheme,
 } & Partial<SSRConfig>;
 
 // eslint-disable-next-line max-len
@@ -47,6 +49,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
   const redirectDestination = !isMaintenanceMode && currentPathname === '/maintenance' ? '/' : isMaintenanceMode && !currentPathname.match('/admin/*') && !(currentPathname === '/maintenance') ? '/maintenance' : null;
   const isCustomizedLogoUploaded = await attachmentService.isBrandLogoExist();
   const isDefaultLogo = crowi.configManager.getConfig('crowi', 'customize:isDefaultLogo') || !isCustomizedLogoUploaded;
+  const forcedColorScheme = crowi.customizeService.forcedColorScheme;
 
   const props: CommonProps = {
     namespacesRequired: ['translation'],
@@ -62,6 +65,7 @@ export const getServerSideCommonProps: GetServerSideProps<CommonProps> = async(c
     redirectDestination,
     currentUser,
     isDefaultLogo,
+    forcedColorScheme,
   };
 
   return { props };

+ 2 - 1
packages/app/src/server/crowi/index.js

@@ -134,7 +134,6 @@ Crowi.prototype.init = async function() {
     this.setUpFileUploaderSwitchService(),
     this.setupAttachmentService(),
     this.setUpAcl(),
-    this.setUpCustomize(),
     this.setUpRestQiitaAPI(),
     this.setupUserGroupService(),
     this.setupExport(),
@@ -145,6 +144,7 @@ Crowi.prototype.init = async function() {
     this.setupActivityService(),
     this.setupCommentService(),
     this.setupSyncPageStatusService(),
+    this.setUpCustomize(), // depends on pluginService
   ]);
 
   // globalNotification depends on slack and mailer
@@ -608,6 +608,7 @@ Crowi.prototype.setUpCustomize = async function() {
     this.customizeService = new CustomizeService(this);
     this.customizeService.initCustomCss();
     this.customizeService.initCustomTitle();
+    this.customizeService.initGrowiTheme();
 
     // add as a message handler
     if (this.s2sMessagingService != null) {

+ 1 - 1
packages/app/src/server/models/config.ts

@@ -116,6 +116,7 @@ export const defaultCrowiConfigs: { [key: string]: any } = {
   'customize:highlightJsStyle' : 'github',
   'customize:highlightJsStyleBorder' : false,
   'customize:theme' : PresetThemes.DEFAULT,
+  'customize:theme:forcedColorScheme' : null,
   'customize:isContainerFluid' : false,
   'customize:isEnabledTimeline' : true,
   'customize:isEnabledAttachTitleHeader' : false,
@@ -229,7 +230,6 @@ schema.statics.getLocalconfig = function(crowi) {
       DRAWIO_URI: env.DRAWIO_URI || null,
       HACKMD_URI: env.HACKMD_URI || null,
       MATHJAX: env.MATHJAX || null,
-      NO_CDN: env.NO_CDN || null,
       GROWI_CLOUD_URI: env.GROWI_CLOUD_URI || null,
       GROWI_APP_ID_FOR_GROWI_CLOUD: env.GROWI_APP_ID_FOR_GROWI_CLOUD || null,
     },

+ 1 - 0
packages/app/src/server/routes/apiv3/customize-setting.js

@@ -325,6 +325,7 @@ module.exports = (crowi) => {
       const customizedParams = {
         theme: await crowi.configManager.getConfig('crowi', 'customize:theme'),
       };
+      customizeService.initGrowiTheme();
       const parameters = { action: SupportedAction.ACTION_ADMIN_THEME_UPDATE };
       activityEvent.emit('update', res.locals.activity._id, parameters);
       return res.apiv3({ customizedParams });

+ 0 - 6
packages/app/src/server/service/config-loader.ts

@@ -73,12 +73,6 @@ const ENV_VAR_NAME_TO_CONFIG_INFO = {
     type:    ValueType.STRING,
     default: null,
   },
-  NO_CDN: {
-    ns:      'crowi',
-    key:     'app:noCdn',
-    type:    ValueType.STRING,
-    default: null,
-  },
   // PLANTUML_URI: {
   //   ns:      ,
   //   key:     ,

+ 41 - 1
packages/app/src/server/service/customize.ts

@@ -1,5 +1,6 @@
 // eslint-disable-next-line no-unused-vars
-import { DevidedPagePath } from '@growi/core';
+import { ColorScheme, DevidedPagePath, getForcedColorScheme } from '@growi/core';
+import { DefaultThemeMetadata, PresetThemesMetadatas } from '@growi/preset-themes';
 import uglifycss from 'uglifycss';
 
 import loggerFactory from '~/utils/logger';
@@ -7,8 +8,10 @@ import loggerFactory from '~/utils/logger';
 import S2sMessage from '../models/vo/s2s-message';
 
 import ConfigManager from './config-manager';
+import type { IPluginService } from './plugin';
 import { S2sMessageHandlable } from './s2s-messaging/handlable';
 
+
 const logger = loggerFactory('growi:service:CustomizeService');
 
 
@@ -25,17 +28,26 @@ class CustomizeService implements S2sMessageHandlable {
 
   xssService: any;
 
+  pluginService: IPluginService;
+
   lastLoadedAt?: Date;
 
   customCss?: string;
 
   customTitleTemplate!: string;
 
+  theme: string;
+
+  themeHref: string;
+
+  forcedColorScheme?: ColorScheme;
+
   constructor(crowi) {
     this.configManager = crowi.configManager;
     this.s2sMessagingService = crowi.s2sMessagingService;
     this.appService = crowi.appService;
     this.xssService = crowi.xssService;
+    this.pluginService = crowi.pluginService;
   }
 
   /**
@@ -60,6 +72,7 @@ class CustomizeService implements S2sMessageHandlable {
     await configManager.loadConfigs();
     this.initCustomCss();
     this.initCustomTitle();
+    this.initGrowiTheme();
   }
 
   async publishUpdatedMessage() {
@@ -137,6 +150,33 @@ class CustomizeService implements S2sMessageHandlable {
     return this.xssService.process(customTitle);
   }
 
+  async initGrowiTheme(): Promise<void> {
+    const theme = this.configManager.getConfig('crowi', 'customize:theme');
+
+    this.theme = theme;
+
+    const resultForThemePlugin = await this.pluginService.findThemePlugin(theme);
+
+    if (resultForThemePlugin != null) {
+      this.forcedColorScheme = getForcedColorScheme(resultForThemePlugin.themeMetadata.schemeType);
+      this.themeHref = resultForThemePlugin.themeHref;
+    }
+    // retrieve preset theme
+    else {
+      // import preset-themes manifest
+      const presetThemesManifest = await import('@growi/preset-themes/dist/themes/manifest.json').then(imported => imported.default);
+
+      const themeMetadata = PresetThemesMetadatas.find(p => p.name === theme);
+      this.forcedColorScheme = getForcedColorScheme(themeMetadata?.schemeType);
+
+      const manifestKey = themeMetadata?.manifestKey ?? DefaultThemeMetadata.manifestKey;
+      if (themeMetadata == null || !(themeMetadata.manifestKey in presetThemesManifest)) {
+        logger.warn(`Use default theme because the key for '${theme} does not exist in preset-themes manifest`);
+      }
+      this.themeHref = `/static/preset-themes/${presetThemesManifest[manifestKey].file}`; // configured by express.static
+    }
+
+  }
 
 }
 

+ 18 - 7
packages/app/src/server/service/plugin.ts

@@ -33,9 +33,16 @@ function retrievePluginManifest(growiPlugin: GrowiPlugin): ViteManifest {
   return JSON.parse(manifestStr);
 }
 
+
+type FindThemePluginResult = {
+  growiPlugin: GrowiPlugin,
+  themeMetadata: GrowiThemeMetadata,
+  themeHref: string,
+}
+
 export interface IPluginService {
   install(origin: GrowiPluginOrigin): Promise<string>
-  retrieveThemeHref(theme: string): Promise<string | undefined>
+  findThemePlugin(theme: string): Promise<FindThemePluginResult | null>
   retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries>
   downloadNotExistPluginRepositories(): Promise<void>
 }
@@ -322,8 +329,7 @@ export class PluginService implements IPluginService {
     return growiPlugins.meta.name;
   }
 
-  async retrieveThemeHref(theme: string): Promise<string | undefined> {
-
+  async findThemePlugin(theme: string): Promise<FindThemePluginResult | null> {
     const GrowiPlugin = mongoose.model('GrowiPlugin') as GrowiPluginModel;
 
     let matchedPlugin: GrowiPlugin | undefined;
@@ -349,15 +355,20 @@ export class PluginService implements IPluginService {
       logger.error(`Could not find the theme '${theme}' from GrowiPlugin documents.`, e);
     }
 
+    if (matchedPlugin == null || matchedThemeMetadata == null) {
+      return null;
+    }
+
+    let themeHref;
     try {
-      if (matchedPlugin != null && matchedThemeMetadata != null) {
-        const manifest = await retrievePluginManifest(matchedPlugin);
-        return `${PLUGINS_STATIC_DIR}/${matchedPlugin.installedPath}/dist/${manifest[matchedThemeMetadata.manifestKey].file}`;
-      }
+      const manifest = retrievePluginManifest(matchedPlugin);
+      themeHref = `${PLUGINS_STATIC_DIR}/${matchedPlugin.installedPath}/dist/${manifest[matchedThemeMetadata.manifestKey].file}`;
     }
     catch (e) {
       logger.error(`Could not read manifest file for the theme '${theme}'`, e);
     }
+
+    return { growiPlugin: matchedPlugin, themeMetadata: matchedThemeMetadata, themeHref };
   }
 
   async retrieveAllPluginResourceEntries(): Promise<GrowiPluginResourceEntries> {

+ 7 - 3
packages/app/src/stores/admin/customize.tsx

@@ -27,7 +27,10 @@ export const useSWRxLayoutSetting = (): SWRResponse<IResLayoutSetting, Error> &
   };
 };
 
-export const useSWRxGrowiThemeSetting = (): SWRResponse<IResGrowiTheme, Error> => {
+type UpdateThemeArgs = {
+  theme: string,
+}
+export const useSWRxGrowiThemeSetting = (): SWRResponse<IResGrowiTheme, Error> & updateConfigMethodForAdmin<UpdateThemeArgs> => {
 
   const fetcher = useCallback(async() => {
     const res = await apiv3Get<IResGrowiTheme>('/customize-setting/theme');
@@ -36,8 +39,9 @@ export const useSWRxGrowiThemeSetting = (): SWRResponse<IResGrowiTheme, Error> =
 
   const swrResponse = useSWR('/customize-setting/theme', fetcher);
 
-  const update = async(theme: string) => {
-    await apiv3Put('/customize-setting/layout', { theme });
+  const update = async({ theme }: UpdateThemeArgs) => {
+
+    await apiv3Put('/customize-setting/theme', { theme });
 
     if (swrResponse.data == null) {
       swrResponse.mutate();

+ 5 - 1
packages/app/src/stores/context.tsx

@@ -1,4 +1,4 @@
-import { IUser } from '@growi/core';
+import type { ColorScheme, IUser } from '@growi/core';
 import { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
@@ -209,6 +209,10 @@ export const useIsCustomizedLogoUploaded = (initialData?: boolean): SWRResponse<
   return useStaticSWR('isCustomizedLogoUploaded', initialData);
 };
 
+export const useForcedColorScheme = (initialData?: ColorScheme): SWRResponse<ColorScheme, Error> => {
+  return useContextSWR('forcedColorScheme', initialData);
+};
+
 export const useGrowiCloudUri = (initialData?: string): SWRResponse<string, Error> => {
   return useStaticSWR('growiCloudUri', initialData);
 };

+ 19 - 26
packages/app/src/stores/page.tsx

@@ -32,19 +32,20 @@ export const useSWRxPage = (
   const swrResponse = useSWRImmutable<IPagePopulatedToShowRevision|null, Error>(
     pageId != null ? ['/page', pageId, shareLinkId, revisionId] : null,
     // TODO: upgrade SWR to v2 and use useSWRMutation
-    //        in order to trigger mutation manually
-    (endpoint, pageId, shareLinkId, revisionId) => apiv3Get<{ page: IPagePopulatedToShowRevision }>(endpoint, { pageId, shareLinkId, revisionId })
-      .then(result => result.data.page)
-      .catch((errs) => {
-        if (!Array.isArray(errs)) { throw Error('error is not array') }
-        const statusCode = errs[0].status;
-        if (statusCode === 403 || statusCode === 404) {
-          // for NotFoundPage
-          return null;
-        }
-        throw Error('failed to get page');
-      }),
-    config,
+    //        in order to avoid complicated fetcher settings
+    Object.assign({
+      fetcher: (endpoint, pageId, shareLinkId, revisionId) => apiv3Get<{ page: IPagePopulatedToShowRevision }>(endpoint, { pageId, shareLinkId, revisionId })
+        .then(result => result.data.page)
+        .catch((errs) => {
+          if (!Array.isArray(errs)) { throw Error('error is not array') }
+          const statusCode = errs[0].status;
+          if (statusCode === 403 || statusCode === 404) {
+            // for NotFoundPage
+            return null;
+          }
+          throw Error('failed to get page');
+        }),
+    }, config ?? {}),
   );
 
   useEffect(() => {
@@ -92,22 +93,14 @@ export const useSWRxCurrentPage = (initialData?: IPagePopulatedToShowRevision|nu
 
 
 export const useSWRxTagsInfo = (pageId: Nullable<string>): SWRResponse<IPageTagsInfo | undefined, Error> => {
+  const { data: shareLinkId } = useShareLinkId();
 
   const endpoint = `/pages.getPageTag?pageId=${pageId}`;
-  const key = [endpoint, pageId];
-
-  const fetcher = async(endpoint: string, pageId: Nullable<string>) => {
-    let tags: string[] = [];
-    // when the page exists
-    if (pageId != null) {
-      const res = await apiGet<IPageTagsInfo>(endpoint, { pageId });
-      tags = res?.tags;
-    }
 
-    return { tags };
-  };
-
-  return useSWRImmutable(key, fetcher);
+  return useSWRImmutable<IPageTagsInfo | undefined, Error>(
+    shareLinkId == null && pageId != null ? [endpoint, pageId] : null,
+    (endpoint, pageId) => apiGet<IPageTagsInfo>(endpoint, { pageId }).then(result => result),
+  );
 };
 
 export const usePageInfoTermManager = (isDisabled?: boolean) : SWRResponse<number, Error> & ITermNumberManagerUtil => {

+ 2 - 2
packages/app/src/stores/ui.tsx

@@ -465,8 +465,8 @@ export const useIsAbleToShowTagLabel = (): SWRResponse<boolean, Error> => {
   );
 };
 
-export const useIsAbleToShowPageEditorModeManager = (): SWRResponse<boolean, Error> => {
-  const key = 'isAbleToShowPageEditorModeManager';
+export const useIsAbleToChangeEditorMode = (): SWRResponse<boolean, Error> => {
+  const key = 'isAbleToChangeEditorMode';
   const { data: isEditable } = useIsEditable();
   const { data: isSharedUser } = useIsSharedUser();
 

+ 14 - 19
packages/app/src/stores/use-next-themes.tsx

@@ -1,35 +1,30 @@
-import { isClient } from '@growi/core';
+import { isClient, ColorScheme } from '@growi/core';
 import { ThemeProvider, useTheme } from 'next-themes';
 import { ThemeProviderProps, UseThemeProps } from 'next-themes/dist/types';
 
+import { useForcedColorScheme } from './context';
+
 export const Themes = {
-  light: 'light',
-  dark: 'dark',
-  system: 'system',
+  ...ColorScheme,
+  SYSTEM: 'system',
 } as const;
 export type Themes = typeof Themes[keyof typeof Themes];
 
-export const ResolvedThemes = {
-  light: Themes.light,
-  dark: Themes.dark,
-} as const;
-export type ResolvedThemes = typeof ResolvedThemes[keyof typeof ResolvedThemes];
-export const ColorScheme = ResolvedThemes;
-export type ColorScheme = ResolvedThemes;
-
 
 const ATTRIBUTE = 'data-theme';
 
 export const NextThemesProvider: React.FC<ThemeProviderProps> = (props) => {
-  return <ThemeProvider {...props} attribute={ATTRIBUTE} />;
+  const { data: forcedColorScheme } = useForcedColorScheme();
+
+  return <ThemeProvider {...props} forcedTheme={forcedColorScheme} attribute={ATTRIBUTE} />;
 };
 
 type UseThemeExtendedProps = Omit<UseThemeProps, 'theme'|'resolvedTheme'> & {
   theme: Themes,
-  resolvedTheme: ResolvedThemes,
+  resolvedTheme: ColorScheme,
   useOsSettings: boolean,
   isDarkMode: boolean,
-  resolvedThemeByAttributes?: ResolvedThemes,
+  resolvedThemeByAttributes?: ColorScheme,
 }
 
 export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
@@ -37,9 +32,9 @@ export const useNextThemes = (): UseThemeProps & UseThemeExtendedProps => {
 
   return Object.assign(props, {
     theme: props.theme as Themes,
-    resolvedTheme: props.resolvedTheme as ResolvedThemes,
-    useOsSettings: props.theme === Themes.system,
-    isDarkMode: props.resolvedTheme === ResolvedThemes.dark,
-    resolvedThemeByAttributes: isClient() ? document.documentElement.getAttribute(ATTRIBUTE) as ResolvedThemes : undefined,
+    resolvedTheme: props.resolvedTheme as ColorScheme,
+    useOsSettings: props.theme === Themes.SYSTEM,
+    isDarkMode: props.resolvedTheme === ColorScheme.DARK,
+    resolvedThemeByAttributes: isClient() ? document.documentElement.getAttribute(ATTRIBUTE) as ColorScheme : undefined,
   });
 };

+ 0 - 32
packages/app/src/styles/_layout.scss

@@ -71,38 +71,6 @@ body.not-found-page .grw-container-convertible {
   top: calc(100px + 4px + 20px);
 }
 
-.grw-fab {
-  position: fixed;
-  right: 1.5rem;
-  bottom: 3rem;
-  z-index: bs.$zindex-fixed;
-
-  transition: all 200ms linear;
-
-  .btn-create-page {
-    width: 60px;
-    height: 60px;
-    font-size: 24px;
-
-    box-shadow: 2px 3px 6px #0000005d;
-    svg {
-      width: 28px;
-      height: 28px;
-    }
-  }
-
-  .btn-scroll-to-top {
-    width: 40px;
-    height: 40px;
-
-    opacity: 0.4;
-    svg {
-      width: 18px;
-      height: 18px;
-    }
-  }
-}
-
 // printable style
 @media print {
   body {

+ 0 - 1
packages/app/src/styles/bootstrap/_variables.scss

@@ -90,7 +90,6 @@ $border-radius-lg: 8px;
 // Buttons
 //
 // For each of Bootstrap's buttons, define text, background, and border color.
-$btn-link-disabled-color: $gray-500;
 $btn-focus-box-shadow: none;
 $btn-active-box-shadow: none;
 

+ 469 - 429
packages/app/src/styles/theme/_apply-colors-dark.scss

@@ -2,537 +2,577 @@
 @use '../bootstrap/init' as *;
 @use '../atoms/mixins/buttons' as mixins-buttons;
 @use './mixins/count-badge';
+@use './mixins/hsl-button';
+@use './hsl-functions' as hsl;
 
 // determine optional variables
-$color-list: $color-global !default;
-$bgcolor-list: $bgcolor-global !default;
-$color-list-hover: $color-global !default;
-$bgcolor-list-hover: lighten($bgcolor-global, 8%) !default;
-$color-list-active: $color-reversal !default;
-$bgcolor-list-active: $primary !default;
-$bgcolor-subnav: lighten($bgcolor-global, 3%) !default;
-$color-table: white !default;
-$bgcolor-table: #343a40 !default;
-$border-color-table: lighten($bgcolor-table, 7.5%) !default;
-$color-table-hover: rgba(white, 0.075) !default;
-$bgcolor-table-hover: lighten($bgcolor-table, 7.5%) !default;
-$bgcolor-sidebar-list-group: $bgcolor-list !default;
-$color-tags: #949494 !default;
-$bgcolor-tags: $dark !default;
-$border-color-global: $gray-500 !default;
-$border-color-toc: $border-color-global !default;
-$color-dropdown: $color-global !default;
-$bgcolor-dropdown: $bgcolor-global !default;
-$color-dropdown-link: $color-global !default;
-$color-dropdown-link-hover: $light !default;
-$bgcolor-dropdown-link-hover: lighten($bgcolor-global, 15%) !default;
-$color-dropdown-link-active: $light !default;
-$bgcolor-dropdown-link-active: $primary !default;
-
-// override bootstrap variables
-$text-muted: $gray-550;
-$table-dark-color: $color-table;
-$table-dark-bg: $bgcolor-table;
-$table-dark-border-color: $border-color-table;
-$table-dark-hover-color: $color-table-hover;
-$table-dark-hover-bg: $bgcolor-table-hover;
-$border-color: $border-color-global;
-$dropdown-color: $color-dropdown;
-$dropdown-bg: $bgcolor-dropdown;
-$dropdown-link-color: $color-dropdown-link;
-$dropdown-link-hover-color: $color-dropdown-link-hover;
-$dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
-$dropdown-link-active-color: $color-dropdown-link-active;
-$dropdown-link-active-bg: $bgcolor-dropdown-link-active;
-
-@import './mixins/list-group';
-@import './reboot-bootstrap-text';
-@import './reboot-bootstrap-border-colors';
-@import './reboot-bootstrap-tables';
-@import './reboot-bootstrap-dropdown';
-
-// List Group
-@include override-list-group-item($color-list, $bgcolor-list, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-
-/*
-  * Form
-  */
-input.form-control,
-select.form-control,
-select.custom-select,
-textarea.form-control {
-  color: $color-global;
-  background-color: darken($bgcolor-global, 5%);
-  border-color: $border-color-global;
-  &:focus {
-    background-color: $bgcolor-global;
-  }
-  // FIXME: accent color
-  // border: 1px solid darken($border, 30%);
-}
-
-.form-control[disabled],
-.form-control[readonly] {
-  color: lighten($color-global, 10%);
-  background-color: lighten($bgcolor-global, 5%);
-}
-
-.input-group > .input-group-prepend > .input-group-text {
-  color: theme-color('light');
-  background-color: theme-color('secondary');
-  border: 1px solid theme-color('secondary');
-  border-right: none;
-  &.text-muted {
-    color: theme-color('light') !important;
+:root[data-theme='dark'] {
+  $color-list: var(--color-list,var(--color-global));
+  $bgcolor-list: var(--bgcolor-list,var(--bgcolor-global));
+  $color-list-hover: var(--color-list-hover,var(--color-global));
+  $color-list-active: var(--color-list-active,var(--color-reversal));
+  $bgcolor-list-hover: var(--bgcolor-list-hover,var(--bgcolor-global));
+  $bgcolor-list-active: var(--bgcolor-list-active,var(--primary));
+  $bgcolor-subnav: var(--bgcolor-subnav);
+  $color-table: var(--color-table,white);
+  $bgcolor-table: var(--bgcolor-table,#343a40);
+  $border-color-table: var(--border-color-table,hsl.lighten(var(--bgcolor-table),7.5%),lighten(#343a40, 7.5%));
+  $color-table-hover: var(--color-table-hover,rgba(white, 0.075));
+  $bgcolor-table-hover: var(--bgcolor-table-hover,hsl.lighten(var(--bgcolor-table),7.5%),lighten(#343a40, 7.5%));
+  $bgcolor-sidebar-list-group: var(--bgcolor-sidebar-list-group,var(--bgcolor-list));
+  $color-tags: var(--color-tags,#949494);
+  $bgcolor-tags: var(--bgcolor-tags,var(--dark));
+  $border-color-global: var(--border-color-global,#{$gray-500});
+  $border-color-toc: var(--border-color-toc,#{$border-color-global});
+  $color-dropdown: var(--color-dropdown,var(--color-global));
+  $bgcolor-dropdown: var(--bgcolor-dropdown,var(--bgcolor-global));
+  $color-dropdown-link: var(--color-dropdown-link,var(--color-global));
+  $color-dropdown-link-hover: var(--color-dropdown-link-hover,var(--light));
+  $bgcolor-dropdown-link-hover: var(--bgcolor-dropdown-link-hover,var(--primary));
+  $color-dropdown-link-active: var(--color-dropdown-link-active,var(--light));
+  $bgcolor-dropdown-link-active: var(--bgcolor-dropdown-link-active,var(--primary));
+
+  // override bootstrap variables
+  $text-muted: $gray-550;
+  $table-dark-color: $color-table;
+  $table-dark-bg: $bgcolor-table;
+  $table-dark-border-color: $border-color-table;
+  $table-dark-hover-color: $color-table-hover;
+  $table-dark-hover-bg: $bgcolor-table-hover;
+  $border-color: $border-color-global;
+  $dropdown-color: $color-dropdown;
+  $dropdown-bg: $bgcolor-dropdown;
+  $dropdown-link-color: $color-dropdown-link;
+  $dropdown-link-hover-color: $color-dropdown-link-hover;
+  $dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
+  $dropdown-link-active-color: $color-dropdown-link-active;
+  $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
+
+  @import './mixins/list-group';
+  @import './reboot-bootstrap-text';
+  @import './reboot-bootstrap-border-colors';
+  @import './reboot-bootstrap-tables';
+  @import './reboot-bootstrap-theme-colors';
+  @import 'hsl-reboot-bootstrap-theme-colors';
+  @import './reboot-bootstrap-dropdown';
+
+  // List Group
+  @include override-list-group-item(
+    $color-list,
+    $bgcolor-sidebar-list-group,
+    $color-list-hover,
+    $bgcolor-list-hover,
+    $color-list-active,
+    $bgcolor-list-active
+  );
+  /*
+    * Form
+    */
+  input.form-control,
+  select.form-control,
+  select.custom-select,
+  textarea.form-control {
+    color: var(--color-global);
+    background-color: hsl.darken(var(--bgcolor-global), 5%);
+    border-color: $border-color-global;
+    &:focus {
+      background-color: var(--bgcolor-global);
+    }
+    // FIXME: accent color
+    // border: 1px solid darken($border, 30%);
   }
-}
-
-.input-group input {
-  border-color: $border-color-global;
-}
 
-label.custom-control-label::before {
-  background-color: darken($bgcolor-global, 5%);
-}
+  .form-control[disabled],
+  .form-control[readonly] {
+    color: hsl.lighten(var(--color-global),10%);
+    background-color: hsl.lighten(var(--bgcolor-global),5%);
+  }
 
-/*
- * Table
- */
-.table {
-  @extend .table-dark;
-}
+  .input-group > .input-group-prepend > .input-group-text {
+    color: theme-color('light');
+    background-color: theme-color('secondary');
+    border: 1px solid theme-color('secondary');
+    border-right: none;
+    &.text-muted {
+      color: theme-color('light') !important;
+    }
+  }
 
-/*
- * Card
- */
-.card:not([class*='bg-']):not(.well):not(.card-disabled) {
-  @extend .bg-dark;
-}
+  .input-group input {
+    border-color: $border-color-global;
+  }
 
-// [TODO] GW-3219 modify common color of well in dark theme, then remove below css.
-.card.well {
-  border-color: $secondary;
-}
+  label.custom-control-label::before {
+    background-color: hsl.darken(var(--bgcolor-global),5%);
+  }
 
-.card.card-disabled {
-  background-color: lighten($dark, 10%);
-  border-color: $secondary;
-}
+  /*
+  * Table
+  */
+  .table {
+    @extend .table-dark;
+  }
 
-/*
- * Pagination
- */
-ul.pagination {
-  li.page-item {
-    button.page-link {
-      @extend .btn-dark;
-    }
+  /*
+  * Card
+  */
+  .card:not([class*=‘bg-’]):not(.well):not(.card-disabled) {
+    @extend .bg-dark;
   }
-}
 
-/*
- * GROWI Login form
- */
-.nologin {
-  // background color
-  $color-gradient: #3c465c;
-  background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-    linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
-    linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
-    linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
+  // [TODO] GW-3219 modify common color of well in dark theme, then remove below css.
+  .card.well {
+    border-color: var(--secondary);
+  }
 
-  .nologin-header {
-    background-color: rgba(black, 0.5);
+  .card.card-disabled {
+    background-color: lighten($dark, 10%);
+    border-color: var(--secondary);
+  }
 
-    .logo {
-      background-color: rgba(white, 0);
-      fill: rgba(white, 0.5);
+  /*
+  * Pagination
+  */
+  ul.pagination {
+    li.page-item.disabled {
+      button.page-link {
+        color: $gray-400;
+      }
     }
-
-    h1 {
-      color: rgba(white, 0.5);
+    li.page-item.active {
+      button.page-link {
+        color: hsl.contrast(var(--primary));
+        background-color: var(--primary);
+        &:hover,
+        &:focus {
+          color: hsl.contrast(var(--primary));
+          background-color: var(--primary);
+        }
+      }
+    }
+    li.page-item {
+      button.page-link {
+        @extend .btn-dark;
+        color: var(--primary);
+      }
     }
   }
 
-  .nologin-dialog {
-    background-color: rgba(black, 0.5);
-  }
+  /*
+  * GROWI Login form
+  */
+  .nologin {
+    // background color
+    $color-gradient: #3c465c;
+    background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
+      linear-gradient(135deg, darken(var.$growi-green, 30%) 10%, hsla(225, 95%, 50%, 0) 70%),
+      linear-gradient(225deg, darken(var.$growi-blue, 20%) 10%, hsla(140, 90%, 50%, 0) 80%),
+      linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
+
+    .nologin-header {
+      background-color: rgba(black, 0.5);
+
+      .logo {
+        background-color: rgba(white, 0);
+        fill: rgba(white, 0.5);
+      }
 
-  .input-group {
-    .input-group-text {
-      color: darken(white, 30%);
-      background-color: rgba($gray-700, 0.7);
+      h1 {
+        color: rgba(white, 0.5);
+      }
     }
 
-    .form-control {
-      color: white;
-      background-color: rgba(#505050, 0.7);
-      box-shadow: unset;
+    .nologin-dialog {
+      background-color: rgba(black, 0.5);
+    }
 
-      &::placeholder {
+    .input-group {
+      .input-group-text {
         color: darken(white, 30%);
+        background-color: rgba($gray-700, 0.7);
       }
-    }
-  }
 
-  .btn-fill {
-    .btn-label {
-      color: $gray-300;
+      .form-control {
+        color: white;
+        background-color: rgba(#505050, 0.7);
+        box-shadow: unset;
+
+        &::placeholder {
+          color: darken(white, 30%);
+        }
+      }
     }
-    .btn-label-text {
-      color: $gray-400;
+
+    .btn-fill {
+      .btn-label {
+        color: $gray-300;
+      }
+      .btn-label-text {
+        color: $gray-400;
+      }
     }
-  }
 
-  .grw-external-auth-form {
-    border-color: gray !important;
-  }
+    .grw-external-auth-form {
+      border-color: gray !important;
+    }
 
-  .btn-external-auth-tab {
-    @extend .btn-dark;
-  }
+    .btn-external-auth-tab {
+      @extend .btn-dark;
+    }
 
-  // footer link text
-  .link-growi-org {
-    color: rgba(white, 0.4);
+    // footer link text
+    .link-growi-org {
+      color: rgba(white, 0.4);
 
-    &:hover,
-    &.focus {
-      color: rgba(white, 0.7);
+      &:hover,
+      &.focus {
+        color: rgba(white, 0.7);
 
-      .growi {
-        color: darken(var.$growi-green, 5%);
-      }
+        .growi {
+          color: darken(var.$growi-green, 5%);
+        }
 
-      .org {
-        color: darken(var.$growi-blue, 5%);
+        .org {
+          color: darken(var.$growi-blue, 5%);
+        }
       }
     }
   }
-}
 
-/*
- * GROWI subnavigation
- */
-.grw-drawer-toggler {
-  @extend .btn-dark;
-  color: $gray-400;
-}
+  /*
+  * GROWI subnavigation
+  */
+  .grw-drawer-toggler {
+    @include button-variant($dark, $dark);
+    @include mixins-buttons.button-svg-icon-variant($dark, $dark);
+    color: $gray-400;
+    box-shadow: none !important;
 
-/*
- * GROWI page list
- */
-.page-list {
-  .page-list-ul {
-    > li {
-      > span.page-list-meta {
-        color: darken($color-global, 10%);
-      }
-    }
   }
 
-  // List group
-  .list-group-item {
-    &.active {
-      background-color: lighten($bgcolor-global, 9%) !important;
-    }
-    .list-group-item-action:hover {
-      background-color: $bgcolor-list-hover;
+  /*
+  * GROWI page list
+  */
+  .page-list {
+    .page-list-ul {
+      > li {
+        > span.page-list-meta {
+          color: hsl.darken(var(--color-global),10%);
+        }
+      }
     }
-    .page-list-snippet {
-      color: darken($body-color, 10%);
+
+    // List group
+    .list-group-item {
+      &.active {
+        background-color: hsl.lighten(var(--bgcolor-global),10%) !important;
+      }
+      &.list-group-item-action:hover {
+        background-color: hsl.lighten(var(--bgcolor-global),10%) !important;
+      }
+      .page-list-snippet {
+        color: hsl.darken(var(--color-global),10%);
+      }
     }
   }
-}
 
-/*
- * GROWI ToC
- */
-.revision-toc-content {
-  ::marker {
-    color: lighten($bgcolor-global, 30%);
+  /*
+  * GROWI ToC
+  */
+  .revision-toc-content {
+    ::marker {
+      color: hsl.lighten(var(--color-global),30%);
+    }
   }
-}
 
-/*
- * GROWI subnavigation
- */
-.grw-subnav {
-  background-color: $bgcolor-subnav;
-}
+  /*
+  * GROWI subnavigation
+  */
+  .grw-subnav {
+    background-color: var(--bgcolor-subnav);
+  }
 
-.grw-subnav-fixed-container .grw-subnav {
-  background-color: rgba($bgcolor-subnav, 0.85);
-}
+  .grw-subnav-fixed-container .grw-subnav {
+    background-color: hsl.alpha(var(--bgcolor-subnav),85%);
+  }
 
-.grw-page-editor-mode-manager {
-  .btn-outline-primary {
-    &:hover {
-      color: $primary;
-      background-color: $gray-700;
+  .grw-page-editor-mode-manager {
+    .btn-outline-primary {
+      &:hover {
+        color: var(--primary);
+        background-color: $gray-700;
+      }
     }
   }
-}
 
-// Search drop down
-#search-typeahead-asynctypeahead {
-  background-color: $bgcolor-global;
-  .table {
-    background-color: transparent;
+  // Search drop down
+  #search-typeahead-asynctypeahead {
+    background-color: var(--bgcolor-global);
+    .table {
+      background-color: transparent;
+    }
   }
-}
 
-/*
- * GROWI Sidebar
- */
-.grw-sidebar {
-  // List
-  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-
-  // Pagetree
-  .grw-pagetree {
-    @include override-list-group-item-for-pagetree(
-      $color-sidebar-context,
-      lighten($bgcolor-sidebar-context, 8%),
-      lighten($bgcolor-sidebar-context, 15%),
-      darken($color-sidebar-context, 15%),
-      darken($color-sidebar-context, 10%),
-      lighten($bgcolor-sidebar-context, 18%),
-      lighten($bgcolor-sidebar-context, 24%)
+  /*
+  * GROWI Sidebar
+  */
+  .grw-sidebar {
+    --gray-500: hsl(var(--gray-500-hs),var(--gray-500-l));
+    --gray-500-hs: 210,13%;
+    --gray-500-l: 61%;
+    // List
+    @include override-list-group-item(
+      $color-list,
+      $bgcolor-sidebar-list-group,
+      $color-list-hover,
+      $bgcolor-list-hover,
+      $color-list-active,
+      $bgcolor-list-active
     );
-    .grw-pagetree-triangle-btn {
-      @include mixins-buttons.button-outline-svg-icon-variant($secondary, $gray-200);
-    }
-    .btn-page-item-control {
-      @include button-outline-variant($gray-500, $gray-500, $secondary, transparent);
-      @include hover() {
-        background-color: lighten($bgcolor-sidebar-context, 20%);
+    // Pagetree
+    .grw-pagetree {
+      @include override-list-group-item-for-pagetree(
+        var(--color-sidebar-context),
+        hsl.lighten(var(--bgcolor-sidebar-context),8%),
+        hsl.lighten(var(--bgcolor-sidebar-context),15%),
+        hsl.darken(var(--color-sidebar-context),15%),
+        hsl.darken(var(--color-sidebar-context),10%),
+        hsl.lighten(var(--bgcolor-sidebar-context),18%),
+        hsl.lighten(var(--bgcolor-sidebar-context),24%)
+      );
+      .grw-pagetree-triangle-btn {
+        @include mixins-buttons.button-outline-svg-icon-variant(var(--secondary), $gray-200);
       }
-      &:not(:disabled):not(.disabled):active,
-      &:not(:disabled):not(.disabled).active {
-        background-color: lighten($bgcolor-sidebar-context, 34%);
+      .btn-page-item-control {
+        @include hsl-button.button-outline-variant(var(--gray-500), var(--gray-500), var(--secondary), transparent);
+        @include hover() {
+          background-color: hsl.lighten(var(--bgcolor-sidebar-context),20%);
+        }
+        &:not(:disabled):not(.disabled):active,
+        &:not(:disabled):not(.disabled).active {
+          background-color: hsl.lighten(var(--bgcolor-sidebar-context),34%);
+        }
+        box-shadow: none !important;
       }
-      box-shadow: none !important;
     }
-  }
-  .private-legacy-pages-link {
-    &:hover {
-      background: $bgcolor-list-hover;
+    .private-legacy-pages-link {
+      &:hover {
+        background: var(--bgcolor-list-hover);
+      }
     }
   }
-}
 
-.btn.btn-page-item-control {
-  @include button-outline-variant($gray-500, $gray-500, $secondary, transparent);
-  @include hover() {
-    background-color: $gray-700;
-  }
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active {
-    color: $gray-200;
-    background-color: $gray-600;
-  }
-  box-shadow: none !important;
-}
-
-/*
- * Popover
- */
-.popover {
-  background-color: $bgcolor-global;
-  border-color: $secondary;
-  .popover-header {
-    color: $white;
-    background-color: $secondary;
-    border-color: $secondary;
-  }
-  .popover-body {
-    color: inherit;
+  .btn.btn-page-item-control {
+    @include hsl-button.button-outline-variant(var(--gray-500), var(--gray-500), var(--secondary), transparent);
+    @include hover() {
+      background-color: $gray-700;
+    }
+    &:not(:disabled):not(.disabled):active,
+    &:not(:disabled):not(.disabled).active {
+      color: $gray-200;
+      background-color: $gray-600;
+    }
+    box-shadow: none !important;
   }
 
-  &.bs-popover-top .arrow {
-    &::before {
-      border-top-color: $secondary;
+  /*
+  * Popover
+  */
+  .popover {
+    background-color: var(--bgcolor-global);
+    border-color: var(--secondary);
+    .popover-header {
+      color: white;
+      background-color: var(--secondary);
+      border-color: var(--secondary);
     }
-
-    &::after {
-      border-top-color: $bgcolor-global;
+    .popover-body {
+      color: inherit;
     }
-  }
-  &.bs-popover-bottom .arrow {
-    &::before {
-      border-bottom-color: $secondary;
+
+    &.bs-popover-top .arrow {
+      &::before {
+        border-top-color: var(--secondary);
+      }
+
+      &::after {
+        border-top-color: var(--bgcolor-global);
+      }
     }
+    &.bs-popover-bottom .arrow {
+      &::before {
+        border-bottom-color: var(--secondary);
+      }
 
-    &::after {
-      border-bottom-color: $bgcolor-global;
+      &::after {
+        border-bottom-color: var(--bgcolor-global);
+      }
     }
-  }
-  &.bs-popover-right .arrow {
-    &::before {
-      border-right-color: $secondary;
+    &.bs-popover-right .arrow {
+      &::before {
+        border-right-color: var(--secondary);
+      }
+
+      &::after {
+        border-right-color: var(--bgcolor-global);
+      }
     }
+    &.bs-popover-left .arrow {
+      &::before {
+        border-left-color: var(--secondary);
+      }
 
-    &::after {
-      border-right-color: $bgcolor-global;
+      &::after {
+        border-left-color: var(--bgcolor-global);
+      }
     }
   }
-  &.bs-popover-left .arrow {
-    &::before {
-      border-left-color: $secondary;
-    }
 
-    &::after {
-      border-left-color: $bgcolor-global;
-    }
+  /*
+  * GROWI Grid Edit Modal
+  */
+  .grw-grid-edit-preview {
+    background: $gray-900;
   }
-}
 
-/*
- * GROWI Grid Edit Modal
- */
-.grw-grid-edit-preview {
-  background: $gray-900;
-}
+  /*
+  * Slack
+  */
+  .grw-slack-notification {
+    background-color: transparent;
+    $color-slack: #4b144c;
 
-/*
- * Slack
- */
-.grw-slack-notification {
-  background-color: transparent;
-  $color-slack: #4b144c;
+    .form-control {
+      background: var(--bgcolor-global);
+    }
 
-  .form-control {
-    background: $bgcolor-global;
-  }
+    .custom-control-label {
+      &::before {
+        background-color: var(--secondary);
+        border-color: transparent;
+      }
+      &::after {
+        background-color: darken($color-slack, 5%);
+        background-image: url(/images/icons/slack/slack-logo-dark-off.svg);
+      }
+    }
 
-  .custom-control-label {
-    &::before {
-      background-color: $secondary;
-      border-color: transparent;
+    .custom-control-input:checked ~ .custom-control-label {
+      &::before {
+        background-color: lighten($color-slack, 10%);
+      }
+      &::after {
+        background-color: darken($color-slack, 5%);
+        background-image: url(/images/icons/slack/slack-logo-dark-on.svg);
+      }
     }
-    &::after {
-      background-color: darken($color-slack, 5%);
-      background-image: url(/images/icons/slack/slack-logo-dark-off.svg);
+    .grw-slack-logo svg {
+      fill: #dd80de;
     }
-  }
 
-  .custom-control-input:checked ~ .custom-control-label {
-    &::before {
-      background-color: lighten($color-slack, 10%);
+    .grw-btn-slack {
+      background-color: black;
+      &:focus,
+      &:hover {
+        background-color: black;
+      }
     }
-    &::after {
-      background-color: darken($color-slack, 5%);
-      background-image: url(/images/icons/slack/slack-logo-dark-on.svg);
+
+    .grw-btn-slack-triangle {
+      color: var(--secondary);
     }
   }
-  .grw-slack-logo svg {
-    fill: #dd80de;
+
+  /*
+  * GROWI HandsontableModal
+  */
+  .grw-hot-modal-navbar {
+    background-color: var(--dark);
   }
 
-  .grw-btn-slack {
-    background-color: black;
-    &:focus,
-    &:hover {
-      background-color: black;
+  .wiki {
+    h1 {
+      border-color: hsl.lighten(var(--border-color-theme),10%);
+    }
+    h2 {
+      border-color: var(--border-color-theme);
     }
   }
 
-  .grw-btn-slack-triangle {
-    color: $secondary;
+  /*
+  * GROWI comment form
+  */
+  .comment-form {
+    #slack-mark-black {
+      display: none;
+    }
   }
-}
 
-/*
- * GROWI HandsontableModal
- */
-.grw-hot-modal-navbar {
-  background-color: $dark;
-}
-
-.wiki {
-  h1 {
-    border-color: lighten($border-color-theme, 10%);
-  }
-  h2 {
-    border-color: $border-color-theme;
+  .page-comments-row {
+    background: var(--bgcolor-subnav);
   }
-}
 
-/*
- * GROWI comment form
- */
-.comment-form {
-  #slack-mark-black {
-    display: none;
+  /*
+  * GROWI tags
+  */
+  .grw-tag-labels {
+    .grw-tag-label {
+      color: $color-tags;
+      background-color: $bgcolor-tags;
+    }
   }
-}
-
-.page-comments-row {
-  background: $bgcolor-subnav;
-}
 
-/*
- * GROWI tags
- */
-.grw-tag-labels {
-  .grw-tag-label {
-    color: $color-tags;
-    background-color: $bgcolor-tags;
+  /*
+  * GROWI popular tags
+  */
+  .grw-popular-tag-labels {
+    .grw-tag-label {
+      color: $color-tags;
+      background-color: $bgcolor-tags;
+    }
   }
-}
 
-/*
- * GROWI popular tags
- */
-.grw-popular-tag-labels {
-  .grw-tag-label {
-    color: $color-tags;
-    background-color: $bgcolor-tags;
+  /*
+  * admin settings
+  */
+  .admin-setting-header {
+    border-color: $border-color-global;
   }
-}
 
-/*
- * admin settings
- */
-.admin-setting-header {
-  border-color: $border-color-global;
-}
+  /*
+  * grw-side-contents
+  */
+  .grw-side-contents-sticky-container {
+    .grw-count-badge {
+      @include count-badge.count-badge($gray-400, $gray-700);
+    }
 
-/*
-* grw-side-contents
-*/
-.grw-side-contents-sticky-container {
-  .grw-count-badge {
-    @include count-badge.count-badge($gray-400, $gray-700);
-  }
+    .grw-border-vr {
+      border-color: $border-color-toc;
+    }
 
-  .grw-border-vr {
-    border-color: $border-color-toc;
+    .revision-toc {
+      border-color: $border-color-toc;
+    }
   }
 
-  .revision-toc {
-    border-color: $border-color-toc;
+  /*
+  * drawio
+  */
+  .drawio-viewer {
+    border-color: $border-color-global;
   }
-}
-
-/*
- * drawio
- */
-.drawio-viewer {
-  border-color: $border-color-global;
-}
 
-/*
- * modal
- */
-.grw-modal-head {
-  border-color: $border-color-global;
-}
+  /*
+  * modal
+  */
+  .grw-modal-head {
+    border-color: $border-color-global;
+  }
 
-/*
- * skeleton
- */
-.grw-skeleton {
-  background-color: lighten($bgcolor-subnav, 15%);
+  /*
+  * skeleton
+  */
+  .grw-skeleton {
+  }
 }

+ 376 - 342
packages/app/src/styles/theme/_apply-colors-light.scss

@@ -2,429 +2,463 @@
 @use '../bootstrap/init' as *;
 @use '../atoms/mixins/buttons' as mixins-buttons;
 @use './mixins/count-badge';
+@use './mixins/hsl-button';
+@use './hsl-functions' as hsl;
 
 // determine optional variables
-$color-list: $color-global !default;
-$bgcolor-list: $bgcolor-global !default;
-$color-list-hover: $color-global !default;
-$bgcolor-list-hover: lighten($primary, 72%) !default;
-$bgcolor-list-active: lighten($primary, 65%) !default;
-$color-list-active: color-yiq($bgcolor-list-active) !default;
-$color-table: $color-global !default;
-$bgcolor-table: null !default;
-$border-color-table: $gray-200 !default;
-$color-table-hover: $color-table !default;
-$bgcolor-table-hover: rgba(black, 0.075) !default;
-$bgcolor-sidebar-list-group: $bgcolor-list !default;
-$color-tags: $secondary !default;
-$bgcolor-tags: $gray-200 !default;
-$border-color-global: $gray-300 !default;
-$border-color-toc: $border-color-global !default;
-$color-dropdown: $color-global !default;
-$color-dropdown-link: $color-global !default;
-$color-dropdown-link-hover: $color-global !default;
-$color-dropdown-link-active: $color-reversal !default;
-$bgcolor-dropdown-link-active: $primary !default;
-
-// override bootstrap variables
-$text-muted: $gray-500;
-$table-color: $color-table;
-$table-bg: $bgcolor-table;
-$table-border-color: $border-color-table;
-$table-hover-color: $color-table-hover;
-$table-hover-bg: $bgcolor-table-hover;
-$border-color: $border-color-global;
-$dropdown-color: $color-dropdown;
-$dropdown-link-color: $color-dropdown-link;
-$dropdown-link-hover-color: $color-dropdown-link-hover;
-$dropdown-link-active-color: $color-dropdown-link-active;
-$dropdown-link-active-bg: $bgcolor-dropdown-link-active;
-
-@import './mixins/list-group';
-@import './reboot-bootstrap-text';
-@import './reboot-bootstrap-border-colors';
-@import './reboot-bootstrap-tables';
-@import './reboot-bootstrap-dropdown';
-
-// List Group
-@include override-list-group-item($color-list, $bgcolor-list, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-
-/*
- * Form
- */
-.form-control {
-  background-color: $bgcolor-global;
-}
+:root[data-theme='light'] {
+  $color-list: var(--color-list,var(--color-global));
+  $bgcolor-list: var(--bgcolor-list,var(--bgcolor-global));
+  $color-list-hover: var(--color-list-hover,var(--color-global));
+  $bgcolor-list-hover: var(--bgcolor-list-hover, var(--bgcolor-global));
+  $bgcolor-list-active: var(--bgcolor-list-active, hsl.lighten(var(--primary),65%));
+  $color-list-active: var(--color-list-active,hsl(var(--primary-hs), clamp(10%, (100% - var(--primary-l)  - 65% - 51%) * 1000, 95%)));
+  $color-table: var(--color-table,var(--color-global));
+  $bgcolor-table: var(--bgcolor-table,null);
+  $border-color-table: var(--border-color-table,#{$gray-200});
+  $color-table-hover: var(--color-table-hover,var(--color-table));
+  $bgcolor-table-hover: var(--bgcolor-table-hover,rgba(black, 0.075));
+  $bgcolor-sidebar-list-group: var(--bgcolor-sidebar-list-group,var(--bgcolor-list));
+  $color-tags: var(--color-tags,var(--secondary));
+  $bgcolor-tags: var(--bgcolor-tags,#{$gray-200});
+  $border-color-global: var(--border-color-global,#{$gray-300});
+  $border-color-toc: var(--border-color-toc,#{$gray-300});
+  $color-dropdown: var(--color-dropdown,var(--color-global));
+  $bgcolor-dropdown: var(--color-dropdown,var(--bgcolor-global));
+  $color-dropdown-link: var(--color-dropdown-link,var(--color-global));
+  $color-dropdown-link-hover: var(--color-dropdown-link-hover,var(--color-global));
+  $color-dropdown-link-active: var(--color-dropdown-link-active,var(--color-reversal));
+  $bgcolor-dropdown-link-hover: hsl.darken(var(--bgcolor-global),15%);
+  $bgcolor-dropdown-link-active: var(--bgcolor-dropdown-link-active,var(--primary));
+
+  // override bootstrap variables
+  $text-muted: $gray-500;
+  $table-color: $color-table;
+  $table-bg: $bgcolor-table;
+  $table-border-color: $border-color-table;
+  $table-hover-color: $color-table-hover;
+  $table-hover-bg: $bgcolor-table-hover;
+  $border-color: $border-color-global;
+  $dropdown-color: $color-dropdown;
+  $dropdown-link-color: $color-dropdown-link;
+  $dropdown-link-hover-color: $color-dropdown-link-hover;
+  $dropdown-link-hover-bg: $bgcolor-dropdown-link-hover;
+  $dropdown-link-active-color: $color-dropdown-link-active;
+  $dropdown-link-active-bg: $bgcolor-dropdown-link-active;
+
+  @import './mixins/list-group';
+  @import './reboot-bootstrap-text';
+  @import './reboot-bootstrap-border-colors';
+  @import './reboot-bootstrap-tables';
+  @import './reboot-bootstrap-theme-colors';
+  @import 'hsl-reboot-bootstrap-theme-colors';
+  @import './reboot-bootstrap-dropdown';
+
+  // List Group
+  @include override-list-group-item(
+    $color-list,
+    $bgcolor-sidebar-list-group,
+    $color-list-hover,
+    $bgcolor-list-hover,
+    $color-list-active,
+    $bgcolor-list-active
+  );
+  /*
+  * Form
+  */
+  .form-control {
+    background-color: var(--bgcolor-global);
+  }
 
-.form-control::placeholder {
-  color: darken($bgcolor-global, 20%);
-}
+  .form-control::placeholder {
+    color: hsl.darken(var(--bgcolor-global), 20%);
+  }
 
-.form-control[disabled],
-.form-control[readonly] {
-  color: lighten($color-global, 10%);
-  background-color: darken($bgcolor-global, 5%);
-}
+  .form-control[disabled],
+  .form-control[readonly] {
+    color: hsl.lighten(var(--color-global),10%);
+    background-color: hsl.darken(var(--bgcolor-global),5%);
+  }
 
-/*
- * card
- */
-.card.card-disabled {
-  background-color: darken($bgcolor-card, 5%);
-  border-color: $gray-200;
-}
+  /*
+  * card
+  */
+  .card.card-disabled {
+    background-color: var(--background-color);
+    border-color: $gray-200;
+  }
 
-/*
- * GROWI Login form
- */
-.nologin {
-  // background color
-  $color-gradient: #3e4d6c;
-  background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
-    linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
-    linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
+  /*
+  * GROWI Login form
+  */
+  .nologin {
+    // background color
+    $color-gradient: #3c465c;
+    background: linear-gradient(45deg, darken($color-gradient, 30%) 0%, hsla(340, 100%, 55%, 0) 70%),
+      linear-gradient(135deg, var.$growi-green 10%, hsla(225, 95%, 50%, 0) 70%), linear-gradient(225deg, var.$growi-blue 10%, hsla(140, 90%, 50%, 0) 80%),
+      linear-gradient(315deg, darken($color-gradient, 25%) 100%, hsla(35, 95%, 55%, 0) 70%);
 
-  .nologin-header {
-    background-color: rgba(white, 0.5);
+    .nologin-header {
+      background-color: rgba(white, 0.5);
 
-    .logo {
-      background-color: rgba(black, 0);
-      fill: rgba(black, 0.5);
-    }
+      .logo {
+        background-color: rgba(black, 0);
+        fill: rgba(black, 0.5);
+      }
 
-    h1 {
-      color: rgba(black, 0.5);
+      h1 {
+        color: rgba(black, 0.5);
+      }
     }
-  }
 
-  .nologin-dialog {
-    background-color: rgba(white, 0.5);
-  }
+    .nologin-dialog {
+      background-color: rgba(white, 0.5);
+    }
 
-  .dropdown-with-icon {
-    .dropdown-toggle {
-      color: white;
-      background-color: rgba($gray-600, 0.7);
-      box-shadow: unset;
-      &:focus {
+    .dropdown-with-icon {
+      .dropdown-toggle {
         color: white;
         background-color: rgba($gray-600, 0.7);
+        box-shadow: unset;
+        &:focus {
+          color: white;
+          background-color: rgba($gray-600, 0.7);
+        }
+      }
+      i {
+        color: darken(white, 30%);
+        background-color: rgba($gray-700, 0.7);
       }
     }
-    i {
-      color: darken(white, 30%);
-      background-color: rgba($gray-700, 0.7);
-    }
-  }
 
-  .input-group {
-    .input-group-text {
-      color: darken(white, 30%);
-      background-color: rgba($gray-700, 0.7);
-    }
+    .input-group {
+      .input-group-text {
+        color: darken(white, 30%);
+        background-color: rgba($gray-700, 0.7);
+      }
 
-    .form-control {
-      color: white;
-      background-color: rgba($gray-600, 0.7);
-      box-shadow: unset;
+      .form-control {
+        color: white;
+        background-color: rgba($gray-600, 0.7);
+        box-shadow: unset;
 
-      &::placeholder {
-        color: darken(white, 30%);
+        &::placeholder {
+          color: darken(white, 30%);
+        }
       }
     }
-  }
 
-  // footer link text
-  .link-growi-org {
-    color: rgba(black, 0.4);
+    // footer link text
+    .link-growi-org {
+      color: rgba(black, 0.4);
 
-    &:hover,
-    &.focus {
-      color: black;
+      &:hover,
+      &.focus {
+        color: black;
 
-      .growi {
-        color: darken(var.$growi-green, 20%);
-      }
+        .growi {
+          color: darken(var.$growi-green, 20%);
+        }
 
-      .org {
-        color: darken(var.$growi-blue, 15%);
+        .org {
+          color: darken(var.$growi-blue, 15%);
+        }
       }
     }
   }
-}
 
-/*
- * GROWI subnavigation
- */
-.grw-subnav {
-  background-color: $bgcolor-subnav;
-}
+  /*
+  * GROWI subnavigation
+  */
+  .grw-subnav {
+    background-color: var(--bgcolor-subnav);
+  }
 
-.grw-subnav-fixed-container .grw-subnav {
-  background-color: rgba($bgcolor-subnav, 0.85);
-}
+  .grw-subnav-fixed-container .grw-subnav {
+    background-color: hsl.alpha(var(--bgcolor-subnav),85%);
+  }
 
-.grw-page-editor-mode-manager {
-  .btn-outline-primary {
-    &:hover {
-      color: $primary;
-      background-color: $gray-200;
+  .grw-page-editor-mode-manager {
+    .btn-outline-primary {
+      &:hover {
+        color: var(--primary);
+        background-color: $gray-200;
+      }
     }
   }
-}
 
-.grw-drawer-toggler {
-  @extend .btn-light;
-  color: $gray-500;
-}
-
-/*
- * GROWI Sidebar
- */
-.grw-sidebar {
-  // List
-  @include override-list-group-item($color-list, $bgcolor-sidebar-list-group, $color-list-hover, $bgcolor-list-hover, $color-list-active, $bgcolor-list-active);
-  // sidebar-centent-bg
-  .grw-navigation-wrap {
-    // Drop a shadow on the light theme. The dark theme makes '$ bgcolor-sidebar-context' brighter than the body.
-    box-shadow: 0px 0px 3px rgba(black, 0.24);
-  }
-  // Pagetree
-  .grw-pagetree {
-    @include override-list-group-item-for-pagetree(
-      $color-sidebar-context,
-      darken($bgcolor-sidebar-context, 5%),
-      darken($bgcolor-sidebar-context, 12%),
-      lighten($color-sidebar-context, 10%),
-      lighten($color-sidebar-context, 8%),
-      darken($bgcolor-sidebar-context, 15%),
-      darken($bgcolor-sidebar-context, 24%)
+  .grw-drawer-toggler {
+    @include button-variant($light, $light);
+    @include mixins-buttons.button-svg-icon-variant($light, $light);
+    color: $gray-500;
+    box-shadow: none !important;
+  }
+
+  /*
+  * GROWI Sidebar
+  */
+  .grw-sidebar {
+    // List
+    @include override-list-group-item(
+      $color-list,
+      $bgcolor-sidebar-list-group,
+      $color-list-hover,
+      $bgcolor-list-hover,
+      $color-list-active,
+      $bgcolor-list-active
     );
-    .grw-pagetree-triangle-btn {
-      @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $primary);
+    // sidebar-centent-bg
+    .grw-navigation-wrap {
+      // Drop a shadow on the light theme. The dark theme makes '$ bgcolor-sidebar-context' brighter than the body.
+      box-shadow: 0px 0px 3px rgba(black, 0.24);
     }
-  }
-  .private-legacy-pages-link {
-    &:hover {
-      background: $bgcolor-list-hover;
+    // Pagetree
+    .grw-pagetree {
+      @include override-list-group-item-for-pagetree(
+        var(--color-sidebar-context),
+        hsl.darken(var(--bgcolor-sidebar-context),5%),
+        hsl.darken(var(--bgcolor-sidebar-context),12%),
+        hsl.lighten(var(--color-sidebar-context),10%),
+        hsl.lighten(var(--color-sidebar-context),8%),
+        hsl.darken(var(--bgcolor-sidebar-context),15%),
+        hsl.darken(var(--bgcolor-sidebar-context),24%)
+      );
+
+      .grw-pagetree-triangle-btn {
+        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, var(--primary));
+      }
+    }
+    .private-legacy-pages-link {
+      &:hover {
+        background: $bgcolor-list-hover;
+      }
     }
   }
-}
-
-.btn.btn-page-item-control {
-  @include button-outline-variant($gray-500, $primary, lighten($primary, 52%), transparent);
-  @include hover() {
-    background-color: lighten($primary, 58%);
-  }
-  &:not(:disabled):not(.disabled):active,
-  &:not(:disabled):not(.disabled).active {
-    color: $primary;
-  }
-  box-shadow: none !important;
-}
 
-/*
- * GROWI page list
- */
-.page-list {
-  .page-list-ul {
-    > li {
-      > span.page-list-meta {
-        color: lighten($color-global, 10%);
+  .btn.btn-page-item-control {
+    --gray-500: hsl(var(--gray-500-hs),var(--gray-500-l));
+    --gray-500-hs: 210,13%;
+    --gray-500-l: 61%;
+    @include hsl-button.button-outline-variant(var(--gray-500), var(--primary), #{hsl.lighten(var(--primary), 52%)}, transparent);
+    @include hover() {
+      background-color: hsl.lighten(var(--primary), 58%);
+    }
+    &:not(:disabled):not(.disabled):active,
+    &:not(:disabled):not(.disabled).active {
+      color: var(--primary);
+    }
+    box-shadow: none !important;
+  }
+
+  /*
+  * GROWI page list
+  */
+  .page-list {
+    .page-list-ul {
+      > li {
+        > span.page-list-meta {
+          color: hsl.lighten(var(--color-global),10%);
+        }
       }
     }
-  }
-  // List group
-  .list-group-item {
-    .page-list-snippet {
-      color: lighten($body-color, 10%);
+    // List group
+    .list-group-item {
+      &.active {
+        background-color: hsl.lighten(var(--primary),77%) !important;
+      }
+      &.list-group-item-action:hover {
+        background-color: hsl.lighten(var(--primary),72%) !important;
+      }
+      .page-list-snippet {
+        color: hsl.lighten(var(--color-global),10%);
+      }
     }
   }
-}
 
-/*
- * GROWI ToC
- */
-.revision-toc-content {
-  ::marker {
-    color: darken($bgcolor-global, 20%);
+  /*
+  * GROWI ToC
+  */
+  .revision-toc-content {
+    ::marker {
+      color: hsl.darken(var(--bgcolor-global),20%);
+    }
   }
-}
 
-/*
- * GROWI Editor
- */
-.grw-editor-navbar-bottom {
-  background-color: $gray-50;
+  /*
+  * GROWI Editor
+  */
+  .grw-editor-navbar-bottom {
+    background-color: $gray-50;
 
-  #slack-mark-white {
-    display: none;
-  }
+    #slack-mark-white {
+      display: none;
+    }
 
-  .input-group-text {
-    margin-right: 1px;
-    color: $secondary;
-    border-color: $light;
-  }
+    .input-group-text {
+      margin-right: 1px;
+      color: var(--secondary);
+      border-color: var(--light);
+    }
 
-  .btn.btn-outline-secondary {
-    border-color: $border-color;
+    .btn.btn-outline-secondary {
+      border-color: $border-color;
+    }
   }
-}
 
-/*
- * GROWI Link Edit Modal
- */
-.link-edit-modal {
-  span i {
-    color: $gray-400;
+  /*
+  * GROWI Link Edit Modal
+  */
+  .link-edit-modal {
+    span i {
+      color: $gray-400;
+    }
   }
-}
 
-/*
- * GROWI Grid Edit Modal
- */
+  /*
+  * GROWI Grid Edit Modal
+  */
 
-.grw-grid-edit-preview {
-  background: $gray-100;
-}
+  .grw-grid-edit-preview {
+    background: $gray-100;
+  }
 
-/*
- * Slack
- */
-.grw-slack-notification {
-  background-color: white;
-  $color-slack: #4b144c;
+  /*
+  * Slack
+  */
+  .grw-slack-notification {
+    background-color: white;
+    $color-slack: #4b144c;
 
-  .form-control {
-    background: white;
-  }
+    .form-control {
+      background: white;
+    }
 
-  .custom-control-label {
-    &::before {
-      background-color: $gray-200;
-      border-color: transparent;
+    .custom-control-label {
+      &::before {
+        background-color: $gray-200;
+        border-color: transparent;
+      }
+      &::after {
+        background-color: white;
+        background-image: url(/images/icons/slack/slack-logo-off.svg);
+      }
     }
-    &::after {
-      background-color: white;
-      background-image: url(/images/icons/slack/slack-logo-off.svg);
+    .custom-control-input:checked ~ .custom-control-label {
+      &::before {
+        background-color: lighten($color-slack, 60%);
+      }
+      &::after {
+        background-image: url(/images/icons/slack/slack-logo-on.svg);
+      }
     }
-  }
-  .custom-control-input:checked ~ .custom-control-label {
-    &::before {
-      background-color: lighten($color-slack, 60%);
+    .grw-slack-logo svg {
+      fill: #af30b0;
     }
-    &::after {
-      background-image: url(/images/icons/slack/slack-logo-on.svg);
+
+    .grw-btn-slack {
+      background-color: white;
+
+      &:hover,
+      &:focus {
+        background-color: white;
+      }
+    }
+
+    .grw-btn-slack-triangle {
+      color: var(--secondary);
     }
-  }
-  .grw-slack-logo svg {
-    fill: #af30b0;
   }
 
-  .grw-btn-slack {
-    background-color: white;
+  /*
+  * GROWI HandsontableModal
+  */
+  .grw-hot-modal-navbar {
+    background-color: var(--light);
+  }
 
-    &:hover,
-    &:focus {
-      background-color: white;
+  .wiki {
+    h1 {
+      border-color: var(--border-color-theme);
+    }
+    h2 {
+      border-color: var(--border-color-theme);
     }
   }
 
-  .grw-btn-slack-triangle {
-    color: $secondary;
+  /*
+  * GROWI comment form
+  */
+  .comment-form {
+    #slack-mark-white {
+      display: none;
+    }
   }
-}
 
-/*
- * GROWI HandsontableModal
- */
-.grw-hot-modal-navbar {
-  background-color: $light;
-}
-
-.wiki {
-  h1 {
-    border-color: $border-color-theme;
+  .page-comments-row {
+    background: var(--bgcolor-subnav);
   }
-  h2 {
-    border-color: $border-color-theme;
+
+  /*
+  * GROWI tags
+  */
+  .grw-tag-labels {
+    .grw-tag-label {
+      color: $color-tags;
+      background-color: $bgcolor-tags;
+    }
   }
-}
 
-/*
- * GROWI comment form
- */
-.comment-form {
-  #slack-mark-white {
-    display: none;
+  /*
+  * GROWI popular tags
+  */
+  .grw-popular-tag-labels {
+    .grw-tag-label {
+      color: $color-tags;
+      background-color: $bgcolor-tags;
+    }
   }
-}
 
-.page-comments-row {
-  background: $bgcolor-subnav;
-}
+  /*
+  * grw-side-contents
+  */
+  .grw-side-contents-sticky-container {
+    .grw-count-badge {
+      @include count-badge.count-badge($gray-600, $gray-200);
+    }
 
-/*
- * GROWI tags
- */
-.grw-tag-labels {
-  .grw-tag-label {
-    color: $color-tags;
-    background-color: $bgcolor-tags;
+    .grw-border-vr {
+      border-color: $border-color-toc;
+    }
+    .revision-toc {
+      border-color: $border-color-toc;
+    }
   }
-}
 
-/*
- * GROWI popular tags
- */
-.grw-popular-tag-labels {
-  .grw-tag-label {
-    color: $color-tags;
-    background-color: $bgcolor-tags;
+  /*
+  * drawio
+  */
+  .drawio-viewer {
+    border-color: $border-color-global;
   }
-}
 
-/*
-* grw-side-contents
-*/
-.grw-side-contents-sticky-container {
-  .grw-count-badge {
-    @include count-badge.count-badge($gray-600, $gray-200);
+  /*
+  * admin settings
+  */
+  .admin-setting-header {
+    border-color: $border-color;
   }
 
-  .grw-border-vr {
-    border-color: $border-color-toc;
+  /*
+  * modal
+  */
+  .grw-modal-head {
+    border-color: $border-color-global;
   }
-  .revision-toc {
-    border-color: $border-color-toc;
-  }
-}
 
-/*
- * drawio
- */
-.drawio-viewer {
-  border-color: $border-color-global;
-}
-
-/*
- * admin settings
- */
-.admin-setting-header {
-  border-color: $border-color;
-}
-
-/*
- * modal
- */
-.grw-modal-head {
-  border-color: $border-color-global;
-}
-
-/*
- * skeleton
- */
-.grw-skeleton {
-  background-color: darken($bgcolor-subnav, 10%);
+  /*
+  * skeleton
+  */
+  .grw-skeleton {
+    background-color: hsl.lighten(var(--bgcolor-navbar),10%);
+  }
 }

+ 121 - 124
packages/app/src/styles/theme/_apply-colors.scss

@@ -1,54 +1,52 @@
 @use '../variables' as var;
 @use '../bootstrap/init' as *;
 @use '../mixins';
-@use './mixins/tables'; // comment out and use _reboot-bootstrap-tables instead -- 2020.05.28 Yuki Takei
 @use '../atoms/mixins/code';
+@use './mixins/hsl-button';
+@use './hsl-functions' as hsl;
 
 //
 //== Apply to Bootstrap
 //
 
 // determine optional variables
-$border-image-navbar: linear-gradient(to right, $gray-300 0%, $gray-300 100%) !default;
-$bgcolor-search-top-dropdown: $secondary !default;
-$bgcolor-sidebar-nav-item-active: darken($bgcolor-sidebar, 10%) !default;
-$text-shadow-sidebar-nav-item-active: 1px 1px 2px $primary !default;
-$bgcolor-inline-code: $gray-100 !default;
-$color-inline-code: darken($red, 15%) !default;
-$bordercolor-inline-code: $gray-400 !default;
-$bordercolor-nav-tabs: $gray-300 !default;
-$bordercolor-nav-tabs-hover: $gray-200 $gray-200 $bordercolor-nav-tabs !default;
-$color-nav-tabs-link-active: $gray-600 !default;
-$bordercolor-nav-tabs-active: $bordercolor-nav-tabs $bordercolor-nav-tabs $bgcolor-global !default;
-$color-btn-reload-in-sidebar: $gray-500;
-$bgcolor-keyword-highlighted: var.$grw-marker-yellow !default;
-$bgcolor-page-list-group-item-active: lighten($primary, 76%) !default;
-$color-page-list-group-item-meta: $gray-500 !default;
-$color-search-page-list-title: $color-global !default;
-$bgcolor-subnav: darken($bgcolor-global, 3%) !default;
+$bgcolor-search-top-dropdown: var(--bgcolor-search-top-dropdown,var(--secondary));
+$bgcolor-sidebar-nav-item-active: var(--bgcolor-sidebar-nav-item-active,#{hsl.darken(var(--primary),10%)});
+$text-shadow-sidebar-nav-item-active: var(--text-shadow-sidebar-nav-item-active,1px 1px 2px var(--primary));
+$bgcolor-inline-code: var(--bgcolor-inline-code, #{$gray-100});
+$color-inline-code: var(--color-inline-code, #{darken($red, 15%)});
+$bordercolor-inline-code: var(--bordercolor-inline-code, #{$gray-400});
+$bordercolor-nav-tabs: var(--bordercolor-nav-tabs, #{$gray-300});
+$bordercolor-nav-tabs-hover: var(--bordercolor-nav-tabs-hover,#{$gray-200} #{$gray-200} #{$bordercolor-nav-tabs});
+$border-nav-tabs-link-active: var(--border-nav-tabs-link-active, #{$gray-600});
+$bordercolor-nav-tabs-active: var(--bordercolor-nav-tabs-active,$bordercolor-nav-tabs $bordercolor-nav-tabs var(--bgcolor-global));
+$color-btn-reload-in-sidebar: var(--color-btn-reload-in-sidebar,#{$gray-500});
+$bgcolor-keyword-highlighted: var(--bgcolor-keyword-highlighted,#{var.$grw-marker-yellow});
+$color-page-list-group-item-meta: var(--color-page-list-group-item-meta,#{$gray-500});
+$color-search-page-list-title: var(--color-search-page-list-title,var(--color-global));
 
 // override bootstrap variables
-$body-bg: $bgcolor-global;
-$body-color: $color-global;
-$link-color: $color-link;
-$link-hover-color: $color-link-hover;
-$input-focus-color: $color-global;
+$body-bg: var(--bgcolor-global);
+$body-color: var(--color-global);
+$link-color: var(--color-link);
+$link-hover-color: var(--color-link-hover);
+$input-focus-color: var(--color-global);
 $nav-tabs-border-color: $bordercolor-nav-tabs;
 $nav-tabs-link-hover-border-color: $bordercolor-nav-tabs-hover;
-$nav-tabs-link-active-color: $color-nav-tabs-link-active;
-$nav-tabs-link-active-bg: $bgcolor-global;
+$nav-tabs-link-active-color: var(--color-nav-tabs-link-active);
+$nav-tabs-link-active-bg: var(--bgcolor-global);
 $nav-tabs-link-active-border-color: $bordercolor-nav-tabs-active;
 $theme-colors: map-merge($theme-colors, ( primary: $primary ));
 
 @import 'reboot-bootstrap-buttons';
 @import 'reboot-bootstrap-colors';
 @import 'reboot-bootstrap-theme-colors';
+@import 'hsl-reboot-bootstrap-theme-colors';
 @import 'reboot-bootstrap-nav';
 @import 'reboot-toastr-colors';
-@import '~emoji-mart/css/emoji-mart'; // Emoji-mart style
 
 // determine variables with bootstrap function (These variables can be used after importing bootstrap above)
-$color-modal-header: color-yiq($primary) !default;
+$color-modal-header: var(--color-modal-header,#{hsl.contrast(var(--primary))});
 
 code:not([class^='language-']) {
   @include code.code-inline-color($color-inline-code, $bgcolor-inline-code, $bordercolor-inline-code);
@@ -75,13 +73,13 @@ code:not([class^='language-']) {
 // Dropdown
 .grw-apperance-mode-dropdown {
   .grw-sidebar-mode-icon svg {
-    fill: $secondary;
+    fill: var(--secondary);
   }
   .grw-color-mode-icon svg {
-    fill: $color-global;
+    fill: var(--color-global);
   }
   .grw-color-mode-icon-muted svg {
-    fill: $secondary;
+    fill: var(--secondary);
   }
 }
 
@@ -92,12 +90,12 @@ code:not([class^='language-']) {
 
 // Tabs
 .nav.nav-tabs .nav-link.active {
-  color: $color-link !important;
+  color: var(--color-link);
   background: transparent;
 
   &:hover,
   &:focus {
-    color: $color-link-hover !important;
+    color: var(--color-link-hover);
   }
 }
 
@@ -110,23 +108,23 @@ ul.pagination {
   }
   li.page-item.active {
     button.page-link {
-      color: color-yiq($primary);
-      background-color: $primary;
+      color: hsl.contrast(var(--primary));
+      background-color: var(--primary);
       &:hover,
       &:focus {
-        color: color-yiq($primary);
-        background-color: $primary;
+        color: hsl.contrast(var(--primary));
+        background-color: var(--primary);
       }
     }
   }
   li.page-item {
     button.page-link {
-      color: $primary;
-      border-color: $secondary;
+      color: var(--primary);
+      border-color: var(--secondary) !important;
       &:hover,
       &:active,
       &:focus {
-        color: $primary;
+        color: var(--primary);
       }
     }
   }
@@ -144,13 +142,13 @@ ul.pagination {
 //
 
 .grw-logo {
-  // set transition for fill
+  set transition for fill
   svg * {
     transition: fill 0.8s ease-out;
   }
 
   svg {
-    fill: $fillcolor-logo-mark;
+    fill: var(--fillcolor-logo-mark);
   }
 
   &:hover {
@@ -167,75 +165,70 @@ ul.pagination {
 }
 
 .grw-navbar {
-  background: $bgcolor-navbar;
+  background: var(--bgcolor-navbar);
   .nav-item .nav-link {
-    color: $color-link-nabvar;
+    color: var(--color-link-nabvar);
   }
 
-  border-image: $border-image-navbar;
-  border-image-slice: 1;
+  border-image: var(--border-image-navbar) !important;
+  border-image-slice: 1 !important;
 
   .grw-app-title {
-    color: $fillcolor-logo-mark;
+    color: var(--fillcolor-logo-mark);
   }
 }
 
 .grw-global-search {
   .btn-secondary.dropdown-toggle {
-    @include button-variant($bgcolor-search-top-dropdown, $bgcolor-search-top-dropdown);
+    @include hsl-button.button-variant(var(--bgcolor-search-top-dropdown), var(--bgcolor-search-top-dropdown));
   }
 
   // for https://youtrack.weseek.co.jp/issue/GW-2603
   .search-typeahead {
-    background-color: rgba($bgcolor-global, 0.9);
+    background-color: hsl.alpha(var(--bgcolor-global),10%);
   }
 }
 
 .grw-sidebar {
+  $color-resize-button: var(--color-resize-button,var(--color-global));
+  $bgcolor-resize-button: var(--bgcolor-resize-button,white);
+  $color-resize-button-hover: var(--color-resize-button-hover,var(--color-reversal));
+  $bgcolor-resize-button-hover: var(--bgcolor-resize-button-hover,#{hsl.lighten(var(--bgcolor-resize-button), 5%)});
   .grw-navigation-resize-button {
-    $color-resize-button: $color-global !default;
-    $bgcolor-resize-button: white !default;
-    $color-resize-button-hover: $color-reversal !default;
-    $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%) !default;
-
     .hexagon-container svg {
       .background {
-        fill: $bgcolor-resize-button;
+        fill: var(--bgcolor-resize-button);
       }
       .icon {
-        fill: $color-resize-button;
+        fill: var(--color-resize-button);
       }
     }
     &:hover .hexagon-container svg {
       .background {
-        fill: $bgcolor-resize-button-hover;
+        fill: var(--bgcolor-resize-button-hover);
       }
       .icon {
-        fill: $color-resize-button-hover;
+        fill: var(--color-resize-button-hover);
       }
     }
   }
   div.grw-global-navigation {
     > div {
-      background-color: $bgcolor-sidebar;
+      background-color: var(--bgcolor-sidebar);
     }
   }
   div.grw-contextual-navigation {
     > div {
-      color: $color-sidebar-context;
-      background-color: $bgcolor-sidebar-context;
+      color: var(--color-sidebar-context);
+      background-color: var(--bgcolor-sidebar-context);
     }
   }
 
   .grw-sidebar-nav {
     .btn {
-      @include button-variant(
-        $bgcolor-sidebar,
-        $bgcolor-sidebar,
-        darken($bgcolor-sidebar, 7.5%),
-        darken($bgcolor-sidebar, 10%),
-        $bgcolor-sidebar-nav-item-active,
-        $bgcolor-sidebar-nav-item-active
+      @include hsl-button.button-variant(
+        var(--bgcolor-sidebar),
+        var(--bgcolor-sidebar),
       );
     }
   }
@@ -246,7 +239,7 @@ ul.pagination {
       }
       // fukidashi
       &:after {
-        border-right-color: $bgcolor-sidebar-context;
+        border-right-color: var(--bgcolor-sidebar-context) !important;
       }
     }
   }
@@ -258,24 +251,24 @@ ul.pagination {
 
     .grw-recent-changes-resize-button {
       .custom-control-label::before {
-        background-color: $primary;
+        background-color: var(--primary);
       }
 
       .custom-control-label::after {
-        background-color: $bgcolor-global;
+        background-color: var(--bgcolor-global);
       }
 
       .custom-control-input:not(:checked) + .custom-control-label::before {
-        color: $bgcolor-global;
+        color: var(--bgcolor-global);
       }
 
       .custom-control-input:checked + .custom-control-label::before {
-        color: $bgcolor-global;
-        background-color: $primary;
-        // border-color: $primary !important;
+        color: var(--bgcolor-global);
+        background-color: var(--primary);
+        border-color: var(--primary);
       }
       .custom-control-input:checked + .custom-control-label::after {
-        color: $bgcolor-global;
+        color: var(--bgcolor-global);
       }
     }
   }
@@ -296,10 +289,10 @@ ul.pagination {
   .grw-recent-changes {
     .list-group {
       .list-group-item {
-        background-color: transparent;
+        background-color: transparent !important;
 
         .icon-lock {
-          color: $color-link;
+          color: var(--color-link);
         }
 
         .grw-recent-changes-item-lower {
@@ -318,7 +311,7 @@ ul.pagination {
  * Icon
  */
 .editor-container .navbar-editor svg {
-  fill: $color-editor-icons;
+  fill: var(--color-editor-icons);
 }
 
 // page preview button in link form
@@ -331,13 +324,14 @@ ul.pagination {
  */
 .modal {
   .modal-header {
-    border-bottom-color: $border-color-theme;
+    border-bottom-color: var(--border-color-theme);
     .modal-title {
       color: $color-modal-header;
     }
     .close {
       color: $color-modal-header;
       opacity: 0.5;
+
       &:hover {
         opacity: 0.9;
       }
@@ -345,11 +339,11 @@ ul.pagination {
   }
 
   .modal-content {
-    background-color: $bgcolor-global;
+    background-color: var(--bgcolor-global);
   }
 
   .modal-footer {
-    border-top-color: $border-color-theme;
+    border-top-color: var(--border-color-theme);
   }
 }
 
@@ -357,13 +351,13 @@ ul.pagination {
   .nav-item {
     &:hover,
     &:focus {
-      background-color: rgba($color-link, 0.08);
+      background-color: hsl.alpha(var(--color-link),10%);
     }
     .nav-link {
       -webkit-appearance: none;
-      color: $color-link;
+      color: var(--color-link);
       svg {
-        fill: $color-link;
+        fill: var(--color-link);
       }
 
       // Disabled state lightens text
@@ -377,14 +371,14 @@ ul.pagination {
   }
 
   .grw-nav-slide-hr {
-    border-color: $color-link;
+    border-color: var(--color-link) !important;
   }
 }
 
 .grw-page-accessories-modal {
   .modal-header {
     .close {
-      color: $secondary;
+      color: var(--secondary);
     }
   }
 }
@@ -393,9 +387,9 @@ ul.pagination {
  * cards
  */
 .card.well {
-  color: $color-global;
-  background-color: $bgcolor-card;
-  border-color: $light;
+  color: var(--color-global);
+  background-color: var(--bgcolor-card);
+  border-color: var(--light);
   box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
 }
 
@@ -438,7 +432,7 @@ ul.pagination {
   h6,
   h7 {
     &.blink {
-      @include mixins.blink-bgcolor($bgcolor-blinked-section);
+      @include mixins.blink-bgcolor(var(--bgcolor-blinked-section));
     }
   }
 
@@ -447,21 +441,21 @@ ul.pagination {
   }
 
   a {
-    color: $color-link-wiki;
+    color: var(--color-link-wiki);
 
     &:hover {
-      color: $color-link-wiki-hover;
+      color: var(--color-link-wiki-hover);
     }
   }
 
   // table with handsontable modal button
   .editable-with-handsontable {
     button {
-      color: $color-link-wiki;
+      color: var(--color-link-wiki);
     }
 
     button:hover {
-      color: $color-link-wiki-hover;
+      color: var(--color-link-wiki-hover);
     }
   }
 }
@@ -473,14 +467,15 @@ ul.pagination {
   // List group
   .list-group {
     .list-group-item {
+      background-color: var(--bgcolor-global) !important;
       a {
         svg {
-          fill: $color-global;
+          fill: var(--color-global);
         }
 
         @include hover() {
           svg {
-            fill: $color-global;
+            fill: var(--color-global);
           }
         }
       }
@@ -493,9 +488,9 @@ ul.pagination {
       }
 
       &.list-group-item-action {
+        background-color: var(--bgcolor-list);
         &.active {
-          background-color: $bgcolor-page-list-group-item-active;
-          border-left-color: $primary;
+          border-left-color: var(--primary);
         }
       }
     }
@@ -507,19 +502,19 @@ ul.pagination {
  */
 .layout-root.editing {
   .main {
-    background-color: darken($bgcolor-global, 2%);
+    background-color: hsl.darken(var(--bgcolor-global),2%);
 
     .page-editor-editor-container {
-      border-right-color: $border-color-theme;
+      border-right-color: var(--border-color-theme);
 
       .navbar-editor {
-        background-color: $bgcolor-global; // same color with active tab
-        border-bottom-color: $border-color-theme;
+        background-color: var(--bgcolor-global); // same color with active tab
+        border-bottom-color: var(--border-color-theme);
       }
     }
 
     .page-editor-preview-container {
-      background-color: $bgcolor-global;
+      background-color: var(--bgcolor-global);
     }
   }
 }
@@ -529,8 +524,8 @@ ul.pagination {
  */
 body.editing-sidebar {
   .page-editor-preview-body {
-    color: $color-sidebar-context;
-    background-color: $bgcolor-sidebar-context;
+    color: var(--color-sidebar-context);
+    background-color: var(--bgcolor-sidebar-context);
   }
 }
 
@@ -541,10 +536,10 @@ body.editing-sidebar {
   .desktop-preview,
   .tablet-preview,
   .mobile-preview {
-    background: $bgcolor-global;
+    background: var(--bgcolor-global);
   }
   .grid-edit-border-for-each-cols {
-    border: 2px solid $bgcolor-global;
+    border: 2px solid var(--bgcolor-global);
   }
 }
 
@@ -568,10 +563,10 @@ body.editing-sidebar {
  * GROWI comment
  */
 .page-comment-meta .page-comment-revision svg {
-  fill: $color-link;
+  fill: var(--color-link);
 
   &:hover {
-    fill: $color-link-hover;
+    fill: var(--color-link-hover);
   }
 }
 
@@ -581,17 +576,17 @@ body.editing-sidebar {
 .page-comments {
   .page-comment .page-comment-main,
   .page-comment-form .comment-form-main {
-    background-color: $bgcolor-global;
+    background-color: var(--bgcolor-global);
 
     &:before {
-      border-right-color: $bgcolor-global;
+      border-right-color: var(--bgcolor-global);
     }
 
     .nav.nav-tabs {
       > li > a.active {
         background: transparent;
-        border-bottom: solid 1px darken($bgcolor-global, 4%);
-        border-bottom-color: darken($bgcolor-global, 4%);
+        border-bottom: solid 1px hsl.darken(var(--bgcolor-global),4%);
+        border-bottom-color: hsl.darken(var(--bgcolor-global),4%);
       }
     }
   }
@@ -602,10 +597,10 @@ body.editing-sidebar {
  */
 .search-result-base {
   .grw-search-page-nav {
-    background-color: $bgcolor-subnav;
+    background-color: var(--bgcolor-subnav);
   }
   .search-control {
-    background-color: $bgcolor-global;
+    background-color: var(--bgcolor-global);
   }
   .page-list {
     .highlighted-keyword {
@@ -626,8 +621,8 @@ mark.rbt-highlight-text {
  * GROWI page content footer
  */
 .page-content-footer {
-  background-color: darken($bgcolor-global, 2%);
-  border-top-color: $border-color-theme;
+  background-color: hsl.darken(var(--bgcolor-global),2%);
+  border-top-color: var(--border-color-theme);
 }
 
 /*
@@ -637,7 +632,7 @@ mark.rbt-highlight-text {
   #layoutOptions {
     .customize-layout-card {
       &.border-active {
-        border-color: $color-theme-color-box;
+        border-color: var(--color-theme-color-box);
       }
     }
   }
@@ -645,11 +640,11 @@ mark.rbt-highlight-text {
   #themeOptions {
     .theme-option-container.active {
       .theme-option-name {
-        color: $color-global;
+        color: var(--color-global);
       }
       a {
-        background-color: $color-theme-color-box;
-        border-color: $color-theme-color-box;
+        background-color: var(--color-theme-color-box);
+        border-color: var(--color-theme-color-box);
       }
     }
   }
@@ -659,12 +654,14 @@ mark.rbt-highlight-text {
  * HackMd
  */
 .bg-box {
-  background-color: $bgcolor-global;
+  background-color: var(--bgcolor-global);
 }
 
 .grw-fab {
   .btn-create-page {
-    fill: color-yiq($primary);
+    svg {
+      fill: hsl.contrast(var(--primary));
+    }
   }
 
   .btn-scroll-to-top {
@@ -687,7 +684,7 @@ mark.rbt-highlight-text {
 .grw-unopend-notification {
   width: 7px;
   height: 7px;
-  background-color: $primary;
+  background-color: var(--primary);
 }
 
 /*

+ 32 - 0
packages/app/src/styles/theme/_hsl-functions.scss

@@ -0,0 +1,32 @@
+@use '~bootstrap/scss/functions' as bs;
+
+@function contrast($color, $darken-degrees: 0%, $alpha-degrees: 100%) {
+  $color: bs.str-replace($color, 'var(');
+  $color: bs.str-replace($color, ')');
+  $color-hs: var(#{$color+'-hs'});
+  $color-l: var(#{$color+'-l'});
+  // @return hsl($color-hs, clamp(10%, calc((100% - $color-l - 51% ) * 1000), 95%));
+  @return hsla($color-hs, clamp(10%, calc((100% - $color-l - $darken-degrees - 51% ) * 1000), 95%), $alpha-degrees);
+}
+
+@function lighten($color, $degrees) {
+  $color: bs.str-replace($color, 'var(');
+  $color: bs.str-replace($color, ')');
+  $color-hs: var(#{$color+'-hs'});
+  $color-l: var(#{$color+'-l'});
+  @return hsl($color-hs, calc($color-l + $degrees));
+}
+@function darken($color, $degrees) {
+  $color: bs.str-replace($color, 'var(');
+  $color: bs.str-replace($color, ')');
+  $color-hs: var(#{$color+'-hs'});
+  $color-l: var(#{$color+'-l'});
+  @return hsl($color-hs, calc($color-l - $degrees));
+}
+@function alpha($color, $degrees) {
+  $color: bs.str-replace($color, 'var(');
+  $color: bs.str-replace($color, ')');
+  $color-hs: var(#{$color+'-hs'});
+  $color-l: var(#{$color+'-l'});
+  @return hsla($color-hs,$color-l,$degrees);
+}

+ 112 - 0
packages/app/src/styles/theme/_hsl-reboot-bootstrap-theme-colors.scss

@@ -0,0 +1,112 @@
+@use '../bootstrap/init' as *;
+@use '../atoms/mixins/buttons' as mixins-buttons;
+@use './mixins/hsl-button';
+@use './mixins/hsl-badge';
+@use './hsl-functions' as hsl;
+
+$hsl-colors: (
+  'primary': var(--primary),
+  'secondary': var(--secondary)
+);
+
+@each $color, $value in $hsl-colors {
+  .bg-#{$color} {
+    background-color: $value !important;
+    a,
+    button {
+      @include hover-focus() {
+        background-color: hsl.darken($value, 10%) !important;
+      }
+    }
+  }
+}
+
+@each $color, $value in $hsl-colors {
+  .border-#{$color} {
+    border-color: $value !important;
+  }
+}
+
+@each $color, $value in $hsl-colors {
+  .text-#{$color} {
+    color: $value !important;
+    @if $emphasized-link-hover-darken-percentage != 0 {
+      a {
+        @include hover-focus() {
+          color: hsl.darken($value, $emphasized-link-hover-darken-percentage) !important;
+        }
+      }
+    }
+  }
+}
+
+@each $color, $value in $hsl-colors {
+  .btn-#{$color} {
+    @include hsl-button.button-variant($value, $value);
+    @include hsl-button.button-svg-icon-variant($value, $value);
+    box-shadow: none !important;
+  }
+}
+
+@each $color, $value in $hsl-colors {
+  .btn-outline-#{$color} {
+    @include hsl-button.button-outline-variant($value, $value, hsl.alpha($value, 10%), $value);
+    @include mixins-buttons.button-outline-svg-icon-variant($value, $value);
+
+    &:not(:disabled):not(.disabled):active,
+    &:not(:disabled):not(.disabled).active {
+      color: $value;
+      background-color: hsl.alpha($value, 10%);
+      border-color: $value;
+    }
+
+    box-shadow: none !important;
+  }
+
+  // separate style: https://github.com/weseek/growi/pull/6804
+  .show > .btn-outline-#{$color}.dropdown-toggle {
+    color: $value;
+    background-color: hsl.alpha($value, 10%);
+    border-color: $value;
+  }
+}
+
+@each $color, $value in $hsl-colors {
+  .alert-#{$color} {
+    $alert-color: rgba(white,90%);
+    $alert-bg-color: hsl.darken($value, calc($alert-bg-level + 0.95) * $theme-color-interval);
+    $alert-border-color: hsl.darken($value, $alert-border-level * $theme-color-interval);
+
+    color: $alert-color;
+    @include gradient-bg($alert-bg-color);
+    border-color: $alert-border-color;
+
+    hr {
+      border-top-color: hsl.darken($value, calc($alert-border-level + 5) * $theme-color-interval);
+    }
+
+    .alert-link {
+      color: hsl.contrast($value);
+    }
+  }
+
+  // Alert link
+  :root, .wiki {
+    .alert.alert-primary {
+      a,
+      a:hover {
+        color: hsl.darken($value, calc($alert-bg-level - 7.7) * $theme-color-interval);
+      }
+    }
+  }
+}
+
+@each $color, $value in $hsl-colors {
+  .badge-#{$color} {
+    @include hsl-badge.badge-variant($value);
+  }
+
+  a.badge-#{$color}  {
+    @include hsl-badge.badge-variant($value);
+  }
+}

+ 1 - 1
packages/app/src/styles/theme/_reboot-bootstrap-border-colors.scss

@@ -25,5 +25,5 @@
 }
 
 .border-info {
-  border-color: ver(--info) !important;
+  border-color: $info !important;
 }

+ 7 - 6
packages/app/src/styles/theme/_reboot-bootstrap-buttons.scss

@@ -1,21 +1,22 @@
 .btn-link {
-  color: $link-color;
+  color: var(--color-link);
   svg {
-    fill: $link-color;
+    fill: var(--color-link);
   }
 
   @include hover() {
-    color: $link-hover-color;
+    color: var(--color-link-hover);
     svg {
-      fill: $link-hover-color;
+      fill: var(--color-link-hover);
     }
   }
 
   &:disabled,
   &.disabled {
-    color: $btn-link-disabled-color;
+    color: var(--color-link-disabled, #{$gray-500});
     svg {
-      fill: $btn-link-disabled-color;
+      fill: var(--color-link-disabled, #{$gray-500});
     }
   }
 }
+

+ 8 - 8
packages/app/src/styles/theme/_reboot-bootstrap-colors.scss

@@ -14,32 +14,32 @@
 // 3. Set an explicit initial text-align value so that we can later use
 //    the `inherit` value on things like `<th>` elements.
 
-&, body {
-  color: $body-color;
-  background-color: $body-bg; // 2
+html, body {
+  color: var(--color-global);
+  background-color: var(--bgcolor-global); // 2
 
   svg {
-    fill: $body-color;
+    fill: var(--color-global);
   }
 }
 
 // Links
 
 a {
-  color: $link-color;
+  color: var(--color-link);
   text-decoration: $link-decoration;
   background-color: transparent; // Remove the gray background on active links in IE 10.
 
   svg {
-    fill: $link-color;
+    fill: var(--color-link);
   }
 
   @include hover() {
-    color: $link-hover-color;
+    color: var(--color-link-hover);
     text-decoration: $link-hover-decoration;
 
     svg {
-      fill: $link-hover-color;
+      fill: var(--color-link-hover);
     }
   }
 }

+ 11 - 11
packages/app/src/styles/theme/_reboot-bootstrap-dropdown.scss

@@ -1,37 +1,37 @@
 @use '../bootstrap/init' as *;
 
 .dropdown-menu {
-  color: $dropdown-color;
+  color: $color-dropdown;
   svg {
-    fill: $dropdown-color;
+    fill: $color-dropdown;
   }
 
-  background-color: $dropdown-bg;
+  background-color: $bgcolor-dropdown;
 }
 
 .dropdown-item {
-  color: $dropdown-link-color;
+  color: $color-dropdown-link;
   svg {
-    fill: $dropdown-link-color;
+    fill: $color-dropdown-link;
   }
 
   @include hover-focus() {
-    color: $dropdown-link-hover-color;
+    color: $color-dropdown-link;
     svg {
-      fill: $dropdown-link-hover-color;
+      fill: $color-dropdown-link-hover;
     }
 
-    @include gradient-bg($dropdown-link-hover-bg);
+    @include gradient-bg($bgcolor-dropdown-link-hover);
   }
 
   &:active,
   &.active,
   &:active:hover,
   &.active:hover {
-    color: $dropdown-link-active-color;
-    background-color: $dropdown-link-active-bg;
+    color: $color-dropdown-link-active;
+    background-color:  $bgcolor-dropdown-link-active;
     svg {
-      fill: $dropdown-link-active-color;
+      fill: $color-dropdown-link-active;
     }
   }
 }

+ 9 - 9
packages/app/src/styles/theme/_reboot-bootstrap-tables.scss

@@ -8,37 +8,37 @@
 //
 
 .table {
-  color: $table-color;
-  background-color: $table-bg; // Reset for nesting within parents with `background-color`.
+  color: $color-table;
+  background-color: $bgcolor-table; // Reset for nesting within parents with `background-color`.
 
   th,
   td {
-    border-top-color: $table-border-color;
+    border-top-color: $border-color-table;
   }
 
   thead th {
-    border-bottom-color: $table-border-color;
+    border-bottom-color: $border-color-table;
   }
 
   tbody + tbody {
-    border-top-color: $table-border-color;
+    border-top-color: $border-color-table;
   }
 }
 
 .table-bordered {
-  border-color: $table-border-color;
+  border-color: $border-color-table;
 
   th,
   td {
-    border-color: $table-border-color;
+    border-color: $border-color-table;
   }
 }
 
 .table-hover {
   tbody tr {
     @include hover() {
-      color: $table-hover-color;
-      background-color: $table-hover-bg;
+      color: $color-table-hover;
+      background-color: $bgcolor-table-hover;
     }
   }
 }

+ 14 - 5
packages/app/src/styles/theme/_reboot-bootstrap-theme-colors.scss

@@ -7,7 +7,7 @@
 
 @each $color, $value in $theme-colors {
   .border-#{$color} {
-    border-color: $value !important;
+    border-color: $value;
   }
 }
 
@@ -57,16 +57,16 @@
       border-color: $color;
     }
     .custom-control-input:checked + .custom-control-label::after {
-      color: $bgcolor-global;
+      color: var(--bgcolor-global);
     }
     .custom-control-input:not(:disabled):active ~ .custom-control-label::before {
-      color: $bgcolor-global;
+      color: var(--bgcolor-global);
       background-color: $color;
       border-color: $color;
     }
     .custom-control-input:focus:not(:checked) ~ .custom-control-label::before {
-      color: $bgcolor-global;
-      background-color: $bgcolor-global;
+      color: var(--bgcolor-global);
+      background-color: var(--bgcolor-global);
       border-color: $input-focus-border-color;
     }
   }
@@ -80,6 +80,15 @@
       theme-color-level($color, $alert-color-level)
     );
   }
+  // Alert link
+  :root, .wiki {
+    .alert.alert-#{$color} {
+      a,
+      a:hover {
+        color: theme-color-level($color, $alert-color-level - 2);
+      }
+    }
+  }
 }
 
 @each $color, $value in $theme-colors {

+ 11 - 4
packages/app/src/styles/theme/_reboot-toastr-colors.scss

@@ -1,15 +1,22 @@
 .toast-success {
-  background-color: var(--success);
+  background-color: var(--success) !important;
 }
 
 .toast-error {
-  background-color: var(--danger);
+  background-color: var(--danger) !important;
 }
 
 .toast-info {
-  background-color: var(--info);
+  background-color: var(--info) !important;
 }
 
 .toast-warning {
-  background-color: var(--warning);
+  background-color: var(--warning) !important;
+}
+
+:root {
+  --toastify-color-info: var(--info);
+  --toastify-color-success: var(--success);
+  --toastify-color-warning: var(--warning);
+  --toastify-color-error: var(--danger);
 }

+ 21 - 0
packages/app/src/styles/theme/mixins/_hsl-badge.scss

@@ -0,0 +1,21 @@
+@use '../../bootstrap/init' as bs;
+@use '../hsl-functions' as hsl;
+
+// @mixin badge-variant($bg) {
+@mixin badge-variant($bg) {
+  color: hsl.contrast($bg);
+  background-color: $bg;
+
+  @at-root a#{&} {
+    @include bs.hover-focus() {
+      color: hsl.contrast($bg);
+      background-color: hsl.darken($bg, 10%);
+    }
+
+    &:focus,
+    &.focus {
+      outline: 0;
+      // box-shadow: 0 0 0 $badge-focus-width hsl.alpha($bg, 50%);
+    }
+  }
+}

+ 142 - 0
packages/app/src/styles/theme/mixins/_hsl-button.scss

@@ -0,0 +1,142 @@
+@use '../../bootstrap/init' as bs;
+@use '../hsl-functions' as hsl;
+
+// @mixin button-variant($background, $border, $hover-background: darken($background, 7.5%), $hover-border: darken($border, 10%), $active-background: darken($background, 10%), $active-border: darken($border, 12.5%)) {
+@mixin button-variant($background, $border, $hover-background-darken-degrees: 7.5%, $hover-border-darken-degrees: 10%, $active-background-darken-degrees: 10%, $active-border-darken-degrees: 12.5%) {
+  $hover-background: hsl.darken($background, $hover-background-darken-degrees);  // DO NOT ... twice
+  $hover-border: hsl.darken($border, $hover-border-darken-degrees);  // DO NOT ... twice
+
+  color: hsl.contrast($background);
+  @include bs.gradient-bg($background);
+  border-color: $border;
+  // @include box-shadow($btn-box-shadow);
+
+  @include bs.hover() {
+    color: hsl.contrast($background);
+    @include bs.gradient-bg($hover-background);
+    border-color: $hover-border;
+  }
+
+  &:focus,
+  &.focus {
+    color: hsl.contrast($background);
+    @include bs.gradient-bg($hover-background);
+    border-color: $hover-border;
+    // @if $enable-shadows {
+    //   @include box-shadow($btn-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
+    // } @else {
+    //   // Avoid using mixin so we can pass custom focus shadow properly
+    //   box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);
+    // }
+  }
+
+  // // Disabled comes first so active can properly restyle
+  &.disabled,
+  &:disabled {
+    color: hsl.contrast($background);
+    @include bs.gradient-bg($background);
+    border-color: $border;
+    // Remove CSS gradients if they're enabled
+    @if bs.$enable-gradients {
+      background-image: none;
+    }
+  }
+
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active,
+  .show > &.dropdown-toggle {
+    color: hsl.contrast($background);
+    background-color: $hover-background;
+    border-color: $hover-border;
+  }
+  //   @if $enable-gradients {
+  //     background-image: none; // Remove the gradient for the pressed/active state
+  //   }
+  //   border-color: $active-border;
+
+  //   &:focus {
+  //     // @if $enable-shadows and $btn-active-box-shadow != none {
+  //     //   @include box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5));
+  //     // } @else {
+  //     //   // Avoid using mixin so we can pass custom focus shadow properly
+  //     //   box-shadow: 0 0 0 $btn-focus-width rgba(mix(color-yiq($background), $border, 15%), .5);
+  //     // }
+  //   }
+  // }
+}
+
+// @mixin button-outline-variant($color, $color-hover: color-yiq($color), $active-background: $color, $active-border: $color) {
+@mixin button-outline-variant($color, $color-hover: hsl.contrast($color), $active-background: $color, $active-border: $color) {
+  color: $color;
+  border-color: $color;
+
+  @include bs.hover() {
+    color: $color-hover;
+    background-color: $active-background;
+    border-color: $active-border;
+  }
+
+  // &:focus,
+  // &.focus {
+  //   box-shadow: 0 0 0 bs.$btn-focus-width hsl.alpha($color,50%);
+  // }
+
+  &.disabled,
+  &:disabled {
+    color: $color;
+    background-color: transparent;
+  }
+
+  // &:not(:disabled):not(.disabled):active,
+  // &:not(:disabled):not(.disabled).active,
+  // .show > &.dropdown-toggle {
+  //   color: hsl.contrast($active-background);
+  //   background-color: $active-background;
+  //   border-color: $active-border;
+
+  // &:focus {
+  //   @if $enable-shadows and $btn-active-box-shadow != none {
+  //     @include bs.box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width hsl.alpha($color,50%));
+  //   } @else {
+  //     // Avoid using mixin so we can pass custom focus shadow properly
+  //     box-shadow: 0 0 0 $btn-focus-width hsl.alpha($color,50%);
+  //   }
+  // }
+  // }
+}
+
+// @mixin button-svg-icon-variant($background, $hover-background: darken($background, 7.5%), $active-background: darken($background, 10%)) {
+@mixin button-svg-icon-variant($background, $hover-background-darken-degrees: 7.5%, $active-background-darken-degrees: 10%) {
+  svg {
+    fill: hsl.contrast($background);
+  }
+
+  @include bs.hover() {
+    svg {
+      fill: hsl.contrast($background);
+    }
+  }
+
+  &:focus,
+  &.focus {
+    svg {
+      fill: hsl.contrast($background);
+    }
+  }
+
+  // Disabled comes first so active can properly restyle
+  &.disabled,
+  &:disabled {
+    svg {
+      fill: hsl.contrast($background);
+    }
+  }
+
+  &:not(:disabled):not(.disabled):active,
+  &:not(:disabled):not(.disabled).active,
+  .show > &.dropdown-toggle {
+    svg {
+      fill: hsl.contrast($background);
+    }
+  }
+}

+ 1 - 1
packages/app/src/styles/theme/mixins/_list-group.scss

@@ -63,7 +63,7 @@
     }
     .grw-pagetree-title-anchor {
       .grw-sidebar-text-muted {
-        color: rgba(desaturate($color, 50%), 0.6);
+        // color: rgba(desaturate($color, 50%), 0.6);
       }
     }
   }

+ 0 - 34
packages/app/src/styles/theme/mixins/_tables.scss

@@ -1,34 +0,0 @@
-//== Table
-// $table-variants: (
-//   'light': $light,
-//   'dark': $dark,
-// );
-
-// remove when master version is released
-// show https://github.com/twbs/bootstrap/blob/28cb1ff2b23253293601c51aff434c39b461025e/scss/mixins/_table-variants.scss
-// @mixin table-variant($state, $background) {
-//   .table-#{$state} {
-//     $table-hover-bg-factor: 0.075 !default;
-//     $table-striped-bg-factor: 0.05 !default;
-//     $body-bg: $white !default;
-//     $table-active-bg-factor: 0.1 !default;
-//     $table-border-factor: 0.1 !default;
-
-//     $color: color-contrast(mix(rgba($background, 1), $body-bg, opacity($background) * 100));
-//     $color: gray;
-//     $hover-bg: mix($color, $background, percentage($table-hover-bg-factor));
-//     $striped-bg: mix($color, $background, percentage($table-striped-bg-factor));
-//     $active-bg: mix($color, $background, percentage($table-active-bg-factor));
-
-//     --bs-table-bg: #{$background};
-//     --bs-table-striped-bg: #{$striped-bg};
-//     --bs-table-striped-color: #{color-contrast($striped-bg)};
-//     --bs-table-active-bg: #{$active-bg};
-//     --bs-table-active-color: #{color-contrast($active-bg)};
-//     --bs-table-hover-bg: #{$hover-bg};
-//     --bs-table-hover-color: #{color-contrast($hover-bg)};
-
-//     color: $color;
-//     border-color: mix($color, $background, percentage($table-border-factor));
-//   }
-// }

+ 6 - 22
packages/app/src/utils/axios.ts

@@ -1,7 +1,7 @@
 // eslint-disable-next-line no-restricted-imports
 import axios from 'axios';
-import parseISO from 'date-fns/parseISO';
-import isIsoDate from 'is-iso-date';
+import dayjs from 'dayjs';
+import qs from 'qs';
 
 const customAxios = axios.create({
   headers: {
@@ -10,26 +10,10 @@ const customAxios = axios.create({
   },
 });
 
-// add an interceptor to convert ISODate
-const convertDates = (body: any): void => {
-  if (body === null || body === undefined || typeof body !== 'object') {
-    return body;
-  }
-
-  for (const key of Object.keys(body)) {
-    const value = body[key];
-    if (isIsoDate(value)) {
-      body[key] = parseISO(value);
-    }
-    else if (typeof value === 'object') {
-      // eslint-disable-next-line @typescript-eslint/no-unused-vars
-      convertDates(value);
-    }
-  }
-};
-customAxios.interceptors.response.use((response) => {
-  convertDates(response.data);
-  return response;
+// serialize Date config: https://github.com/axios/axios/issues/1548#issuecomment-548306666
+customAxios.interceptors.request.use((config) => {
+  config.paramsSerializer = params => qs.stringify(params, { serializeDate: (date: Date) => dayjs(date).format('YYYY-MM-DDTHH:mm:ssZ') });
+  return config;
 });
 
 export default customAxios;

+ 2 - 2
packages/app/test/cypress/integration/20-basic-features/20-basic-features--access-to-page.spec.ts

@@ -29,8 +29,8 @@ context('Access to page', () => {
     // https://redmine.weseek.co.jp/issues/111384
     // cy.get('.toc-link').should('be.visible');
 
-    // hide fab // disable fab for sticky-events warning
-    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab
+    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
     // remove animation for screenshot
     // remove 'blink' class because ::after element cannot be operated

+ 2 - 2
packages/app/test/cypress/integration/21-basic-features-for-guest/21-basic-features-for-guest--access-to-page.spec.ts

@@ -14,8 +14,8 @@ context('Access to page by guest', () => {
     cy.visit('/Sandbox#Headers');
     cy.waitUntilSkeletonDisappear();
 
-    // hide fab // disable fab for sticky-events warning
-    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab
+    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
 
     // remove animation for screenshot
     // remove 'blink' class because ::after element cannot be operated

+ 2 - 2
packages/app/test/cypress/integration/60-home/60-home--home.spec.ts

@@ -46,8 +46,8 @@ context('Access User settings', () => {
     });
     cy.visit('/me');
     cy.collapseSidebar(true, true);
-    // hide fab // disable fab for sticky-events warning
-    // cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
+    // hide fab
+    cy.getByTestid('grw-fab-container').invoke('attr', 'style', 'display: none');
   });
 
   it('Access User information', () => {

+ 1 - 1
packages/codemirror-textlint/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/codemirror-textlint",
-  "version": "6.0.2-RC.0",
+  "version": "6.0.3-RC.0",
   "license": "MIT",
   "main": "dist/index.js",
   "scripts": {

+ 1 - 1
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/core",
-  "version": "6.0.2-RC.0",
+  "version": "6.0.3-RC.0",
   "description": "GROWI Core Libraries",
   "license": "MIT",
   "keywords": [

+ 2 - 0
packages/core/src/index.ts

@@ -14,6 +14,7 @@ export * as pageUtils from './utils/page-utils';
 // export all
 export * from './plugin/interfaces/option-parser';
 export * from './interfaces/attachment';
+export * from './interfaces/color-scheme';
 export * from './interfaces/common';
 export * from './interfaces/growi-facade';
 export * from './interfaces/growi-theme-metadata';
@@ -31,4 +32,5 @@ export * from './models/vo/error-apiv3';
 export * from './service/localstorage-manager';
 export * from './utils/basic-interceptor';
 export * from './utils/browser-utils';
+export * from './utils/growi-theme-metadata';
 export * from './utils/with-utils';

+ 5 - 0
packages/core/src/interfaces/color-scheme.ts

@@ -0,0 +1,5 @@
+export const ColorScheme = {
+  LIGHT: 'light',
+  DARK: 'dark',
+} as const;
+export type ColorScheme = typeof ColorScheme[keyof typeof ColorScheme];

+ 3 - 2
packages/core/src/interfaces/growi-theme-metadata.ts

@@ -1,7 +1,8 @@
+import { ColorScheme } from './color-scheme';
+
 export const GrowiThemeSchemeType = {
+  ...ColorScheme,
   BOTH: 'both',
-  LIGHT: 'light',
-  DARK: 'dark',
 } as const;
 export type GrowiThemeSchemeType = typeof GrowiThemeSchemeType[keyof typeof GrowiThemeSchemeType];
 

+ 8 - 0
packages/core/src/utils/growi-theme-metadata.ts

@@ -0,0 +1,8 @@
+import type { ColorScheme } from '../interfaces/color-scheme';
+import { GrowiThemeSchemeType } from '../interfaces/growi-theme-metadata';
+
+export const getForcedColorScheme = (growiThemeSchemeType?: GrowiThemeSchemeType): ColorScheme | undefined => {
+  return growiThemeSchemeType == null || growiThemeSchemeType === GrowiThemeSchemeType.BOTH
+    ? undefined
+    : growiThemeSchemeType as ColorScheme;
+};

+ 1 - 1
packages/hackmd/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@growi/hackmd",
-  "version": "6.0.2-RC.0",
+  "version": "6.0.3-RC.0",
   "description": "GROWI js and css files to use hackmd",
   "license": "MIT",
   "main": "dist/index.js",

+ 1 - 1
packages/preset-themes/package.json

@@ -1,7 +1,7 @@
 {
   "name": "@growi/preset-themes",
   "description": "GROWI preset themes",
-  "version": "6.0.2-RC.0",
+  "version": "6.0.3-RC.0",
   "license": "MIT",
   "main": "dist/libs/index.js",
   "files": [

+ 89 - 66
packages/preset-themes/src/styles/antarctic.scss

@@ -1,18 +1,7 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
-
-// == Define Bootstrap theme colors
-//
-
-// colors for overriding bootstrap $theme-colors
-// $secondary: #;
-// $info: #;
-// $success: #;
-// $warning: #;
-// $danger: #;
-// $light: #;
-// $dark: #;
+@use './theme/hsl-functions' as hsl;
 
 .growi:not(.login-page) {
   // add background-image
@@ -32,84 +21,118 @@
   background-size: cover;
 }
 
-$themecolor: #000080;
-$themelight: #f0f8ff;
-$accentcolor: #ffd700;
-
 .grw-navbar {
-  border-bottom: $accentcolor 4px solid;
+  border-bottom: #ffd700 4px solid;
 }
 
 //== Light Mode
 //
-:root {
-  $primary: $themecolor;
+:root[data-theme='light'] {
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 240,100%;
+  --primary-l: 25%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
 
   // Background colors
-  $bgcolor-global: $themelight;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: $gray-50;
-  $bgcolor-blinked-section: rgba($primary, 0.15);
-  //$bgcolor-keyword-highlighted: $grw-marker-yellow;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 208,100%;
+  --bgcolor-global-l: 97%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: #{$gray-50};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 85%)};
+  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
 
   // Font colors
-  $color-global: black;
-  $color-reversal: #eeeeee;
-  // $color-header: #2b2b2b;
-  $color-link: lighten($themecolor, 20%);
-  $color-link-hover: lighten($color-link, 20%);
-  $color-link-wiki: lighten($primary, 20%);
-  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: #c7254e; // optional
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 0,0%;
+  --color-global-l: 0%;
+  --color-reversal: #eeeeee;
+  // --color-header: #2b2b2b;
+  --color-link: #{hsl.lighten(var(--primary), 20%)};
+  --color-link-hs: var(--primary-hs);
+  --color-link-l: calc(var(--primary-l) + 20%);
+  --color-link-hover: #{hsl.lighten(var(--primary), 40%)};
+  --color-link-wiki: #{hsl.lighten(var(--primary), 20%)};
+  --color-link-wiki-hs: var(--primary-hs);
+  --color-link-wiki-l: calc(var(--primary-l) + 20%);
+  --color-link-wiki-hover: l#{hsl.lighten(var(--primary), 40%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #c7254e; // optional
 
   // List Group colors
-  // $color-list: $color-global;
-  // $bgcolor-list: $bgcolor-global;
-  // $color-list-hover: $color-reversal;
-  // $bgcolor-list-hover: ;
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  // --bgcolor-list: var(--bgcolor-global);
+  // --color-list-hover: var(--color-reversal);
+  // --bgcolor-list-hover: ;
+  // --color-list-active: var(--color-reversal);
+  // --bgcolor-list-active: var(--primary);
+
+  // Table colors
+  // --color-table: #; // optional
+  --bgcolor-table: var(--bgcolor-global); // optional
+  // --border-color-table: #; // optional
+  // --color-table-hover: #; // optional
+  // --bgcolor-table-hover: #; // optional
 
   // Navbar
-  $bgcolor-navbar: #35393f;
-  $bgcolor-search-top-dropdown: #fa9913;
-  $border-image-navbar: linear-gradient(to right, #f6d02e 0%, #f87c00 47%, #f6d02e 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-hs: 216,9%;
+  --bgcolor-navbar-l: 23%;
+  --bgcolor-search-top-dropdown: hsl(var(--bgcolor-search-top-dropdown-hs),var(--bgcolor-search-top-dropdown-l));
+  --bgcolor-search-top-dropdown-hs: 35,96%;
+  --bgcolor-search-top-dropdown-l: 53%;
+  --border-image-navbar: linear-gradient(to right, #f6d02e 0%, #f87c00 47%, #f6d02e 100%);
+
   // Logo colors
-  $bgcolor-logo: $bgcolor-navbar;
-  $fillcolor-logo-mark: lighten(desaturate($bgcolor-inline-code, 10%), 15%);
+  --bgcolor-logo: var(--bgcolor-navbar);
+  --fillcolor-logo-mark: #{lighten(desaturate($gray-100, 10%), 15%)};
 
   // Sidebar
-  $bgcolor-sidebar: $themecolor;
-  $bgcolor-sidebar-nav-item-active: rgba(black, 0.37); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+  --bgcolor-sidebar: var(--primary);
+  --bgcolor-sidebar-hs: var(--primary-hs);
+  --bgcolor-sidebar-l: var(--primary-l);
+  --bgcolor-sidebar-nav-item-active: rgba(black, 0.37); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+
   // Sidebar resize button
-  $color-resize-button: $color-reversal;
-  $bgcolor-resize-button: #fa9913;
-  $color-resize-button-hover: $color-reversal;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
+  --color-resize-button: var(--color-reversal);
+  --bgcolor-resize-button: hsl(var(--bgcolor-resize-button-hs),var(--bgcolor-resize-button-l));
+  --bgcolor-resize-button-hs: 35,96%;
+  --bgcolor-resize-button-l: 53%;
+  --color-resize-button-hover: var(--color-reversal);
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--bgcolor-resize-button), 5%)};
+
   // Sidebar contents
-  $color-sidebar-context: $themecolor;
-  $bgcolor-sidebar-context: #f4f6fc;
+  --color-sidebar-context: var(--primary);
+  --color-sidebar-context-hs: var(--primary-hs);
+  --color-sidebar-context-l: var(--primary-l);
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 225,57%;
+  --bgcolor-sidebar-context-l: 97%;
+
   // Sidebar list group
-  $bgcolor-sidebar-list-group: #fafbff; // optional
+  --bgcolor-sidebar-list-group: #fafbff; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $gray-400;
-  $border-color-global: $gray-400;
-  $bordercolor-inline-code: #ccc8c8; // optional
+  --border-color-theme: #{$gray-400};
+  --border-color-global: #{gray-400};
+  --bordercolor-inline-code: #ccc8c8; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $growi-blue;
+  --bgcolor-dropdown-link-active: #{$growi-blue};
 
   // admin theme box
-  $color-theme-color-box: lighten($themecolor, 20%);
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-light';
+  --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
 
   &, body {
     background-image: url('../images/antarctic/bg.svg');
@@ -121,16 +144,16 @@ $accentcolor: #ffd700;
   //Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 10%), lighten($primary, 55%), lighten($primary, 60%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.darken(var(--primary), 10%)}, #{hsl.lighten(var(--primary), 55%)}, #{hsl.lighten(var(--primary), 60%)});
     }
   }
 
   .table {
-    background-color: $themelight;
+    background-color:  var(--bgcolor-global);
   }
 
   #search-typeahead-asynctypeahead {
-    background-color: $bgcolor-global;
+    background-color: var(--bgcolor-global);
     .table {
       background-color: transparent;
     }

+ 92 - 64
packages/preset-themes/src/styles/blackboard.scss

@@ -1,99 +1,127 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
+@use './theme/hsl-functions' as hsl;
 
-:root {
+:root[data-theme='dark']{
   // Theme colors
-  $themecolor: #da8506;
-  $themelight: #223729;
-  $accentcolor: #739aff;
-  $subthemecolor: #192a1f;
-
-  $primary: $themecolor;
-  $dark: #223729;
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 36,95%;
+  --primary-l: 44%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --subthemecolor: hsl(var(--subthemecolor-hs),var(--subthemecolor-l));
+  --subthemecolor-hs: 141,25%;
+  --subthemecolor-l: 13%;
 
   // Background colors
-  $bgcolor-global: $themelight;
-  $bgcolor-navbar: #563e23;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: darken($themelight, 5%);
-  $bgcolor-blinked-section: rgba($primary, 0.5);
-  $bgcolor-keyword-highlighted: darken($grw-marker-red, 30%);
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 140,24%;
+  --bgcolor-global-l: 17%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: #{hsl.darken(var(--bgcolor-global), 5%)};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 50%)};
+  --bgcolor-keyword-highlighted: #{darken($grw-marker-red, 30%)};
 
   // Font colors
-  $color-global: #ffffff;
-  $color-reversal: $gray-100;
-  $color-link: $accentcolor;
-  $color-link-hover: lighten($color-link, 12%);
-  $color-link-wiki: $accentcolor;
-  $color-link-wiki-hover: lighten($color-link-wiki, 12%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: $subthemecolor;
-  $color-inline-code: #c7254e; // optional
-  $color-search: $dark;
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 0,0%;
+  --color-global-l: 100%;
+  --color-reversal: var(--gray-100);
+  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+  --color-link-hs: 223,100%;
+  --color-link-l: 73%;
+  --color-link-hover: #{hsl.lighten(var(--color-link), 12%)};
+  --color-link-wiki: var(--color-link);
+  --color-link-wiki-hs: var(--color-link-hs);
+  --color-link-wiki-l: var(--color-link-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link), 12%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: var(--subthemecolor);
+  --color-search: $dark;
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  $color-list-hover: $accentcolor;
-  // $bgcolor-list-hover: lighten($bgcolor-global, 3%);
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  --color-list-hover: var(--color-link);
+  // --bgcolor-list-hover: #{hsl.lighten(var(--bgcolor-global), 3%);
+  // --color-list-active: var(--color-reversal);
+  // --bgcolor-list-active: var(--primary);
+
+  // Table colors
+  // --color-table: #; // optional
+  --bgcolor-table: var(--bgcolor-global); // optional
+  // --border-color-table: #; // optional
+  // --color-table-hover: #; // optional
+  // --bgcolor-table-hover: #; // optional
 
   // Navbar
-  $bgcolor-navbar: #563e23;
-  $bgcolor-search-top-dropdown: $themecolor;
-  $border-image-navbar: linear-gradient(to right, #bebebe 0%, #d8d8d8 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-hs: 32,42%;
+  --bgcolor-navbar-l: 24%;
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(to right, #bebebe 0%, #d8d8d8 100%);
 
   // Logo colors
-  $bgcolor-logo: $color-global;
-  $fillcolor-logo-mark: $color-global;
-  // $fillcolor-logo-mark: #4e5a60;
+  --bgcolor-logo: var(--color-global);
+  --fillcolor-logo-mark: var(--color-global);
 
   // Sidebar
-  $bgcolor-sidebar: #7b5932;
-  // $bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px $primary; // optional
+  --bgcolor-sidebar: hsl(var(--bgcolor-sidebar-hs),var(--bgcolor-sidebar-l));
+  --bgcolor-sidebar-hs: 32,42%;
+  --bgcolor-sidebar-l: 34%;
+  // 00bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px var(--primary); // optional
+
   // Sidebar resize button
-  $color-resize-button: $color-global;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: $color-global;
-  $bgcolor-resize-button-hover: darken($bgcolor-resize-button, 5%);
+  --color-resize-button: var(--color-global);
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-resize-button-hs: var(--primary-hs);
+  --bgcolor-resize-button-l: var(--primary-l);
+  --color-resize-button-hover: var(--color-global);
+  --bgcolor-resize-button-hover: #{hsl.darken(var(--color-global), 5%)};
+
   // Sidebar contents
-  $bgcolor-sidebar-context: lighten($subthemecolor, 8%);
-  $color-sidebar-context: $color-global;
+  --color-sidebar-context: var(--color-global);
+  --color-sidebar-context-hs: var(--color-global-hs);
+  --color-sidebar-context-l: var(--color-global-l);
+  --bgcolor-sidebar-context: #{hsl.lighten(var(--subthemecolor),8%)};
+  --bgcolor-sidebar-context-hs: var(--subthemecolor-hs);
+  --bgcolor-sidebar-context-l: calc(var(--subthemecolor-l) + 8%);
+
   // Sidebar list group
-  // $bgcolor-sidebar-list-group: #; // optional
+  // --bgcolor-sidebar-list-group: #; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $color-global;
-  $bordercolor-inline-code: #4d4d4d; // optional
-
-  // Dropdown colors
-  $color-dropdown-link-active: $color-global;
-  $color-dropdown-link-hover: $color-reversal;
+  --border-color-theme: var(--color-global);
+  --bordercolor-inline-code: #4d4d4d; // optional
 
   // admin theme box
-  $color-theme-color-box: $primary;
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-dark';
+  --color-theme-color-box: var(--primary);
 
   // Navs
   .nav-tabs {
-    border-bottom: $border-color-theme 1px solid;
+    border-bottom: var(--border-color-theme) 1px solid;
     .nav-link {
       &:hover {
-        border-color: lighten($border-color-theme, 10%);
+        border-color: #{hsl.lighten(var(--color-global), 10%)};
         border-bottom: none;
       }
       &.active {
-        color: $color-link;
+        color: var(--color-link);
         background-color: transparent;
-        border-color: $border-color-theme;
+        border-color: var(--border-color-theme);
       }
     }
   }
@@ -101,14 +129,14 @@
   // Table
   .table {
     color: white;
-    background-color: $themelight;
-    border-color: $border-color-theme;
+    background-color: var(--bgcolor-global);
+    border-color: var(--border-color-theme);
   }
 
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.darken(var(--primary), 20%)});
     }
   }
 }

+ 0 - 1
packages/preset-themes/src/styles/bootstrap/_variables.scss

@@ -90,7 +90,6 @@ $border-radius-lg: 8px;
 // Buttons
 //
 // For each of Bootstrap's buttons, define text, background, and border color.
-$btn-link-disabled-color: $gray-500;
 $btn-focus-box-shadow: none;
 $btn-active-box-shadow: none;
 

+ 94 - 77
packages/preset-themes/src/styles/christmas.scss

@@ -1,34 +1,12 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
-
-// == Define Bootstrap theme colors
-//
-
-// colors for overriding bootstrap $theme-colors
-// $secondary: #;
-// $info: #;
-// $success: #;
-// $warning: #;
-// $danger: #;
-// $light: #;
-// $dark: #;
-
-$themecolor: #b3000c;
-$themelight: white;
-$subthemecolor: #30882c;
-$bgcolor-global: $themelight;
-$linktext: $subthemecolor;
-$linktext-hover: lighten($subthemecolor, 15%);
-$sidebar-text: white;
-$fillcolor-logo-mark: lighten(desaturate($themecolor, 50%), 50%);
-$color-link-wiki: lighten($subthemecolor, 5%);
-$color-link-wiki-hover: lighten($color-link-wiki, 15%);
+@use './theme/hsl-functions' as hsl;
 
 .growi:not(.login-page) {
   // add background-image
   .page-editor-preview-container {
-    background-image: url('/images/themes/christmas/christmas.jpg');
+    background-image: url('../images/christmas/christmas.jpg');
     background-attachment: fixed;
     background-position: center center;
     background-size: cover;
@@ -37,72 +15,110 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
 
 //== Light Mode
 //
-:root {
-  $primary: #d3c665;
+:root[data-theme='light'] {
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 53,56%;
+  --primary-l: 61%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+
   // Background colors
-  $bgcolor-card: $gray-50;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-blinked-section: rgba($primary, 0.5);
-  //$bgcolor-keyword-highlighted: $grw-marker-yellow;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 0,0%;
+  --bgcolor-global-l: 100%;
+  --bgcolor-card: #{$gray-50};
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),50%)};
+  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
 
   // Font colors
-  $color-global: #112744;
-  $color-reversal: $gray-100;
-  $color-link: $subthemecolor;
-  $color-link-hover: lighten($color-link, 10%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: #c7254e; // optional
-  $color-modal-header: $themelight;
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 214,60%;
+  --color-global-l: 17%;
+  --color-reversal: #{$gray-100};
+  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+  --color-link-hs: 117,51%;
+  --color-link-l: 35%;
+  --color-link-hover: #{hsl.lighten(var(--color-link), 15%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-link-wiki: #{hsl.lighten(var(--color-link), 5%)};
+  --color-link-wiki-hs: var(--color-link-hs);
+  --color-link-wiki-l: calc(var(--color-link-l) + 5%);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link), 20%)};
+  --color-inline-code: #c7254e; // optional
+  --color-modal-header: var(--bgcolor-global);
 
   // Table colors
-  $border-color-table: $gray-400; // optional
+  --border-color-table: #{$gray-400}; // optional
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  // $color-list-hover: $color-reversal;
-  $color-list-active: $themelight;
-  $bgcolor-list-active: $themecolor;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  // --color-list-hover: var(--color-reversal);
+  --color-list-active: var(--bgcolor-global);
+  --bgcolor-list-active: var(--bgcolor-navbar);
 
   // Navbar
-  $bgcolor-navbar: $themecolor;
-  $bgcolor-search-top-dropdown: $primary;
-  $border-color-navbar-gradient-left: #545fff;
-  $border-color-navbar-gradient-right: #00a6e5;
-  $border-image-navbar: linear-gradient(to right, $primary 0%, $subthemecolor 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-h: 356;
+  --bgcolor-navbar-s: 100%;
+  --bgcolor-navbar-l: 35%;
+  --bgcolor-navbar-hs: var(--bgcolor-navbar-h),var(--bgcolor-navbar-s);
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-color-navbar-gradient-left: #545fff;
+  --border-color-navbar-gradient-right: #00a6e5;
+  --border-image-navbar: linear-gradient(to right, var(--primary) 0%, var(--color-link) 100%);
 
   // Logo colors
-  $bgcolor-logo: $themecolor;
+  --bgcolor-logo: var(--bgcolor-navbar);
+  --fillcolor-logo-mark: hsl(var(--bgcolor-navbar-h),calc(var(--bgcolor-navbar-s) - 50%),calc(var(--bgcolor-navbar-l) + 50%));
 
   // Sidebar
-  $bgcolor-sidebar: $subthemecolor;
-  $bgcolor-sidebar-nav-item-active: rgba(black, 0.37); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px $primary; // optional
+  --bgcolor-sidebar: var(--color-link);
+  --bgcolor-sidebar-hs: var(--color-link-hs);
+  --bgcolor-sidebar-l: var(--color-link-l);
+  --bgcolor-sidebar-nav-item-active: rgba(black, 0.37); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px var(--primary); // optional
+
   // Sidebar resize button
-  $color-resize-button: $color-reversal;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: $color-reversal;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
-  $color-sidebar-context: $linktext;
-  $bgcolor-sidebar-context: #f4fcf6;
+  --color-resize-button: var(--color-reversal);
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-resize-button-hs: var(--primary-hs);
+  --bgcolor-resize-button-l: var(--primary-l);
+  --color-resize-button-hover: var(--color-reversal);
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--primary), 5%)};
+
+  // Sidebar contents
+  --color-sidebar-context: var(--color-link);
+  --color-sidebar-context-hs: var(--color-link-hs);
+  --color-sidebar-context-l: var(--color-link-l);
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 135,57%;
+  --bgcolor-sidebar-context-l: 97%;
+
   // Sidebar list group
-  $bgcolor-sidebar-list-group: #fafbff; // optional
+  --bgcolor-sidebar-list-group: #fafbff; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $gray-300; // former: `$navbar-border: $gray-300;`
-  $bordercolor-inline-code: #ccc8c8; // optional
+  --border-color-theme: #{$gray-300}; // former: `$navbar-border: $gray-300;`
+  --bordercolor-inline-code: #ccc8c8; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $themecolor;
+  --bgcolor-dropdown-link-active: var(--bgcolor-navbar);
 
   // admin theme box
-  $color-theme-color-box: lighten($themecolor, 20%);
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-light';
+  --color-theme-color-box: #{hsl.lighten(var(--bgcolor-navbar), 20%)};
 
   &, body {
     background-image: url('../images/christmas/christmas.jpg');
@@ -113,15 +129,15 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
   // change color of highlighted header in wiki (default: orange)
 
   .sidebar {
-    background: $themecolor;
+    background: var(--bgcolor-navbar);
   }
 
   .rbt-menu {
-    background: $themelight;
+    background: var(--bgcolor-global);
   }
 
   .page-editor-preview-container {
-    background-image: url('/images/themes/christmas/christmas.jpg');
+    background-image: url('../images/christmas/christmas.jpg');
     background-attachment: fixed;
     background-size: cover;
   }
@@ -158,8 +174,8 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
   * Modal
   */
   .modal-dialog .modal-header.bg-primary {
-    background-image: url('/images/themes/christmas/christmas-navbar.jpg');
-    border-bottom: 2px solid $subthemecolor;
+    background-image: url('../images/christmas/christmas-navbar.jpg');
+    border-bottom: 2px solid var(--color-link);
   }
 
   /*
@@ -167,22 +183,23 @@ $color-link-wiki-hover: lighten($color-link-wiki, 15%);
   */
   .card {
     &.border-primary {
-      border-color: $themecolor !important;
+      border-color: var(--bgcolor-navbar) !important;
     }
     .card-header.bg-primary {
       color: white;
-      background-image: url('/images/themes/christmas/christmas-navbar.jpg') !important;
+      background-image: url('../images/christmas/christmas-navbar.jpg') !important;
     }
   }
 
   .grw-navbar {
-    background-image: url('/images/themes/christmas/christmas-navbar.jpg');
+    background-image: url('../images/christmas/christmas-navbar.jpg');
   }
 
   // Button
-  .grw-page-editor-mode-manager {
+  .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($subthemecolor, 15%), lighten($subthemecolor, 35%), lighten($subthemecolor, 45%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.darken(var(--color-link), 15%)}, #{hsl.lighten(var(--color-link), 35%)}, #{hsl.lighten(var(--color-link), 45%)});
     }
   }
+
 }

+ 172 - 127
packages/preset-themes/src/styles/default.scss

@@ -1,115 +1,136 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
-
-// == Define Bootstrap theme colors
-//
-
-// colors for overriding bootstrap $theme-colors
-// $secondary: #;
-// --info: #;
-// --success: #;
-// --warning: #;
-// --danger: #;
-// $light: #;
-// $dark: #;
+@use './theme/hsl-functions' as hsl;
 
 //== Light Mode
 //
 :root[data-theme='light'] {
-  $primary: #122c55;
-  $accent: #209fd8;
+  --primary: hsl(var(--primary-hs),var(--primary-l));
+  --primary-hs: 216.7,65%;
+  --primary-l: 20.2%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l));
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --accent: hsl(var(--accent-hs),var(--accent-l));
+  --accent-hs: 198.6,74.2%;
+  --accent-l: 48.6%;
 
   // Background colors
-  $bgcolor-global: white;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: $gray-50;
-  $bgcolor-blinked-section: rgba($primary, 0.1);
-  //$bgcolor-keyword-highlighted: $grw-marker-yellow;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 0,0%;
+  --bgcolor-global-l: 100%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: #{$gray-50};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),90%)};
+  // --bgcolor-keyword-highlighted: #{$grw-marker-yellow};
 
   // Font colors
-  $color-global: #112744;
-  $color-reversal: $light;
-  // $color-header: #2b2b2b;
-  $color-link: #1938ba;
-  $color-link-hover: lighten($color-link, 20%);
-  $color-link-wiki: $color-link;
-  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
-  $color-link-nabvar: $gray-500;
-  $color-inline-code: darken($red, 15%); // optional
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 214.1,60%;
+  --color-global-l: 16.7%;
+  --color-reversal: var(--light);
+  // --color-header: #2b2b2b;
+  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+  --color-link-hs: 228.4,76.3%;
+  --color-link-l: 41.4%;
+  --color-link-hover: #{hsl.lighten(var(--color-link),20%)};
+  --color-link-wiki: var(--color-link);
+  --color-link-wiki-hs: var(--color-link-hs);
+  --color-link-wiki-l: var(--color-link-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link-wiki),20%)};
+  --color-link-nabvar: #{$gray-500};
+  --color-inline-code: #{darken($red, 15%)}; // optional
 
   // List Group colors
-  // $color-list: $color-global; // optional
-  // $bgcolor-list: $bgcolor-global; // optional
-  // $color-list-hover: $color-global; // optional
-  // $bgcolor-list-hover: darken($bgcolor-global, 3%); // optional
-  // $color-list-active: $color-reversal; // optional
-  // $bgcolor-list-active: $primary; // optional
+  --color-list: var(--color-global); // optional
+  --bgcolor-list: var(--bgcolor-global); // optional
+  // --color-list-hover: var(--color-global); // optional
+  --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};// optional
+  // --color-list-active: white ; // optional
+  // --bgcolor-list-active: #{hsl.lighten(var(--bgcolor-global),3%)}; // optional
+  // --color-page-list-group-item-meta: #{$gray-500}; // optional
 
   // Table colors
-  // $bgcolor-subnav: #; // optional
-  // $color-table: #; // optional
-  // $bgcolor-table: #; // optional
-  // $border-color-table: #; // optional
-  // $color-table-hover: #; // optional
-  // $bgcolor-table-hover: #; // optional
+  // --color-table: #; // optional
+  // --bgcolor-table: #; // optional
+  // --border-color-table: #; // optional
+  // --color-table-hover: #; // optional
+  // --bgcolor-table-hover: #; // optional
 
   // Navbar
-  $bgcolor-navbar: $gray-900;
-  $bgcolor-search-top-dropdown: $accent;
-  $border-image-navbar: linear-gradient(to right, #36c9ff 0%, #36c9ff 33%, #7926ff 66%, #ff2eff 100%);
+  --bgcolor-navbar: #{$gray-900};
+  --bgcolor-navbar-h:0;
+  --bgcolor-navbar-s:0%;
+  --bgcolor-navbar-l:12.94%;
+  --bgcolor-navbar-hs: 0,0%;
+  --bgcolor-search-top-dropdown: var(--accent);
+  --bgcolor-search-top-dropdown-hs: var(--accent-hs);
+  --bgcolor-search-top-dropdown-l: var(--accent-l);
+  --border-image-navbar: linear-gradient(to right, #36c9ff 0%, #36c9ff 33%, #7926ff 66%, #ff2eff 100%);
 
   // Logo colors
-  $bgcolor-logo: $bgcolor-navbar;
-  $fillcolor-logo-mark: lighten(desaturate($bgcolor-navbar, 10%), 15%);
+  --bgcolor-logo: var(--bgcolor-navbar);
+  --fillcolor-logo-mark: hsl(var(--bgcolor-navbar-h),calc(var(--bgcolor-navbar-s) - 20%),calc(var(--bgcolor-navbar-l) + 15%));
 
   // Sidebar
-  $bgcolor-sidebar: $primary;
-  $bgcolor-sidebar-nav-item-active: rgba(black, 0.37); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+  --bgcolor-sidebar: var(--primary);
+  --bgcolor-sidebar-hs: var(--primary-hs);
+  --bgcolor-sidebar-l: var(--primary-l);
+  --bgcolor-sidebar-nav-item-active: rgba(black, 0.37); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+
   // Sidebar resize button
-  $color-resize-button: $color-reversal;
-  $bgcolor-resize-button: $accent;
-  $color-resize-button-hover: $color-reversal;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
+  --color-resize-button: var(--color-reversal);
+  --bgcolor-resize-button: var(--accent);
+  --bgcolor-resize-button-hs: var(--accent-hs);
+  --bgcolor-resize-button-l: var(--accent-l);
+  --color-resize-button-hover: var(--color-reversal);
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--accent), 5%)};
+
   // Sidebar contents
-  $color-sidebar-context: $color-global;
-  $bgcolor-sidebar-context: lighten($primary, 77%);
+  --color-sidebar-context: var(--color-global);
+  --color-sidebar-context-hs: var(--color-global-hs);
+  --color-sidebar-context-l: var(--color-global-l);
+  --bgcolor-sidebar-context: #{hsl.lighten(var(--primary),77%)};
+  --bgcolor-sidebar-context-hs: var(--primary-hs);
+  --bgcolor-sidebar-context-l: calc(var(--primary-l) + 77%);
+
   // Sidebar list group
-  $bgcolor-sidebar-list-group: $gray-50; // optional
+  // --bgcolor-sidebar-list-group: #{$gray-50}; // optional
 
   // Subnavigation
-  // $bgcolor-subnav: #fafafa; // optional
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Tabs
-  // $color-nav-tabs-link-active: #; //optional
-  // $bordercolor-nav-tabs-hover: # # $bordercolor-nav-tabs; // optional
-  // $bordercolor-nav-tabs-active: # # $bgcolor-global; // optional
+  // --bordercolor-nav-tabs: #{$gray-300}; // optional
+  // --color-nav-tabs-link-active: #; //optional
+  // --bordercolor-nav-tabs-hover: # # $bordercolor-nav-tabs; // optional
+  // --bordercolor-nav-tabs-active: # # // optional
 
   // Tags
-  // $color-tags: #; //optional
-  // $bgcolor-tags: #; //optional
+  // --color-tags: #; //optional
+  // --bgcolor-tags: #; //optional
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $gray-400;
-  $bordercolor-inline-code: $gray-400; // optional
+  --border-color-theme: #{$gray-400};
+  --bordercolor-inline-code: #{$gray-400}; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $growi-blue;
+  --bgcolor-dropdown-link-active: #{$growi-blue};
 
   // admin theme box
-  $color-theme-color-box: lighten($primary, 20%);
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-light';
+  --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
 
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager($primary, lighten($primary, 65%), lighten($primary, 70%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager (var(--primary),#{hsl.lighten(var(--primary),65%)}, #{hsl.lighten(var(--primary),70%)});
     }
   }
 }
@@ -117,96 +138,120 @@
 //== Dark Mode
 //
 :root[data-theme='dark'] {
-  $primary: #115cd3;
-  $accent: #db00c2;
+  --primary: hsl(var(--primary-hs),var(--primary-l));
+  --primary-hs: 216.8,85.1%;
+  --primary-l: 44.7%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l));
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --accent: hsl(var(--accent-hs),var(--accent-l));
+  --accent-hs: 307,100%;
+  --accent-l: 43%;
 
   // Background colors
-  $bgcolor-global: #131418;
-  $bgcolor-inline-code: #1f1f22; //optional
-  $bgcolor-card: darken($bgcolor-global, 5%);
-  $bgcolor-blinked-section: rgba($primary, 0.4);
-  $bgcolor-keyword-highlighted: darken($grw-marker-red, 30%);
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 228,12%;
+  --bgcolor-global-l: 8%;
+  --bgcolor-inline-code: #1f1f22; //optional
+  --bgcolor-card: #{hsl.darken(var(--bgcolor-global),5%)};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 60%)};
+  --bgcolor-keyword-highlighted: #{darken($grw-marker-red, 30%)};
 
   // Font colors
-  $color-global: $gray-400;
-  $color-reversal: $gray-900;
-  // $color-header: desaturate($primary, 20%);
-  $color-link: #7b9ad5;
-  $color-link-hover: lighten($color-link, 10%);
-  $color-link-wiki: $color-link;
-  $color-link-wiki-hover: lighten($color-link-wiki, 10%);
-  $color-link-nabvar: #a7a7a7;
-  $color-inline-code: #c7254e; // optional
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 0,0%;
+  --color-global-l: 74%;
+  --color-reversal: #{$gray-900};
+  // --color-header: desaturate($primary, 20%);
+  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+  --color-link-hs: 219.3,51.7%;
+  --color-link-l: 65.9%;
+  --color-link-hover: #{hsl.lighten(var(--color-link),10%)};
+  --color-link-wiki: var(--color-link);
+  --color-link-wiki-hs: var(--color-link-hs);
+  --color-link-wiki-l: var(--color-link-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link-wiki),10%)};
+  --color-link-nabvar: #a7a7a7;
+  --color-inline-code: #c7254e; // optional
 
   // List Group colors
-  // $color-list: $color-global; // optional
-  // $bgcolor-list: $bgcolor-global; // optional
-  // $color-list-hover: $color-global; // optional
-  // $bgcolor-list-hover: lighten($bgcolor-global, 3%); // optional
-  // $color-list-active:white ; // optional
-  // $bgcolor-list-active: $primary; // optional
+  --color-list: var(--color-global); // optional
+  --bgcolor-list: var(--bgcolor-global); // optional
+  // --color-list-hover: var(--color-global); // optional
+  --bgcolor-list-hover: #{hsl.lighten(var(--bgcolor-global),3%)}; // optional
+  // --color-list-active: white ; // optional
+  // --bgcolor-list-active: var(--primary); // optional
 
   // Table colors
-  // $color-table: #; // optional
-  // $bgcolor-table: #; // optional
-  // $border-color-table: #; // optional
-  // $color-table-hover: #; // optional
-  // $bgcolor-table-hover: #; // optional
+  // --color-table: #; // optional
+  // --bgcolor-table: #; // optional
+  // --border-color-table: #; // optional
+  // --color-table-hover: #; // optional
+  // --bgcolor-table-hover: #; // optional
 
   // Navbar
-  $bgcolor-navbar: #2a2929;
-  $bgcolor-search-top-dropdown: $accent;
-  $border-image-navbar: linear-gradient(to right, #44bfe3 0%, #b04aff 50%, #ff1794 100%);
+  --bgcolor-navbar: #2a2929;
+  --bgcolor-search-top-dropdown: var(--accent);
+  --bgcolor-search-top-dropdown-hs: var(--accent-hs);
+  --bgcolor-search-top-dropdown-l: var(--accent-l);
+  --border-image-navbar: linear-gradient(to right, #44bfe3 0%, #b04aff 50%, #ff1794 100%);
 
   // Logo colors
-  $bgcolor-logo: $bgcolor-navbar;
-  $fillcolor-logo-mark: $gray-700;
+  --bgcolor-logo: var(--bgcolor-navbar);
+  --fillcolor-logo-mark: #{$gray-700};
 
   // Sidebar
-  $bgcolor-sidebar: #122c55;
-  $bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+  --bgcolor-sidebar: hsl(var(--bgcolor-sidebar-hs),var(--bgcolor-sidebar-l));
+  --bgcolor-sidebar-hs: 216.7,65%;
+  --bgcolor-sidebar-l: 20.2%;
+  --bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 228,12%;
+  --bgcolor-sidebar-context-l: 16%;
   // Sidebar resize button
-  $color-resize-button: white;
-  $bgcolor-resize-button: $accent;
-  $color-resize-button-hover: white;
-  $bgcolor-resize-button-hover: darken($bgcolor-resize-button, 5%);
+  --color-resize-button: white;
+  --bgcolor-resize-button: var(--accent);
+  --color-resize-button-hover: white;
+  --bgcolor-resize-button-hover: #{hsl.darken(var(--accent), 5%)};
   // Sidebar contents
-  $bgcolor-sidebar-context: lighten($bgcolor-global, 8%);
-  $color-sidebar-context: $color-global;
+  --color-sidebar-context: var(--color-global);
+  --color-sidebar-context-hs: var(--color-global-hs);
+  --color-sidebar-context-l: var(--color-global-l);
   // Sidebar list group
-  $bgcolor-sidebar-list-group: #1c2a3e; // optional
+  // --bgcolor-sidebar-list-group: #1c2a3e; // optional
 
   // Subnavigation
-  $bgcolor-subnav: lighten($bgcolor-global, 4%); // optional
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) + 4%);
 
   // Tabs
-  $bordercolor-nav-tabs: $gray-700; // optional
-  // $color-nav-tabs-link-active: #; //optional
-  $bordercolor-nav-tabs-hover: #666 #666 $bordercolor-nav-tabs; // optional
-  // $bordercolor-nav-tabs-active: # # $bgcolor-global; // optional
+  --bordercolor-nav-tabs: #{$gray-700}; // optional
+  // --color-nav-tabs-link-active: #; //optional
+  --bordercolor-nav-tabs-hover: #666 #666 var(--bordercolor-nav-tabs); // optional
+  --bordercolor-nav-tabs-active: var(--bordercolor-nav-tabs) var(--bordercolor-nav-tabs) var(--bgcolor-global); // optional
 
   // Tags
-  // $color-tags: #; //optional
-  // $bgcolor-tags: #; //optional
+  // --color-tags: #; //optional
+  // --bgcolor-tags: #; //optional
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $gray-400;
-  $bordercolor-inline-code: $secondary; // optional
+  --border-color-theme: hsl(var(--border-color-theme-hs),var(--border-color-theme-l));
+  --border-color-theme-hs: 210,13%;
+  --border-color-theme-l: 71%;
+  --bordercolor-inline-code: var(--secondary); // optional
 
   // admin theme box
-  $color-theme-color-box: $primary;
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-dark';
+  --color-theme-color-box: var(--primary);
 
   //Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(lighten($primary, 30%), lighten($primary, 20%), $primary, darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.lighten(var(--primary), 30%)}, #{hsl.lighten(var(--primary), 20%)}, var(--primary), #{hsl.darken(var(--primary), 20%)});
     }
   }
 }

+ 180 - 112
packages/preset-themes/src/styles/fire-red.scss

@@ -1,85 +1,114 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
+@use './theme/hsl-functions' as hsl;
 
 :root[data-theme='light'] {
   // Theme colors
-  $themecolor: #ea5532;
-  $themelight: #ffffff;
-  $accentcolor: #bfbfbf;
-  $subthemecolor: #e6e6e6;
-
-  $primary: $themecolor;
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 11,81%;
+  --primary-l: 56%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+  --accentcolor-hs: 0,0%;
+  --accentcolor-l: 75%;
 
   // Background colors
-  $bgcolor-global: $themelight;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: $accentcolor;
-  $bgcolor-blinked-section: rgba($primary, 0.1);
-  //$bgcolor-keyword-highlighted: $grw-marker-yellow;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 0,0%;
+  --bgcolor-global-l: 100%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: var(--accentcolor);
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),90%)};
+  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
 
   // Font colors
-  $color-global: #2c2c2c;
-  $color-reversal: $gray-100;
-  $color-link: $primary;
-  $color-link-hover: lighten($color-link, 12%);
-  $color-link-wiki: $primary;
-  $color-link-wiki-hover: lighten($color-link-wiki, 12%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: #c7254e; // optional
-  $color-search: $color-global;
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 0,0%;
+  --color-global-l: 17%;
+  --color-reversal: #{$gray-100};
+  --color-link: var(--primary);
+  --color-link-hs: var(--primary-hs);
+  --color-link-l: var(--primary-l);
+  --color-link-hover: #{hsl.lighten(var(--primary), 12%)};
+  --color-link-wiki: var(--primary);
+  --color-link-wiki-hs: var(--primary-hs);
+  --color-link-wiki-l: var(--primary-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--primary), 12%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #c7254e; // optional
+  --color-search: var(--color-global);
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  $color-list-hover: $color-search;
-  $bgcolor-list-hover: darken($bgcolor-global, 3%);
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  --color-list-hover: var(--color-global);
+  --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};
+  // --color-list-active: var(--color-reversal);
+  // --bgcolor-list-active: var(--primary);
 
   // Navbar
-  $bgcolor-navbar: $color-global;
-  $bgcolor-search-top-dropdown: $themecolor;
-  $border-image-navbar: linear-gradient(to right, $primary 0%, darken($primary, 5%) 100%);
+  --bgcolor-navbar: var(--color-global);
+  --bgcolor-navbar-hs: var(--color-global-hs);
+  --bgcolor-navbar-l: var(--color-global-l);
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(to right, var(--primary) 0%, #{hsl.darken(var(--primary), 5%)} 100%);
 
   // Logo colors
-  $bgcolor-logo: $themelight;
-  $fillcolor-logo-mark: $themelight;
+  --bgcolor-logo: var(--bgcolor-global);
+  --fillcolor-logo-mark: var(--bgcolor-global);
 
   // Sidebar
-  $bgcolor-sidebar: $accentcolor;
-  // $bgcolor-sidebar-nav-item-active: rgba(#, 0.37); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #ffffff; // optional
+  --bgcolor-sidebar: var(--accentcolor);
+  --bgcolor-sidebar-hs: var(--accentcolor-hs);
+  --bgcolor-sidebar-l: var(--accentcolor-l);
+  // --bgcolor-sidebar-nav-item-active: rgba(#, 0.37); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #ffffff; // optional
+
   // Sidebar resize button
-  $color-resize-button: #ffffff;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: $color-reversal;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
+  --color-resize-button: #ffffff;
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-sidebar-context-hs: var(--primary-hs);
+  --bgcolor-sidebar-context-l: var(--primary-l);
+  --color-resize-button-hover: var(--color-reversal);
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--primary), 5%)};
+
   // Sidebar contents
-  $color-sidebar-context: $color-global;
-  $bgcolor-sidebar-context: #ececec;
+  --color-sidebar-context: var(--color-global);
+  --color-sidebar-context-hs: var(--color-global-hs);
+  --color-sidebar-context-l: var(--color-global-l);
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 0,0%;
+  --bgcolor-sidebar-context-l: 93%;
+
   // Sidebar list group
-  // $bgcolor-sidebar-list-group: #; // optional
+  // --bgcolor-sidebar-list-group: #; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $primary;
-  $bordercolor-inline-code: #ccc8c8; // optional
+  --border-color-theme: var(--primary);
+  --bordercolor-inline-code: #ccc8c8; // optional
 
   // admin theme box
-  $color-theme-color-box: $primary;
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-light';
+  --color-theme-color-box: var(--primary);
 
   // Navs {
   .nav-tabs {
-    border-bottom: $border-color-theme 1px solid;
+    border-bottom: var(--primary) 1px solid;
     .nav-link {
       &:hover {
-        border-color: lighten($border-color-theme, 10%);
+        border-color: #{hsl.lighten(var(--primary), 10%)};
         border-bottom: none;
       }
       &.active {
@@ -87,106 +116,136 @@
       }
     }
   }
+
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, lighten($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.lighten(var(--primary), 20%)});
     }
   }
+
+  .grw-global-search .btn-secondary.dropdown-toggle {
+    color: white;
+  }
+
+  .btn-primary {
+    color:white !important;
+  }
 }
 
 :root[data-theme='dark'] {
   // Theme colors
-  $themecolor: #ea5532;
-  $themedark: #333333;
-  $accentcolor: #212121;
-  $subthemecolor: #2e2e2e;
-
-  $primary: #ea5532;
-  $dark: #a7a7a7;
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 11,81%;
+  --primary-l: 56%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+  --accentcolor-hs: 0,0%;
+  --accentcolor-l: 13%;
 
   // Background colors
-  $bgcolor-global: $themedark;
-  $bgcolor-navbar: #2b2b2b;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: darken($themedark, 5%);
-  $bgcolor-blinked-section: rgba($primary, 0.5);
-  $bgcolor-keyword-highlighted: darken($grw-marker-red, 30%);
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 0,0%;
+  --bgcolor-global-l: 20%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: #{hsl.darken(var(--bgcolor-global), 5%)};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),50%)};
+  --bgcolor-keyword-highlighted: #{darken($grw-marker-red, 30%)};
 
   // Font colors
-  $color-global: #ffffff;
-  $color-reversal: $gray-100;
-  $color-link: $primary;
-  $color-link-hover: lighten($color-link, 12%);
-  $color-link-wiki: $primary;
-  $color-link-wiki-hover: lighten($color-link-wiki, 12%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: $subthemecolor;
-  $color-inline-code: #c7254e; // optional
-  $color-search: $dark;
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 0,0%;
+  --color-global-l: 100%;
+  --color-reversal: #{$gray-100};
+  --color-link: var(--primary);
+  --color-link-hs: var(--primary-hs);
+  --color-link-l: var(--primary-l);
+  --color-link-hover: #{hsl.lighten(var(--color-link),12%)};
+  --color-link-wiki: var(--primary);
+  --color-link-wiki-hs: var(--primary-hs);
+  --color-link-wiki-l: var(--primary-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link),12%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #c7254e; // optional
+  --color-search: #a7a7a7;
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  $color-list-hover: $accentcolor;
-  // $bgcolor-list-hover: lighten($bgcolor-global, 3%);
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  --color-list-hover: var(--accentcolor);
+  // --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};// optional
+  // --color-list-active: white ; // optional
+  // --bgcolor-list-active: #{hsl.lighten(var(--bgcolor-global),3%)}; // optional
 
   // Navbar
-  $bgcolor-navbar: #2c2c2c;
-  $bgcolor-search-top-dropdown: $themecolor;
-  $border-image-navbar: linear-gradient(to right, #ea5532 0%, #c9171e 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-hs: 0,0%;
+  --bgcolor-navbar-l: 17%;
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(to right, #ea5532 0%, #c9171e 100%);
 
   // Logo colors
-  $bgcolor-logo: #ffffff;
-  $fillcolor-logo-mark: #ffffff;
-  // $fillcolor-logo-mark: #4e5a60;
+  --bgcolor-logo: var(--color-global);
+  --fillcolor-logo-mark: var(--color-global);
+  // --fillcolor-logo-mark: #4e5a60;
 
   // Sidebar
-  $bgcolor-sidebar: $accentcolor;
-  // $bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px $primary; // optional
+  --bgcolor-sidebar: var(--accentcolor);
+  --bgcolor-sidebar-hs: var(--accentcolor-hs);
+  --bgcolor-sidebar-l: var(--accentcolor-l);
+  // --bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px var(--primary); // optional
+
   // Sidebar resize button
-  $color-resize-button: $color-global;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: $color-global;
-  $bgcolor-resize-button-hover: darken($bgcolor-resize-button, 5%);
+  --color-resize-button: var(--color-global);
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-resize-button-hs: var(--primary-hs);
+  --bgcolor-resize-button-l: var(--primary-l);
+  --color-resize-button-hover: var(--color-global);
+  --bgcolor-resize-button-hover: #{hsl.darken(var(--primary), 5%)};
+
   // Sidebar contents
-  $bgcolor-sidebar-context: #413f3f;
-  $color-sidebar-context: $color-global;
+  --color-sidebar-context: var(--color-global);
+  --color-sidebar-context-hs: var(--color-global-hs);
+  --color-sidebar-context-l: var(--color-global-l);
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 0,2%;
+  --bgcolor-sidebar-context-l: 25%;
+
   // Sidebar list group
-  // $bgcolor-sidebar-list-group: #; // optional
+  // --bgcolor-sidebar-list-group: #; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $primary;
-  $bordercolor-inline-code: #4d4d4d; // optional
-
-  // Dropdown colors
-  $color-dropdown-link-active: $color-global;
-  $color-dropdown-link-hover: $color-reversal;
+  --border-color-theme: var(--primary);
+  --bordercolor-inline-code: #4d4d4d; // optional
 
   // admin theme box
-  $color-theme-color-box: $primary;
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-dark';
+  --color-theme-color-box: var(--primary);
 
   // Navs
   .nav-tabs {
-    border-bottom: $border-color-theme 1px solid;
+    border-bottom: var(--primary) 1px solid;
     .nav-link {
       &:hover {
-        border-color: lighten($border-color-theme, 10%);
+        border-color: #{hsl.lighten(var(--primary), 10%)};
         border-bottom: none;
       }
       &.active {
-        color: $color-link;
+        color: var(--primary);
         background-color: transparent;
-        border-color: $border-color-theme;
+        border-color: var(--primary);
       }
     }
   }
@@ -199,7 +258,16 @@
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.darken(var(--primary), 20%)});
     }
   }
+
+  .grw-global-search .btn-secondary.dropdown-toggle {
+    color: white;
+  }
+
+  .btn-primary {
+    color:white !important;
+  }
+
 }

+ 92 - 58
packages/preset-themes/src/styles/future.scss

@@ -1,95 +1,129 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
+@use './theme/hsl-functions' as hsl;
+
+:root[data-theme='dark']{
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 181,100%;
+  --primary-l: 36%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --themecolor: hsl(var(--themecolor-hs),var(--themecolor-l));
+  --themecolor-hs: 193,34%;
+  --themecolor-l: 13%;
+  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+  --accentcolor-hs: 178,100%;
+  --accentcolor-l: 50%;
 
-$primary: #00b5b7;
-$themecolor: #16282d;
-$accentcolor: #00fff5;
-
-:root {
   // Background colors
-  $bgcolor-global: $themecolor;
-  $bgcolor-inline-code: #1f1f22; //optional
-  $bgcolor-card: darken($themecolor, 5%);
-  $bgcolor-blinked-section: rgba($primary, 0.4);
-  $bgcolor-keyword-highlighted: darken($grw-marker-red, 30%);
+  --bgcolor-global: var(--themecolor);
+  --bgcolor-inline-code: #1f1f22; //optional
+  --bgcolor-card: #{hsl.darken(var(--themecolor), 5%)};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 60%)};
+  --bgcolor-keyword-highlighted: #{darken($grw-marker-red, 30%)};
 
   // Font colors
-  $color-global: #95abba;
-  $color-reversal: $gray-900;
-  $color-header: #95abba;
-  $color-link: $accentcolor;
-  $color-link-hover: lighten($color-link, 20%);
-  $color-link-wiki: $accentcolor;
-  $color-link-wiki-hover: darken($color-link-wiki, 5%);
-  $color-link-nabvar: #a7a7a7;
-  $color-inline-code: #c7254e; // optional
-  $color-search: $primary;
+  --color-global: #{hsl(var(--color-global-hs),var(--color-global-l))};
+  --color-global-hs: 204,21%;
+  --color-global-l: 66%;
+  --color-reversal: #{$gray-900};
+  --color-header: var(--color-global);
+  --color-link: var(--accentcolor);
+  --color-link-hover: #{hsl.lighten(var(--accentcolor), 20%)};
+  --color-link-wiki: var(--accentcolor);
+  --color-link-wiki-hover: #{hsl.darken(var(--accentcolor), 5%)};
+  --color-link-nabvar: #a7a7a7;
+  --color-inline-code: #c7254e; // optional
+  --color-search: var(--primary);
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  // $color-list-hover: $color-reversal;
-  $color-list-active: white;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  // --color-list-hover: var(--color-reversal);
+  --color-list-active: white;
+  --bgcolor-list-active: var(--primary);
+  // --color-page-list-group-item-meta: #{$gray-500}; // optional
 
   // Table colors
-  // $color-table: #; // optional
-  $bgcolor-table: darken($themecolor, 3%); // optional
-  // $border-color-table: #; // optional
-  // $color-table-hover: #; // optional
-  // $bgcolor-table-hover: #; // optional
+  // --color-table: #; // optional
+  --bgcolor-table: #{hsl.darken(var(--themecolor), 3%)}; // optional
+  // --border-color-table: #; // optional
+  // --color-table-hover: #; // optional
+  // --bgcolor-table-hover: #; // optional
 
   // Navbar
-  $bgcolor-navbar: #01181a;
-  $bgcolor-search-top-dropdown: #00c2c4;
-  $border-image-navbar: linear-gradient(90deg, #6cfff9 0%, #0034c1 45%, #6cfff9 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-hs: 185,93%;
+  --bgcolor-navbar-l: 5%;
+  --bgcolor-search-top-dropdown: hsl(var(--bgcolor-search-top-dropdown-hs),var(--bgcolor-search-top-dropdown-l));
+  --bgcolor-search-top-dropdown-hs: 181,100%;
+  --bgcolor-search-top-dropdown-l: 38%;
+  --border-image-navbar: linear-gradient(90deg, #6cfff9 0%, #0034c1 45%, #6cfff9 100%);
 
   // Logo colors
-  $bgcolor-logo: darken($themecolor, 10%);
-  $fillcolor-logo-mark: #dedede;
+  --bgcolor-logo: #{hsl.darken(var(--themecolor), 10%)};
+  --fillcolor-logo-mark: #dedede;
 
   // Sidebar
-  $bgcolor-sidebar: #052e2f;
-  $bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #969494; // optional
+  --bgcolor-sidebar: hsl(var(--bgcolor-sidebar-hs),var(--bgcolor-sidebar-l));
+  --bgcolor-sidebar-hs: 181,81%;
+  --bgcolor-sidebar-l: 10%;
+  --bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #969494; // optional
+
+  // Sidebar resize button
+  --color-resize-button: #0e2329;
+  --bgcolor-resize-button: var(--bgcolor-search-top-dropdown);
+  --bgcolor-resize-button-hs: var(--bgcolor-search-top-dropdown-hs);
+  --bgcolor-resize-button-l: var(--bgcolor-search-top-dropdown-l);
+  --color-resize-button-hover: #0e2329;
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--bgcolor-search-top-dropdown), 5%)};
 
   // Sidebar contents
-  $color-sidebar-context: #2cfbff;
-  $bgcolor-sidebar-context: #184040;
+  --color-sidebar-context: hsl(var(--color-sidebar-context-hs),var(--color-sidebar-context-l));
+  --color-sidebar-context-hs: 181,100%;
+  --color-sidebar-context-l: 59%;
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 180,45%;
+  --bgcolor-sidebar-context-l: 17%;
 
   // Sidebar list group
-  $bgcolor-sidebar-list-group: #162126; // optional
+  --bgcolor-sidebar-list-group: #162126; // optional
 
-  // Sidebar resize button
-  $color-resize-button: #0e2329;
-  $bgcolor-resize-button: #00c2c4;
-  $color-resize-button-hover: #0e2329;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Tabs
-  $bordercolor-nav-tabs: #4c9eb4; // optional
-  // $color-nav-tabs-link-active: #; //optional
-  $bordercolor-nav-tabs-hover: #295561 #295561 $bordercolor-nav-tabs; // optional
-  // $bordercolor-nav-tabs-active: # # $bgcolor-global; // optional
+  --bordercolor-nav-tabs: #4c9eb4; // optional
+  // --color-nav-tabs-link-active: #; //optional
+  --bordercolor-nav-tabs-hover: #295561 #295561 var(--bordercolor-nav-tabs); // optional
+  // --bordercolor-nav-tabs-active: # # var(--bgcolor-global); // optional
+
+  // Tags
+  // --color-tags: #; //optional
+  // --bgcolor-tags: #; //optional
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: #407483;
-  $bordercolor-inline-code: #4d4d4d; // optional
+  --border-color-theme: #407483;
+  --bordercolor-inline-code: #4d4d4d; // optional
 
-  // admin theme box
-  $color-theme-color-box: lighten($primary, 20%);
+  // Dropdown colors
+  --bgcolor-dropdown-link-active: #{$growi-blue};
 
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-dark';
+  // admin theme box
+  --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
 
   //Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(lighten($primary, 10%), $primary, darken($primary, 10%), darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.lighten(var(--primary), 10%)}, var(--primary), #{hsl.darken(var(--primary), 10%)}, #{hsl.darken(var(--primary), 20%)});
     }
   }
 

+ 76 - 63
packages/preset-themes/src/styles/halloween.scss

@@ -1,24 +1,12 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
+@use './theme/hsl-functions' as hsl;
 
-$themecolor: #aa4a04;
-$themelight: #f0f8ff;
-$accentcolor: #ffd700;
 $bordercolor: #7e0d7e;
 
-// == Define Bootstrap theme colors
-//
-
 // colors for overriding bootstrap $theme-colors
-$primary: $themecolor;
-$light: lighten($secondary, 10%);
 
-// $info: #;
-// $success: #;
-// $warning: #;
-// $danger: #;
-// $dark: #;
 
 .growi:not(.login-page) {
   // add background-image
@@ -28,79 +16,104 @@ $light: lighten($secondary, 10%);
 }
 
 
-:root {
+:root[data-theme='dark'] {
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 25,95%;
+  --primary-l: 34%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+
   // Background colors
-  $bgcolor-global: #050000;
-  $bgcolor-inline-code: #1f1f22; //optional
-  $bgcolor-card: $bgcolor-global;
-  $bgcolor-blinked-section: rgba($primary, 0.4);
-  $bgcolor-keyword-highlighted: darkviolet;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 0,100%;
+  --bgcolor-global-l: 1%;
+  --bgcolor-inline-code: #1f1f22; //optional
+  --bgcolor-card: var(--bgcolor-global);
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 60%)};
+  --bgcolor-keyword-highlighted: darkviolet;
 
   // Font colors
-  $color-global: #e9af2b;
-  $color-reversal: #eeeeee;
-  // $color-header: #2b2b2b;
-  $color-link: #aa97cb;
-  $color-link-hover: lighten($color-link, 20%);
-  $color-link-wiki: #aa97cb;
-  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
-  $color-link-nabvar: #a7a7a7;
-  $color-inline-code: #c7254e; // optional
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 42,81%;
+  --color-global-l: 54%;
+  --color-reversal: #eeeeee;
+  // --color-header: #2b2b2b;
+  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+  --color-link-hs: 262,33%;
+  --color-link-l: 69%;
+  --color-link-hover: #{hsl.lighten(var(--color-link), 20%)};
+  --color-link-wiki: var(--color-link);
+  --color-link-wiki-hs: var(--color-link-hs);
+  --color-link-wiki-l: var(--color-link-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link), 20%)};
+  --color-link-nabvar: #a7a7a7;
+  --color-inline-code: #c7254e; // optional
 
   // List Group colors
-  $color-list: #979797;
-  $bgcolor-list: transparent;
-  $color-list-hover: $themecolor;
-  // $bgcolor-list-hover: ;
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  --color-list: #979797;
+  --bgcolor-list: transparent;
+  --color-list-hover: var(--primary);
+  // --bgcolor-list-hover: ;
+  // --color-list-active: var(--color-reversal);
+  // --bgcolor-list-active: var(--primary);
 
   // Search Top
-  $color-search: $primary;
+  --color-search: var(--primary);
 
   // Navbar
-  $bgcolor-navbar: #eceded;
-  $bgcolor-search-top-dropdown: $primary;
-  $border-image-navbar: linear-gradient(90deg, #e3b7ff 0%, #134774 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-hs: 180,3%;
+  --bgcolor-navbar-l: 93%;
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(90deg, #e3b7ff 0%, #134774 100%);
 
   // Logo colors
-  $bgcolor-logo: darken($themecolor, 10%);
-  $fillcolor-logo-mark: #dedede;
+  --bgcolor-logo: #{hsl.darken(var(--primary),10%)};
+  --fillcolor-logo-mark: #dedede;
 
   // Sidebar
-  $bgcolor-sidebar: #162b33;
-  $bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #969494; // optional
+  --bgcolor-sidebar: hsl(var(--bgcolor-sidebar-hs),var(--bgcolor-sidebar-l));
+  --bgcolor-sidebar-hs: 197,40%;
+  --bgcolor-sidebar-l: 14%;
+  --bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #969494; // optional
 
   // Sidebar contents
-  $color-sidebar-context: #aa97cb;
-  $bgcolor-sidebar-context: #302b3c;
+  --color-sidebar-context: hsl(var(--color-sidebar-context-hs),var(--color-sidebar-context-l));
+  --color-sidebar-context-hs: 262,33%;
+  --color-sidebar-context-l: 69%;
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 258,17%;
+  --bgcolor-sidebar-context-l: 20%;
 
   // Sidebar list group
-  $bgcolor-sidebar-list-group: #2c2926; // optional
+  --bgcolor-sidebar-list-group: #2c2926; // optional
 
   // Sidebar resize button
-  $color-resize-button: #effcfa;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: #effcfa;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
+  --color-resize-button: #effcfa;
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-resize-button-hs: var(--primary-hs);
+  --bgcolor-resize-button-l: var(--primary-l);
+  --color-resize-button-hover: #effcfa;
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--primary), 5%)};
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) + 4%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $gray-300; // former: `$navbar-border: $gray-300;`
-  $bordercolor-inline-code: #4d4d4d; // optional
-
-  // Dropdown colors
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
+  --border-color-theme: #{$gray-300}; // former: `$navbar-border: $gray-300;`
+  --bordercolor-inline-code: #4d4d4d; // optional
 
   // admin theme box
-  $color-theme-color-box: lighten($primary, 20%);
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-dark';
+  --color-theme-color-box: #{hsl.lighten(var(--primary), 20%)};
 
   &, body {
     background-image: url('../images/halloween/halloween.jpg');
@@ -113,13 +126,13 @@ $light: lighten($secondary, 10%);
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(lighten($primary, 35%), $primary, lighten($primary, 5%), darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.lighten(var(--primary), 35%)}, var(--primary), #{hsl.lighten(var(--primary), 5%)}, #{hsl.darken(var(--primary), 20%)});
     }
   }
 
   // Table
   .table {
-    color: $color-global;
+    color: var(--color-global);
   }
 
   pre {

+ 188 - 137
packages/preset-themes/src/styles/hufflepuff.scss

@@ -1,118 +1,142 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
+@use './theme/hsl-functions' as hsl;
 
 // == Define Bootstrap theme colors
 //
 
 // colors for overriding bootstrap $theme-colors
-// $secondary: #;
-// $info: #;
-// $success: #;
-// $warning: #;
-// $danger: #;
-// $light: #;
-// $dark: #;
-
-// .grw-navbar {
-//   border-bottom: $accentcolor 4px solid;
-// }
+// --info: #;
+// --success: #;
+// --warning: #;
+// --danger: #;
+// --light: #;
+// --dark: #;
 
 //== Light Mode
 //
 :root[data-theme='light'] {
   // Theme colors
-  $themecolor: #eaab20;
-  $themelight: #efe2cf;
-  $subthemecolor: #231e1d;
-  $third-main-color: #f0c05a;
-  $accentcolor: #993439;
-
-  $primary: $themecolor;
-  // $secondary: $subthemecolor;
-  $secondary: $third-main-color;
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 41,83%;
+  --primary-l: 52%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 41,83%;
+  --secondary-l: 65%;
+  --subthemecolor: hsl(var(--subthemecolor-hs),var(--subthemecolor-l));
+  --subthemecolor-hs: 10,9%;
+  --subthemecolor-l: 13%;
 
   // Background colors
-  $bgcolor-global: lighten($themelight, 10%);
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: $gray-100;
-  $bgcolor-blinked-section: rgba($primary, 0.5);
-  $bgcolor-keyword-highlighted: $grw-marker-green;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 36,50%;
+  --bgcolor-global-l: 97%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: #{$gray-100};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),50%)};
+  --bgcolor-keyword-highlighted: $grw-marker-green;
 
   // Font colors
-  $color-global: $subthemecolor;
-  $color-reversal: white;
-  $color-link: $accentcolor;
-  $color-link-hover: lighten($accentcolor, 10%);
-  $color-link-wiki: $accentcolor;
-  $color-link-wiki-hover: lighten($color-link-wiki, 10%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: #c7254e; // optional
+  --color-global: var(--subthemecolor);
+  --color-global-hs: var(--subthemecolor-hs);
+  --color-global-l: var(--subthemecolor-l);
+  --color-reversal: white;
+  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+  --color-link-hs: 357,49%;
+  --color-link-l: 40%;
+  --color-link-hover: #{hsl.lighten(var(--color-link), 10%)};
+  --color-link-wiki: var(--color-link);
+  --color-link-wiki-hs: var(--color-link-hs);
+  --color-link-wiki-l: var(--color-link-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link), 10%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #c7254e; // optional
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  $color-list-hover: lighten($themecolor, 10%);
-  // $bgcolor-list-hover: darken($bgcolor-list, 2%);
-  // $color-list-active: $bgcolor-global;
-  // $bgcolor-list-active: $accentcolor;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  --color-list-hover: #{hsl.lighten(var(--primary), 10%)};
+  // --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-list), 2%)};
+  // --color-list-active: var(--bgcolor-global);
+  // --bgcolor-list-active: var(--color-link);
 
   // Navbar
-  $bgcolor-navbar: $third-main-color;
-  $bgcolor-search-top-dropdown: $themecolor;
-  $border-image-navbar: linear-gradient(to right, #90a555 0%, #a84be6 50%, #eaab20 100%);
+  --bgcolor-navbar: var(--secondary);
+  --bgcolor-navbar-hs: var(--secondary-hs);
+  --bgcolor-navbar-l: var(--secondary-l);
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(to right, #90a555 0%, #a84be6 50%, #eaab20 100%);
 
   // Logo colors
-  $bgcolor-logo: $bgcolor-navbar;
-  $fillcolor-logo-mark: lighten(desaturate($bgcolor-inline-code, 10%), 15%);
+  --bgcolor-logo: var(--bgcolor-navbar);
+  --fillcolor-logo-mark: #{lighten(desaturate($gray-100, 10%), 15%)};
 
   // Sidebar
-  $bgcolor-sidebar: $themecolor;
+  --bgcolor-sidebar: var(--primary);
+  --bgcolor-sidebar-hs: var(--primary-hs);
+  --bgcolor-sidebar-l: var(--primary-l);
+
   // Sidebar resize button
-  $color-resize-button: $color-reversal;
-  $bgcolor-resize-button: $subthemecolor;
-  $color-resize-button-hover: $color-reversal;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 10%);
+  --color-resize-button: var(--color-reversal);
+  --bgcolor-resize-button: var(--subthemecolor);
+  --bgcolor-resize-button-hs: var(--subthemecolor-hs);
+  --bgcolor-resize-button-l: var(--subthemecolor-l);
+  --color-resize-button-hover: var(--color-reversal);
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--bgcolor-resize-button), 10%)};
+
   // Sidebar contents
-  $color-sidebar-context: $accentcolor;
-  $bgcolor-sidebar-context: lighten($themelight, 8%);
+  --color-sidebar-context: var(--color-link);
+  --color-sidebar-context-hs: var(--color-link-hs);
+  --color-sidebar-context-l: var(--color-link-l);
+  --bgcolor-sidebar-context: #{hsl.darken(var(--bgcolor-global), 2%)};
+  --bgcolor-sidebar-context-hs: var(--bgcolor-global-hs);
+  --bgcolor-sidebar-context-l: calc(var(--bgcolor-global-l) - 2%);
+
   // Sidebar list group
-  $bgcolor-sidebar-list-group: lighten($themelight, 10%);
+  --bgcolor-sidebar-list-group: #{hsl.lighten(var(--bgcolor-global), 10%)};
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $accentcolor;
+  --color-editor-icons: var(--color-link);
 
   // Border colors
-  $border-color-theme: lighten($subthemecolor, 40%);
-  $bordercolor-inline-code: #ccc8c8; // optional
+  --border-color-theme: #{hsl.lighten(var(--subthemecolor), 40%)};
+  --bordercolor-inline-code: #ccc8c8; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $growi-blue;
+  --bgcolor-dropdown-link-active: #{$growi-blue};
 
   // admin theme box
-  $color-theme-color-box: darken($primary, 5%);
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-light';
+  --color-theme-color-box: #{hsl.darken(var(--primary), 5%)};
 
   &, body {
     background-image: url('../images/hufflepuff/badger-light3.png');
+    background-attachment: fixed;
+    background-position: bottom;
+    background-size: cover;
   }
 
   // Button
   .btn.btn-outline-primary {
-    @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 50%), darken($primary, 50%), lighten($primary, 20%));
+    @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.darken(var(--primary), 50%)}, #{hsl.darken(var(--primary), 50%)}, #{hsl.lighten(var(--primary), 20%)});
   }
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 70%), lighten($primary, 5%), lighten($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.darken(var(--primary), 70%)}, #{hsl.lighten(var(--primary), 5%)}, #{hsl.lighten(var(--primary), 20%)});
     }
   }
 
   .growi:not(.login-page) {
     // add background-image
     .page-editor-preview-container {
-      background-image: url('../images/themes/hufflepuff/badger-light3.png');
+      background-image: url('../images/hufflepuff/badger-light3.png');
       background-attachment: fixed;
       background-position: bottom;
       background-size: cover;
@@ -122,7 +146,7 @@
   // login and register
   .nologin {
     background: unset !important;
-    background-image: url('../images/themes/hufflepuff/badger-light.png');
+    background-image: url('../images/hufflepuff/badger-light.png');
     background-attachment: fixed;
     background-position: bottom;
     background-size: cover;
@@ -133,26 +157,26 @@
     }
 
     .link-switch {
-      color: $color-global;
+      color: #{hsl.darken(var(--color-global),10%)};
     }
 
     .grw-external-auth-form {
-      border-color: $accentcolor !important;
+      border-color: #993439 !important;
     }
   }
 
   .table {
-    background-color: $bgcolor-global;
+    background-color: var(--bgcolor-global);
   }
 
   .card-timeline > .card-header {
-    background-color: $third-main-color;
+    background-color: var(--secondary);
   }
 
   .nav.nav-tabs {
     > .nav-item {
       > .nav-link.active {
-        color: $subthemecolor;
+        color: #231e1d;
       }
     }
   }
@@ -160,101 +184,128 @@
 
 :root[data-theme='dark'] {
   // Theme colors
-  $themecolor: #eaab20;
-  $themedark: #3d3f38;
-  $subthemecolor: #231e1d;
-  $third-main-color: #967224;
-  $accentcolor: #993439;
-
-  $primary: darken($themecolor, 10%);
-  $secondary: $third-main-color;
-  $dark: #031018;
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 41,83%;
+  --primary-l: 62%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 41,61%;
+  --secondary-l: 36%;
+  --subthemecolor: hsl(var(--subthemecolor-hs),var(--subthemecolor-l));
+  --subthemecolor-hs: 10,9%;
+  --subthemecolor-l: 13%;
+  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+  --accentcolor-hs: 357,49%;
+  --accentcolor-l: 40%;
 
   // Background colors
-  $bgcolor-global: $themedark;
-  // $bgcolor-navbar: #27343b;
-  $bgcolor-inline-code: $subthemecolor;
-  $bgcolor-card: darken($themedark, 5%);
-  $bgcolor-blinked-section: rgba($primary, 0.5);
-  $bgcolor-keyword-highlighted: darken($grw-marker-cyan, 40%);
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 77,6%;
+  --bgcolor-global-l: 23%;
+  // --bgcolor-navbar: #27343b;
+  --bgcolor-inline-code: var(--subthemecolor);
+  --bgcolor-card: #{hsl.darken(var(--bgcolor-global), 5%)};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 50%)};
+  --bgcolor-keyword-highlighted: #{darken($grw-marker-cyan, 40%)};
 
   // Font colors
-  $color-global: #efe2cf;
-  $color-reversal: $gray-100;
-  $color-link: lighten($themecolor, 20%);
-  $color-link-hover: lighten($color-link, 10%);
-  $color-link-wiki: lighten($primary, 20%);
-  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: $themecolor;
-  // $color-inline-code: #c7254e; // optional
-  // $color-search: #000102;
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 36,50%;
+  --color-global-l: 87%;
+  --color-reversal: #{$gray-100};
+  --color-link: #{hsl.lighten(var(--primary), 30%)};
+  --color-link-hs: var(--primary-hs);
+  --color-link-l: calc(var(--primary-l) + 30%);
+  --color-link-hover: #{hsl.lighten(var(--primary), 40%)};
+  --color-link-wiki: #{hsl.lighten(var(--primary), 20%)};
+  --color-link-wiki-hs: var(--primary-hs);
+  --color-link-wiki-l: calc(var(--primary-l) + 20%);
+  --color-link-wiki-hover: #{hsl.lighten(var(--primary), 50%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #{hsl.lighten(var(--primary), 10%)};
+  // --color-inline-code: #c7254e; // optional
+  // --color-search: #000102;
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  $color-list-hover: $accentcolor;
-  // $bgcolor-list-hover: lighten($bgcolor-global, 3%);
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  --color-list-hover: var(--accentcolor);
+  // --bgcolor-list-hover: #{hsl.lighten(var(--bgcolor-global), 3%)};
+  // --color-list-active: var(--color-reversal);
+  // --bgcolor-list-active: var(--primary);
 
   // Navbar
-  $bgcolor-navbar: $third-main-color;
-  $bgcolor-search-top-dropdown: $themecolor;
-  $border-image-navbar: linear-gradient(to right, #90a555 0%, #3d98a3 50%, #eaab20 100%);
+  --bgcolor-navbar: var(--secondary);
+  --bgcolor-navbar-hs: var(--secondary-hs);
+  --bgcolor-navbar-l: var(--secondary-l);
+  --bgcolor-search-top-dropdown: #{hsl.lighten(var(--primary), 10%)};
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: calc(var(--primary-l) + 10%);
+  --border-image-navbar: linear-gradient(to right, #90a555 0%, #3d98a3 50%, #eaab20 100%);
 
   // Logo colors
-  $bgcolor-logo: #13191c;
-  $fillcolor-logo-mark: white;
+  --bgcolor-logo: #13191c;
+  --fillcolor-logo-mark: white;
 
   // Sidebar
-  $bgcolor-sidebar: $themecolor;
-  // $bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #cc951e; // optional
+  --bgcolor-sidebar: #{hsl.lighten(var(--primary), 10%)};
+  --bgcolor-sidebar-hs: var(--primary-hs);
+  --bgcolor-sidebar-l: calc(var(--primary-l) + 10%);
+  // --bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #cc951e; // optional
+
   // Sidebar resize button
-  $color-resize-button: $color-global;
-  $bgcolor-resize-button: $accentcolor;
-  $color-resize-button-hover: $color-global;
-  $bgcolor-resize-button-hover: darken($bgcolor-resize-button, 7%);
+  --color-resize-button: var(--color-global);
+  --bgcolor-resize-button: var(--accentcolor);
+  --bgcolor-resize-button-hs: var(--accentcolor-hs);
+  --bgcolor-resize-button-l: var(--accentcolor-l);
+  --color-resize-button-hover: var(--color-global);
+  --bgcolor-resize-button-hover: #{hsl.darken(var(--accentcolor), 7%)};
+
   // Sidebar contents
-  $color-sidebar-context: $color-global;
-  $bgcolor-sidebar-context: lighten($themedark, 5%);
+  --color-sidebar-context: var(--color-link);
+  --color-sidebar-context-hs: var(--color-link-hs);
+  --color-sidebar-context-l: var(--color-link-l);
+  --bgcolor-sidebar-context: #{hsl.lighten(var(--bgcolor-global), 5%)};
+  --bgcolor-sidebar-context-hs: var(--bgcolor-global-hs);
+  --bgcolor-sidebar-context-l: calc(var(--bgcolor-global-l) + 5%);
+
   // Sidebar list group
-  $bgcolor-sidebar-list-group: lighten($subthemecolor, 5%);
+  --bgcolor-sidebar-list-group: #{hsl.lighten(var(--subthemecolor), 5%)};
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $themecolor;
+  --color-editor-icons: #{hsl.lighten(var(--primary), 10%)};
 
   // Border colors
-  $border-color-theme: darken($themecolor, 25%);
-  $bordercolor-inline-code: #4d4d4d; // optional
-
-  // Dropdown colors
-  $color-dropdown-link-active: $color-reversal;
-  $color-dropdown-link-hover: $color-global;
+  --border-color-theme: #{hsl.darken(var(--primary), 15%)};
+  --bordercolor-inline-code: #4d4d4d; // optional
 
   // admin theme box
-  $color-theme-color-box: $primary;
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-dark';
+  --color-theme-color-box: var(--primary);
 
   &, body {
     background-image: url('../images/hufflepuff/badger-dark.jpg');
+    background-attachment: fixed;
+    background-position: bottom;
+    background-size: cover;
   }
 
   // Navs
   .nav-tabs {
-    border-bottom: $border-color-theme 1px solid;
+    border-bottom: var(--border-color-theme) 1px solid;
     .nav-link {
       &:hover {
-        border-color: lighten($border-color-theme, 10%);
+        border-color: #{hsl.darken(var(--primary), 5%)};
         border-bottom: none;
       }
       &.active {
-        color: $color-link;
+        color: var(--color-link);
         background-color: transparent;
-        border-color: $border-color-theme;
+        border-color: var(--border-color-theme);
       }
     }
   }
@@ -266,22 +317,22 @@
 
   // Button
   .btn.btn-outline-primary {
-    @include page-editor-mode-manager.btn-page-editor-mode-manager(lighten($primary, 40%), lighten($primary, 15%), darken($primary, 10%), darken($primary, 30%));
+    @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.lighten(var(--primary), 40%)}, #{hsl.lighten(var(--primary), 15%)}, #{hsl.darken(var(--primary), 10%)}, #{hsl.darken(var(--primary), 30%)});
   }
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(lighten($primary, 40%), lighten($primary, 15%), darken($primary, 0%), darken($primary, 30%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.lighten(var(--primary), 40%)}, #{hsl.lighten(var(--primary), 15%)}, #{hsl.darken(var(--primary), 10%)}, #{hsl.darken(var(--primary), 30%)});
     }
   }
 
   .card-timeline > .card-header {
-    background-color: $accentcolor;
+    background-color: var(--accentcolor);
   }
 
   .growi:not(.login-page) {
     // add background-image
     .page-editor-preview-container {
-      background-image: url('../images/themes/hufflepuff/badger-dark.jpg');
+      background-image: url('../images/hufflepuff/badger-dark.jpg');
       background-attachment: fixed;
       background-position: bottom;
       background-size: cover;
@@ -291,7 +342,7 @@
   // login and register
   .nologin {
     background: unset !important;
-    background-image: url('../images/themes/hufflepuff/badger-light.png');
+    background-image: url('../images/hufflepuff/badger-light.png');
     background-attachment: fixed;
     background-position: bottom;
     background-size: cover;
@@ -302,11 +353,11 @@
     }
 
     .link-switch {
-      color: $color-global;
+      color: var(--color-global);
     }
 
     .grw-external-auth-form {
-      border-color: $accentcolor !important;
+      border-color: var(--accentcolor) !important;
     }
   }
 }

+ 91 - 60
packages/preset-themes/src/styles/island.scss

@@ -2,88 +2,119 @@
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
 @use './atoms/mixins/buttons' as mixins-buttons;
+@use './theme/hsl-functions' as hsl;
+
+//== Light Mode
+//
+:root[data-theme='light'] {
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 171,33%;
+  --primary-l: 69%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
 
-$color-primary: #97cbc3;
-$color-themelight: rgba(183, 226, 219, 1);
-
-:root {
-  $primary: $color-primary;
   // Background colors
-  $bgcolor-card: $gray-50;
-  $bgcolor-global: lighten($color-themelight, 10%);
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-blinked-section: rgba($primary, 0.3);
-  //$bgcolor-keyword-highlighted: $grw-marker-yellow;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 170,43%;
+  --bgcolor-global-l: 90%;
+  --bgcolor-card: #{$gray-50};
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary), 70%)};
+  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
 
   // Font colors
-  $color-global: #112744;
-  $color-reversal: #eeeeee;
-  // $color-header: #2b2b2b;
-  $color-link: #3c6d72;
-  $color-link-hover: lighten($color-link, 20%);
-  $color-link-wiki: $color-link;
-  $color-link-wiki-hover: lighten($color-link-wiki, 20%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: #c7254e; // optional
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 214,60%;
+  --color-global-l: 17%;
+  --color-reversal: #eeeeee;
+  // --color-header: #2b2b2b;
+  --color-link: hsl(var(--color-link-hs),var(--color-link-l));
+  --color-link-hs: 186,31%;
+  --color-link-l: 34%;
+  --color-link-hover: #{hsl.lighten(var(--color-link), 20%)};
+  --color-link-wiki: var(--color-link);
+  --color-link-wiki-hs: var(--color-link-hs);
+  --color-link-wiki-l: var(--color-link-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link), 20%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #c7254e; // optional
 
   // List Group colors
-  // $color-list: $color-global;
-  // $bgcolor-list: lighten($color-themelight, 10%);
-  // $color-list-hover: ;
-  $bgcolor-list-hover: $color-themelight;
-  $color-list-active: $color-global;
-  $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  // --bgcolor-list: var(--bgcolor-global);
+  // --color-list-hover: ;
+  --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),10%)};
+  --color-list-active: var(--color-global);
+  --bgcolor-list-active: var(--primary);
 
   // Table colors
-  // $color-table: #; // optional
-  // $bgcolor-table: #; // optional
-  $border-color-table: $primary; // optional
-  // $color-table-hover: #; // optional
-  // $bgcolor-table-hover: #; // optional
+  // --color-table: #; // optional
+  // --bgcolor-table: #; // optional
+  --border-color-table: var(--primary); // optional
+  // --color-table-hover: #; // optional
+  // --bgcolor-table-hover: #; // optional
 
   // Navbar
-  $bgcolor-navbar: #302e2e;
-  $bgcolor-search-top-dropdown: $color-primary;
-  $border-image-navbar: linear-gradient(to right, #5ce4ef 0%, #5953eb 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-hs: 0,2%;
+  --bgcolor-navbar-l: 18%;
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(to right, #5ce4ef 0%, #5953eb 100%);
 
   // Logo colors
-  $bgcolor-logo: #0d3955;
-  $fillcolor-logo-mark: lighten(desaturate($bgcolor-inline-code, 10%), 15%);
+  --bgcolor-logo: #0d3955;
+  --fillcolor-logo-mark: #{lighten(desaturate($gray-100, 10%), 15%)};
 
   // Sidebar
-  $bgcolor-sidebar: #0d3955;
-  $bgcolor-sidebar-nav-item-active: rgba(black, 0.37);
-  // $bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+  --bgcolor-sidebar: hsl(var(--bgcolor-sidebar-hs),var(--bgcolor-sidebar-l));
+  --bgcolor-sidebar-hs: 203,73%;
+  --bgcolor-sidebar-l: 19%;
+  --bgcolor-sidebar-nav-item-active: rgba(black, 0.37);
+  // --bgcolor-sidebar-nav-item-active: rgba(#969494, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #0099ff; // optional
+
   // Sidebar resize button
-  $color-resize-button: white;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: white;
-  $bgcolor-resize-button-hover: darken($bgcolor-resize-button, 5%);
+  --color-resize-button: white;
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-resize-button-hs: var(--primary-hs);
+  --bgcolor-resize-button-l: var(--primary-l);
+  --color-resize-button-hover: white;
+  --bgcolor-resize-button-hover: #{hsl.darken(var(--primary),5%)};
+
   // Sidebar contents
-  $bgcolor-sidebar-context: #e2f3f1;
-  $color-sidebar-context: $color-link;
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 173,41%;
+  --bgcolor-sidebar-context-l: 92%;
+  --color-sidebar-context: var(--color-link);
+  --color-sidebar-context-hs: var(--color-link-hs);
+  --color-sidebar-context-l: var(--color-link-l);
+
   // Sidebar list group
-  $bgcolor-sidebar-list-group: #eff8f7; // optional
+  --bgcolor-sidebar-list-group: #eff8f7; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) + 4%);
 
   // Tabs
-  $bordercolor-nav-tabs: $gray-300; // optional
+  --bordercolor-nav-tabs: #{$gray-300}; // optional
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $gray-300;
-  $bordercolor-inline-code: #ccc8c8; // optional
+  --border-color-theme: #{$gray-300};
+  --bordercolor-inline-code: #ccc8c8; // optional
 
   // Dropdown colors
-  $bgcolor-dropdown-link-active: $growi-blue;
+  --bgcolor-dropdown-link-active: #{$growi-blue};
 
   // admin theme box
-  $color-theme-color-box: darken($primary, 15%);
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-light';
+  --color-theme-color-box: #{hsl.darken(var(--primary), 15%)};
 
   &, body {
     background-image: url('../images/island/island.png');
@@ -91,11 +122,11 @@ $color-themelight: rgba(183, 226, 219, 1);
   }
 
   .rbt-menu {
-    background: lighten($color-themelight, 5%);
+    background: #{hsl.darken(var(--bgcolor-global), 5%)};
   }
 
   .page-editor-preview-container {
-    background-image: url('/images/themes/island/island.png');
+    background-image: url('../images/island/island.png');
     background-attachment: fixed;
   }
 
@@ -107,14 +138,14 @@ $color-themelight: rgba(183, 226, 219, 1);
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(darken($primary, 50%), lighten($primary, 5%), darken($primary, 5%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#{hsl.darken(var(--primary), 50%)}, #{hsl.lighten(var(--primary), 5%)}, #{hsl.darken(var(--primary), 5%)});
     }
   }
 
   // Cards
   .admin-bot-card {
     .grw-botcard-title-active {
-      color: $color-reversal;
+      color: var(--color-reversal);
     }
   }
 
@@ -125,7 +156,7 @@ $color-themelight: rgba(183, 226, 219, 1);
     // Pagetree
     .grw-pagetree {
       .grw-pagetree-triangle-btn {
-        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, $bgcolor-sidebar);
+        @include mixins-buttons.button-outline-svg-icon-variant($gray-400, #0d3955);
       }
     }
   }

+ 163 - 112
packages/preset-themes/src/styles/jade-green.scss

@@ -1,85 +1,114 @@
 @use './variables' as *;
 @use './bootstrap/variables' as *;
 @use './theme/mixins/page-editor-mode-manager';
+@use './theme/hsl-functions' as hsl;
 
 :root[data-theme='light'] {
   // Theme colors
-  $themecolor: #38b48b;
-  $themelight: #ffffff;
-  $accentcolor: #bfbfbf;
-  $subthemecolor: #e6e6e6;
-
-  $primary: $themecolor;
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 160,53%;
+  --primary-l: 46%;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+  --accentcolor-hs: 0,0%;
+  --accentcolor-l: 75%;
 
   // Background colors
-  $bgcolor-global: $themelight;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: $accentcolor;
-  $bgcolor-blinked-section: rgba($primary, 0.1);
-  //$bgcolor-keyword-highlighted: $grw-marker-yellow;
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 0,0%;
+  --bgcolor-global-l: 100%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: var(--accentcolor);
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),90%)};
+  //--bgcolor-keyword-highlighted: #{$grw-marker-yellow};
 
   // Font colors
-  $color-global: #2c2c2c;
-  $color-reversal: $gray-100;
-  $color-link: $primary;
-  $color-link-hover: lighten($color-link, 12%);
-  $color-link-wiki: $primary;
-  $color-link-wiki-hover: lighten($color-link-wiki, 12%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: #c7254e; // optional
-  $color-search: $color-global;
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 0,0%;
+  --color-global-l: 17%;
+  --color-reversal: #{$gray-100};
+  --color-link: var(--primary);
+  --color-link-hs: var(--primary-hs);
+  --color-link-l: var(--primary-l);
+  --color-link-hover: #{hsl.lighten(var(--primary), 12%)};
+  --color-link-wiki: var(--primary);
+  --color-link-wiki-hs: var(--primary-hs);
+  --color-link-wiki-l: var(--primary-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--primary), 12%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #c7254e; // optional
+  --color-search: var(--color-global);
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  $color-list-hover: $color-search;
-  $bgcolor-list-hover: darken($bgcolor-global, 3%);
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  --color-list-hover: var(--color-global);
+  --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};
+  // --color-list-active: var(--color-reversal);
+  // --bgcolor-list-active: var(--primary);
 
   // Navbar
-  $bgcolor-navbar: $color-global;
-  $bgcolor-search-top-dropdown: $themecolor;
-  $border-image-navbar: linear-gradient(to right, $primary 0%, darken($primary, 5%) 100%);
+  --bgcolor-navbar: var(--color-global);
+  --bgcolor-navbar-hs: var(--color-global-hs);
+  --bgcolor-navbar-l: var(--color-global-l);
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(to right, var(--primary) 0%, #{hsl.darken(var(--primary), 5%)} 100%);
+
 
   // Logo colors
-  $bgcolor-logo: $themelight;
-  $fillcolor-logo-mark: $themelight;
+  --bgcolor-logo: var(--bgcolor-global);
+  --fillcolor-logo-mark: var(--bgcolor-global);
 
   // Sidebar
-  $bgcolor-sidebar: $accentcolor;
-  // $bgcolor-sidebar-nav-item-active: rgba(#, 0.37); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px #ffffff; // optional
+  --bgcolor-sidebar: var(--accentcolor);
+  --bgcolor-sidebar-hs: var(--accentcolor-hs);
+  --bgcolor-sidebar-l: var(--accentcolor-l);
+  // --bgcolor-sidebar-nav-item-active: rgba(#, 0.37); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px #ffffff; // optional
+
   // Sidebar resize button
-  $color-resize-button: #ffffff;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: $color-reversal;
-  $bgcolor-resize-button-hover: lighten($bgcolor-resize-button, 5%);
+  --color-resize-button: #ffffff;
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-sidebar-context-hs: var(--primary-hs);
+  --bgcolor-sidebar-context-l: var(--primary-l);
+  --color-resize-button-hover: var(--color-reversal);
+  --bgcolor-resize-button-hover: #{hsl.lighten(var(--primary), 5%)};
+
   // Sidebar contents
-  $color-sidebar-context: $color-global;
-  $bgcolor-sidebar-context: #ebebeb;
+  --color-sidebar-context: var(--color-global);
+  --color-sidebar-context-hs: var(--color-global-hs);
+  --color-sidebar-context-l: var(--color-global-l);
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 0,0%;
+  --bgcolor-sidebar-context-l: 93%;
+
   // Sidebar list group
-  // $bgcolor-sidebar-list-group: #; // optional
+  // --bgcolor-sidebar-list-group: #; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $primary;
-  $bordercolor-inline-code: #ccc8c8; // optional
+  --border-color-theme: var(--primary);
+  --bordercolor-inline-code: #ccc8c8; // optional
 
   // admin theme box
-  $color-theme-color-box: $primary;
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-light';
+  --color-theme-color-box: var(--primary);
 
   // Navs {
   .nav-tabs {
-    border-bottom: $border-color-theme 1px solid;
+    border-bottom: var(--primary) 1px solid;
     .nav-link {
       &:hover {
-        border-color: lighten($border-color-theme, 10%);
+        border-color: #{hsl.lighten(var(--primary), 10%)};
         border-bottom: none;
       }
       &.active {
@@ -87,106 +116,128 @@
       }
     }
   }
+
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, lighten($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.lighten(var(--primary), 20%)});
     }
   }
 }
 
 :root[data-theme='dark'] {
   // Theme colors
-  $themecolor: #38b48b;
-  $themedark: #333333;
-  $accentcolor: #212121;
-  $subthemecolor: #2e2e2e;
-
-  $primary: #38b48b;
-  $dark: #a7a7a7;
+  --primary: hsl(var(--primary-hs),var(--primary-l)) !important;
+  --primary-hs: 160,53%;
+  --primary-l: 46%;
+  --secondary: hsl(var(--secondary-hs),var(--secondary-l)) !important;
+  --secondary-hs: 208,7%;
+  --secondary-l: 46%;
+  --accentcolor: hsl(var(--accentcolor-hs),var(--accentcolor-l));
+  --accentcolor-hs: 0,0%;
+  --accentcolor-l: 13%;
 
   // Background colors
-  $bgcolor-global: $themedark;
-  $bgcolor-navbar: #2b2b2b;
-  $bgcolor-inline-code: $gray-100; //optional
-  $bgcolor-card: darken($themedark, 5%);
-  $bgcolor-blinked-section: rgba($primary, 0.5);
-  $bgcolor-keyword-highlighted: darken($grw-marker-red, 30%);
+  --bgcolor-global: hsl(var(--bgcolor-global-hs),var(--bgcolor-global-l));
+  --bgcolor-global-hs: 0,0%;
+  --bgcolor-global-l: 20%;
+  --bgcolor-inline-code: #{$gray-100}; //optional
+  --bgcolor-card: #{hsl.darken(var(--bgcolor-global), 5%)};
+  --bgcolor-blinked-section: #{hsl.alpha(var(--primary),50%)};
+  --bgcolor-keyword-highlighted: #{darken($grw-marker-red, 30%)};
 
   // Font colors
-  $color-global: #ffffff;
-  $color-reversal: $gray-100;
-  $color-link: $primary;
-  $color-link-hover: lighten($color-link, 12%);
-  $color-link-wiki: $primary;
-  $color-link-wiki-hover: lighten($color-link-wiki, 12%);
-  $color-link-nabvar: $color-reversal;
-  $color-inline-code: $subthemecolor;
-  $color-inline-code: #c7254e; // optional
-  $color-search: $dark;
+  --color-global: hsl(var(--color-global-hs),var(--color-global-l));
+  --color-global-hs: 0,0%;
+  --color-global-l: 100%;
+  --color-reversal: #{$gray-100};
+  --color-link: var(--primary);
+  --color-link-hs: var(--primary-hs);
+  --color-link-l: var(--primary-l);
+  --color-link-hover: #{hsl.lighten(var(--color-link),12%)};
+  --color-link-wiki: var(--primary);
+  --color-link-wiki-hs: var(--primary-hs);
+  --color-link-wiki-l: var(--primary-l);
+  --color-link-wiki-hover: #{hsl.lighten(var(--color-link),12%)};
+  --color-link-nabvar: var(--color-reversal);
+  --color-inline-code: #c7254e; // optional
+  --color-search: #a7a7a7;
 
   // List Group colors
-  // $color-list: $color-global;
-  $bgcolor-list: transparent;
-  $color-list-hover: $accentcolor;
-  // $bgcolor-list-hover: lighten($bgcolor-global, 3%);
-  // $color-list-active: $color-reversal;
-  // $bgcolor-list-active: $primary;
+  // --color-list: var(--color-global);
+  --bgcolor-list: transparent;
+  --color-list-hover: var(--accentcolor);
+  // --bgcolor-list-hover: #{hsl.darken(var(--bgcolor-global),3%)};// optional
+  // --color-list-active: white ; // optional
+  // --bgcolor-list-active: #{hsl.lighten(var(--bgcolor-global),3%)}; // optional
 
   // Navbar
-  $bgcolor-navbar: #2c2c2c;
-  $bgcolor-search-top-dropdown: $themecolor;
-  $border-image-navbar: linear-gradient(to right, $primary 0%, darken($primary, 5%) 100%);
+  --bgcolor-navbar: hsl(var(--bgcolor-navbar-hs),var(--bgcolor-navbar-l));
+  --bgcolor-navbar-hs: 0,0%;
+  --bgcolor-navbar-l: 17%;
+  --bgcolor-search-top-dropdown: var(--primary);
+  --bgcolor-search-top-dropdown-hs: var(--primary-hs);
+  --bgcolor-search-top-dropdown-l: var(--primary-l);
+  --border-image-navbar: linear-gradient(to right, var(--primary) 0%, #{hsl.darken(var(--primary), 5%)} 100%);
 
   // Logo colors
-  $bgcolor-logo: #ffffff;
-  $fillcolor-logo-mark: #ffffff;
-  // $fillcolor-logo-mark: #4e5a60;
+  --bgcolor-logo: var(--color-global);
+  --fillcolor-logo-mark: var(--color-global);
+  // --fillcolor-logo-mark: #4e5a60;
 
   // Sidebar
-  $bgcolor-sidebar: $accentcolor;
-  // $bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
-  $text-shadow-sidebar-nav-item-active: 0px 0px 10px $primary; // optional
+  --bgcolor-sidebar: var(--accentcolor);
+  --bgcolor-sidebar-hs: var(--accentcolor-hs);
+  --bgcolor-sidebar-l: var(--accentcolor-l);
+  // --bgcolor-sidebar-nav-item-active: rgba(#, 0.3); // optional
+  --text-shadow-sidebar-nav-item-active: 0px 0px 10px var(--primary); // optional
+
   // Sidebar resize button
-  $color-resize-button: $color-global;
-  $bgcolor-resize-button: $primary;
-  $color-resize-button-hover: $color-global;
-  $bgcolor-resize-button-hover: darken($bgcolor-resize-button, 5%);
+  --color-resize-button: var(--color-global);
+  --bgcolor-resize-button: var(--primary);
+  --bgcolor-resize-button-hs: var(--primary-hs);
+  --bgcolor-resize-button-l: var(--primary-l);
+  --color-resize-button-hover: var(--color-global);
+  --bgcolor-resize-button-hover: #{hsl.darken(var(--primary), 5%)};
+
   // Sidebar contents
-  $bgcolor-sidebar-context: #3c403c;
-  $color-sidebar-context: $color-global;
+  --color-sidebar-context: var(--color-global);
+  --color-sidebar-context-hs: var(--color-global-hs);
+  --color-sidebar-context-l: var(--color-global-l);
+  --bgcolor-sidebar-context: hsl(var(--bgcolor-sidebar-context-hs),var(--bgcolor-sidebar-context-l));
+  --bgcolor-sidebar-context-hs: 0,2%;
+  --bgcolor-sidebar-context-l: 25%;
+
   // Sidebar list group
-  // $bgcolor-sidebar-list-group: #; // optional
+  // --bgcolor-sidebar-list-group: #; // optional
+
+  // Subnavigation
+  --bgcolor-subnav: hsl(var(--bgcolor-subnav-hs),var(--bgcolor-subnav-l));
+  --bgcolor-subnav-hs: var(--bgcolor-global-hs);
+  --bgcolor-subnav-l: calc(var(--bgcolor-global-l) - 3%);
 
   // Icon colors
-  $color-editor-icons: $color-global;
+  --color-editor-icons: var(--color-global);
 
   // Border colors
-  $border-color-theme: $primary;
-  $bordercolor-inline-code: #4d4d4d; // optional
-
-  // Dropdown colors
-  $color-dropdown-link-active: $color-global;
-  $color-dropdown-link-hover: $color-reversal;
+  --border-color-theme: var(--primary);
+  --bordercolor-inline-code: #4d4d4d; // optional
 
   // admin theme box
-  $color-theme-color-box: $primary;
-
-  @import './theme/apply-colors';
-  @import './theme/apply-colors-dark';
+  --color-theme-color-box: var(--primary);
 
   // Navs
   .nav-tabs {
-    border-bottom: $border-color-theme 1px solid;
+    border-bottom: var(--primary) 1px solid;
     .nav-link {
       &:hover {
-        border-color: lighten($border-color-theme, 10%);
+        border-color: #{hsl.lighten(var(--primary), 10%)};
         border-bottom: none;
       }
       &.active {
-        color: $color-link;
+        color: var(--primary);
         background-color: transparent;
-        border-color: $border-color-theme;
+        border-color: var(--primary);
       }
     }
   }
@@ -199,7 +250,7 @@
   // Button
   .btn-group.grw-page-editor-mode-manager {
     .btn.btn-outline-primary {
-      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, $primary, $primary, darken($primary, 20%));
+      @include page-editor-mode-manager.btn-page-editor-mode-manager(#ffffff, var(--primary), var(--primary), #{hsl.darken(var(--primary), 20%)});
     }
   }
 }

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff