feat: support SSR

This commit is contained in:
luoxiao 2024-07-30 14:26:59 +08:00
parent 66791d88f2
commit e86d273502
6 changed files with 135 additions and 105 deletions

View file

@ -1,8 +1,5 @@
use super::switch_version::SwitchVersion;
use leptos::{
ev::{self, MouseEvent},
prelude::*,
};
use leptos::{ev::MouseEvent, prelude::*};
use leptos_meta::Style;
use leptos_router::hooks::use_navigate;
// use leptos_use::{storage::use_local_storage, utils::FromToStringCodec};
@ -77,16 +74,20 @@ pub fn SiteHeader() -> impl IntoView {
}
};
let auto_complete_ref = ComponentRef::<AutoCompleteRef>::new();
let handle = window_event_listener(ev::keydown, move |event| {
let key = event.key();
if key == *"/" {
if let Some(auto_complete_ref) = auto_complete_ref.get_untracked() {
event.prevent_default();
auto_complete_ref.focus();
#[cfg(any(feature = "csr", feature = "hydrate"))]
{
use leptos::ev;
let handle = window_event_listener(ev::keydown, move |event| {
let key = event.key();
if key == *"/" {
if let Some(auto_complete_ref) = auto_complete_ref.get_untracked() {
event.prevent_default();
auto_complete_ref.focus();
}
}
}
});
on_cleanup(move || handle.remove());
});
on_cleanup(move || handle.remove());
}
// let menu_value = use_menu_value(change_theme);
view! {

View file

@ -20,7 +20,8 @@ wasm-bindgen = "=0.2.92"
thiserror = "1"
tracing = { version = "0.1", optional = true }
http = "1"
console_log = "1"
log = "0.4"
demo = { path = "../../demo", default-features = false }
[features]

View file

@ -4,6 +4,7 @@ pub mod app;
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn hydrate() {
use crate::app::*;
let _ = console_log::init_with_level(log::Level::Debug);
console_error_panic_hook::set_once();
leptos::mount::hydrate_body(App);
}

View file

@ -2,9 +2,8 @@ mod anchor_link;
pub use anchor_link::AnchorLink;
use leptos::{context::Provider, ev, html, prelude::*};
use std::cmp::Ordering;
use thaw_utils::{add_event_listener_with_bool, class_list, mount_style, throttle};
use leptos::{context::Provider, html, prelude::*};
use thaw_utils::{class_list, mount_style};
use web_sys::{DomRect, Element};
#[component]
@ -12,7 +11,8 @@ pub fn Anchor(
#[prop(optional, into)] class: MaybeProp<String>,
/// The element or selector used to calc offset of link elements.
/// If you are not scrolling the entire document but only a part of it, you may need to set this.
#[prop(into, optional)] offset_target: Option<OffsetTarget>,
#[prop(into, optional)]
offset_target: Option<OffsetTarget>,
children: Children,
) -> impl IntoView {
mount_style("anchor", include_str!("./anchor.css"));
@ -21,72 +21,102 @@ pub fn Anchor(
let element_ids = RwSignal::new(Vec::<String>::new());
let active_id = RwSignal::new(None::<String>);
let offset_target = send_wrapper::SendWrapper::new(offset_target);
let on_scroll = move || {
element_ids.with(|ids| {
let offset_target_top = if let Some(offset_target) = offset_target.as_ref() {
if let Some(rect) = offset_target.get_bounding_client_rect() {
rect.top()
} else {
return;
}
} else {
0.0
};
#[cfg(any(feature = "csr", feature = "hydrate"))]
{
use leptos::ev;
use std::cmp::Ordering;
use thaw_utils::{add_event_listener_with_bool, throttle};
let mut links: Vec<LinkInfo> = vec![];
for id in ids.iter() {
if let Some(link_el) = document().get_element_by_id(id) {
let link_rect = link_el.get_bounding_client_rect();
links.push(LinkInfo {
top: link_rect.top() - offset_target_top,
id: id.clone(),
});
struct LinkInfo {
top: f64,
id: String,
}
impl OffsetTarget {
fn get_bounding_client_rect(&self) -> Option<DomRect> {
match self {
OffsetTarget::Selector(selector) => {
let el = document().query_selector(selector).ok().flatten()?;
Some(el.get_bounding_client_rect())
}
OffsetTarget::Element(el) => Some(el.get_bounding_client_rect()),
}
}
links.sort_by(|a, b| {
if a.top > b.top {
Ordering::Greater
} else {
Ordering::Less
}
});
}
let mut temp_link = None::<LinkInfo>;
for link in links.into_iter() {
if link.top >= 0.0 {
if link.top <= 12.0 {
temp_link = Some(link);
break;
} else if temp_link.is_some() {
break;
let offset_target = send_wrapper::SendWrapper::new(offset_target);
let on_scroll = move || {
element_ids.with(|ids| {
let offset_target_top = if let Some(offset_target) = offset_target.as_ref() {
if let Some(rect) = offset_target.get_bounding_client_rect() {
rect.top()
} else {
temp_link = None;
return;
}
} else {
temp_link = Some(link);
0.0
};
let mut links: Vec<LinkInfo> = vec![];
for id in ids.iter() {
if let Some(link_el) = document().get_element_by_id(id) {
let link_rect = link_el.get_bounding_client_rect();
links.push(LinkInfo {
top: link_rect.top() - offset_target_top,
id: id.clone(),
});
}
}
}
active_id.set(temp_link.map(|link| link.id));
links.sort_by(|a, b| {
if a.top > b.top {
Ordering::Greater
} else {
Ordering::Less
}
});
let mut temp_link = None::<LinkInfo>;
for link in links.into_iter() {
if link.top >= 0.0 {
if link.top <= 12.0 {
temp_link = Some(link);
break;
} else if temp_link.is_some() {
break;
} else {
temp_link = None;
}
} else {
temp_link = Some(link);
}
}
active_id.set(temp_link.map(|link| link.id));
});
};
let cb = throttle(
move || {
on_scroll();
},
std::time::Duration::from_millis(200),
);
let scroll_handle = add_event_listener_with_bool(
document(),
ev::scroll,
move |_| {
cb();
},
true,
);
on_cleanup(move || {
scroll_handle.remove();
});
};
let cb = throttle(
move || {
on_scroll();
},
std::time::Duration::from_millis(200),
);
let scroll_handle = add_event_listener_with_bool(
document(),
ev::scroll,
move |_| {
cb();
},
true,
);
on_cleanup(move || {
scroll_handle.remove();
});
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
{
let _ = offset_target;
}
view! {
<div
class=class_list!["thaw-anchor", class]
@ -177,27 +207,12 @@ impl AnchorInjection {
}
}
struct LinkInfo {
top: f64,
id: String,
}
pub enum OffsetTarget {
Selector(String),
Element(Element),
}
impl OffsetTarget {
fn get_bounding_client_rect(&self) -> Option<DomRect> {
match self {
OffsetTarget::Selector(selector) => {
let el = document().query_selector(selector).ok().flatten()?;
Some(el.get_bounding_client_rect())
}
OffsetTarget::Element(el) => Some(el.get_bounding_client_rect()),
}
}
}
impl From<&'static str> for OffsetTarget {
fn from(value: &'static str) -> Self {

View file

@ -3,7 +3,9 @@ use leptos::{
reactive_graph::traits::{Get, Update, With, WithUntracked},
tachys::renderer::DomRenderer,
};
use std::{collections::HashSet, sync::Arc};
use std::collections::HashSet;
#[cfg(not(feature = "ssr"))]
use std::sync::Arc;
#[derive(Clone, Default)]
pub struct ClassList {
@ -21,6 +23,7 @@ impl ClassList {
Default::default()
}
#[allow(unused_mut)]
pub fn add(mut self, value: impl IntoClass) -> Self {
let class = value.into_class();
match class {
@ -188,11 +191,18 @@ where
fn hydrate<const FROM_SERVER: bool>(self, el: &R::Element) -> Self::State {
let el = el.to_owned();
RenderEffect::new(move |prev| {
if let Some(_) = prev {
unreachable!()
let mut class = String::new();
self.write_class_string(&mut class);
if let Some(state) = prev {
let (el, prev_class) = state;
if class != prev_class {
R::set_attribute(&el, "class", &class);
(el, class)
} else {
(el, prev_class)
}
} else {
let mut class = String::new();
self.write_class_string(&mut class);
if !class.is_empty() {
if !FROM_SERVER {
R::set_attribute(&el, "class", &class);

View file

@ -1,13 +1,14 @@
use cfg_if::cfg_if;
pub fn mount_style(id: &str, content: &'static str) {
let id = format!("thaw-id-{id}");
cfg_if! {
if #[cfg(feature = "ssr")] {
use leptos::{tachys::view::Render, view};
use leptos::view;
use leptos_meta::Style;
let _ = view! {
<Style attr:data-thaw-id=id>
<Style id=id>
{content}
</Style>
};
@ -15,7 +16,7 @@ pub fn mount_style(id: &str, content: &'static str) {
use leptos::prelude::document;
let head = document().head().expect("head no exist");
let style = head
.query_selector(&format!("style[data-thaw-id=\"{id}\"]"))
.query_selector(&format!("style#{id}"))
.expect("query style element error");
if style.is_some() {
@ -25,7 +26,7 @@ pub fn mount_style(id: &str, content: &'static str) {
let style = document()
.create_element("style")
.expect("create style element error");
_ = style.set_attribute("data-thaw-id", id);
_ = style.set_attribute("id", &id);
style.set_text_content(Some(content));
_ = head.prepend_with_node_1(&style);
}
@ -33,13 +34,14 @@ pub fn mount_style(id: &str, content: &'static str) {
}
pub fn mount_dynamic_style<T: Fn() -> String + Send + Sync + 'static>(id: String, f: T) {
let id = format!("thaw-id-{id}");
cfg_if! {
if #[cfg(feature = "ssr")] {
use leptos::{tachys::view::Render, view};
use leptos::view;
use leptos_meta::Style;
let _ = view! {
<Style attr:data-thaw-id=id>
<Style id=id>
{f()}
</Style>
};
@ -49,12 +51,12 @@ pub fn mount_dynamic_style<T: Fn() -> String + Send + Sync + 'static>(id: String
let head = document().head().expect("head no exist");
let style = head
.query_selector(&format!("style[data-thaw-id=\"{id}\"]"))
.query_selector(&format!("style#{id}"))
.expect("query style element error").unwrap_or_else(|| {
let style = document()
.create_element("style")
.expect("create style element error");
_ = style.set_attribute("data-thaw-id", &id);
_ = style.set_attribute("id", &id);
_ = head.prepend_with_node_1(&style);
style