Compare commits
218 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f219ab83f8 | |||
| 88ab0538d9 | |||
| 90ee21b577 | |||
| 79f4d96742 | |||
| 61707ffb96 | |||
| 38c1d68ad0 | |||
| ff7433f434 | |||
| a4e207688a | |||
| fdc87ed8f1 | |||
| 0ab9c297a5 | |||
| 04d6fcc1e1 | |||
| bf18ac0ab0 | |||
| bbe9605f6f | |||
| bd34c194cb | |||
| c0aa707315 | |||
| b6fa448202 | |||
| 2e141be0fc | |||
| 93b5e3e68f | |||
| 39dac5065f | |||
| a16a313f71 | |||
| 82560ddc20 | |||
| 83f3cae6f0 | |||
| 99df4a5575 | |||
| d33c211a01 | |||
| 21ee89ada4 | |||
| dcedb1c6c0 | |||
| d5cc65983e | |||
| 3cb150b090 | |||
| 305ae59086 | |||
| 8fe8930def | |||
| 8730bdbfb9 | |||
| 3ee47e9481 | |||
| 4e649acd60 | |||
| 4768d70a8a | |||
| c7bff7ed09 | |||
| 3035d2a645 | |||
| 0750037385 | |||
| 8c4d61ee65 | |||
| 1a3ccb112e | |||
| 118172d27f | |||
| 69f1d85360 | |||
| f0d042c85b | |||
| 550830c232 | |||
| 6be12b4e58 | |||
| 33aa37ed7d | |||
| 43910fd75d | |||
| 5efcc86a63 | |||
| 17b61a5a41 | |||
| 5dae5c31b7 | |||
| 96deea7f31 | |||
| 7632e6a464 | |||
| 23e2a2e3ff | |||
| b19512dffc | |||
| 11c6fc820b | |||
| 1a8231851d | |||
| 1dfb83b1aa | |||
| 93cb1ae8ba | |||
| 631b168e05 | |||
| af8a43c93a | |||
| 875d12f514 | |||
| a9a2ef67ea | |||
| 5caf3ed6e7 | |||
| 580ec704f2 | |||
| a9918ae555 | |||
| 4b3f4bfab8 | |||
| fed3b1581b | |||
| 9b2f0aab32 | |||
| 818a6bd2ff | |||
| 94a43347a1 | |||
| 16d793c588 | |||
| 0322e5bd8d | |||
| cfe76a9ab3 | |||
| ff6014317b | |||
| 64220f2e27 | |||
| e75414abfc | |||
| d6fae42dc3 | |||
| 0db1245a6b | |||
| 91c0a9ec31 | |||
| 7670c37685 | |||
| ae41c5629c | |||
| 3e7fa05fce | |||
| 6833640c47 | |||
| 18541c45de | |||
| 45bb0dd0d2 | |||
| db0aad2c37 | |||
| d55ca12125 | |||
| ad2cada94b | |||
| f9545f513b | |||
| c75d83dc7c | |||
| 91236e5533 | |||
| 1356be248e | |||
| 7e8be92cd5 | |||
| c3fda4a3a5 | |||
| dfc66cfd50 | |||
| 0ef1252e1f | |||
| cf56b3a53e | |||
| 592f402ffd | |||
| 1dc77fc473 | |||
| 09cd772d74 | |||
| 1c69e10e6a | |||
| 1af18e4c91 | |||
| a3709609c5 | |||
| 70a79faee0 | |||
| 07cb7dae9d | |||
| 9a1fb9c4a1 | |||
| a3f7ed7f61 | |||
| 5302fd2779 | |||
| ccd4e02b6a | |||
| f6593097b6 | |||
| 230cd22bff | |||
| f4367f1755 | |||
| 7b7447f9a0 | |||
| d12d8404ec | |||
| d1b12173d5 | |||
| f3bf13da59 | |||
| f236b75f55 | |||
| 2434231f9b | |||
| b8b39e69a1 | |||
| f8158dfd8e | |||
| f1ab7df5ce | |||
| 986de49052 | |||
| d09ca5548a | |||
| 77e22c40b6 | |||
| de74647c43 | |||
| 498601c21a | |||
| e99f06ddf5 | |||
| f06af59168 | |||
| d50e3a7fc0 | |||
| 3e4e7ff1ab | |||
| 21ff3c8655 | |||
| 166e61bab4 | |||
| 93cd8bd8da | |||
| f5c1641af2 | |||
| 62ffed0624 | |||
| 8d0a4a7417 | |||
| b1b274ea12 | |||
| fc38865965 | |||
| 911adaaecb | |||
| 7626ed8d1d | |||
| 6135fd7e1b | |||
| 14bea36356 | |||
| 4abc163a59 | |||
| 5bb9cf1e7c | |||
| a0cdf408a1 | |||
| 8467506915 | |||
| 704255774d | |||
| 10bb7b1cca | |||
| 7f82af3d3b | |||
| d7c729c035 | |||
| 51fa904f9b | |||
| 6191e55745 | |||
| c6e93c266a | |||
| bcf7e5b5ab | |||
| 466f122cc4 | |||
| 539484ea25 | |||
| db1cee25bf | |||
| 96502b18d0 | |||
| c491ece8c5 | |||
| c76464bf8a | |||
| 08b75b1a79 | |||
| 90da869277 | |||
| 80c4affaa8 | |||
| aa087193fe | |||
| af7e51f69b | |||
| 1b62d8ddc2 | |||
| 0839579733 | |||
| 7f9bf08f76 | |||
| 79ec1c6922 | |||
| c68246df83 | |||
| abc0ad7120 | |||
| 4356ca1cff | |||
| d6215d009c | |||
| 0b501e95a5 | |||
| f157ff37e0 | |||
| 11d425faa9 | |||
| 5fc68a1926 | |||
| ec4577c187 | |||
| 8052de8ad5 | |||
| 2fda959cd6 | |||
| 05fa8a5ae2 | |||
| a341b41e9a | |||
| a2e0a4d78a | |||
| 4fb1d6077c | |||
| 53ab927544 | |||
| 897cc2daf2 | |||
| 73ff31daf5 | |||
| c387a9e17e | |||
| 1f92931076 | |||
| d3105a1532 | |||
| 7f50f31837 | |||
| 4c3e90ea4b | |||
| 8d80fc4aa9 | |||
| 78a04ffcf1 | |||
| bcb2c91823 | |||
| 9058f209dd | |||
| 1f172d583e | |||
| 439b4757df | |||
| 9f8809bd51 | |||
| 4e326650cc | |||
| e27be27692 | |||
| 88eb2a1897 | |||
| 8c84ef73c8 | |||
| 859e49f16c | |||
| 14ba8b0a23 | |||
| d7c70f75f3 | |||
| 2760e3ea08 | |||
| f2f041fe55 | |||
| 9ec9700ed4 | |||
| c6b047fc02 | |||
| 6727264e39 | |||
| d1a5fe234d | |||
| e84c7cd276 | |||
| 087813fab9 | |||
| 3a4e7398c6 | |||
| 1630fd7e71 | |||
| 4aa99beaff | |||
| eaa0ba28d8 | |||
| 3f3e1120b2 |
@@ -0,0 +1,7 @@
|
|||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
insert_final_newline = true
|
||||||
|
end_of_line = lf
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
max_line_length = 80
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"reportUnusedDisableDirectives": true,
|
|
||||||
"ignorePatterns": ["**/build", "**/dist"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["@typescript-eslint", "import"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/stylistic",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2020": true,
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"tsconfigRootDir": ".",
|
|
||||||
"project": true,
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"import/parsers": {
|
|
||||||
"@typescript-eslint/parser": [".ts", ".tsx"],
|
|
||||||
},
|
|
||||||
"import/resolver": {
|
|
||||||
"typescript": true,
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"version": "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/array-type": "error",
|
|
||||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
|
||||||
"@typescript-eslint/no-empty-object-type": "error",
|
|
||||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
|
||||||
"@typescript-eslint/consistent-type-definitions": "error",
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"error",
|
|
||||||
{ "prefer": "type-imports" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
|
||||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
|
||||||
"@typescript-eslint/naming-convention": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"selector": "typeParameter",
|
|
||||||
"format": ["PascalCase"],
|
|
||||||
"leadingUnderscore": "forbid",
|
|
||||||
"trailingUnderscore": "forbid",
|
|
||||||
"custom": {
|
|
||||||
"regex": "^(T|T[A-Z][A-Za-z]+)$",
|
|
||||||
"match": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-deprecated": "error",
|
|
||||||
"@typescript-eslint/no-empty-function": "error",
|
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "error",
|
|
||||||
"@typescript-eslint/no-inferrable-types": [
|
|
||||||
"error",
|
|
||||||
{ "ignoreParameters": true },
|
|
||||||
],
|
|
||||||
"import/default": "error",
|
|
||||||
"import/export": "error",
|
|
||||||
"import/namespace": "error",
|
|
||||||
"import/newline-after-import": "error",
|
|
||||||
"import/no-cycle": "error",
|
|
||||||
"import/no-duplicates": "off",
|
|
||||||
"import/no-named-as-default-member": "error",
|
|
||||||
"import/no-unused-modules": "error",
|
|
||||||
"import/order": [
|
|
||||||
"off",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"external",
|
|
||||||
"internal",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index",
|
|
||||||
"object",
|
|
||||||
"type",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-case-declarations": "error",
|
|
||||||
"no-empty": "error",
|
|
||||||
"no-prototype-builtins": "error",
|
|
||||||
"no-redeclare": "error",
|
|
||||||
"no-shadow": "error",
|
|
||||||
"no-undef": "off",
|
|
||||||
"sort-imports": "off",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -32,55 +32,24 @@ body:
|
|||||||
description: If applicable, add screenshots to help explain your problem.
|
description: If applicable, add screenshots to help explain your problem.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
- type: input
|
- type: textarea
|
||||||
id: tuono-version
|
id: system-info
|
||||||
attributes:
|
attributes:
|
||||||
label: Tuono version
|
label: System Info
|
||||||
placeholder: '[e.g. 0.4.0]'
|
description: Output of `npx envinfo --system --npmPackages 'tuono' --binaries --browsers`
|
||||||
|
render: shell
|
||||||
|
placeholder: System, Binaries, Browsers
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: input
|
- type: textarea
|
||||||
id: os-version
|
id: system-info-rust
|
||||||
attributes:
|
attributes:
|
||||||
label: OS
|
label: System info (Rust)
|
||||||
placeholder: '[e.g. MacOS, Windows]'
|
description: Output of `rustc --version && cargo --version && tuono --version`
|
||||||
|
render: shell
|
||||||
|
placeholder: 'rustc, cargo and tuono binary version'
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: true
|
||||||
- type: input
|
|
||||||
id: browser-version
|
|
||||||
attributes:
|
|
||||||
label: Browser
|
|
||||||
placeholder: '[e.g. chrome, safari]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: node-version
|
|
||||||
attributes:
|
|
||||||
label: Node version
|
|
||||||
placeholder: '[e.g. 20.0.0]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: rust-version
|
|
||||||
attributes:
|
|
||||||
label: Rust version
|
|
||||||
placeholder: '[e.g. 1.79.0]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: create-version
|
|
||||||
attributes:
|
|
||||||
label: Crate version
|
|
||||||
placeholder: '[e.g. 1.78.0]'
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: input
|
|
||||||
id: node-package-manager-version
|
|
||||||
attributes:
|
|
||||||
label: Node Package Manger version
|
|
||||||
placeholder: '[e.g. pnpm: 9.5.0] '
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: additional-context
|
id: additional-context
|
||||||
attributes:
|
attributes:
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
# @see https://github.com/actions/labeler
|
||||||
|
|
||||||
|
rust:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['crates/**', 'Cargo.toml']
|
||||||
|
|
||||||
|
typescript:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
[
|
||||||
|
'packages/**',
|
||||||
|
'package.json',
|
||||||
|
'pnpm-*.yaml',
|
||||||
|
'eslint.config.js',
|
||||||
|
'tsconfig.json',
|
||||||
|
]
|
||||||
|
|
||||||
|
documentation:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['apps/documentation/**']
|
||||||
|
|
||||||
|
'CI/CD':
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: ['.github/**']
|
||||||
|
|
||||||
|
'repo maintenance':
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
[
|
||||||
|
'.npmrc',
|
||||||
|
'.nvmrc',
|
||||||
|
'.prettierrc',
|
||||||
|
'.prettierignore',
|
||||||
|
'renovate.json',
|
||||||
|
'turbo.json',
|
||||||
|
]
|
||||||
@@ -1,13 +1,24 @@
|
|||||||
## Context & Description
|
<!--
|
||||||
|
👋 Thank you for your Pull Request 🙏
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] I have read [Contributing > Pull requests](https://tuono.dev/documentation/contributing/pull-requests)
|
||||||
|
|
||||||
|
### Related issue
|
||||||
|
|
||||||
|
Fixes #0
|
||||||
|
|
||||||
|
<!-- Replace the content with "None" if you haven't an issue to link -->
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
Thank you for your Pull Request.
|
|
||||||
|
|
||||||
Explain the context and why you're making that change. What is the problem
|
Explain the context and why you're making that change.
|
||||||
you're trying to solve? If a new feature is being added, describe the intended
|
What is the problem you're trying to solve?
|
||||||
use case that feature fulfills.
|
If a new feature is being added,
|
||||||
|
describe the intended use case that feature fulfills.
|
||||||
|
|
||||||
Bug fixes and new features should include tests.
|
|
||||||
|
|
||||||
Contributors guide: https://github.com/Valerioageno/tuono/blob/main/CONTRIBUTING.md
|
|
||||||
-->
|
-->
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: Docker CI
|
||||||
|
|
||||||
|
# If we need support more OS refer to
|
||||||
|
# https://github.com/tuono-labs/tuono/pull/332#issuecomment-2614439692
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: Build and Test Docker Image on ${{ matrix.os }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Build and Run Docker Compose
|
||||||
|
run: |
|
||||||
|
docker compose -f docker/compose.yml up --build -d
|
||||||
|
|
||||||
|
- name: Check if the container is running
|
||||||
|
run: |
|
||||||
|
docker ps --filter "name=tuono-source-container" --format "table {{.Names}}\t{{.Status}}"
|
||||||
|
if ! docker ps --filter "name=tuono-source-container" --format "{{.Status}}" | grep -q "Up"; then
|
||||||
|
echo "Error: Container 'tuono-source-container' is not running."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check commands inside container
|
||||||
|
run: |
|
||||||
|
docker exec tuono-source-container pnpm --version || { echo "Error: pnpm is not available."; exit 1; }
|
||||||
|
docker exec tuono-source-container tuono --version || { echo "Error: tuono is not available."; exit 1; }
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
run: docker exec tuono-source-container cargo test
|
||||||
|
|
||||||
|
- name: Install node dependencies in each tuono's sub-projets
|
||||||
|
run: docker exec tuono-source-container bash -c 'for dir in /tuono/packages/*; do [ -d "$dir" ] && (cd "$dir" && yes | pnpm install --frozen-lockfile); done'
|
||||||
|
|
||||||
|
- name: Run pnpm test
|
||||||
|
run: docker exec tuono-source-container pnpm test
|
||||||
|
|
||||||
|
- name: Tear Down Docker Compose
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
docker compose -f docker/compose.yml down --volumes
|
||||||
@@ -2,20 +2,33 @@ name: Documentation Website CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
# match
|
||||||
|
# - all files inside `apps/documentation` and `.github`
|
||||||
|
# - all files in the root expect `Cargo.toml`
|
||||||
paths:
|
paths:
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
|
- '*'
|
||||||
|
- '!Cargo.toml'
|
||||||
- 'apps/documentation/**'
|
- 'apps/documentation/**'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
# same as `on.push.paths`
|
||||||
paths:
|
paths:
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
|
- '*'
|
||||||
|
- '!Cargo.toml'
|
||||||
- 'apps/documentation/**'
|
- 'apps/documentation/**'
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
name: Check build
|
name: Check build
|
||||||
timeout-minutes: 15
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -28,15 +41,14 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install tuono
|
- name: Install tuono
|
||||||
run: cargo install tuono@0.12.3
|
run: cargo install tuono@0.17.6
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
working-directory: ./apps/documentation
|
working-directory: ./apps/documentation
|
||||||
run: tuono build --static
|
run: tuono build --static
|
||||||
|
|
||||||
fmt-lint-and-types:
|
code_check:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
name: Check format, lint and types
|
||||||
name: Check format and lint
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 15
|
timeout-minutes: 15
|
||||||
|
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
name: Deploy documentation website on AWS S3
|
name: Deploy documentation website on AWS S3
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- 'main'
|
||||||
paths:
|
paths:
|
||||||
- '.github/**'
|
- '.github/**'
|
||||||
- 'apps/documentation/**'
|
- 'apps/documentation/**'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-documentation:
|
deploy-documentation:
|
||||||
|
name: Deploy
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -25,7 +27,7 @@ jobs:
|
|||||||
uses: actions-rust-lang/setup-rust-toolchain@v1
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
- name: Install tuono
|
- name: Install tuono
|
||||||
run: cargo install tuono@0.12.3
|
run: cargo install tuono@0.17.6
|
||||||
|
|
||||||
- name: Build project
|
- name: Build project
|
||||||
working-directory: ./apps/documentation
|
working-directory: ./apps/documentation
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
name: 'PR Labeler'
|
||||||
|
|
||||||
|
# @see https://github.com/actions/labeler
|
||||||
|
# @see .github/labeler.yml
|
||||||
|
|
||||||
|
on:
|
||||||
|
- pull_request_target
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
name: 'Manage labels'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Add checkout step to load labeler.yml locally,
|
||||||
|
# rather than from an API request
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
sync-labels: true
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
name: 'PR Title Checker'
|
||||||
|
|
||||||
|
# This workflow ensures that PR titles adhere to a pattern
|
||||||
|
# similar to conventional commits standard
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- synchronize
|
||||||
|
- labeled
|
||||||
|
- unlabeled
|
||||||
|
|
||||||
|
env:
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check_pr_title:
|
||||||
|
name: 'PR Title Checker'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check type
|
||||||
|
id: type
|
||||||
|
# @warning Keep in sync with apps/documentation/src/routes/documentation/contributing/pull-requests.mdx
|
||||||
|
run: |
|
||||||
|
VALID_COMMIT_TYPES="chore|ci|docs|feat|fix|refactor|test"
|
||||||
|
REGEX="^(${VALID_COMMIT_TYPES})(\(.*\))?!?: .*"
|
||||||
|
|
||||||
|
if ! [[ $PR_TITLE =~ $REGEX ]]; then
|
||||||
|
echo "::error title=Type::The title has an incorrect type. Valid types are ${VALID_COMMIT_TYPES}"
|
||||||
|
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::notice title=Type::The title is using a valid type"
|
||||||
|
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check length
|
||||||
|
id: length
|
||||||
|
run: |
|
||||||
|
MAX_LENGTH=100
|
||||||
|
LENGTH=${#PR_TITLE}
|
||||||
|
|
||||||
|
if [ $LENGTH -gt $MAX_LENGTH ]; then
|
||||||
|
echo "::error title=Length::The title is longer than $MAX_LENGTH characters"
|
||||||
|
echo "result=fail" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "::notice title=Length::The title length is within the maximum value of $MAX_LENGTH characters"
|
||||||
|
echo "result=ok" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check result
|
||||||
|
env:
|
||||||
|
FAILURE: ${{ contains(join(steps.*.outputs.result, ','), 'fail') }}
|
||||||
|
run: |
|
||||||
|
echo "Failure: $FAILURE"
|
||||||
|
if [ "$FAILURE" = "false" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
name: Publish on npm and crates
|
name: Publish on npm and crates
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-on-npm-dry-run:
|
publish_npm_dry_run:
|
||||||
|
name: NPM publish (dry run)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -23,16 +26,18 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
publish-on-crates-dry-run:
|
publish_crates_dry_run:
|
||||||
|
name: Crates publish (dry run)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: 'stable'
|
||||||
override: true
|
|
||||||
|
|
||||||
- uses: katyo/publish-crates@v2
|
- uses: katyo/publish-crates@v2
|
||||||
with:
|
with:
|
||||||
@@ -41,9 +46,11 @@ jobs:
|
|||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
check-repo: true
|
check-repo: true
|
||||||
|
|
||||||
publish-on-npm:
|
publish_npm:
|
||||||
needs: [publish-on-crates-dry-run, publish-on-npm-dry-run]
|
name: NPM publish
|
||||||
|
needs: [publish_npm_dry_run, publish_crates_dry_run]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -64,17 +71,19 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
publish-on-crates:
|
publish_crates:
|
||||||
needs: [publish-on-crates-dry-run, publish-on-npm-dry-run]
|
name: Crates publish
|
||||||
|
needs: [publish_npm_dry_run, publish_crates_dry_run]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions-rs/toolchain@v1
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: stable
|
toolchain: 'stable'
|
||||||
override: true
|
|
||||||
|
|
||||||
- uses: katyo/publish-crates@v2
|
- uses: katyo/publish-crates@v2
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -1,24 +1,24 @@
|
|||||||
name: Repo root CI
|
name: Repo root CI
|
||||||
|
|
||||||
# All workflows are triggered based on specific paths,
|
# All workflows are triggered based on specific paths,
|
||||||
# the root repository is something that is not applicable to rust nor typescript
|
# the root repository is something that is not applicable to rust nor typescript
|
||||||
# so it has been moved in a standalone workflow
|
# so it has been moved in a standalone workflow
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
paths:
|
branches:
|
||||||
- '.github/**'
|
- 'main'
|
||||||
- './*'
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- '.github/**'
|
concurrency:
|
||||||
- './*'
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
format:
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
name: Check format
|
name: Check format
|
||||||
timeout-minutes: 15
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
@@ -28,4 +28,24 @@ jobs:
|
|||||||
uses: ./.github/actions/install-node-dependencies
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
- name: Test project
|
- name: Test project
|
||||||
run: pnpm repo:root:format
|
run: pnpm repo:root:format:check
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Repo root CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs: [format]
|
||||||
|
env:
|
||||||
|
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||||
|
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
|
||||||
|
steps:
|
||||||
|
- name: Check for failure or cancelled jobs result
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
|
||||||
|
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
name: Rust CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: Build and test crates on ${{ matrix.os }} with rust ${{ matrix.toolchain }}
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
toolchain:
|
||||||
|
- 'stable'
|
||||||
|
|
||||||
|
# Test `beta` and `nightly` toolchains only on `ubuntu`
|
||||||
|
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#expanding-or-adding-matrix-configurations
|
||||||
|
include:
|
||||||
|
- toolchain: 'beta'
|
||||||
|
os: 'ubuntu-latest'
|
||||||
|
- toolchain: 'nightly'
|
||||||
|
os: 'ubuntu-latest'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ matrix.toolchain }}
|
||||||
|
|
||||||
|
- run: cargo build --verbose
|
||||||
|
- run: cargo test --verbose
|
||||||
|
|
||||||
|
lint_and_fmt:
|
||||||
|
name: Check format and lint rust crates
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup rust toolchain
|
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
|
||||||
|
- run: cargo fmt --all -- --check
|
||||||
|
- run: cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Rust CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs: [build_and_test, lint_and_fmt]
|
||||||
|
env:
|
||||||
|
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||||
|
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
|
||||||
|
steps:
|
||||||
|
- name: Check for failure or cancelled jobs result
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
|
||||||
|
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
name: Rust CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'crates/**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'crates/**'
|
|
||||||
|
|
||||||
env:
|
|
||||||
CARGO_TERM_COLOR: always
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
lint_and_fmt:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
name: Check format and lint rust crates
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- run: cargo fmt --all -- --check
|
|
||||||
- run: cargo clippy -- -D warnings
|
|
||||||
|
|
||||||
build_and_test:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
name: Build and test rust crates
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
toolchain:
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}
|
|
||||||
- run: cargo build --verbose
|
|
||||||
- run: cargo test --verbose
|
|
||||||
@@ -1,23 +1,70 @@
|
|||||||
name: Spell Checker
|
name: Spell Checker
|
||||||
|
|
||||||
on: [pull_request]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
spell_checking:
|
spell_checking:
|
||||||
name: runner / LanguageTools
|
name: LanguageTool
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: reviewdog/action-languagetool@v1
|
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.REVIEWDOG_TOKEN }}
|
# Keep this value in order to show the error comments
|
||||||
|
# also on edited files.
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
# If the workflow is triggered by a push use check reporter
|
||||||
|
#
|
||||||
|
# If the workflow is triggered by a PR coming from the
|
||||||
|
# same repository, the author has write access so we can
|
||||||
|
# use pr-review reporter since the GITHUB_TOKEN will have review
|
||||||
|
# write permissions
|
||||||
|
#
|
||||||
|
# Otherwise use annotation reporter that do not require any
|
||||||
|
# particular permission
|
||||||
|
#
|
||||||
|
# Why this?
|
||||||
|
# https://github.com/Valerioageno/tuono/pull/137#pullrequestreview-2455455272
|
||||||
|
- name: Set reporter
|
||||||
|
id: reporter
|
||||||
|
env:
|
||||||
|
EVENT_NAME: ${{ github.event_name }}
|
||||||
|
FROM_MAIN_REPO: ${{ github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }}
|
||||||
|
run: |
|
||||||
|
echo "EVENT_NAME: $EVENT_NAME FROM_MAIN_REPO: $FROM_MAIN_REPO"
|
||||||
|
if [ "$EVENT_NAME" == 'push' ]; then
|
||||||
|
echo "name=github-check" >> $GITHUB_OUTPUT
|
||||||
|
elif [ "$FROM_MAIN_REPO" == 'true' ]; then
|
||||||
|
echo "name=github-pr-review" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "name=github-pr-annotations" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Review dog languagetool
|
||||||
|
uses: reviewdog/action-languagetool@v1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
|
# Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].
|
||||||
reporter: 'github-pr-review'
|
reporter: ${{ steps.reporter.outputs.name }}
|
||||||
# Change reporter level if you need.
|
# Change reporter level if you need.
|
||||||
level: 'error'
|
level: 'warning'
|
||||||
patterns: '**/*.md **/*.txt **/*.mdx'
|
patterns: '**/*.md **/*.txt **/*.mdx'
|
||||||
#enabled_categories: 'STYLE,PLAIN_ENGLISH,WHITESPACE_RULE,EN_QUOTES,DASH_RULE,WORD_CONTAINS_UNDERSCORE,UPPERCASE_SENTENCE_START,ARROWS,COMMA_PARENTHESIS_WHITESPACE,UNLIKELY_OPENING_PUNCTUATION,SENTENCE_WHITESPACE,EN_UNPAIRED_BRACKETS,PHRASE_REPETITION,PUNCTUATION_PARAGRAPH_END'
|
# enabled_categories: "STYLE,PLAIN_ENGLISH,WHITESPACE_RULE,EN_QUOTES,DASH_RULE,WORD_CONTAINS_UNDERSCORE,UPPERCASE_SENTENCE_START,ARROWS,COMMA_PARENTHESIS_WHITESPACE,UNLIKELY_OPENING_PUNCTUATION,SENTENCE_WHITESPACE,EN_UNPAIRED_BRACKETS,PHRASE_REPETITION,PUNCTUATION_PARAGRAPH_END"
|
||||||
disabled_categories: ''
|
disabled_categories: ''
|
||||||
disabled_rules: ''
|
disabled_rules: ''
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
name: Typescript CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
|
||||||
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_and_test:
|
||||||
|
name: Check build and test
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- 'ubuntu-latest'
|
||||||
|
- 'macos-latest'
|
||||||
|
- 'windows-latest'
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Build project
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Test project
|
||||||
|
run: pnpm test
|
||||||
|
|
||||||
|
lint_and_fmt:
|
||||||
|
name: Check format and lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 15
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install NodeJS Dependencies
|
||||||
|
uses: ./.github/actions/install-node-dependencies
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
run: pnpm format:check
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm lint
|
||||||
|
|
||||||
|
- name: Types
|
||||||
|
run: pnpm types
|
||||||
|
|
||||||
|
ci_ok:
|
||||||
|
name: Typescript CI OK
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 1
|
||||||
|
if: always()
|
||||||
|
needs: [build_and_test, lint_and_fmt]
|
||||||
|
env:
|
||||||
|
FAILURE: ${{ contains(join(needs.*.result, ','), 'failure') }}
|
||||||
|
CANCELLED: ${{ contains(join(needs.*.result, ','), 'cancelled') }}
|
||||||
|
steps:
|
||||||
|
- name: Check for failure or cancelled jobs result
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "Failure: $FAILURE - Cancelled: $CANCELLED"
|
||||||
|
if [ "$FAILURE" = "false" ] && [ "$CANCELLED" = "false" ]; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
name: Typescript CI
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'packages/**'
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '.github/**'
|
|
||||||
- 'packages/**'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-and-test:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
name: Check build and test
|
|
||||||
timeout-minutes: 15
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install NodeJS Dependencies
|
|
||||||
uses: ./.github/actions/install-node-dependencies
|
|
||||||
|
|
||||||
- name: Build project
|
|
||||||
run: pnpm build
|
|
||||||
|
|
||||||
- name: Test project
|
|
||||||
run: pnpm test
|
|
||||||
|
|
||||||
fmt-lint-and-types:
|
|
||||||
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
|
|
||||||
name: Check format and lint
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 15
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install NodeJS Dependencies
|
|
||||||
uses: ./.github/actions/install-node-dependencies
|
|
||||||
|
|
||||||
- name: Check formatting
|
|
||||||
run: pnpm format:check
|
|
||||||
|
|
||||||
- name: Lint
|
|
||||||
run: pnpm lint
|
|
||||||
|
|
||||||
- name: Types
|
|
||||||
run: pnpm types
|
|
||||||
@@ -8,6 +8,8 @@ dist/
|
|||||||
|
|
||||||
examples/*/pnpm-lock.yaml
|
examples/*/pnpm-lock.yaml
|
||||||
|
|
||||||
|
vite.config.ts.timestamp*
|
||||||
|
|
||||||
## Rust related ignores
|
## Rust related ignores
|
||||||
|
|
||||||
# Generated by Cargo
|
# Generated by Cargo
|
||||||
@@ -24,3 +26,6 @@ examples/*/pnpm-lock.yaml
|
|||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
# Files, generated by the code editors
|
||||||
|
.vscode/
|
||||||
@@ -1 +1,8 @@
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
dist
|
||||||
|
.tuono
|
||||||
|
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
|
||||||
|
packages/tuono-lazy-fn-vite-plugin/tests/sources/*
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
* @Valerioageno @marcalexiei
|
||||||
|
|
||||||
|
# Rust
|
||||||
|
/crates/ @Valerioageno
|
||||||
|
/Cargo.toml @Valerioageno
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
/.github/ @marcalexiei
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||

|

|
||||||

|

|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
|
|
||||||
# How to Contribute to Tuono
|
# How to Contribute to Tuono
|
||||||
@@ -7,18 +7,20 @@
|
|||||||
## Contributions
|
## Contributions
|
||||||
|
|
||||||
Any feature contribution or suggestion is strongly appreciated.
|
Any feature contribution or suggestion is strongly appreciated.
|
||||||
Since the current project size, there isn't yet a defined way to start a discussion. Consider [opening a new issue](https://github.com/Valerioageno/tuono/issues/new/choose)
|
|
||||||
or to reach me using my email address, [valerioageno@yahoo.it](mailto:valerioageno@ahoo.it). I'm also available on Twitter (X); DM @valerioageno.
|
We are currently managing the tasks' prioritization in the [Road to V1](https://github.com/orgs/tuono-labs/projects/2) project. You can pick any task that you see in the `ready` column that is not already assigned.
|
||||||
|
|
||||||
|
If you want to contribute on something that is in the `backlog` column, or it hasn't been scoped yet, please drop a message in the [#general](https://discord.com/invite/khQzPa654B) discord channel so we can all have visibility of it.
|
||||||
|
|
||||||
|
Consider [opening a new issue](https://github.com/tuono-labs/tuono/issues/new/choose) if you find a bug.
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
**Did you find a bug?**
|
**Did you find a bug?**
|
||||||
|
|
||||||
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/Valerioageno/tuono/issues).
|
- Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/tuono-labs/tuono/issues).
|
||||||
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Valerioageno/tuono/issues/new/choose). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
|
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/tuono-labs/tuono/issues/new/choose). Be sure to include a title and clear description, as much relevant information as possible, and a code sample or an executable test case demonstrating the expected behavior that is not occurring.
|
||||||
|
|
||||||
**Did you write a patch that fixes a bug?**
|
**Did you write a patch that fixes a bug?**
|
||||||
|
|
||||||
- Open a new GitHub pull request with the patch.
|
[Visit the documentation to learn how to open a pull request](https://tuono.dev/documentation/contributing/pull-requests)
|
||||||
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number, if applicable.
|
|
||||||
- The pull requests must pass all the CI pipelines
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"crates/tuono",
|
"crates/tuono",
|
||||||
"crates/tuono_lib",
|
"crates/tuono_internal",
|
||||||
"crates/tuono_lib_macros",
|
"crates/tuono_lib",
|
||||||
|
"crates/tuono_lib_macros"
|
||||||
]
|
]
|
||||||
exclude = [
|
exclude = [
|
||||||
"apps/documentation",
|
"apps/documentation",
|
||||||
"examples/wit-mdx",
|
"examples/with-mdx",
|
||||||
"examples/tuono-app",
|
"examples/tuono-app",
|
||||||
"examples/tuono-tutorial",
|
"examples/tuono-tutorial",
|
||||||
"benches/tuono"
|
"examples/with-tailwind",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,12 +3,13 @@
|
|||||||
|
|
||||||
# Tuono
|
# Tuono
|
||||||
|
|
||||||
<img align="right" src="https://raw.githubusercontent.com/Valerioageno/tuono/main/assets/logo.png" width="160px">
|
<img align="right" src="https://raw.githubusercontent.com/tuono-labs/tuono/main/assets/logo.png" width="160px">
|
||||||
|
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
**Tuono is the fastest React framework out there**
|
**Tuono is a full-stack web framework for building React applications using Rust as the backend with
|
||||||
|
a strong focus on usability and performance.**
|
||||||
|
|
||||||
Tuono (Italian word for "thunder", pronounced /2 Oh No/).
|
Tuono (Italian word for "thunder", pronounced /2 Oh No/).
|
||||||
Why Tuono? Just a badass name.
|
Why Tuono? Just a badass name.
|
||||||
@@ -23,54 +24,34 @@ Some of its features are:
|
|||||||
- 🧬 Server Side Rendering
|
- 🧬 Server Side Rendering
|
||||||
- 🔥 Hot Module Reload
|
- 🔥 Hot Module Reload
|
||||||
|
|
||||||
## 📖 Documentation
|
## Documentation
|
||||||
|
|
||||||
The [documentation](https://tuono.dev/documentation) is available on
|
The [documentation](https://tuono.dev/) is available on
|
||||||
[tuono.dev](https://tuono.dev/).
|
[tuono.dev](https://tuono.dev/).
|
||||||
|
|
||||||
## Introduction
|
## Installation
|
||||||
|
|
||||||
**NodeJs/Deno/Bun are the only runtimes that allow a React app to be full-stack right? (no)**
|
Available on MacOS, Linux and Windows.
|
||||||
|
|
||||||
Tuono is a full-stack React framework with the server side written in Rust.
|
```sh
|
||||||
Because of this, Tuono is extremely fast, and the requests are handled by a multithreaded Rust server.
|
cargo install tuono
|
||||||
React is still React - it is just superpowered.
|
```
|
||||||
|
|
||||||
**Rust is a hard language, then writing server-side code is hard as well, right? (no again)**
|
|
||||||
|
|
||||||
Tuono provides a collection of utilities to handle the server side code seamlessly with the React code.
|
|
||||||
Each server-side route is managed in a separate file alongside the React route. The routing is handled
|
|
||||||
by Tuono based on the files defined within the `./src/routes` directory.
|
|
||||||
|
|
||||||
**How is Tuono different from Next.js?**
|
|
||||||
|
|
||||||
The Tuono API tries to stick as much as possible to the Next.js one (or at least takes a huge inspiration
|
|
||||||
from it). The major difference is the backend system. While Next.js relies entirely on Node/Deno/Bun,
|
|
||||||
Tuono runs the server without any intermediary runtime. This enables impressive performance improvements
|
|
||||||
(check the benchmarks [here](https://github.com/Valerioageno/tuono/tree/main/benches)).
|
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
As already mentioned above, I strongly suggest you take a look at the
|
Create a new project with:
|
||||||
[tutorial](https://tuono.dev/documentation/tutorial).
|
|
||||||
|
|
||||||
Tuono is the CLI that provides all the needed commands to handle the full-stack project.
|
```sh
|
||||||
To download it is required [cargo](https://doc.rust-lang.org/cargo/), which is the [rust](https://www.rust-lang.org/)
|
tuono new [PROJECT_NAME]
|
||||||
package manager.
|
```
|
||||||
|
|
||||||
Then run `cargo install tuono`.
|
> You can optionally pass the --template (or -t) flag to directly start from a [template](https://github.com/tuono-labs/tuono/tree/main/examples)
|
||||||
|
|
||||||
To list all the available commands, run `tuono -h`
|
Now just install the dependencies with you favourite JS package manager (i.e. `npm install`) and start the development environment
|
||||||
|
|
||||||
To create a new project, run `tuono new [NAME]` (optionally, you can pass the `--template` (or `-t`) flag - check the
|
```sh
|
||||||
[examples](https://github.com/Valerioageno/tuono/tree/main/examples) folder).
|
tuono dev
|
||||||
|
```
|
||||||
Then, to run the local development environment, run it inside the project folder `tuono dev`
|
|
||||||
|
|
||||||
Finally, when the project is ready to be deployed, just run `tuono build` to create the final React assets
|
|
||||||
and to set the server project in `production` mode.
|
|
||||||
|
|
||||||
Now to execute it just run `cargo run --release`.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
@@ -1,106 +0,0 @@
|
|||||||
{
|
|
||||||
"root": true,
|
|
||||||
"reportUnusedDisableDirectives": true,
|
|
||||||
"ignorePatterns": ["**/build", "**/dist"],
|
|
||||||
"parser": "@typescript-eslint/parser",
|
|
||||||
"plugins": ["@typescript-eslint", "import"],
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:@typescript-eslint/recommended",
|
|
||||||
"plugin:@typescript-eslint/stylistic",
|
|
||||||
"plugin:import/recommended",
|
|
||||||
"plugin:import/typescript",
|
|
||||||
"prettier",
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es2020": true,
|
|
||||||
},
|
|
||||||
"parserOptions": {
|
|
||||||
"tsconfigRootDir": ".",
|
|
||||||
"project": true,
|
|
||||||
"sourceType": "module",
|
|
||||||
"ecmaVersion": 2020,
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"import/parsers": {
|
|
||||||
"@typescript-eslint/parser": [".ts", ".tsx"],
|
|
||||||
},
|
|
||||||
"import/resolver": {
|
|
||||||
"typescript": true,
|
|
||||||
},
|
|
||||||
"react": {
|
|
||||||
"version": "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/array-type": "error",
|
|
||||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
|
||||||
"@typescript-eslint/no-empty-object-type": "error",
|
|
||||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
|
||||||
"@typescript-eslint/ban-ts-comment": "error",
|
|
||||||
"@typescript-eslint/consistent-type-definitions": "error",
|
|
||||||
"@typescript-eslint/consistent-type-imports": [
|
|
||||||
"error",
|
|
||||||
{ "prefer": "type-imports" },
|
|
||||||
],
|
|
||||||
"@typescript-eslint/explicit-module-boundary-types": "error",
|
|
||||||
"@typescript-eslint/method-signature-style": ["error", "property"],
|
|
||||||
"@typescript-eslint/naming-convention": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"selector": "typeParameter",
|
|
||||||
"format": ["PascalCase"],
|
|
||||||
"leadingUnderscore": "forbid",
|
|
||||||
"trailingUnderscore": "forbid",
|
|
||||||
"custom": {
|
|
||||||
"regex": "^(T|T[A-Z][A-Za-z]+)$",
|
|
||||||
"match": true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"@typescript-eslint/no-deprecated": "error",
|
|
||||||
"@typescript-eslint/no-empty-function": "error",
|
|
||||||
"@typescript-eslint/no-empty-interface": "error",
|
|
||||||
"@typescript-eslint/no-explicit-any": "off",
|
|
||||||
"@typescript-eslint/no-non-null-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-condition": "error",
|
|
||||||
"@typescript-eslint/no-unnecessary-type-assertion": "error",
|
|
||||||
"@typescript-eslint/no-unused-vars": "error",
|
|
||||||
"@typescript-eslint/explicit-function-return-type": "error",
|
|
||||||
"@typescript-eslint/no-inferrable-types": [
|
|
||||||
"error",
|
|
||||||
{ "ignoreParameters": true },
|
|
||||||
],
|
|
||||||
"import/default": "error",
|
|
||||||
"import/export": "error",
|
|
||||||
"import/namespace": "error",
|
|
||||||
"import/newline-after-import": "error",
|
|
||||||
"import/no-cycle": "error",
|
|
||||||
"import/no-duplicates": "off",
|
|
||||||
"import/no-named-as-default-member": "error",
|
|
||||||
"import/no-unused-modules": "error",
|
|
||||||
"import/order": [
|
|
||||||
"off",
|
|
||||||
{
|
|
||||||
"groups": [
|
|
||||||
"builtin",
|
|
||||||
"external",
|
|
||||||
"internal",
|
|
||||||
"parent",
|
|
||||||
"sibling",
|
|
||||||
"index",
|
|
||||||
"object",
|
|
||||||
"type",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"no-case-declarations": "error",
|
|
||||||
"no-empty": "error",
|
|
||||||
"no-prototype-builtins": "error",
|
|
||||||
"no-redeclare": "error",
|
|
||||||
"no-shadow": "error",
|
|
||||||
"no-undef": "off",
|
|
||||||
"sort-imports": "off",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Documentation Contributing
|
||||||
|
|
||||||
|
## Update `tuono` version in `apps/documentation`
|
||||||
|
|
||||||
|
Update `tuono` version in the following files
|
||||||
|
|
||||||
|
- `.github/workflows/ci-documentation.yml`
|
||||||
|
- `.github/workflows/deploy-documentation.yml`
|
||||||
|
- `apps/documentation/Cargo.toml`
|
||||||
|
- `apps/documentation/package.json`
|
||||||
|
|
||||||
|
[Refer to this PR for additional information](https://github.com/tuono-labs/tuono/pull/236/files)
|
||||||
|
|
||||||
|
In the future we might:
|
||||||
|
|
||||||
|
- move the documentation in a separate repo
|
||||||
|
- make a script to perform this operation
|
||||||
@@ -8,8 +8,7 @@ name = "tuono"
|
|||||||
path = ".tuono/main.rs"
|
path = ".tuono/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tuono_lib = "0.12.3"
|
tuono_lib = "0.17.6"
|
||||||
glob = "0.3.1"
|
glob = "0.3.1"
|
||||||
time = { version = "0.3", features = ["macros"] }
|
time = { version = "0.3", features = ["macros"] }
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
serde = { version = "1.0.202", features = ["derive"] }
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
# Tuono starter
|
|
||||||
|
|
||||||
This is the starter tuono project. To download it run in your terminal:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
$ tuono new [NAME]
|
|
||||||
```
|
|
||||||
@@ -3,29 +3,31 @@
|
|||||||
"description": "The react/rust fullstack framework documentation",
|
"description": "The react/rust fullstack framework documentation",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext .ts,.tsx ./src -c .eslintrc",
|
"lint": "eslint .",
|
||||||
"format": "prettier -u --write --ignore-unknown './src/**/*'",
|
"format": "prettier --write --ignore-unknown .",
|
||||||
"format:check": "prettier --check --ignore-unknown './src/**/*'",
|
"format:check": "prettier --check --ignore-unknown .",
|
||||||
"types": "tsc --noEmit"
|
"types": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/code-highlight": "^7.11.2",
|
"@mantine/code-highlight": "7.16.1",
|
||||||
"@mantine/core": "^7.11.2",
|
"@mantine/core": "7.16.1",
|
||||||
"@mantine/hooks": "^7.11.2",
|
"@mantine/hooks": "7.16.1",
|
||||||
"@mdx-js/react": "^3.0.1",
|
"@mdx-js/react": "3.1.0",
|
||||||
"@tabler/icons-react": "^3.11.0",
|
"@tabler/icons-react": "3.28.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "2.1.1",
|
||||||
"react": "18.3.1",
|
"react": "19.0.0",
|
||||||
"react-dom": "18.3.1",
|
"react-dom": "19.0.0",
|
||||||
"tuono": "npm:tuono@0.12.3"
|
"remark-gfm": "4.0.0",
|
||||||
|
"tuono": "npm:tuono@0.17.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/mdx": "^2.0.13",
|
"@mdx-js/rollup": "3.1.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "19.0.8",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "19.0.3",
|
||||||
"postcss": "^8.4.39",
|
"postcss": "8.5.1",
|
||||||
"postcss-preset-mantine": "^1.17.0",
|
"postcss-preset-mantine": "1.17.0",
|
||||||
"postcss-simple-vars": "^7.0.1"
|
"postcss-simple-vars": "7.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
module.exports = {
|
export default {
|
||||||
plugins: {
|
plugins: {
|
||||||
'postcss-preset-mantine': {},
|
'postcss-preset-mantine': {},
|
||||||
'postcss-simple-vars': {
|
'postcss-simple-vars': {
|
||||||
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 550 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 314 KiB After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"polyfills": [
|
||||||
|
{
|
||||||
|
"identifier": "HTMLElement",
|
||||||
|
"import": {
|
||||||
|
"package": "@lit-labs/ssr-dom-shim",
|
||||||
|
"name": "HTMLElement"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identifier": "customElements",
|
||||||
|
"import": {
|
||||||
|
"package": "@lit-labs/ssr-dom-shim",
|
||||||
|
"name": "customElements"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1 +1,19 @@
|
|||||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
{
|
||||||
|
"name": "",
|
||||||
|
"short_name": "",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-192x192.png",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "/android-chrome-512x512.png",
|
||||||
|
"sizes": "512x512",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"theme_color": "#ffffff",
|
||||||
|
"background_color": "#ffffff",
|
||||||
|
"display": "standalone"
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import { useMemo, type JSX } from 'react'
|
||||||
|
import { Breadcrumbs as MantineBreadcrumbs, Button } from '@mantine/core'
|
||||||
|
import { Link } from 'tuono'
|
||||||
|
|
||||||
|
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
|
||||||
|
|
||||||
|
interface BreadcrumbData {
|
||||||
|
href?: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
interface BreadcrumbsProps {
|
||||||
|
breadcrumbs: Array<BreadcrumbData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Breadcrumbs({
|
||||||
|
breadcrumbs = [],
|
||||||
|
}: BreadcrumbsProps): JSX.Element {
|
||||||
|
const ldJson = useMemo(() => {
|
||||||
|
const _ldJson = {
|
||||||
|
'@context': 'https://schema.org',
|
||||||
|
'@type': 'BreadcrumbList',
|
||||||
|
itemListElement: [
|
||||||
|
{
|
||||||
|
'@type': 'ListItem',
|
||||||
|
position: 1,
|
||||||
|
name: 'Tuono - The React/Rust fullstack framework',
|
||||||
|
item: 'https://tuono.dev' as string | undefined,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
_ldJson.itemListElement.push(
|
||||||
|
...breadcrumbs.map((br, i) => ({
|
||||||
|
'@type': 'ListItem' as const,
|
||||||
|
position: i + 3,
|
||||||
|
name: br.label,
|
||||||
|
item: br.href ? `https://tuono.dev${br.href}` : undefined,
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
|
return _ldJson
|
||||||
|
}, [breadcrumbs])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{JSON.stringify(ldJson, null, 2)}
|
||||||
|
</script>
|
||||||
|
<MantineBreadcrumbs
|
||||||
|
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
|
||||||
|
mb="md"
|
||||||
|
mt="md"
|
||||||
|
>
|
||||||
|
<Button href="/" component={Link} variant="subtle" radius="xl" p={5}>
|
||||||
|
<IconBolt />
|
||||||
|
</Button>
|
||||||
|
{breadcrumbs.map((br) => (
|
||||||
|
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
|
||||||
|
))}
|
||||||
|
</MantineBreadcrumbs>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BreadcrumbElementProps {
|
||||||
|
href?: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BreadcrumbElement({
|
||||||
|
href,
|
||||||
|
label,
|
||||||
|
}: BreadcrumbElementProps): JSX.Element {
|
||||||
|
if (href) {
|
||||||
|
return (
|
||||||
|
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button component="span" variant="light" radius="xl">
|
||||||
|
{label}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Breadcrumbs from './Breadcrumbs'
|
||||||
|
|
||||||
|
export default Breadcrumbs
|
||||||
@@ -4,7 +4,7 @@ import { IconEdit } from '@tabler/icons-react'
|
|||||||
import { useRouter } from 'tuono'
|
import { useRouter } from 'tuono'
|
||||||
|
|
||||||
const GITHUB_URL =
|
const GITHUB_URL =
|
||||||
'https://github.com/Valerioageno/tuono/tree/main/apps/documentation/src/routes'
|
'https://github.com/tuono-labs/tuono/tree/main/apps/documentation/src/routes'
|
||||||
|
|
||||||
export default function EditPage(): JSX.Element {
|
export default function EditPage(): JSX.Element {
|
||||||
const { pathname } = useRouter()
|
const { pathname } = useRouter()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import EditPage from './EditPage'
|
||||||
|
|
||||||
|
export default EditPage
|
||||||
@@ -36,7 +36,7 @@ export default function Hero(): JSX.Element {
|
|||||||
size="lg"
|
size="lg"
|
||||||
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
style={{ border: 'solid 1px var(--mantine-color-violet-1)' }}
|
||||||
color="gray"
|
color="gray"
|
||||||
leftSection="$ cargo install tuono"
|
leftSection="cargo install tuono"
|
||||||
rightSection={
|
rightSection={
|
||||||
copied ? (
|
copied ? (
|
||||||
<IconCheck style={{ width: rem(20) }} />
|
<IconCheck style={{ width: rem(20) }} />
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Hero from './Hero'
|
||||||
|
|
||||||
|
export default Hero
|
||||||
@@ -2,5 +2,5 @@ import type { JSX } from 'react'
|
|||||||
import { Text, type TextProps } from '@mantine/core'
|
import { Text, type TextProps } from '@mantine/core'
|
||||||
|
|
||||||
export default function MdxBold(props: TextProps): JSX.Element {
|
export default function MdxBold(props: TextProps): JSX.Element {
|
||||||
return <Text fw={700} {...props} />
|
return <Text component="span" fw={700} {...props} />
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxBold from './MdxBold'
|
||||||
|
|
||||||
|
export default MdxBold
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import type { JSX } from 'react'
|
import type { JSX, HTMLAttributes } from 'react'
|
||||||
import { Code } from '@mantine/core'
|
import { Code } from '@mantine/core'
|
||||||
import type { HTMLAttributes } from 'react'
|
|
||||||
|
|
||||||
export default function MdxCode(
|
export default function MdxCode(
|
||||||
props: HTMLAttributes<HTMLPreElement>,
|
props: HTMLAttributes<HTMLPreElement>,
|
||||||
): JSX.Element {
|
): JSX.Element {
|
||||||
return <Code {...props} style={{ fontSize: 14 }} />
|
return <Code {...props} style={{ fontSize: 'inherit' }} />
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxCode from './MdxCode'
|
||||||
|
|
||||||
|
export default MdxCode
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxLink from './MdxLink'
|
||||||
|
|
||||||
|
export default MdxLink
|
||||||
@@ -1,10 +1,17 @@
|
|||||||
import type { JSX } from 'react'
|
import type { JSX } from 'react'
|
||||||
import { CodeHighlight } from '@mantine/code-highlight'
|
import { CodeHighlight } from '@mantine/code-highlight'
|
||||||
import styles from './mdx-pre.module.css'
|
|
||||||
|
import styles from './MdxPre.module.css'
|
||||||
|
|
||||||
interface PreProps {
|
interface PreProps {
|
||||||
children: any
|
children: {
|
||||||
|
props: {
|
||||||
|
children: string
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MdxPre({ children }: PreProps): JSX.Element {
|
export default function MdxPre({ children }: PreProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<CodeHighlight
|
<CodeHighlight
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxPre from './MdxPre'
|
||||||
|
|
||||||
|
export default MdxPre
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import type { JSX } from 'react'
|
import type { JSX, ReactNode } from 'react'
|
||||||
import { MDXProvider } from '@mdx-js/react'
|
import { MDXProvider } from '@mdx-js/react'
|
||||||
|
|
||||||
import MdxLink from './mdx-link'
|
import MdxLink from './MdxLink'
|
||||||
import type { ReactNode } from 'react'
|
import MdxPre from './MdxPre'
|
||||||
import MdxPre from './mdx-pre'
|
import MdxQuote from './MdxQuote'
|
||||||
import MdxQuote from './mdx-quote'
|
import MdxCode from './MdxCode'
|
||||||
import MdxCode from './mdx-code'
|
import { h } from './MdxTitle'
|
||||||
import { h } from './mdx-title'
|
import MdxBold from './MdxBold'
|
||||||
import MdxBold from './mdx-bold/mdx-bold'
|
import MdxTable from './MdxTable'
|
||||||
|
|
||||||
interface MdxProviderProps {
|
interface MdxProviderProps {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
@@ -20,7 +20,6 @@ export default function MdxProvider({
|
|||||||
<MDXProvider
|
<MDXProvider
|
||||||
components={{
|
components={{
|
||||||
a: MdxLink,
|
a: MdxLink,
|
||||||
// @ts-expect-error: useless finding the correct props types
|
|
||||||
pre: MdxPre,
|
pre: MdxPre,
|
||||||
blockquote: MdxQuote,
|
blockquote: MdxQuote,
|
||||||
code: MdxCode,
|
code: MdxCode,
|
||||||
@@ -31,6 +30,12 @@ export default function MdxProvider({
|
|||||||
h5: h(5),
|
h5: h(5),
|
||||||
h6: h(6),
|
h6: h(6),
|
||||||
strong: MdxBold,
|
strong: MdxBold,
|
||||||
|
table: MdxTable,
|
||||||
|
thead: MdxTable.Thead,
|
||||||
|
tbody: MdxTable.Tbody,
|
||||||
|
tr: MdxTable.Tr,
|
||||||
|
th: MdxTable.Th,
|
||||||
|
td: MdxTable.Td,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxQuote from './MdxQuote'
|
||||||
|
|
||||||
|
export default MdxQuote
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { DetailedHTMLProps, TableHTMLAttributes, JSX } from 'react'
|
||||||
|
import { Table, ScrollArea } from '@mantine/core'
|
||||||
|
|
||||||
|
type MdxTableProps = DetailedHTMLProps<
|
||||||
|
TableHTMLAttributes<HTMLTableElement>,
|
||||||
|
HTMLTableElement
|
||||||
|
>
|
||||||
|
|
||||||
|
function MdxTable(props: MdxTableProps): JSX.Element {
|
||||||
|
const { children, ...rest } = props
|
||||||
|
return (
|
||||||
|
<ScrollArea type="auto">
|
||||||
|
<Table highlightOnHover {...rest}>
|
||||||
|
{children}
|
||||||
|
</Table>
|
||||||
|
</ScrollArea>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
MdxTable.Thead = Table.Thead
|
||||||
|
MdxTable.Tbody = Table.Tbody
|
||||||
|
MdxTable.Tr = Table.Tr
|
||||||
|
MdxTable.Th = Table.Th
|
||||||
|
MdxTable.Td = Table.Td
|
||||||
|
MdxTable.Caption = Table.Caption
|
||||||
|
|
||||||
|
export default MdxTable
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxTable from './MdxTable'
|
||||||
|
|
||||||
|
export default MdxTable
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { ElementType, JSX, ReactNode } from 'react'
|
||||||
|
import { Title, type TitleProps } from '@mantine/core'
|
||||||
|
|
||||||
|
export default function MdxTitle(props: TitleProps): JSX.Element {
|
||||||
|
const headingId = getIdFrom(props.children)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Title
|
||||||
|
data-heading={headingId}
|
||||||
|
data-order={props.order}
|
||||||
|
style={{ scrollMargin: 70 }}
|
||||||
|
{...props}
|
||||||
|
id={headingId}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIdFrom(children: ReactNode): string {
|
||||||
|
const getTextContent = (node: ReactNode): string => {
|
||||||
|
if (typeof node === 'string') return node
|
||||||
|
if (typeof node === 'object' && node !== null && 'props' in node) {
|
||||||
|
const child = node as { props?: { children?: ReactNode } }
|
||||||
|
return getTextContent(child.props?.children)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const textContent = Array.isArray(children)
|
||||||
|
? children.map(getTextContent).join('')
|
||||||
|
: getTextContent(children)
|
||||||
|
|
||||||
|
return textContent
|
||||||
|
.normalize('NFKD') // separate accented characters into their base form and diacritical marks
|
||||||
|
.replace(/[\u0300-\u036f]/g, '') // remove all the accents
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/\./g, '-') // some titles (configuration) contain keypath, so replace dots with hyphens
|
||||||
|
.replace(/[^a-z0-9 -]/g, '') // remove non-alphanumeric characters
|
||||||
|
.replace(/\s+/g, '-') // replace spaces with hyphens
|
||||||
|
.replace(/-+/g, '-') // remove consecutive hyphens
|
||||||
|
}
|
||||||
|
|
||||||
|
export const h = (order: 1 | 2 | 3 | 4 | 5 | 6): ElementType<TitleProps> => {
|
||||||
|
function render(props: TitleProps): JSX.Element {
|
||||||
|
return <MdxTitle order={order} {...props} />
|
||||||
|
}
|
||||||
|
render.displayName = 'H'
|
||||||
|
|
||||||
|
return render
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import MdxTitle, { h } from './MdxTitle'
|
||||||
|
|
||||||
|
export default MdxTitle
|
||||||
|
|
||||||
|
export { h }
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MdxProvider from './MdxProvider'
|
||||||
|
|
||||||
|
export default MdxProvider
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import type { JSX, ReactNode } from 'react'
|
||||||
|
import { Box, Container } from '@mantine/core'
|
||||||
|
|
||||||
|
import TableOfContents from '@/components/TableOfContents'
|
||||||
|
|
||||||
|
import EditPage from '../EditPage'
|
||||||
|
import MdxProvider from '../MdxProvider'
|
||||||
|
|
||||||
|
interface MdxWrapperProps {
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MdxWrapper({ children }: MdxWrapperProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<Container size={1000} w="100%" display="flex" style={{ gap: 12 }}>
|
||||||
|
<Box
|
||||||
|
id="mdx-root"
|
||||||
|
component="article"
|
||||||
|
mt="xl"
|
||||||
|
px={16}
|
||||||
|
py={36}
|
||||||
|
style={{ maxWidth: '100%' }}
|
||||||
|
>
|
||||||
|
<MdxProvider>{children}</MdxProvider>
|
||||||
|
<EditPage />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<TableOfContents />
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { MdxWrapper } from './MdxWrapper'
|
||||||
|
|
||||||
|
export default MdxWrapper
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from 'react'
|
||||||
import { Head } from 'tuono'
|
|
||||||
|
|
||||||
interface MetaTagsProps {
|
interface MetaTagsProps {
|
||||||
title: string
|
title: string
|
||||||
@@ -15,7 +14,7 @@ export default function MetaTags({
|
|||||||
canonical,
|
canonical,
|
||||||
}: MetaTagsProps): ReactNode {
|
}: MetaTagsProps): ReactNode {
|
||||||
return (
|
return (
|
||||||
<Head>
|
<>
|
||||||
<title>{title}</title>
|
<title>{title}</title>
|
||||||
<link rel="canonical" href={canonical} />
|
<link rel="canonical" href={canonical} />
|
||||||
<meta
|
<meta
|
||||||
@@ -41,6 +40,6 @@ export default function MetaTags({
|
|||||||
<meta name="twitter:title" content={title} />
|
<meta name="twitter:title" content={title} />
|
||||||
<meta name="twitter:description" content={description} />
|
<meta name="twitter:description" content={description} />
|
||||||
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
|
<meta name="twitter:image" content="https://tuono.dev/og-cover.png" />
|
||||||
</Head>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import MetaTags from './MetaTags'
|
||||||
|
|
||||||
|
export default MetaTags
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import type { JSX } from 'react'
|
||||||
|
import { AppShell, Box, Burger, Button, Flex } from '@mantine/core'
|
||||||
|
import { Link } from 'tuono'
|
||||||
|
|
||||||
|
import NavbarActions from './NavbarActions'
|
||||||
|
|
||||||
|
interface NavbarProps {
|
||||||
|
toggle: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Navbar({ toggle }: NavbarProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<AppShell.Header p="sm">
|
||||||
|
<Flex justify="space-between">
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
href="/"
|
||||||
|
variant="transparent"
|
||||||
|
p={0}
|
||||||
|
fz={28}
|
||||||
|
hiddenFrom="sm"
|
||||||
|
>
|
||||||
|
Tuono
|
||||||
|
</Button>
|
||||||
|
<Box />
|
||||||
|
<Flex align="center" gap={8}>
|
||||||
|
<NavbarActions />
|
||||||
|
<Burger onClick={toggle} hiddenFrom="sm" size="sm" />
|
||||||
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</AppShell.Header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,33 +1,18 @@
|
|||||||
import type { JSX } from 'react'
|
import type { JSX } from 'react'
|
||||||
import { Flex, Button, ActionIcon, Group } from '@mantine/core'
|
import { Flex, ActionIcon, Group } from '@mantine/core'
|
||||||
import {
|
import { IconBrandGithub, IconBrandDiscord } from '@tabler/icons-react'
|
||||||
IconBrandGithub,
|
|
||||||
IconBook,
|
|
||||||
IconBrandDiscord,
|
|
||||||
} from '@tabler/icons-react'
|
|
||||||
import { Link } from 'tuono'
|
|
||||||
|
|
||||||
import ThemeBtn from '../theme-btn'
|
import ThemeBtn from '../ThemeBtn'
|
||||||
|
|
||||||
export default function Actions(): JSX.Element {
|
export default function NavbarActions(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Flex gap={8}>
|
<Flex gap={8}>
|
||||||
<Group gap={8}>
|
<Group gap={8}>
|
||||||
<Button
|
|
||||||
component={Link}
|
|
||||||
href="/documentation"
|
|
||||||
visibleFrom="sm"
|
|
||||||
size="compact-lg"
|
|
||||||
rightSection={<IconBook />}
|
|
||||||
autoContrast
|
|
||||||
>
|
|
||||||
Get started
|
|
||||||
</Button>
|
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
aria-label="Check the project on github"
|
aria-label="Check the project on github"
|
||||||
href="https://github.com/Valerioageno/tuono"
|
href="https://github.com/tuono-labs/tuono"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
component="a"
|
component="a"
|
||||||
>
|
>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Navbar from './Navbar'
|
||||||
|
|
||||||
|
export default Navbar
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
import type { JSX } from 'react'
|
import type { JSX } from 'react'
|
||||||
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
import { Box, Button, Text, Title, Flex } from '@mantine/core'
|
||||||
import { Link } from 'tuono'
|
|
||||||
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
import { IconArrowRight, IconArrowLeft } from '@tabler/icons-react'
|
||||||
|
import { Link } from 'tuono'
|
||||||
|
|
||||||
interface NavigationButton {
|
interface NavigationButtonData {
|
||||||
href: string
|
href: string
|
||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavigationButtonsProps {
|
interface NavigationButtonsProps {
|
||||||
prev?: NavigationButton
|
prev?: NavigationButtonData
|
||||||
next?: NavigationButton
|
next?: NavigationButtonData
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NavigationButtons({
|
export default function NavigationButtons({
|
||||||
@@ -25,7 +25,7 @@ export default function NavigationButtons({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavigationButtonProps extends NavigationButton {
|
interface NavigationBtnProps extends NavigationButtonData {
|
||||||
type: 'next' | 'prev'
|
type: 'next' | 'prev'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ const NavigationBtn = ({
|
|||||||
type,
|
type,
|
||||||
title,
|
title,
|
||||||
href,
|
href,
|
||||||
}: NavigationButtonProps): JSX.Element => {
|
}: NavigationBtnProps): JSX.Element => {
|
||||||
const heading = type === 'next' ? 'Next' : 'Previous'
|
const heading = type === 'next' ? 'Next' : 'Previous'
|
||||||
const textAlign = type === 'next' ? 'left' : 'right'
|
const textAlign = type === 'next' ? 'left' : 'right'
|
||||||
const variant = type === 'next' ? 'filled' : 'outline'
|
const variant = type === 'next' ? 'filled' : 'outline'
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import NavigationButtons from './NavigationButtons'
|
||||||
|
|
||||||
|
export default NavigationButtons
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import type { JSX } from 'react'
|
||||||
|
|
||||||
|
import {
|
||||||
|
AppShell,
|
||||||
|
Badge,
|
||||||
|
Flex,
|
||||||
|
Divider,
|
||||||
|
Title,
|
||||||
|
Button,
|
||||||
|
ScrollArea,
|
||||||
|
Text,
|
||||||
|
} from '@mantine/core'
|
||||||
|
|
||||||
|
import { IconX } from '@tabler/icons-react'
|
||||||
|
import { useMediaQuery } from '@mantine/hooks'
|
||||||
|
|
||||||
|
import { sidebarElements } from './sidebarElements'
|
||||||
|
import SidebarLink from './SidebarLink'
|
||||||
|
|
||||||
|
interface SidebarProps {
|
||||||
|
close: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function SidebarHeader({ close }: SidebarProps): JSX.Element {
|
||||||
|
const isSm = useMediaQuery('(min-width: 48em)')
|
||||||
|
return (
|
||||||
|
<AppShell.Section>
|
||||||
|
<Flex mb={20} w="100%" justify="space-between">
|
||||||
|
<Flex
|
||||||
|
gap={10}
|
||||||
|
align="center"
|
||||||
|
w={isSm ? '100%' : 'auto'}
|
||||||
|
justify="center"
|
||||||
|
>
|
||||||
|
<Title order={3}>Tuono</Title>
|
||||||
|
<Badge mt={4} size="xs" variant="outline">
|
||||||
|
Docs
|
||||||
|
</Badge>
|
||||||
|
</Flex>
|
||||||
|
<Button onClick={close} hiddenFrom="sm" variant="transparent" p="0">
|
||||||
|
<IconX />
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</AppShell.Section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function SidebarElements({ close }: SidebarProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<AppShell.Section component={ScrollArea}>
|
||||||
|
{sidebarElements.map((el, i) => {
|
||||||
|
if (el.type === 'divider') {
|
||||||
|
return <Divider key={`${el.type}-${i}`} my="md" mx={10} />
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.type === 'title') {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
|
size="xs"
|
||||||
|
fw={700}
|
||||||
|
fz={12}
|
||||||
|
pl={12}
|
||||||
|
py={5}
|
||||||
|
>
|
||||||
|
{el.label}
|
||||||
|
</Text>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.children?.length) {
|
||||||
|
return (
|
||||||
|
<SidebarLink
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
|
href={el.href}
|
||||||
|
label={el.label}
|
||||||
|
leftSection={el.leftIcon}
|
||||||
|
>
|
||||||
|
{el.children.map((child, index) => (
|
||||||
|
<SidebarLink
|
||||||
|
href={child.href}
|
||||||
|
label={child.label}
|
||||||
|
key={index}
|
||||||
|
onClick={close}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</SidebarLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarLink
|
||||||
|
key={`${el.type}-${i}`}
|
||||||
|
href={el.href}
|
||||||
|
label={el.label}
|
||||||
|
onClick={close}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</AppShell.Section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Sidebar({ close }: SidebarProps): JSX.Element {
|
||||||
|
return (
|
||||||
|
<AppShell.Navbar p="md">
|
||||||
|
<SidebarHeader close={close} />
|
||||||
|
<SidebarElements close={close} />
|
||||||
|
</AppShell.Navbar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
.link {
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.link span {
|
||||||
|
font-size: 14px !important;
|
||||||
|
color: var(--mantine-color-sidebar-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
.link:hover span,
|
||||||
|
.active span {
|
||||||
|
color: var(--mantine-color-sidebar-text-hover);
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import type { JSX, ReactNode } from 'react'
|
import type { JSX, ReactNode } from 'react'
|
||||||
import { useState } from 'react'
|
|
||||||
import { NavLink, type NavLinkProps } from '@mantine/core'
|
import { NavLink, type NavLinkProps } from '@mantine/core'
|
||||||
|
import clsx from 'clsx'
|
||||||
import { Link, useRouter } from 'tuono'
|
import { Link, useRouter } from 'tuono'
|
||||||
import { IconChevronRight } from '@tabler/icons-react'
|
import { IconChevronRight } from '@tabler/icons-react'
|
||||||
|
|
||||||
import styles from './sidebar-link.module.css'
|
import styles from './SidebarLink.module.css'
|
||||||
|
|
||||||
interface SidebarLinkProps {
|
interface SidebarLinkProps {
|
||||||
label: string
|
label: string
|
||||||
@@ -17,24 +17,19 @@ export default function SidebarLink(
|
|||||||
props: SidebarLinkProps & NavLinkProps,
|
props: SidebarLinkProps & NavLinkProps,
|
||||||
): JSX.Element {
|
): JSX.Element {
|
||||||
const { pathname } = useRouter()
|
const { pathname } = useRouter()
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(!!props.defaultOpened)
|
|
||||||
|
const isActive = pathname === props.href
|
||||||
|
|
||||||
const internalProps = {
|
const internalProps = {
|
||||||
active: pathname === props.href,
|
active: isActive,
|
||||||
className: styles.link,
|
className: clsx(styles.link, isActive && styles.active),
|
||||||
rightSection: props.children && (
|
rightSection: props.children && (
|
||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
size="1.2rem"
|
size="1.2rem"
|
||||||
stroke={1.5}
|
stroke={1.5}
|
||||||
className="mantine-rotate-rtl"
|
className="mantine-rotate-rtl"
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
setIsOpen((state) => !state)
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
opened: isOpen,
|
|
||||||
autoContrast: true,
|
autoContrast: true,
|
||||||
...props,
|
...props,
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Sidebar from './Sidebar'
|
||||||
|
|
||||||
|
export default Sidebar
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
import type { ReactNode } from 'react'
|
||||||
|
|
||||||
|
interface SidebarElementBase<T extends string> {
|
||||||
|
type: T
|
||||||
|
}
|
||||||
|
|
||||||
|
type SidebarDivider = SidebarElementBase<'divider'>
|
||||||
|
|
||||||
|
interface SidebarLink extends SidebarElementBase<'element'> {
|
||||||
|
label: string
|
||||||
|
href: string
|
||||||
|
children?: Array<SidebarLink>
|
||||||
|
leftIcon?: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SidebarTitle extends SidebarElementBase<'title'> {
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SidebarElement = SidebarDivider | SidebarLink | SidebarTitle
|
||||||
|
|
||||||
|
export const sidebarElements: Array<SidebarElement> = [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Home',
|
||||||
|
href: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Getting started',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Installation',
|
||||||
|
href: '/documentation/installation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'How is tuono different?',
|
||||||
|
href: '/documentation/how-is-tuono-different',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Tutorial',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial',
|
||||||
|
label: 'Intro',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/development-setup',
|
||||||
|
label: 'Development setup',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/api-fetching',
|
||||||
|
label: 'API fetching',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/components',
|
||||||
|
label: 'Components',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/dynamic-routes',
|
||||||
|
label: 'Dynamic routes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/error-handling',
|
||||||
|
label: 'Error handling',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/seo',
|
||||||
|
label: 'SEO and meta tags',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/redirections',
|
||||||
|
label: 'Redirections',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/production',
|
||||||
|
label: 'Production build',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
href: '/documentation/tutorial/conclusion',
|
||||||
|
label: 'Conclusion',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ type: 'title', label: 'Overview' },
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Routing',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Defining routes',
|
||||||
|
href: '/documentation/routing/defining-routes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Dynamic routes',
|
||||||
|
href: '/documentation/routing/dynamic-routes',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Link and navigation',
|
||||||
|
href: '/documentation/routing/link-and-navigation',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Pages',
|
||||||
|
href: '/documentation/routing/pages',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Loading state',
|
||||||
|
href: '/documentation/routing/loading-state',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Layouts',
|
||||||
|
href: '/documentation/routing/layouts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Redirecting',
|
||||||
|
href: '/documentation/routing/redirecting',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Rendering',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Server side rendering',
|
||||||
|
href: '/documentation/rendering/server-side-rendering',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Static site rendering',
|
||||||
|
href: '/documentation/rendering/static-site-rendering',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Styles',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'CSS modules',
|
||||||
|
href: '/documentation/styles/css-modules',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Tailwind CSS',
|
||||||
|
href: '/documentation/styles/tailwind-css',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Integrations',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'MDX',
|
||||||
|
href: '/documentation/integrations/mdx',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Core concepts',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Multithreading',
|
||||||
|
href: '/documentation/core-concepts/multithreading',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ type: 'title', label: 'API reference' },
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Components',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Link',
|
||||||
|
href: '/documentation/components/link',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Hooks',
|
||||||
|
href: '#focus',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'useRouter',
|
||||||
|
href: '/documentation/hooks/use-router',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'CLI',
|
||||||
|
href: '/documentation/cli',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Application state',
|
||||||
|
href: '/documentation/application-state',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Configuration',
|
||||||
|
href: '/documentation/configuration',
|
||||||
|
},
|
||||||
|
|
||||||
|
{ type: 'divider' },
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Contributing',
|
||||||
|
href: '#focus',
|
||||||
|
leftIcon: '✨',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Guildelines',
|
||||||
|
href: '/documentation/contributing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Local development',
|
||||||
|
href: '/documentation/contributing/local-development',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'element',
|
||||||
|
label: 'Pull requests',
|
||||||
|
href: '/documentation/contributing/pull-requests',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -1,47 +1,33 @@
|
|||||||
/*
|
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
|
||||||
*/
|
|
||||||
.wrapper {
|
.wrapper {
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: var(--mantine-spacing-xl);
|
top: var(--mantine-spacing-xl);
|
||||||
right: 0;
|
|
||||||
padding-top: 55px;
|
padding-top: 55px;
|
||||||
flex: 0 0 calc(var(--docs-table-of-contents-width) - 20px);
|
flex: 0 0 calc(250px - 20px);
|
||||||
width: 200px;
|
min-width: 220px;
|
||||||
|
max-width: 220px;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
@mixin rtl {
|
/* var(--mantine-breakpoint-md) = 62em */
|
||||||
padding-left: 0;
|
@media (max-width: 62em) {
|
||||||
padding-right: var(--mantine-spacing-md);
|
display: none;
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-with-tabs] {
|
|
||||||
padding-top: 0;
|
|
||||||
top: calc(var(--mantine-spacing-xl) + 60px);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner {
|
.inner {
|
||||||
padding-bottom: var(--mantine-spacing-xl);
|
|
||||||
padding-left: var(--mantine-spacing-md);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|
||||||
@mixin rtl {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
white-space: normal;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: break-word;
|
||||||
display: block;
|
display: block;
|
||||||
border-left: 1px solid transparent;
|
border-left: 1px solid transparent;
|
||||||
padding: 8px var(--mantine-spacing-md);
|
padding: 8px var(--mantine-spacing-md);
|
||||||
margin-left: -1px;
|
margin-left: -1px;
|
||||||
padding-left: calc(var(--toc-link-offset) * var(--mantine-spacing-lg));
|
padding-left: calc(1 * var(--mantine-spacing-lg));
|
||||||
border-top-right-radius: var(--mantine-radius-sm);
|
border-top-right-radius: var(--mantine-radius-sm);
|
||||||
border-bottom-right-radius: var(--mantine-radius-sm);
|
border-bottom-right-radius: var(--mantine-radius-sm);
|
||||||
|
|
||||||
@@ -58,10 +44,19 @@
|
|||||||
border-right: 1px solid transparent;
|
border-right: 1px solid transparent;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: -1px;
|
margin-right: -1px;
|
||||||
border-top-left-radius: var(--mantine-radius-sm);
|
border-radius: var(--mantine-radius-sm) 0 0 var(--mantine-radius-sm);
|
||||||
border-bottom-left-radius: var(--mantine-radius-sm);
|
}
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
&:hover {
|
||||||
|
border-color: var(--mantine-color-violet-1);
|
||||||
|
|
||||||
|
@mixin light {
|
||||||
|
color: var(--mantine-color-black);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin dark {
|
||||||
|
color: var(--mantine-color-violet-1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&[data-active] {
|
&[data-active] {
|
||||||
@@ -69,22 +64,14 @@
|
|||||||
|
|
||||||
@mixin light {
|
@mixin light {
|
||||||
color: var(--mantine-color-violet-8);
|
color: var(--mantine-color-violet-8);
|
||||||
background-color: var(--mantine-color-violet-0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin dark {
|
@mixin dark {
|
||||||
color: var(--mantine-color-violet-1);
|
color: var(--mantine-color-violet-1);
|
||||||
background-color: #534a6d;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: var(--mantine-spacing-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-left: var(--mantine-spacing-md);
|
margin-left: var(--mantine-spacing-md);
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import type { JSX, MouseEvent } from 'react'
|
||||||
|
import { useRef, useState, useEffect } from 'react'
|
||||||
|
import { useRouter } from 'tuono'
|
||||||
|
import { Box, Text } from '@mantine/core'
|
||||||
|
|
||||||
|
import { getHeadings, type Heading } from './getHeadings'
|
||||||
|
import classes from './TableOfContents.module.css'
|
||||||
|
|
||||||
|
export function TableOfContents(): JSX.Element | null {
|
||||||
|
const [activeHeadingIndex, setActiveHeadingIndex] = useState<number | null>(
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
const [headings, setHeadings] = useState<Array<Heading>>([])
|
||||||
|
const headingsRef = useRef<Array<HTMLElement>>([])
|
||||||
|
const observerRef = useRef<IntersectionObserver | null>(null)
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const _headings = getHeadings()
|
||||||
|
setHeadings(_headings)
|
||||||
|
headingsRef.current = _headings.map((heading) => heading.getNode())
|
||||||
|
|
||||||
|
if (observerRef.current) {
|
||||||
|
observerRef.current.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
const visibleEntries = entries
|
||||||
|
.filter((entry) => entry.isIntersecting)
|
||||||
|
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top)
|
||||||
|
|
||||||
|
if (visibleEntries.length > 0) {
|
||||||
|
setActiveHeadingIndex(
|
||||||
|
_headings.findIndex((h) => h.id === visibleEntries[0].target.id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rootMargin: '-50px 0px -80% 0px',
|
||||||
|
threshold: [0.1, 0.5, 1.0],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
headingsRef.current.forEach((node) => {
|
||||||
|
observer.observe(node)
|
||||||
|
})
|
||||||
|
observerRef.current = observer
|
||||||
|
|
||||||
|
const handleHashChange = (): void => {
|
||||||
|
setTimeout(() => {
|
||||||
|
observerRef.current?.disconnect()
|
||||||
|
observerRef.current = observer
|
||||||
|
headingsRef.current.forEach((node) => {
|
||||||
|
observer.observe(node)
|
||||||
|
})
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', handleHashChange)
|
||||||
|
|
||||||
|
return (): void => {
|
||||||
|
observer.disconnect()
|
||||||
|
window.removeEventListener('hashchange', handleHashChange)
|
||||||
|
}
|
||||||
|
}, [router.pathname])
|
||||||
|
|
||||||
|
const handleHeadingClick = (
|
||||||
|
event: MouseEvent<HTMLAnchorElement>,
|
||||||
|
id: string,
|
||||||
|
): void => {
|
||||||
|
event.preventDefault()
|
||||||
|
const element = document.getElementById(id)
|
||||||
|
if (element) {
|
||||||
|
element.scrollIntoView({
|
||||||
|
behavior: 'instant',
|
||||||
|
block: 'start',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid to show it in case of a TODO page
|
||||||
|
if (headings.length === 1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box component="nav" className={classes.wrapper}>
|
||||||
|
<div className={classes.inner}>
|
||||||
|
<div>
|
||||||
|
<Text className={classes.title} mb={8}>
|
||||||
|
On this page
|
||||||
|
</Text>
|
||||||
|
<div className={classes.items}>
|
||||||
|
{headings.slice(1).map((heading, index) => (
|
||||||
|
<Text
|
||||||
|
key={heading.id}
|
||||||
|
component="a"
|
||||||
|
fz="sm"
|
||||||
|
px={8}
|
||||||
|
w="fit-content"
|
||||||
|
py={4}
|
||||||
|
className={classes.link}
|
||||||
|
mod={{ active: activeHeadingIndex === index + 1 }}
|
||||||
|
href={`#${heading.id}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
handleHeadingClick(e, heading.id)
|
||||||
|
}}
|
||||||
|
__vars={{ '--toc-link-offset': `${heading.depth - 1}` }}
|
||||||
|
>
|
||||||
|
{heading.content}
|
||||||
|
</Text>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
||||||
|
*/
|
||||||
|
export interface Heading {
|
||||||
|
depth: number
|
||||||
|
content: string
|
||||||
|
id: string
|
||||||
|
getNode: () => HTMLHeadingElement
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCleanedText(element: HTMLElement): string {
|
||||||
|
const clone = element.cloneNode(true) as HTMLElement
|
||||||
|
|
||||||
|
clone.querySelectorAll('code, pre').forEach((codeBlock) => {
|
||||||
|
const textNode = document.createTextNode(codeBlock.textContent || '')
|
||||||
|
codeBlock.replaceWith(textNode)
|
||||||
|
})
|
||||||
|
|
||||||
|
return clone.textContent?.trim() || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHeadingsData(headings: Array<HTMLHeadingElement>): Array<Heading> {
|
||||||
|
const result: Array<Heading> = []
|
||||||
|
|
||||||
|
for (const heading of headings) {
|
||||||
|
const depth = parseInt(heading.getAttribute('data-order') || '1', 10)
|
||||||
|
|
||||||
|
if (depth <= 3 && heading.id) {
|
||||||
|
result.push({
|
||||||
|
depth,
|
||||||
|
content: getCleanedText(heading),
|
||||||
|
id: heading.id,
|
||||||
|
getNode: () =>
|
||||||
|
document.getElementById(heading.id) as HTMLHeadingElement,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getHeadings(): Array<Heading> {
|
||||||
|
const root = document.getElementById('mdx-root')
|
||||||
|
|
||||||
|
if (!root) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
return getHeadingsData(Array.from(root.querySelectorAll('[data-heading]')))
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
* Component inspired by: https://github.com/mantinedev/mantine/tree/master/apps/mantine.dev/src/components/TableOfContents
|
||||||
*/
|
*/
|
||||||
import { TableOfContents } from './table-of-content'
|
import { TableOfContents } from './TableOfContents'
|
||||||
|
|
||||||
export default TableOfContents
|
export default TableOfContents
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import { IconSun, IconMoon } from '@tabler/icons-react'
|
import { IconSun, IconMoon } from '@tabler/icons-react'
|
||||||
import cx from 'clsx'
|
import cx from 'clsx'
|
||||||
|
|
||||||
import classes from './theme-btn.module.css'
|
import classes from './ThemeBtn.module.css'
|
||||||
|
|
||||||
export default function ThemeBtn(): JSX.Element {
|
export default function ThemeBtn(): JSX.Element {
|
||||||
const { setColorScheme } = useMantineColorScheme()
|
const { setColorScheme } = useMantineColorScheme()
|
||||||
@@ -17,9 +17,9 @@ export default function ThemeBtn(): JSX.Element {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
setColorScheme(computedColorScheme === 'light' ? 'dark' : 'light')
|
||||||
}
|
}}
|
||||||
variant="default"
|
variant="default"
|
||||||
size="lg"
|
size="lg"
|
||||||
aria-label="Toggle color scheme"
|
aria-label="Toggle color scheme"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import ThemeBtn from './ThemeBtn'
|
||||||
|
|
||||||
|
export default ThemeBtn
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Breadcrumbs, Button } from '@mantine/core'
|
|
||||||
import { Link, Head } from 'tuono'
|
|
||||||
|
|
||||||
import { IconChevronRight, IconBolt } from '@tabler/icons-react'
|
|
||||||
|
|
||||||
interface Breadcrumb {
|
|
||||||
href?: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
interface BreadcrumbsProps {
|
|
||||||
breadcrumbs: Breadcrumb[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function TuonoBreadcrumbs({
|
|
||||||
breadcrumbs = [],
|
|
||||||
}: BreadcrumbsProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Head>
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{`{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "BreadcrumbList",
|
|
||||||
"itemListElement": [
|
|
||||||
{
|
|
||||||
"@type": "ListItem",
|
|
||||||
"position": 1,
|
|
||||||
"name": "Tuono - The React/Rust fullstack framework",
|
|
||||||
"item": "https://tuono.dev"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "ListItem",
|
|
||||||
"position": 2,
|
|
||||||
"name": "Tuono - Documentation",
|
|
||||||
"item": "https://tuono.dev/documentation"
|
|
||||||
}${breadcrumbs.length > 0 ? ',' : ''}
|
|
||||||
${breadcrumbs
|
|
||||||
.map((br, i) =>
|
|
||||||
JSON.stringify({
|
|
||||||
'@type': 'ListItem',
|
|
||||||
position: i + 3,
|
|
||||||
name: br.label,
|
|
||||||
item: br.href ? `https://tuono.dev${br.href}` : undefined,
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.join(',')}]
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</script>
|
|
||||||
</Head>
|
|
||||||
<Breadcrumbs
|
|
||||||
separator={<IconChevronRight size="1.1rem" stroke={1.5} />}
|
|
||||||
mb="md"
|
|
||||||
mt="md"
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
href="/documentation"
|
|
||||||
component={Link}
|
|
||||||
variant="subtle"
|
|
||||||
radius="xl"
|
|
||||||
p={5}
|
|
||||||
>
|
|
||||||
<IconBolt />
|
|
||||||
</Button>
|
|
||||||
{breadcrumbs.map((br) => (
|
|
||||||
<BreadcrumbElement href={br.href} label={br.label} key={br.label} />
|
|
||||||
))}
|
|
||||||
</Breadcrumbs>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BreadcrumbElementProps {
|
|
||||||
href?: string
|
|
||||||
label: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BreadcrumbElement({
|
|
||||||
href,
|
|
||||||
label,
|
|
||||||
}: BreadcrumbElementProps): JSX.Element {
|
|
||||||
if (href) {
|
|
||||||
return (
|
|
||||||
<Button component={Link} href={href} variant="subtle" radius="xl" px={10}>
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button component="span" variant="light" radius="xl">
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import Breadcrumbs, { BreadcrumbElement } from './breadcrumbs'
|
|
||||||
|
|
||||||
export default Breadcrumbs
|
|
||||||
|
|
||||||
export { BreadcrumbElement as Element }
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import EditPage from './edit-page'
|
|
||||||
|
|
||||||
export default EditPage
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Hero from './hero'
|
|
||||||
|
|
||||||
export default Hero
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxProvider from './mdx-provider'
|
|
||||||
|
|
||||||
export default MdxProvider
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxCode from './mdx-code'
|
|
||||||
|
|
||||||
export default MdxCode
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxLink from './mdx-link'
|
|
||||||
|
|
||||||
export default MdxLink
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxPre from './mdx-pre'
|
|
||||||
|
|
||||||
export default MdxPre
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MdxQuote from './mdx-quote'
|
|
||||||
|
|
||||||
export default MdxQuote
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import MdxTitle, { h } from './mdx-title'
|
|
||||||
|
|
||||||
export default MdxTitle
|
|
||||||
|
|
||||||
export { h }
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { Title, type TitleProps } from '@mantine/core'
|
|
||||||
|
|
||||||
export default function MdxTitle(props: TitleProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<Title
|
|
||||||
data-heading={props.children}
|
|
||||||
data-order={props.order}
|
|
||||||
mt={20}
|
|
||||||
{...props}
|
|
||||||
id={String(props.children ?? '')
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll(' ', '-')}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const h =
|
|
||||||
(order: 1 | 2 | 3 | 4 | 5 | 6) =>
|
|
||||||
(props: TitleProps): JSX.Element => <MdxTitle order={order} {...props} />
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import MetaTags from './meta-tags'
|
|
||||||
|
|
||||||
export default MetaTags
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Navbar from './navbar'
|
|
||||||
|
|
||||||
export default Navbar
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import type { JSX } from 'react'
|
|
||||||
import { AppShell, Burger, Button, Flex } from '@mantine/core'
|
|
||||||
import { Link, useRouter } from 'tuono'
|
|
||||||
|
|
||||||
import Actions from './actions'
|
|
||||||
|
|
||||||
interface NavbarProps {
|
|
||||||
opened: boolean
|
|
||||||
toggle: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Navbar({ opened, toggle }: NavbarProps): JSX.Element {
|
|
||||||
const { pathname } = useRouter()
|
|
||||||
return (
|
|
||||||
<AppShell.Header p="sm">
|
|
||||||
<Flex justify="space-between">
|
|
||||||
<Button component={Link} href="/" variant="transparent" p={0} fz={28}>
|
|
||||||
Tuono
|
|
||||||
</Button>
|
|
||||||
<Flex align="center" gap={8}>
|
|
||||||
<Actions />
|
|
||||||
{pathname.startsWith('/documentation') && (
|
|
||||||
<Burger
|
|
||||||
opened={opened}
|
|
||||||
onClick={toggle}
|
|
||||||
hiddenFrom="sm"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
</AppShell.Header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import NavigationButtons from './navigation-buttons'
|
|
||||||
|
|
||||||
export default NavigationButtons
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import Sidebar from './sidebar'
|
|
||||||
|
|
||||||
export default Sidebar
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
.link {
|
|
||||||
border-radius: 8px;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||