Compare commits

...

51 Commits

Author SHA1 Message Date
Greg Johnston
c750f57ddc v0.6.10 2024-04-02 09:39:53 -04:00
Joseph Cruz
cc1f6f0a94 chore(ci): run semver checks on push (#2483) 2024-04-01 20:38:28 -04:00
Greg Johnston
a9034a92b0 fix: handle directives properly in SSR mode (closes #2488) (#2477) 2024-04-01 17:29:30 -04:00
zakstucke
9f1c09e131 feat: add View::on support for CoreComponent::{DynChild, Each} (#2422) 2024-04-01 17:09:05 -04:00
Greg Johnston
b79037b96f fix: correctly handle empty view! {} in hot-reloading code (closes #2421) (#2478) 2024-04-01 16:23:29 -04:00
Greg Johnston
41f3c46830 chore: bump nightly version in examples (#2479) 2024-04-01 15:16:53 -04:00
Greg Johnston
bfac4cba2a chore: cargo fmt 2024-03-31 14:12:33 -04:00
Paolo Barbolini
3e18edb8f9 chore: add repository field to server_fn_macro (#2474) 2024-03-31 14:10:47 -04:00
Joseph Cruz
e926ff24a6 ci: disable semver checks (#2471) 2024-03-30 20:05:20 +00:00
Gunnar Raßmann
d528cbd828 Fix: Environment variables do not overwrite Config.toml options (#2433)
* Fix environment variable parsing

* Fix failing tests

dfgdfgfd

dsf

* Add new test
2024-03-30 00:02:52 +00:00
Alex Lazar
642504f2ba Remove panic for axum ResponseOptions (#2468) 2024-03-29 07:37:12 +00:00
zakstucke
fd2817de26 Allow CDN_PKG_PATH at runtime as well as current build time, preferring it when available. (#2466) 2024-03-28 08:30:54 +00:00
Bart Toersche
73b8c7872e Fix: Small fix for location hash/fragment (#2464) 2024-03-27 06:45:29 +00:00
martin frances
f3d19ca744 Minor: Ran cargo clippy --fix (#2461)
Manually reviewed the changes. All look like reasonable nudges.

A summary :-

In one place removed a redundant call to .clone().

In two places, now using clone_from() which clippy says
**MAY** be an optimisation.
2024-03-23 18:27:31 -07:00
boyswan
0abcc348ca Persist parent span context within resource fetchers (#2456) 2024-03-22 15:51:50 -07:00
Joseph Cruz
572ae5bbdf test(ci): check semver (#2450)
* test(ci): check semver

* chore: simulate change

* fix(ci): add checkout

* fix(ci): version typo

* chore: remove simulated change
2024-03-22 15:51:13 -07:00
martin frances
0b70949118 Bumped base64 to 0.22. (#2457) 2024-03-22 15:07:04 -07:00
martin frances
5819014ccc Chore(ci) bumping tj-actions/changed-files to version 43. (#2454) 2024-03-22 07:23:57 -04:00
Joseph Cruz
630fd4570d fix(ci): trunk command not found (#2453)
* chore: simulate change

* chore: remove print trunk version

* Revert "chore: remove print trunk version"

This reverts commit c203a83b44.

* chore(ci): use jetli/trunk-action

* chore: remove simulated change
2024-03-22 07:23:48 -04:00
Ratul
d1560f9e1f Added missing link for #[server] macro (#2437)
* Added missing link for #[server] macro

Added missing link for #[server] macro

* Removed spurious entry
2024-03-20 14:24:54 -07:00
martin frances
841d7a690a chore: examples/tailwind_axum bumped tailwindcss to 3.4.2. (#2443) 2024-03-19 09:40:24 -07:00
sify21
104c09f3bf register server_fn first to allow for wildcard Route path (#2435)
It's normal to have a `NotFound` page with a wildcard path like this
```
<Routes>
    ...
    <Route path="*any" view=NotFound>
</Routes>
```
In `ssr` mode, most servers do a `first match win` approach, so we
should register server functions before view routes, or else a wildcard
route would block all api requests.

https://discord.com/channels/1031524867910148188/1218508054442545185

Signed-off-by: 司芳源 <sify21@163.com>
2024-03-19 09:37:42 -07:00
Joseph Cruz
ac75999c9f chore(ci): upgrade actions to node 20 (#2444)
* chore(ci): install jq with apt

* chore(ci): install trunk with cargo

* chore(ci): replace toolchain action

* chore(ci): upgrade pnpm cache action

* chore: simulate change

* fix(ci): pnpm cache action typo

* chore: remove simulated change
2024-03-19 09:36:30 -07:00
Richard Laughlin
7ef186f642 For the session_auth_axum example, move the passhash into a separate (#2446)
non-serializable struct.

This prevents it from being returned in the
get_user() API, and prevents it from being unintentionally returned on any
new API the end-user may create on top of this example code.
2024-03-19 09:35:53 -07:00
Joseph Cruz
fda4dba237 build(examples): clean more output (#2420)
* chore(examples): update workspace members

* build(examples): clean e2e crates

* build(examples): clean pkg directories

* chore: remove simulated change comment

* chore: add simulated change

* chore: remove simulated change
2024-03-18 11:58:37 -04:00
Roland Fredenhagen
4e578e335b chore: update attribute-derive (#2438) 2024-03-18 11:39:34 -04:00
Joseph Cruz
97fd8ff6c4 fix(ci): leptos examples fail with bindgen schema error (#2428) 2024-03-13 22:33:54 -04:00
battmdpkq
4faf3fa894 chore: fix types in some comments (#2413)
Signed-off-by: battmdpkq <cmaker@163.com>
2024-03-09 07:38:25 -05:00
Greg Johnston
480d741749 chore: update to gloo-net 0.5 (closes #2411) (#2416) 2024-03-08 15:22:12 -05:00
Álvaro Mondéjar
7928f61401 chore: add lint to disallow prints to stdout (#2404) 2024-03-08 13:18:37 -05:00
Giovanni
2b4f5e0f58 docs: runtime error if setting the same event listener 2x rather than silent failure (#2383)
Delegated event listeners do not support adding more than one event listener of the same type. This can cause confusion if two listeners are added, as one is silently dropped.
2024-03-07 16:49:23 -05:00
Giovanni
943a992570 fix: re-throw errors in delegated event listeners (#2382) 2024-03-07 16:48:21 -05:00
ARSON
372a241d78 feat: allow #[prop(attrs)] on slots (#2396) 2024-03-04 17:34:21 -08:00
Chris Biscardi
c06f6bede2 fix: remove erroneous debug println!()s in islands (#2402) 2024-03-04 06:56:18 -05:00
benwis
3e93a686f4 Fix and release deps 2024-03-03 17:04:34 -08:00
benwis
34cdff4cb3 Update deps in one crate to 0.6.8 2024-03-03 17:02:50 -08:00
John Lewis
530087d77d Add MessagePack codec (#2371)
* feat: added messagepack codec

* fix: deserialize msgpack from bytes, not string
2024-03-03 13:54:23 -08:00
martin frances
4bb43f6207 examples/todomvc - Rename Todos::new() Todos::default(). (#2390) 2024-03-03 13:48:40 -08:00
benwis
9e2fb62857 0.6.8 2024-03-02 18:01:10 -08:00
Ben Wishovich
1da2fff706 Fix missed stuff (#2398) 2024-03-02 17:57:20 -08:00
Greg Johnston
9fd2987447 fix: correctly reset hydration status in islands mode Suspense (closes #2332) (#2393) 2024-03-02 11:57:35 -05:00
zroug
7996f835d0 fix: remove unnecessary trait bound PartialEq from create_owning_memo (#2394) 2024-03-02 07:27:22 -05:00
Greg Johnston
d72b12524e Merge pull request #2395 from leptos-rs/int-ax-doc 2024-03-01 20:08:18 -05:00
Greg Johnston
8e79c5be5c fix: ignore as with other doctests for now 2024-03-01 18:39:55 -05:00
Greg Johnston
de25658c36 Merge pull request #2392 from paul-hansen/fix-ci
fix(ci): "needless borrow" error and example never exiting
2024-03-01 18:37:48 -05:00
Paul Hansen
e2e35a9659 fix(ci) Wait a bit longer for server to start
It took longer than I thought in Github and barely worked, giving it a
bit more of a buffer.
2024-03-01 15:47:59 -06:00
Paul Hansen
bf1ba589c5 fix(ci): Another attempt to fix hanging example 2024-03-01 15:41:22 -06:00
Sam Judelson
f70ebc1289 docs: add note on how to get ResponseOptions (#2380) 2024-03-01 10:47:02 -05:00
Paul Hansen
3cab09e015 fix(ci): error_boundary example never exiting 2024-03-01 09:21:58 -06:00
Paul Hansen
b431315f7c fix(ci): "needless borrow" error 2024-03-01 09:21:58 -06:00
Baptiste
5b40881e77 fix: specify path to wasm_bindgen in island macro (#2387) 2024-03-01 10:15:19 -05:00
121 changed files with 850 additions and 1301 deletions

View File

@@ -29,4 +29,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly-2024-01-29
toolchain: nightly-2024-03-31

View File

@@ -24,4 +24,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly-2024-01-29
toolchain: nightly-2024-03-31

28
.github/workflows/ci-semver.yml vendored Normal file
View File

@@ -0,0 +1,28 @@
name: CI semver
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
get-leptos-changed:
uses: ./.github/workflows/get-leptos-changed.yml
test:
needs: [get-leptos-changed]
if: needs.get-leptos-changed.outputs.leptos_changed == 'true'
name: Run semver check (nightly-2024-03-31)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Sember Checks
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
rust-toolchain: nightly-2024-03-31

View File

@@ -40,4 +40,4 @@ jobs:
with:
directory: ${{ matrix.directory }}
cargo_make_task: "ci"
toolchain: nightly-2024-01-29
toolchain: nightly-2024-03-31

View File

@@ -21,7 +21,7 @@ jobs:
- name: Get example files that changed
id: changed-files
uses: tj-actions/changed-files@v41
uses: tj-actions/changed-files@v43
with:
files: |
examples/**

View File

@@ -17,8 +17,8 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install JQ Tool
uses: mbround18/install-jq@v1
- name: Install jq
run: sudo apt-get install jq
- name: Set Matrix
id: set-matrix

View File

@@ -19,7 +19,7 @@ jobs:
- name: Get source files that changed
id: changed-source
uses: tj-actions/changed-files@v41
uses: tj-actions/changed-files@v43
with:
files: |
integrations/**

View File

@@ -27,11 +27,9 @@ jobs:
- uses: actions/checkout@v4
- name: Setup Rust
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ inputs.toolchain }}
override: true
components: rustfmt
- name: Add wasm32-unknown-unknown
run: rustup target add wasm32-unknown-unknown
@@ -44,6 +42,18 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Install binstall
uses: cargo-bins/cargo-binstall@main
- name: Install wasm-bindgen
run: cargo binstall wasm-bindgen-cli --no-confirm
- name: Install wasm-pack
run: cargo binstall wasm-pack --no-confirm
- name: Install cargo-leptos
run: cargo binstall cargo-leptos --no-confirm
- name: Install Trunk
uses: jetli/trunk-action@v0.4.0
with:
@@ -69,7 +79,7 @@ jobs:
run: |
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
name: Setup pnpm cache
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}

View File

@@ -25,23 +25,23 @@ members = [
exclude = ["benchmarks", "examples"]
[workspace.package]
version = "0.6.7"
version = "0.6.10"
rust-version = "1.75"
[workspace.dependencies]
leptos = { path = "./leptos", version = "0.6.5" }
leptos_dom = { path = "./leptos_dom", version = "0.6.5" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.5" }
leptos_macro = { path = "./leptos_macro", version = "0.6.5" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.5" }
leptos_server = { path = "./leptos_server", version = "0.6.5" }
server_fn = { path = "./server_fn", version = "0.6.5" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.5" }
leptos = { path = "./leptos", version = "0.6.10" }
leptos_dom = { path = "./leptos_dom", version = "0.6.10" }
leptos_hot_reload = { path = "./leptos_hot_reload", version = "0.6.10" }
leptos_macro = { path = "./leptos_macro", version = "0.6.10" }
leptos_reactive = { path = "./leptos_reactive", version = "0.6.10" }
leptos_server = { path = "./leptos_server", version = "0.6.10" }
server_fn = { path = "./server_fn", version = "0.6.10" }
server_fn_macro = { path = "./server_fn_macro", version = "0.6.10" }
server_fn_macro_default = { path = "./server_fn/server_fn_macro_default", version = "0.6" }
leptos_config = { path = "./leptos_config", version = "0.6.5" }
leptos_router = { path = "./router", version = "0.6.5" }
leptos_meta = { path = "./meta", version = "0.6.5" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.5" }
leptos_config = { path = "./leptos_config", version = "0.6.10" }
leptos_router = { path = "./router", version = "0.6.10" }
leptos_meta = { path = "./meta", version = "0.6.10" }
leptos_integration_utils = { path = "./integrations/utils", version = "0.6.10" }
[profile.release]
codegen-units = 1

View File

@@ -3,5 +3,5 @@ alias = "check-all"
[tasks.check-all]
command = "cargo"
args = ["+nightly-2024-01-29", "check-all-features"]
args = ["+nightly-2024-03-31", "check-all-features"]
install_crate = "cargo-all-features"

View File

@@ -8,4 +8,11 @@ args = ["fmt", "--", "--check", "--config-path", "${LEPTOS_PROJECT_DIRECTORY}"]
[tasks.clippy-each-feature]
dependencies = ["install-clippy"]
command = "cargo"
args = ["hack", "clippy", "--all", "--each-feature", "--no-dev-deps"]
args = [
"clippy",
"--all-features",
"--no-deps",
"--",
"-D",
"clippy::print_stdout",
]

View File

@@ -3,5 +3,5 @@ alias = "test-all"
[tasks.test-all]
command = "cargo"
args = ["+nightly-2024-01-29", "test-all-features"]
args = ["+nightly-2024-03-31", "test-all-features"]
install_crate = "cargo-all-features"

View File

@@ -5,6 +5,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CARGO_BUILD_TEST_FLAGS = ""
CARGO_MAKE_WORKSPACE_EMULATION = true
CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
"action-form-error-handling",
"animated_show",
"counter",
"counter_isomorphic",
@@ -12,27 +13,33 @@ CARGO_MAKE_CRATE_WORKSPACE_MEMBERS = [
"counters_stable",
"counter_url_query",
"counter_without_macros",
"directives",
"error_boundary",
"errors_axum",
"fetch",
"hackernews",
"hackernews_axum",
"hackernews_islands_axum",
"hackernews_js_fetch",
"js-framework-benchmark",
"login_with_token_csr_only",
"parent_child",
"portal",
"router",
"server_fns_axum",
"session_auth_axum",
"slots",
"sso_auth_axum",
"ssr_modes",
"ssr_modes_axum",
"suspense_tests",
"tailwind_actix",
"tailwind_csr",
"tailwind_axum",
"tailwind_csr",
"timer",
"todo_app_sqlite",
"todo_app_sqlite_axum",
"todo_app_sqlite_csr",
"todomvc",
]

View File

@@ -15,13 +15,13 @@ clear = true
dependencies = ["check-debug", "check-release"]
[tasks.check-debug]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"
[tasks.check-release]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features", "--release"]
install_crate = "cargo-all-features"

View File

@@ -4,11 +4,13 @@ dependencies = [
"clean-trunk",
"clean-node_modules",
"clean-playwright",
"clean-pkg",
]
[tasks.clean-cargo]
command = "rm"
args = ["-rf", "target"]
script = '''
find . -type d -name target | xargs rm -rf
'''
[tasks.clean-trunk]
script = '''
@@ -27,3 +29,8 @@ fi
script = '''
find . -name playwright-report -name playwright -name test-results | xargs rm -rf
'''
[tasks.clean-pkg]
script = '''
find . -type d -name pkg | xargs rm -rf
'''

View File

@@ -1,11 +1,11 @@
[tasks.build]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["build-all-features"]
install_crate = "cargo-all-features"
[tasks.check]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"

View File

@@ -12,13 +12,13 @@ clear = true
dependencies = ["check-debug", "check-release"]
[tasks.check-debug]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features"]
install_crate = "cargo-all-features"
[tasks.check-release]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features", "--release"]
install_crate = "cargo-all-features"

View File

@@ -6,9 +6,17 @@ extend = [
[tasks.integration-test]
description = "Run integration test with automated start and stop of processes"
env = { SPAWN_CLIENT_PROCESS = "1" }
dependencies = ["start", "wait-one", "test-playwright", "stop"]
run_task = { name = ["start", "wait-test-stop"], parallel = true }
[tasks.wait-one]
[tasks.wait-test-stop]
private = true
dependencies = ["wait-server", "test-playwright", "stop"]
[tasks.wait-server]
script = '''
sleep 1
for run in {1..12}; do
echo "Waiting to ensure server is started..."
sleep 10
done
echo "Times up, running tests"
'''

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -25,7 +25,7 @@ leptos_router = { path = "../../router" }
log = "0.4"
once_cell = "1.18"
gloo-net = { git = "https://github.com/rustwasm/gloo" }
wasm-bindgen = "0.2.87"
wasm-bindgen = "0.2"
serde = { version = "1", features = ["derive"] }
simple_logger = "4.3"
tracing = { version = "0.1", optional = true }

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -15,7 +15,7 @@ log = "0.4"
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.84"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.34"
pretty_assertions = "1.3.0"
rstest = "0.17.0"

View File

@@ -1,4 +1,4 @@
use leptos::{ev, html::*, *};
use leptos::{html::*, *};
/// A simple counter view.
// A component is really just a function call: it runs once to create the DOM and reactive system

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,4 +1,4 @@
use leptos::{For, *};
use leptos::*;
const MANY_COUNTERS: usize = 1000;

View File

@@ -12,7 +12,7 @@ console_log = "1"
console_error_panic_hook = "0.1.7"
[dev-dependencies]
wasm-bindgen = "0.2.87"
wasm-bindgen = "0.2"
wasm-bindgen-test = "0.3.37"
pretty_assertions = "1.4.0"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,5 +1,5 @@
use crate::errors::AppError;
use leptos::{logging::log, Errors, *};
use leptos::{logging::log, *};
#[cfg(feature = "ssr")]
use leptos_axum::ResponseOptions;

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -5,13 +5,13 @@ extend = [
]
[tasks.build]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["build-all-features", "--target", "wasm32-unknown-unknown"]
install_crate = "cargo-all-features"
[tasks.check]
toolchain = "nightly-2024-01-29"
toolchain = "nightly-2024-03-31"
command = "cargo"
args = ["check-all-features", "--target", "wasm32-unknown-unknown"]
install_crate = "cargo-all-features"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,4 +1,4 @@
use leptos::{ev, *};
use leptos::*;
#[component]
pub fn CredentialsForm(

View File

@@ -16,7 +16,7 @@ impl AppState {
credentials: Credentials,
) -> Result<(), CreateUserError> {
let Credentials { email, password } = credentials;
let user_exists = self.users.get(&email).is_some();
let user_exists = self.users.contains_key(&email);
if user_exists {
return Err(CreateUserError::UserExists);
}
@@ -124,6 +124,7 @@ impl EmailAddress {
#[derive(Clone)]
pub struct CurrentUser {
pub email: EmailAddress,
#[allow(dead_code)] // possibly a lint regression, this is used at line 65
pub token: Uuid,
}

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,7 +0,0 @@
CREATE TABLE IF NOT EXISTS todos
(
id INTEGER NOT NULL PRIMARY KEY,
title VARCHAR,
completed BOOLEAN
);

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,5 +1,5 @@
use crate::errors::TodoAppError;
use leptos::{Errors, *};
use leptos::*;
#[cfg(feature = "ssr")]
use leptos_axum::ResponseOptions;

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -6,10 +6,13 @@ use std::collections::HashSet;
pub struct User {
pub id: i64,
pub username: String,
pub password: String,
pub permissions: HashSet<String>,
}
// Explicitly is not Serialize/Deserialize!
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UserPasshash(String);
impl Default for User {
fn default() -> Self {
let permissions = HashSet::new();
@@ -17,7 +20,6 @@ impl Default for User {
Self {
id: -1,
username: "Guest".into(),
password: "".into(),
permissions,
}
}
@@ -25,7 +27,7 @@ impl Default for User {
#[cfg(feature = "ssr")]
pub mod ssr {
pub use super::User;
pub use super::{User, UserPasshash};
pub use axum_session_auth::{
Authentication, HasPermission, SessionSqlitePool,
};
@@ -42,7 +44,10 @@ pub mod ssr {
pub use bcrypt::{hash, verify, DEFAULT_COST};
impl User {
pub async fn get(id: i64, pool: &SqlitePool) -> Option<Self> {
pub async fn get_with_passhash(
id: i64,
pool: &SqlitePool,
) -> Option<(Self, UserPasshash)> {
let sqluser = sqlx::query_as::<_, SqlUser>(
"SELECT * FROM users WHERE id = ?",
)
@@ -63,10 +68,16 @@ pub mod ssr {
Some(sqluser.into_user(Some(sql_user_perms)))
}
pub async fn get_from_username(
pub async fn get(id: i64, pool: &SqlitePool) -> Option<Self> {
User::get_with_passhash(id, pool)
.await
.map(|(user, _)| user)
}
pub async fn get_from_username_with_passhash(
name: String,
pool: &SqlitePool,
) -> Option<Self> {
) -> Option<(Self, UserPasshash)> {
let sqluser = sqlx::query_as::<_, SqlUser>(
"SELECT * FROM users WHERE username = ?",
)
@@ -86,6 +97,15 @@ pub mod ssr {
Some(sqluser.into_user(Some(sql_user_perms)))
}
pub async fn get_from_username(
name: String,
pool: &SqlitePool,
) -> Option<Self> {
User::get_from_username_with_passhash(name, pool)
.await
.map(|(user, _)| user)
}
}
#[derive(sqlx::FromRow, Clone)]
@@ -137,20 +157,22 @@ pub mod ssr {
pub fn into_user(
self,
sql_user_perms: Option<Vec<SqlPermissionTokens>>,
) -> User {
User {
id: self.id,
username: self.username,
password: self.password,
permissions: if let Some(user_perms) = sql_user_perms {
user_perms
.into_iter()
.map(|x| x.token)
.collect::<HashSet<String>>()
} else {
HashSet::<String>::new()
) -> (User, UserPasshash) {
(
User {
id: self.id,
username: self.username,
permissions: if let Some(user_perms) = sql_user_perms {
user_perms
.into_iter()
.map(|x| x.token)
.collect::<HashSet<String>>()
} else {
HashSet::<String>::new()
},
},
}
UserPasshash(self.password),
)
}
}
}
@@ -180,11 +202,12 @@ pub async fn login(
let pool = pool()?;
let auth = auth()?;
let user: User = User::get_from_username(username, &pool)
.await
.ok_or_else(|| ServerFnError::new("User does not exist."))?;
let (user, UserPasshash(expected_passhash)) =
User::get_from_username_with_passhash(username, &pool)
.await
.ok_or_else(|| ServerFnError::new("User does not exist."))?;
match verify(password, &user.password)? {
match verify(password, &expected_passhash)? {
true => {
auth.login_user(user.id);
auth.remember_user(remember.is_some());

View File

@@ -1,5 +1,5 @@
use crate::errors::TodoAppError;
use leptos::{Errors, *};
use leptos::*;
#[cfg(feature = "ssr")]
use leptos_axum::ResponseOptions;

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -8,12 +8,12 @@ use axum::{
Router,
};
use axum_session::{Key, SessionConfig, SessionLayer, SessionStore};
use axum_session_auth::{AuthConfig, AuthSessionLayer, SessionSqlitePool};
use axum_session_auth::{AuthConfig, AuthSessionLayer};
use leptos::{get_configuration, logging::log, provide_context, view};
use leptos_axum::{
generate_route_list, handle_server_fns_with_context, LeptosRoutes,
};
use sqlx::{sqlite::SqlitePoolOptions, SqlitePool};
use sqlx::sqlite::SqlitePoolOptions;
use sso_auth_axum::{
auth::*, fallback::file_and_error_handler, state::AppState,
};

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -16,7 +16,7 @@ leptos_actix = { path = "../../integrations/actix", optional = true }
leptos_router = { path = "../../router" }
log = "0.4"
simple_logger = "4"
wasm-bindgen = "0.2.87"
wasm-bindgen = "0.2"
serde = "1.0.159"
tokio = { version = "1.29", features = ["time", "rt"], optional = true }

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,908 +0,0 @@
{
"name": "leptos-tailwind",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "leptos-tailwind",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"tailwindcss": "^3.3.2"
}
},
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
"integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.18",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz",
"integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==",
"dependencies": {
"@jridgewell/resolve-uri": "3.1.0",
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
"node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.stat": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"engines": {
"node": ">= 8"
}
},
"node_modules/@nodelib/fs.walk": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"engines": {
"node": ">=8"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dependencies": {
"fill-range": "^7.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/camelcase-css": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"engines": {
"node": ">= 6"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
"funding": [
{
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
],
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/chokidar/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"engines": {
"node": ">= 6"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"bin": {
"cssesc": "bin/cssesc"
},
"engines": {
"node": ">=4"
}
},
"node_modules/didyoumean": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="
},
"node_modules/fast-glob": {
"version": "3.2.12",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
"integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.4"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/fast-glob/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fastq": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
"dependencies": {
"reusify": "^1.0.4"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dependencies": {
"is-glob": "^4.0.3"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-core-module": {
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/jiti": {
"version": "1.18.2",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz",
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
"bin": {
"jiti": "bin/jiti.js"
}
},
"node_modules/lilconfig": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"engines": {
"node": ">= 8"
}
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dependencies": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dependencies": {
"any-promise": "^1.0.0",
"object-assign": "^4.0.1",
"thenify-all": "^1.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-hash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"engines": {
"node": ">= 6"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
},
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pify": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/pirates": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
"integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
"engines": {
"node": ">= 6"
}
},
"node_modules/postcss": {
"version": "8.4.23",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.23.tgz",
"integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.6",
"picocolors": "^1.0.0",
"source-map-js": "^1.0.2"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/postcss-import": {
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dependencies": {
"postcss-value-parser": "^4.0.0",
"read-cache": "^1.0.0",
"resolve": "^1.1.7"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
"postcss": "^8.0.0"
}
},
"node_modules/postcss-js": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dependencies": {
"camelcase-css": "^2.0.1"
},
"engines": {
"node": "^12 || ^14 || >= 16"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.4.21"
}
},
"node_modules/postcss-load-config": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz",
"integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==",
"dependencies": {
"lilconfig": "^2.0.5",
"yaml": "^2.1.1"
},
"engines": {
"node": ">= 14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": ">=8.0.9",
"ts-node": ">=9.0.0"
},
"peerDependenciesMeta": {
"postcss": {
"optional": true
},
"ts-node": {
"optional": true
}
}
},
"node_modules/postcss-nested": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz",
"integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==",
"dependencies": {
"postcss-selector-parser": "^6.0.11"
},
"engines": {
"node": ">=12.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
"peerDependencies": {
"postcss": "^8.2.14"
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/postcss-value-parser": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dependencies": {
"pify": "^2.3.0"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/resolve": {
"version": "1.22.2",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
"integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
"dependencies": {
"is-core-module": "^2.11.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"queue-microtask": "^1.2.2"
}
},
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/sucrase": {
"version": "3.32.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz",
"integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0",
"glob": "7.1.6",
"lines-and-columns": "^1.1.6",
"mz": "^2.7.0",
"pirates": "^4.0.1",
"ts-interface-checker": "^0.1.9"
},
"bin": {
"sucrase": "bin/sucrase",
"sucrase-node": "bin/sucrase-node"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tailwindcss": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz",
"integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
"chokidar": "^3.5.3",
"didyoumean": "^1.2.2",
"dlv": "^1.1.3",
"fast-glob": "^3.2.12",
"glob-parent": "^6.0.2",
"is-glob": "^4.0.3",
"jiti": "^1.18.2",
"lilconfig": "^2.1.0",
"micromatch": "^4.0.5",
"normalize-path": "^3.0.0",
"object-hash": "^3.0.0",
"picocolors": "^1.0.0",
"postcss": "^8.4.23",
"postcss-import": "^15.1.0",
"postcss-js": "^4.0.1",
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
"bin": {
"tailwind": "lib/cli.js",
"tailwindcss": "lib/cli.js"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dependencies": {
"any-promise": "^1.0.0"
}
},
"node_modules/thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/yaml": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
"engines": {
"node": ">= 14"
}
}
}
}

View File

@@ -4,13 +4,13 @@
"description": "<picture>\r <source srcset=\"https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_Solid_White.svg\" media=\"(prefers-color-scheme: dark)\">\r <img src=\"https://raw.githubusercontent.com/leptos-rs/leptos/main/docs/logos/Leptos_logo_RGB.svg\" alt=\"Leptos Logo\">\r </picture>",
"main": "index.js",
"scripts": {
"build":"npx tailwindcss -i ./input.css -o ./style/output.css",
"watch":"npx tailwindcss -i ./input.css -o ./style/output.css --watch"
"build": "npx tailwindcss -i ./input.css -o ./style/output.css",
"watch": "npx tailwindcss -i ./input.css -o ./style/output.css --watch"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"tailwindcss": "^3.3.2"
"tailwindcss": "^3.4.1"
}
}
}

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,5 +1,5 @@
use crate::errors::TodoAppError;
use leptos::{Errors, *};
use leptos::*;
#[cfg(feature = "ssr")]
use leptos_axum::ResponseOptions;

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -1,5 +1,5 @@
use crate::errors::TodoAppError;
use leptos::{Errors, *};
use leptos::*;
#[cfg(feature = "ssr")]
use leptos_axum::ResponseOptions;

View File

@@ -1,2 +1,2 @@
[toolchain]
channel = "nightly-2024-01-29"
channel = "nightly-2024-03-31"

View File

@@ -7,9 +7,8 @@ pub struct Todos(pub Vec<Todo>);
const STORAGE_KEY: &str = "todos-leptos";
// Basic operations to manipulate the todo list: nothing really interesting here
impl Todos {
pub fn new() -> Self {
impl Default for Todos {
fn default() -> Self {
let starting_todos =
window()
.local_storage()
@@ -23,7 +22,10 @@ impl Todos {
.unwrap_or_default();
Self(starting_todos)
}
}
// Basic operations to manipulate the todo list: nothing really interesting here
impl Todos {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
@@ -86,12 +88,6 @@ impl Todos {
}
}
impl Default for Todos {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
pub struct Todo {
pub id: Uuid,
@@ -136,7 +132,7 @@ const ENTER_KEY: u32 = 13;
#[component]
pub fn TodoMVC() -> impl IntoView {
// The `todos` are a signal, since we need to reactively update the list
let (todos, set_todos) = create_signal(Todos::new());
let (todos, set_todos) = create_signal(Todos::default());
// We provide a context that each <Todo/> component can use to update the list
// Here, I'm just passing the `WriteSignal`; a <Todo/> doesn't need to read the whole list

View File

@@ -1210,6 +1210,15 @@ where
IV: IntoView + 'static,
{
let mut router = self;
// register server functions first to allow for wildcard route in Leptos's Router
for (path, _) in server_fn::actix::server_fn_paths() {
let additional_context = additional_context.clone();
let handler = handle_server_fns_with_context(additional_context);
router = router.route(path, handler);
}
// register routes defined in Leptos's Router
for listing in paths.iter() {
let path = listing.path();
let mode = listing.mode();
@@ -1272,13 +1281,6 @@ where
}
}
// register server functions
for (path, _) in server_fn::actix::server_fn_paths() {
let additional_context = additional_context.clone();
let handler = handle_server_fns_with_context(additional_context);
router = router.route(path, handler);
}
router
}
}
@@ -1311,6 +1313,15 @@ impl LeptosRoutes for &mut ServiceConfig {
IV: IntoView + 'static,
{
let mut router = self;
// register server functions first to allow for wildcard route in Leptos's Router
for (path, _) in server_fn::actix::server_fn_paths() {
let additional_context = additional_context.clone();
let handler = handle_server_fns_with_context(additional_context);
router = router.route(path, handler);
}
// register routes defined in Leptos's Router
for listing in paths.iter() {
let path = listing.path();
let mode = listing.mode();
@@ -1355,13 +1366,6 @@ impl LeptosRoutes for &mut ServiceConfig {
}
}
// register server functions
for (path, _) in server_fn::actix::server_fn_paths() {
let additional_context = additional_context.clone();
let handler = handle_server_fns_with_context(additional_context);
router = router.route(path, handler);
}
router
}
}

View File

@@ -79,6 +79,20 @@ impl ResponseParts {
}
/// Allows you to override details of the HTTP response like the status code and add Headers/Cookies.
///
/// `ResponseOptions` is provided via context when you use most of the handlers provided in this
/// crate, including [`.leptos_routes`](LeptosRoutes::leptos_routes),
/// [`.leptos_routes_with_context`](LeptosRoutes::leptos_routes_with_context), [`handle_server_fns`], etc.
/// You can find the full set of provided context types in each handler function.
///
/// If you provide your own handler, you will need to provide `ResponseOptions` via context
/// yourself if you want to access it via context.
/// ```rust,ignore
/// #[server]
/// pub async fn get_opts() -> Result<(), ServerFnError> {
/// let opts = expect_context::<leptos_axum::ResponseOptions>();
/// Ok(())
/// }
#[derive(Debug, Clone, Default)]
pub struct ResponseOptions(pub Arc<RwLock<ResponseParts>>);
@@ -288,14 +302,8 @@ async fn handle_server_fns_inner(
// actually run the server fn
let mut res = service.run(req).await;
// update response as needed
let res_options = expect_context::<ResponseOptions>().0;
let res_options_inner = res_options.read();
let (status, mut res_headers) =
(res_options_inner.status, res_options_inner.headers.clone());
// it it accepts text/html (i.e., is a plain form post) and doesn't already have a
// Location set, then redirect to to Referer
// if it accepts text/html (i.e., is a plain form post) and doesn't already have a
// Location set, then redirect to Referer
if accepts_html {
if let Some(referrer) = referrer {
let has_location = res.headers().get(LOCATION).is_some();
@@ -306,11 +314,22 @@ async fn handle_server_fns_inner(
}
}
// apply status code and headers if used changed them
if let Some(status) = status {
*res.status_mut() = status;
// update response as needed
if let Some(res_options) = use_context::<ResponseOptions>() {
let res_options_inner = res_options.0.read();
let (status, mut res_headers) = (
res_options_inner.status,
res_options_inner.headers.clone(),
);
// apply status code and headers if used changed them
if let Some(status) = status {
*res.status_mut() = status;
}
res.headers_mut().extend(res_headers.drain());
} else {
eprintln!("Failed to find ResponseOptions for {path}");
}
res.headers_mut().extend(res_headers.drain());
// clean up the scope
runtime.dispose();
@@ -1061,7 +1080,7 @@ where
headers.extend(res_headers.drain());
// This one doesn't use generate_response(), so we need to do this seperately
// This one doesn't use generate_response(), so we need to do this separately
if !headers.contains_key(header::CONTENT_TYPE) {
// Set the Content Type headers on all responses. This makes Firefox show the page source
// without complaining
@@ -1610,6 +1629,30 @@ where
let mut router = self;
// register server functions first to allow for wildcard router path
for (path, method) in server_fn::axum::server_fn_paths() {
let cx_with_state = cx_with_state.clone();
let handler = move |req: Request<Body>| async move {
handle_server_fns_with_context(cx_with_state, req).await
};
router = router.route(
path,
match method {
Method::GET => get(handler),
Method::POST => post(handler),
Method::PUT => put(handler),
Method::DELETE => delete(handler),
Method::PATCH => patch(handler),
_ => {
panic!(
"Unsupported server function HTTP method: \
{method:?}"
);
}
},
);
}
// register router paths
for listing in paths.iter() {
let path = listing.path();
@@ -1708,30 +1751,6 @@ where
}
}
// register server functions
for (path, method) in server_fn::axum::server_fn_paths() {
let cx_with_state = cx_with_state.clone();
let handler = move |req: Request<Body>| async move {
handle_server_fns_with_context(cx_with_state, req).await
};
router = router.route(
path,
match method {
Method::GET => get(handler),
Method::POST => post(handler),
Method::PUT => put(handler),
Method::DELETE => delete(handler),
Method::PATCH => patch(handler),
_ => {
panic!(
"Unsupported server function HTTP method: \
{method:?}"
);
}
},
);
}
router
}

View File

@@ -56,9 +56,14 @@ pub fn html_parts_separated(
options: &LeptosOptions,
meta: Option<&MetaContext>,
) -> (String, &'static str) {
let pkg_path = option_env!("CDN_PKG_PATH")
.map(Cow::from)
.unwrap_or_else(|| format!("/{}", options.site_pkg_dir).into());
// First check runtime env, then build time, then default:
let pkg_path = match std::env::var("CDN_PKG_PATH").ok().map(Cow::from) {
Some(path) => path,
None => match option_env!("CDN_PKG_PATH").map(Cow::from) {
Some(path) => path,
None => format!("/{}", options.site_pkg_dir).into(),
},
};
let output_name = &options.output_name;
let nonce = use_nonce();
let nonce = nonce
@@ -104,7 +109,7 @@ pub fn html_parts_separated(
"() => mod.hydrate()"
};
let (js_hash, wasm_hash, css_hash) = get_hashes(&options);
let (js_hash, wasm_hash, css_hash) = get_hashes(options);
let head = head.replace(
&format!("{output_name}.css"),
@@ -150,7 +155,7 @@ fn get_hashes(options: &LeptosOptions) -> (String, String, String) {
("css".to_string(), "".to_string()),
]);
if options.frontend_files_content_hashes {
if options.hash_files {
let hash_path = env::current_exe()
.map(|path| {
path.parent().map(|p| p.to_path_buf()).unwrap_or_default()

View File

@@ -16,13 +16,18 @@ leptos_macro = { workspace = true }
leptos_reactive = { workspace = true }
leptos_server = { workspace = true }
leptos_config = { workspace = true }
leptos-spin-macro = { version="0.1", optional = true}
leptos-spin-macro = { version = "0.1", optional = true }
tracing = "0.1"
typed-builder = "0.18"
typed-builder-macro = "0.18"
serde = { version = "1", optional = true }
serde_json = { version = "1", optional = true }
server_fn = { workspace = true, features = ["form-redirects", "browser", "url", "cbor"] }
server_fn = { workspace = true, features = [
"form-redirects",
"browser",
"url",
"cbor",
] }
web-sys = { version = "0.3.63", features = [
"ShadowRoot",
"ShadowRootInit",
@@ -68,10 +73,7 @@ miniserde = ["leptos_reactive/miniserde"]
rkyv = ["leptos_reactive/rkyv"]
tracing = ["leptos_macro/tracing"]
nonce = ["leptos_dom/nonce"]
spin = [
"leptos_reactive/spin",
"leptos-spin-macro"
]
spin = ["leptos_reactive/spin", "leptos-spin-macro"]
experimental-islands = [
"leptos_dom/experimental-islands",
"leptos_macro/experimental-islands",
@@ -81,7 +83,7 @@ experimental-islands = [
]
trace-component-props = [
"leptos_dom/trace-component-props",
"leptos_macro/trace-component-props"
"leptos_macro/trace-component-props",
]
[package.metadata.cargo-all-features]
@@ -92,9 +94,10 @@ denylist = [
"rustls",
"default-tls",
"wasm-bindgen",
"rkyv", # was causing clippy issues on nightly
"trace-component-props",
"spin",
"experimental-islands"
"experimental-islands",
]
skip_feature_sets = [
[

View File

@@ -1,10 +1,7 @@
use crate::Children;
use leptos_dom::{Errors, HydrationCtx, IntoView};
use leptos_macro::{component, view};
use leptos_reactive::{
create_rw_signal, provide_context, run_as_child, signal_prelude::*,
RwSignal,
};
use leptos_reactive::{provide_context, run_as_child, signal_prelude::*};
/// When you render a `Result<_, _>` in your view, in the `Err` case it will
/// render nothing, and search up through the view tree for an `<ErrorBoundary/>`.

View File

@@ -1,6 +1,6 @@
use leptos::{component, ChildrenFn, ViewFn};
use leptos_dom::IntoView;
use leptos_reactive::{create_memo, signal_prelude::*};
use leptos_reactive::signal_prelude::*;
/// A component that will show its children when the `when` condition is `true`,
/// and show the fallback when it is `false`, without rerendering every time

View File

@@ -173,6 +173,9 @@ where
runtime,
);
#[cfg(feature = "experimental-islands")]
let prev_no_hydrate =
SharedContext::no_hydrate();
#[cfg(feature = "experimental-islands")]
{
SharedContext::set_no_hydrate(
@@ -180,7 +183,7 @@ where
);
}
with_owner(owner, {
let rendered = with_owner(owner, {
move || {
HydrationCtx::continue_from(
current_id,
@@ -194,7 +197,15 @@ where
.render_to_string()
.to_string()
}
})
});
#[cfg(feature = "experimental-islands")]
SharedContext::set_no_hydrate(
prev_no_hydrate,
);
#[allow(clippy::let_and_return)]
rendered
}
},
// in-order streaming
@@ -205,6 +216,9 @@ where
runtime,
);
#[cfg(feature = "experimental-islands")]
let prev_no_hydrate =
SharedContext::no_hydrate();
#[cfg(feature = "experimental-islands")]
{
SharedContext::set_no_hydrate(
@@ -212,7 +226,7 @@ where
);
}
with_owner(owner, {
let rendered = with_owner(owner, {
move || {
HydrationCtx::continue_from(
current_id,
@@ -225,7 +239,15 @@ where
.into_view()
.into_stream_chunks()
}
})
});
#[cfg(feature = "experimental-islands")]
SharedContext::set_no_hydrate(
prev_no_hydrate,
);
#[allow(clippy::let_and_return)]
rendered
}
},
);

View File

@@ -206,5 +206,3 @@ fn ssr_option() {
runtime.dispose();
}
// TODO: remove simulated change

View File

@@ -10,7 +10,7 @@ readme = "../README.md"
rust-version.workspace = true
[dependencies]
config = { version = "0.14", default-features = false, features = ["toml"] }
config = { version = "0.14", default-features = false, features = ["toml", "convert-case"] }
regex = "1.7.0"
serde = { version = "1.0.151", features = ["derive"] }
thiserror = "1.0.38"
@@ -19,3 +19,4 @@ typed-builder = "0.18"
[dev-dependencies]
tokio = { version = "1", features = ["rt", "macros"] }
tempfile = "3"
temp-env = { version = "0.3.6", features = ["async_closure"] }

View File

@@ -3,12 +3,9 @@
pub mod errors;
use crate::errors::LeptosConfigError;
use config::{Config, File, FileFormat};
use config::{Case, Config, File, FileFormat};
use regex::Regex;
use std::{
convert::TryFrom, env::VarError, fs, net::SocketAddr, path::Path,
str::FromStr,
};
use std::{env::VarError, fs, net::SocketAddr, path::Path, str::FromStr};
use typed_builder::TypedBuilder;
/// A Struct to allow us to parse LeptosOptions from the file. Not really needed, most interactions should
@@ -76,9 +73,9 @@ pub struct LeptosOptions {
pub hash_file: String,
/// If true, hashes will be generated for all files in the site_root and added to their file names.
/// Defaults to `true`.
#[builder(default = default_frontend_files_content_hashes())]
#[serde(default = "default_frontend_files_content_hashes")]
pub frontend_files_content_hashes: bool,
#[builder(default = default_hash_files())]
#[serde(default = "default_hash_files")]
pub hash_files: bool,
}
impl LeptosOptions {
@@ -118,14 +115,7 @@ impl LeptosOptions {
)?,
not_found_path: env_w_default("LEPTOS_NOT_FOUND_PATH", "/404")?,
hash_file: env_w_default("LEPTOS_HASH_FILE_NAME", "hash.txt")?,
frontend_files_content_hashes: env_w_default(
"LEPTOS_FRONTEND_FILES_CONTENT_HASHES",
"ON",
)?
.to_uppercase()
.replace("ON", "true")
.replace("OFF", "false")
.parse()?,
hash_files: env_w_default("LEPTOS_HASH_FILES", "false")?.parse()?,
})
}
}
@@ -168,8 +158,8 @@ fn default_hash_file_name() -> String {
"hash.txt".to_string()
}
fn default_frontend_files_content_hashes() -> bool {
true
fn default_hash_files() -> bool {
false
}
fn env_wo_default(key: &str) -> Result<Option<String>, LeptosConfigError> {
@@ -307,7 +297,9 @@ impl TryFrom<String> for ReloadWSProtocol {
/// Loads [LeptosOptions] from a Cargo.toml text content with layered overrides.
/// If an env var is specified, like `LEPTOS_ENV`, it will override a setting in the file.
pub fn get_config_from_str(text: &str) -> Result<ConfFile, LeptosConfigError> {
pub fn get_config_from_str(
text: &str,
) -> Result<LeptosOptions, LeptosConfigError> {
let re: Regex = Regex::new(r"(?m)^\[package.metadata.leptos\]").unwrap();
let re_workspace: Regex =
Regex::new(r"(?m)^\[\[workspace.metadata.leptos\]\]").unwrap();
@@ -331,14 +323,18 @@ pub fn get_config_from_str(text: &str) -> Result<ConfFile, LeptosConfigError> {
// so that serde error messages have right line number
let newlines = text[..start].matches('\n').count();
let input = "\n".repeat(newlines) + &text[start..];
let toml = input.replace(metadata_name, "[leptos-options]");
// so the settings will be interpreted as root level settings
let toml = input.replace(metadata_name, "");
let settings = Config::builder()
// Read the "default" configuration file
.add_source(File::from_str(&toml, FileFormat::Toml))
// Layer on the environment-specific values.
// Add in settings from environment variables (with a prefix of LEPTOS and '_' as separator)
// Add in settings from environment variables (with a prefix of LEPTOS)
// E.g. `LEPTOS_RELOAD_PORT=5001 would set `LeptosOptions.reload_port`
.add_source(config::Environment::with_prefix("LEPTOS").separator("_"))
.add_source(
config::Environment::with_prefix("LEPTOS")
.convert_case(Case::Kebab),
)
.build()?;
settings
@@ -368,7 +364,8 @@ pub async fn get_config_from_file<P: AsRef<Path>>(
) -> Result<ConfFile, LeptosConfigError> {
let text = fs::read_to_string(path)
.map_err(|_| LeptosConfigError::ConfigNotFound)?;
get_config_from_str(&text)
let leptos_options = get_config_from_str(&text)?;
Ok(ConfFile { leptos_options })
}
/// Loads [LeptosOptions] from environment variables or rely on the defaults

View File

@@ -30,44 +30,53 @@ fn ws_from_str_test() {
#[test]
fn env_w_default_test() {
std::env::set_var("LEPTOS_CONFIG_ENV_TEST", "custom");
assert_eq!(
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
String::from("custom")
);
std::env::remove_var("LEPTOS_CONFIG_ENV_TEST");
assert_eq!(
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
String::from("default")
);
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
assert_eq!(
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
String::from("custom")
);
});
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
assert_eq!(
env_w_default("LEPTOS_CONFIG_ENV_TEST", "default").unwrap(),
String::from("default")
);
});
}
#[test]
fn env_wo_default_test() {
std::env::set_var("LEPTOS_CONFIG_ENV_TEST", "custom");
assert_eq!(
env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(),
Some(String::from("custom"))
);
std::env::remove_var("LEPTOS_CONFIG_ENV_TEST");
assert_eq!(env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(), None);
_ = temp_env::with_var("LEPTOS_CONFIG_ENV_TEST", Some("custom"), || {
assert_eq!(
env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(),
Some(String::from("custom"))
);
});
_ = temp_env::with_var_unset("LEPTOS_CONFIG_ENV_TEST", || {
assert_eq!(env_wo_default("LEPTOS_CONFIG_ENV_TEST").unwrap(), None);
});
}
#[test]
fn try_from_env_test() {
// Test config values from environment variables
std::env::set_var("LEPTOS_OUTPUT_NAME", "app_test");
std::env::set_var("LEPTOS_SITE_ROOT", "my_target/site");
std::env::set_var("LEPTOS_SITE_PKG_DIR", "my_pkg");
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
std::env::set_var("LEPTOS_ENV", "PROD");
std::env::set_var("LEPTOS_RELOAD_WS_PROTOCOL", "WSS");
let config = temp_env::with_vars(
[
("LEPTOS_OUTPUT_NAME", Some("app_test")),
("LEPTOS_SITE_ROOT", Some("my_target/site")),
("LEPTOS_SITE_PKG_DIR", Some("my_pkg")),
("LEPTOS_SITE_ADDR", Some("0.0.0.0:80")),
("LEPTOS_RELOAD_PORT", Some("8080")),
("LEPTOS_RELOAD_EXTERNAL_PORT", Some("8080")),
("LEPTOS_ENV", Some("PROD")),
("LEPTOS_RELOAD_WS_PROTOCOL", Some("WSS")),
],
|| LeptosOptions::try_from_env().unwrap(),
);
let config = LeptosOptions::try_from_env().unwrap();
assert_eq!(config.output_name, "app_test");
assert_eq!(config.site_root, "my_target/site");
assert_eq!(config.site_pkg_dir, "my_pkg");
assert_eq!(

View File

@@ -23,13 +23,7 @@ env = "PROD"
const CARGO_TOML_CONTENT_ERR: &str = r#"\
[package.metadata.leptos]
_output-name = "app-test"
_site-root = "my_target/site"
_site-pkg-dir = "my_pkg"
_site-addr = "0.0.0.0:80"
_reload-port = "8080"
_reload-external-port = "8080"
_env = "PROD"
- invalid toml -
"#;
#[tokio::test]
@@ -43,10 +37,23 @@ async fn get_configuration_from_file_ok() {
let path: &Path = cargo_tmp.as_ref();
let path_s = path.to_string_lossy().to_string();
let config = get_configuration(Some(&path_s))
.await
.unwrap()
.leptos_options;
let config = temp_env::async_with_vars(
[
("LEPTOS_OUTPUT_NAME", None::<&str>),
("LEPTOS_SITE_ROOT", None::<&str>),
("LEPTOS_SITE_PKG_DIR", None::<&str>),
("LEPTOS_SITE_ADDR", None::<&str>),
("LEPTOS_RELOAD_PORT", None::<&str>),
("LEPTOS_RELOAD_EXTERNAL_PORT", None::<&str>),
],
async {
get_configuration(Some(&path_s))
.await
.unwrap()
.leptos_options
},
)
.await;
assert_eq!(config.output_name, "app-test");
assert_eq!(config.site_root, "my_target/site");
@@ -91,10 +98,23 @@ async fn get_config_from_file_ok() {
write!(output, "{CARGO_TOML_CONTENT_OK}").unwrap();
}
let config = get_config_from_file(&cargo_tmp)
.await
.unwrap()
.leptos_options;
let config = temp_env::async_with_vars(
[
("LEPTOS_OUTPUT_NAME", None::<&str>),
("LEPTOS_SITE_ROOT", None::<&str>),
("LEPTOS_SITE_PKG_DIR", None::<&str>),
("LEPTOS_SITE_ADDR", None::<&str>),
("LEPTOS_RELOAD_PORT", None::<&str>),
("LEPTOS_RELOAD_EXTERNAL_PORT", None::<&str>),
],
async {
get_config_from_file(&cargo_tmp)
.await
.unwrap()
.leptos_options
},
)
.await;
assert_eq!(config.output_name, "app-test");
assert_eq!(config.site_root, "my_target/site");
@@ -129,9 +149,18 @@ async fn get_config_from_file_empty() {
#[test]
fn get_config_from_str_content() {
let config = get_config_from_str(CARGO_TOML_CONTENT_OK)
.unwrap()
.leptos_options;
let config = temp_env::with_vars_unset(
[
"LEPTOS_OUTPUT_NAME",
"LEPTOS_SITE_ROOT",
"LEPTOS_SITE_PKG_DIR",
"LEPTOS_SITE_ADDR",
"LEPTOS_RELOAD_PORT",
"LEPTOS_RELOAD_EXTERNAL_PORT",
],
|| get_config_from_str(CARGO_TOML_CONTENT_OK).unwrap(),
);
assert_eq!(config.output_name, "app-test");
assert_eq!(config.site_root, "my_target/site");
assert_eq!(config.site_pkg_dir, "my_pkg");
@@ -146,16 +175,20 @@ fn get_config_from_str_content() {
#[tokio::test]
async fn get_config_from_env() {
// Test config values from environment variables
std::env::set_var("LEPTOS_OUTPUT_NAME", "app-test");
std::env::set_var("LEPTOS_SITE_ROOT", "my_target/site");
std::env::set_var("LEPTOS_SITE_PKG_DIR", "my_pkg");
std::env::set_var("LEPTOS_SITE_ADDR", "0.0.0.0:80");
std::env::set_var("LEPTOS_RELOAD_PORT", "8080");
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "8080");
let config = temp_env::async_with_vars(
[
("LEPTOS_OUTPUT_NAME", Some("app-test")),
("LEPTOS_SITE_ROOT", Some("my_target/site")),
("LEPTOS_SITE_PKG_DIR", Some("my_pkg")),
("LEPTOS_SITE_ADDR", Some("0.0.0.0:80")),
("LEPTOS_RELOAD_PORT", Some("8080")),
("LEPTOS_RELOAD_EXTERNAL_PORT", Some("8080")),
],
async { get_configuration(None).await.unwrap().leptos_options },
)
.await;
let config = get_configuration(None).await.unwrap().leptos_options;
assert_eq!(config.output_name, "app-test");
assert_eq!(config.site_root, "my_target/site");
assert_eq!(config.site_pkg_dir, "my_pkg");
assert_eq!(
@@ -166,13 +199,19 @@ async fn get_config_from_env() {
assert_eq!(config.reload_external_port, Some(8080));
// Test default config values
std::env::remove_var("LEPTOS_SITE_ROOT");
std::env::remove_var("LEPTOS_SITE_PKG_DIR");
std::env::remove_var("LEPTOS_SITE_ADDR");
std::env::remove_var("LEPTOS_RELOAD_PORT");
std::env::set_var("LEPTOS_RELOAD_EXTERNAL_PORT", "443");
let config = temp_env::async_with_vars(
[
("LEPTOS_OUTPUT_NAME", None::<&str>),
("LEPTOS_SITE_ROOT", None::<&str>),
("LEPTOS_SITE_PKG_DIR", None::<&str>),
("LEPTOS_SITE_ADDR", None::<&str>),
("LEPTOS_RELOAD_PORT", None::<&str>),
("LEPTOS_RELOAD_EXTERNAL_PORT", None::<&str>),
],
async { get_configuration(None).await.unwrap().leptos_options },
)
.await;
let config = get_configuration(None).await.unwrap().leptos_options;
assert_eq!(config.site_root, "target/site");
assert_eq!(config.site_pkg_dir, "pkg");
assert_eq!(
@@ -180,7 +219,7 @@ async fn get_config_from_env() {
SocketAddr::from_str("127.0.0.1:3000").unwrap()
);
assert_eq!(config.reload_port, 3001);
assert_eq!(config.reload_external_port, Some(443));
assert_eq!(config.reload_external_port, None);
}
#[test]
@@ -197,3 +236,52 @@ fn leptos_options_builder_default() {
assert_eq!(conf.reload_port, 3001);
assert_eq!(conf.reload_external_port, None);
}
#[test]
fn environment_variable_override() {
// first check without variables set
let config = temp_env::with_vars_unset(
[
"LEPTOS_OUTPUT_NAME",
"LEPTOS_SITE_ROOT",
"LEPTOS_SITE_PKG_DIR",
"LEPTOS_SITE_ADDR",
"LEPTOS_RELOAD_PORT",
"LEPTOS_RELOAD_EXTERNAL_PORT",
],
|| get_config_from_str(CARGO_TOML_CONTENT_OK).unwrap(),
);
assert_eq!(config.output_name, "app-test");
assert_eq!(config.site_root, "my_target/site");
assert_eq!(config.site_pkg_dir, "my_pkg");
assert_eq!(
config.site_addr,
SocketAddr::from_str("0.0.0.0:80").unwrap()
);
assert_eq!(config.reload_port, 8080);
assert_eq!(config.reload_external_port, Some(8080));
// check the override
let config = temp_env::with_vars(
[
("LEPTOS_OUTPUT_NAME", Some("app-test2")),
("LEPTOS_SITE_ROOT", Some("my_target/site2")),
("LEPTOS_SITE_PKG_DIR", Some("my_pkg2")),
("LEPTOS_SITE_ADDR", Some("0.0.0.0:82")),
("LEPTOS_RELOAD_PORT", Some("8082")),
("LEPTOS_RELOAD_EXTERNAL_PORT", Some("8082")),
],
|| get_config_from_str(CARGO_TOML_CONTENT_OK).unwrap(),
);
assert_eq!(config.output_name, "app-test2");
assert_eq!(config.site_root, "my_target/site2");
assert_eq!(config.site_pkg_dir, "my_pkg2");
assert_eq!(
config.site_addr,
SocketAddr::from_str("0.0.0.0:82").unwrap()
);
assert_eq!(config.reload_port, 8082);
assert_eq!(config.reload_external_port, Some(8082));
}

View File

@@ -10,7 +10,7 @@ rust-version.workspace = true
[dependencies]
async-recursion = "1"
base64 = { version = "0.21", optional = true }
base64 = { version = "0.22", optional = true }
cfg-if = "1"
drain_filter_polyfill = "0.1"
futures = "0.3"

View File

@@ -516,8 +516,10 @@ where
}
}
#[cfg(all(target_arch = "wasm32", feature = "web"))]
struct HashRun<T>(T);
#[cfg(all(target_arch = "wasm32", feature = "web"))]
impl<T> fmt::Debug for HashRun<T> {
#[inline]
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
@@ -1065,7 +1067,7 @@ fn unpack_moves(diff: &Diff) -> (Vec<DiffOpMove>, Vec<DiffOpAdd>) {
// ]
// );
// // Now we're going to to the same as above, just with more items
// // Now we're going to do the same as above, just with more items
// //
// // A = 1
// // B = 2, 3

View File

@@ -1,6 +1,6 @@
use crate::{HydrationCtx, IntoView};
use cfg_if::cfg_if;
use leptos_reactive::{signal_prelude::*, use_context, RwSignal};
use leptos_reactive::{signal_prelude::*, use_context};
use server_fn::error::Error;
use std::{borrow::Cow, collections::HashMap};

View File

@@ -70,6 +70,12 @@ pub fn add_event_listener<E>(
let cb = Closure::wrap(cb as Box<dyn FnMut(E)>).into_js_value();
let key = intern(&key);
debug_assert_eq!(
Ok(false),
js_sys::Reflect::has(target, &JsValue::from_str(&key)),
"Error while adding {key} event listener, a listener of type {key} \
already present."
);
_ = js_sys::Reflect::set(target, &JsValue::from_str(&key), &cb);
add_delegated_event_listener(&key, event_name, options);
}
@@ -150,7 +156,10 @@ pub(crate) fn add_delegated_event_listener(
if !maybe_handler.is_undefined() {
let f = maybe_handler
.unchecked_ref::<js_sys::Function>();
let _ = f.call1(&node, &ev);
if let Err(e) = f.call1(&node, &ev) {
wasm_bindgen::throw_val(e);
}
if ev.cancel_bubble() {
return;

View File

@@ -39,7 +39,13 @@ pub fn location_hash() -> Option<String> {
if is_server() {
None
} else {
location().hash().ok().map(|hash| hash.replace('#', ""))
location()
.hash()
.ok()
.map(|hash| match hash.chars().next() {
Some('#') => hash[1..].to_string(),
_ => hash,
})
}
}

View File

@@ -771,9 +771,26 @@ impl View {
});
}
Self::CoreComponent(c) => match c {
CoreComponent::DynChild(_) => {}
CoreComponent::Each(_) => {}
_ => {}
CoreComponent::DynChild(d) => {
if let Some(subview) = *d.child.take() {
let subview = subview.on(event, event_handler);
d.child.replace(Box::new(Some(subview)));
}
}
CoreComponent::Each(each) => {
let event_handler = Rc::new(RefCell::new(event_handler));
let new_children = each.children.take().into_iter().map(|item| {
if let Some(mut item) = item {
let event_handler = Rc::clone(&event_handler);
item.child = item.child.on(event.clone(), Box::new(move |e| event_handler.borrow_mut()(e)));
Some(item)
} else {
None
}
}).collect::<Vec<_>>();
each.children.replace(new_children);
}
CoreComponent::Unit(_) => {}
},
_ => {}
}

View File

@@ -44,6 +44,7 @@ macro_rules! debug_warn {
/// Log a string to the console (in the browser)
/// or via `println!()` (if not in the browser).
pub fn console_log(s: &str) {
#[allow(clippy::print_stdout)]
if is_server() {
println!("{s}");
} else {

View File

@@ -155,9 +155,8 @@ fn match_serialize() {
}
#[test]
#[allow(clippy::needless_borrow)]
fn match_no_serialize() {
#![allow(clippy::needless_borrow)]
struct CustomStruct {
field: &'static str,
}

View File

@@ -1,7 +1,5 @@
use crate::{html::ElementDescriptor, HtmlElement};
use leptos_reactive::{
create_render_effect, create_rw_signal, signal_prelude::*, RwSignal,
};
use leptos_reactive::{create_render_effect, signal_prelude::*};
use std::cell::Cell;
/// Contains a shared reference to a DOM node created while using the `view`

View File

@@ -9,7 +9,7 @@ use crate::{
use cfg_if::cfg_if;
use futures::{stream::FuturesUnordered, Future, Stream, StreamExt};
use itertools::Itertools;
use leptos_reactive::{Oco, *};
use leptos_reactive::*;
use std::pin::Pin;
type PinnedFuture<T> = Pin<Box<dyn Future<Output = T>>>;

View File

@@ -78,12 +78,20 @@ impl ViewMacros {
for view in visitor.views {
let span = view.span();
let id = span_to_stable_id(path, span.start().line);
let tokens = view.tokens.clone().into_iter();
// TODO handle class = ...
let rsx =
rstml::parse2(tokens.collect::<proc_macro2::TokenStream>())?;
let template = LNode::parse_view(rsx)?;
views.push(MacroInvocation { id, template });
if view.tokens.is_empty() {
views.push(MacroInvocation {
id,
template: LNode::Fragment(Vec::new()),
});
} else {
let tokens = view.tokens.clone().into_iter();
// TODO handle class = ...
let rsx = rstml::parse2(
tokens.collect::<proc_macro2::TokenStream>(),
)?;
let template = LNode::parse_view(rsx)?;
views.push(MacroInvocation { id, template });
}
}
Ok(views)
}

View File

@@ -13,7 +13,7 @@ rust-version.workspace = true
proc-macro = true
[dependencies]
attribute-derive = { version = "0.8", features = ["syn-full"] }
attribute-derive = { version = "0.9", features = ["syn-full"]}
cfg-if = "1"
html-escape = "0.2"
itertools = "0.12"

View File

@@ -11,13 +11,13 @@ dependencies = [
[tasks.test-leptos_macro-example]
description = "Tests the leptos_macro/example to check if macro handles doc comments correctly"
command = "cargo"
args = ["+nightly-2024-01-29", "test", "--doc"]
args = ["+nightly-2024-03-31", "test", "--doc"]
cwd = "example"
install_crate = false
[tasks.doc-leptos_macro-example]
description = "Docs the leptos_macro/example to check if macro handles doc comments correctly"
command = "cargo"
args = ["+nightly-2024-01-29", "doc"]
args = ["+nightly-2024-03-31", "doc"]
cwd = "example"
install_crate = false

View File

@@ -1,4 +1,4 @@
use attribute_derive::Attribute as AttributeDerive;
use attribute_derive::FromAttr;
use convert_case::{
Case::{Pascal, Snake},
Casing,
@@ -447,7 +447,7 @@ impl ToTokens for Model {
};
quote! {
#[::leptos::wasm_bindgen::prelude::wasm_bindgen]
#[::leptos::wasm_bindgen::prelude::wasm_bindgen(wasm_bindgen = ::leptos::wasm_bindgen)]
#[allow(non_snake_case)]
pub fn #hydrate_fn_name(el: ::leptos::web_sys::HtmlElement) {
if let Some(Ok(key)) = el.dataset().get(::leptos::wasm_bindgen::intern("hkc")).map(|key| std::str::FromStr::from_str(&key)) {
@@ -688,7 +688,7 @@ impl Docs {
const RSX_START: &str = "# ::leptos::view! {";
const RSX_END: &str = "# };";
// Seperated out of chain to allow rustfmt to work
// Separated out of chain to allow rustfmt to work
let map = |(doc, span): (String, Span)| {
doc.split('\n')
.map(str::trim_end)
@@ -705,8 +705,8 @@ impl Docs {
{
view_code_fence_state = ViewCodeFenceState::Rust;
let view = trimmed_doc.find('v').unwrap();
quotes = trimmed_doc[..view].to_owned();
quote_ws = leading_ws.to_owned();
trimmed_doc[..view].clone_into(&mut quotes);
leading_ws.clone_into(&mut quote_ws);
let rust_options = &trimmed_doc
[view + "view".len()..]
.trim_start();
@@ -808,7 +808,7 @@ impl Docs {
}
}
#[derive(Clone, Debug, AttributeDerive)]
#[derive(Clone, Debug, FromAttr)]
#[attribute(ident = prop)]
struct PropOpt {
#[attribute(conflicts = [optional_no_strip, strip_option])]

View File

@@ -1,7 +1,7 @@
use crate::component::{
convert_from_snake_case, drain_filter, is_option, unwrap_option, Docs,
};
use attribute_derive::Attribute as AttributeDerive;
use attribute_derive::FromAttr;
use proc_macro2::{Ident, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
@@ -65,8 +65,10 @@ impl ToTokens for Model {
body,
} = self;
let (_, generics, where_clause) = body.generics.split_for_impl();
let (impl_generics, generics, where_clause) =
body.generics.split_for_impl();
let builder_name = quote::format_ident!("{name}Builder");
let prop_builder_fields = prop_builder_fields(vis, props);
let prop_docs = generate_prop_docs(props);
let builder_name_doc = LitStr::new(
@@ -74,6 +76,39 @@ impl ToTokens for Model {
name.span(),
);
let count = props
.iter()
.filter(
|Prop {
prop_opts: PropOpt { attrs, .. },
..
}| *attrs,
)
.count();
let dyn_attrs_props = props
.iter()
.filter(
|Prop {
prop_opts: PropOpt { attrs, .. },
..
}| *attrs,
)
.enumerate()
.map(|(idx, Prop { name, .. })| {
let ident = &name;
if idx < count - 1 {
quote! {
self.#ident = v.clone().into();
}
} else {
quote! {
self.#ident = v.into();
}
}
})
.collect::<TokenStream>();
let output = quote! {
#[doc = #builder_name_doc]
#[doc = ""]
@@ -90,6 +125,20 @@ impl ToTokens for Model {
vec![value]
}
}
impl #impl_generics ::leptos::Props for #name #generics #where_clause {
type Builder = #builder_name #generics;
fn builder() -> Self::Builder {
#name::builder()
}
}
impl #impl_generics ::leptos::DynAttrs for #name #generics #where_clause {
fn dyn_attrs(mut self, v: Vec<(&'static str, ::leptos::Attribute)>) -> Self {
#dyn_attrs_props
self
}
}
};
tokens.append_all(output)
@@ -130,7 +179,7 @@ impl Prop {
}
}
#[derive(Clone, Debug, AttributeDerive)]
#[derive(Clone, Debug, FromAttr)]
#[attribute(ident = prop)]
struct PropOpt {
#[attribute(conflicts = [optional_no_strip, strip_option])]
@@ -142,6 +191,7 @@ struct PropOpt {
#[attribute(example = "5 * 10")]
pub default: Option<syn::Expr>,
pub into: bool,
pub attrs: bool,
}
struct TypedBuilderOpts {
@@ -154,7 +204,7 @@ struct TypedBuilderOpts {
impl TypedBuilderOpts {
pub fn from_opts(opts: &PropOpt, is_ty_option: bool) -> Self {
Self {
default: opts.optional || opts.optional_no_strip,
default: opts.optional || opts.optional_no_strip || opts.attrs,
default_with_value: opts.default.clone(),
strip_option: opts.strip_option || opts.optional && is_ty_option,
into: opts.into,

View File

@@ -429,6 +429,21 @@ fn attribute_to_tokens_ssr<'a>(
{ _ = #value; }
});
}
} else if let Some(directive_name) = name.strip_prefix("use:") {
let handler = syn::Ident::new(directive_name, attr.key.span());
let value = attr.value();
let value = value.map(|value| {
quote! {
_ = #value;
}
});
exprs_for_compiler.push(quote! {
#[allow(unused_braces)]
{
_ = #handler;
#value
}
});
} else if name == "inner_html" {
return attr.value();
} else {

View File

@@ -50,6 +50,7 @@ pub(crate) fn slot_to_tokens(
.filter(|attr| {
!attr.key.to_string().starts_with("let:")
&& !attr.key.to_string().starts_with("clone:")
&& !attr.key.to_string().starts_with("attr:")
})
.map(|attr| {
let name = &attr.key;
@@ -86,6 +87,24 @@ pub(crate) fn slot_to_tokens(
})
.collect::<Vec<_>>();
let dyn_attrs = attrs
.filter(|attr| attr.key.to_string().starts_with("attr:"))
.filter_map(|attr| {
let name = &attr.key.to_string();
let name = name.strip_prefix("attr:");
let value = attr.value().map(|v| {
quote! { #v }
})?;
Some(quote! { (#name, ::leptos::IntoAttribute::into_attribute(#value)) })
})
.collect::<Vec<_>>();
let dyn_attrs = if dyn_attrs.is_empty() {
quote! {}
} else {
quote! { .dyn_attrs(vec![#(#dyn_attrs),*]) }
};
let mut slots = HashMap::new();
let children = if node.children.is_empty() {
quote! {}
@@ -159,6 +178,7 @@ pub(crate) fn slot_to_tokens(
#(#slots)*
#children
.build()
#dyn_attrs
.into(),
};

View File

@@ -38,16 +38,22 @@ error: `optional_no_strip` conflicts with mutually exclusive `strip_option`
30 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected end of input, expected assignment `=`
--> tests/ui/component.rs:36:40
error: unexpected end of input, expected `=` or `(`
= help: try `#[prop(default = 5 * 10)]`
--> tests/ui/component.rs:35:1
|
36 | fn default_without_value(#[prop(default)] default: bool) -> impl IntoView {
| ^
35 | #[component]
| ^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `component` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
= help: try `#[prop(default=5 * 10)]`
--> tests/ui/component.rs:42:22
= help: try `#[prop(default = 5 * 10)]`
--> tests/ui/component.rs:40:1
|
42 | #[prop(default= |)] default: bool,
| ^
40 | #[component]
| ^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `component` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@@ -30,16 +30,22 @@ error: `optional_no_strip` conflicts with mutually exclusive `strip_option`
25 | #[prop(optional_no_strip, strip_option)] conflicting: bool,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error: unexpected end of input, expected assignment `=`
--> tests/ui/component_absolute.rs:32:19
error: unexpected end of input, expected `=` or `(`
= help: try `#[prop(default = 5 * 10)]`
--> tests/ui/component_absolute.rs:30:1
|
32 | #[prop(default)] default: bool,
| ^
30 | #[::leptos::component]
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `::leptos::component` (in Nightly builds, run with -Z macro-backtrace for more info)
error: unexpected end of input, expected one of: identifier, `::`, `<`, `_`, literal, `const`, `ref`, `mut`, `&`, parentheses, square brackets, `..`, `const`
= help: try `#[prop(default=5 * 10)]`
--> tests/ui/component_absolute.rs:39:22
= help: try `#[prop(default = 5 * 10)]`
--> tests/ui/component_absolute.rs:37:1
|
39 | #[prop(default= |)] default: bool,
| ^
37 | #[::leptos::component]
| ^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `::leptos::component` (in Nightly builds, run with -Z macro-backtrace for more info)

View File

@@ -28,7 +28,7 @@ rustc-hash = "1"
serde-wasm-bindgen = "0.6"
serde_json = "1"
spin-sdk = { version = "2", optional = true }
base64 = "0.21"
base64 = "0.22"
thiserror = "1"
tokio = { version = "1", features = [
"rt",
@@ -72,7 +72,7 @@ hydrate = [
"dep:web-sys",
]
ssr = ["dep:tokio"]
nightly = ["rkyv?/copy"]
nightly = [] #["rkyv?/copy"] # not working on more recent nightlys
serde = []
serde-lite = ["dep:serde-lite"]
miniserde = ["dep:miniserde"]
@@ -81,7 +81,7 @@ experimental-islands = []
spin = ["ssr", "dep:spin-sdk"]
[package.metadata.cargo-all-features]
denylist = ["nightly"]
denylist = ["nightly", "rkyv"]
skip_feature_sets = [
[
"csr",

View File

@@ -238,7 +238,7 @@ mod tests {
fn clone_callback() {
let rt = create_runtime();
let callback = Callback::new(move |_no_clone: NoClone| NoClone {});
let _cloned = callback.clone();
let _cloned = callback;
rt.dispose();
}

View File

@@ -165,7 +165,7 @@ pub fn create_owning_memo<T>(
f: impl Fn(Option<T>) -> (T, bool) + 'static,
) -> Memo<T>
where
T: PartialEq + 'static,
T: 'static,
{
Runtime::current().create_owning_memo(f)
}
@@ -352,7 +352,7 @@ impl<T> Memo<T> {
#[track_caller]
pub fn new_owning(f: impl Fn(Option<T>) -> (T, bool) + 'static) -> Memo<T>
where
T: PartialEq + 'static,
T: 'static,
{
create_owning_memo(f)
}

View File

@@ -1389,6 +1389,7 @@ where
}
}
let current_span = tracing::Span::current();
// run the Future
let serializable = self.serializable;
spawn_local({
@@ -1397,6 +1398,8 @@ where
let set_loading = self.set_loading;
let last_version = self.version.clone();
async move {
// continue trace context within resource fetcher
let _guard = current_span.enter();
let res = fut.await;
if version == last_version.get() {

Some files were not shown because too many files have changed in this diff Show More