#![deny(missing_docs)]
#![forbid(unsafe_code)]
use cfg_if::cfg_if;
use std::{
cell::{Cell, RefCell},
collections::HashMap,
fmt::Debug,
rc::Rc,
};
use leptos::{leptos_dom::debug_warn, *};
mod link;
mod meta_tags;
mod script;
mod style;
mod stylesheet;
mod title;
pub use link::*;
pub use meta_tags::*;
pub use script::*;
pub use style::*;
pub use stylesheet::*;
pub use title::*;
#[derive(Clone, Default, Debug)]
pub struct MetaContext {
pub(crate) title: TitleContext,
pub(crate) tags: MetaTagsContext,
}
#[derive(Clone, Default)]
pub(crate) struct MetaTagsContext {
next_id: Rc<Cell<MetaTagId>>,
#[allow(clippy::type_complexity)]
els: Rc<RefCell<HashMap<String, (HtmlElement<AnyElement>, Scope, Option<web_sys::Element>)>>>,
}
impl std::fmt::Debug for MetaTagsContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("MetaTagsContext").finish()
}
}
impl MetaTagsContext {
#[cfg(feature = "ssr")]
pub fn as_string(&self) -> String {
self.els
.borrow()
.iter()
.map(|(_, (builder_el, cx, _))| builder_el.clone().into_view(*cx).render_to_string(*cx))
.collect()
}
pub fn register(&self, cx: Scope, id: String, builder_el: HtmlElement<AnyElement>) {
cfg_if! {
if #[cfg(any(feature = "csr", feature = "hydrate"))] {
use leptos::document;
let element_to_hydrate = document()
.get_element_by_id(&id);
let el = element_to_hydrate.unwrap_or_else({
let builder_el = builder_el.clone();
move || {
let head = document().head().unwrap_throw();
head
.append_child(&builder_el)
.unwrap_throw();
(*builder_el).clone().unchecked_into()
}
});
on_cleanup(cx, {
let el = el.clone();
let els = self.els.clone();
let id = id.clone();
move || {
let head = document().head().unwrap_throw();
_ = head.remove_child(&el);
els.borrow_mut().remove(&id);
}
});
self
.els
.borrow_mut()
.insert(id, (builder_el.into_any(), cx, Some(el)));
} else {
self.els.borrow_mut().insert(id, (builder_el, cx, None));
}
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
struct MetaTagId(usize);
impl MetaTagsContext {
fn get_next_id(&self) -> MetaTagId {
let current_id = self.next_id.get();
let next_id = MetaTagId(current_id.0 + 1);
self.next_id.set(next_id);
next_id
}
}
pub fn provide_meta_context(cx: Scope) {
if use_context::<MetaContext>(cx).is_none() {
provide_context(cx, MetaContext::new());
}
}
pub fn use_head(cx: Scope) -> MetaContext {
match use_context::<MetaContext>(cx) {
None => {
debug_warn!(
"use_head() is being called without a MetaContext being provided. \
We'll automatically create and provide one, but if this is being called in a child \
route it may cause bugs. To be safe, you should provide_meta_context(cx) \
somewhere in the root of the app."
);
let meta = MetaContext::new();
provide_context(cx, meta.clone());
meta
}
Some(ctx) => ctx,
}
}
impl MetaContext {
pub fn new() -> Self {
Default::default()
}
#[cfg(feature = "ssr")]
pub fn dehydrate(&self) -> String {
let prev_key = HydrationCtx::peek();
let mut tags = String::new();
if let Some(title) = self.title.as_string() {
tags.push_str("<title>");
tags.push_str(&title);
tags.push_str("</title>");
}
tags.push_str(&self.tags.as_string());
HydrationCtx::continue_from(prev_key);
tags
}
}
#[derive(Clone)]
pub struct TextProp(Rc<dyn Fn() -> String>);
impl TextProp {
fn get(&self) -> String {
(self.0)()
}
}
impl Debug for TextProp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TextProp").finish()
}
}
impl From<String> for TextProp {
fn from(s: String) -> Self {
TextProp(Rc::new(move || s.clone()))
}
}
impl From<&str> for TextProp {
fn from(s: &str) -> Self {
let s = s.to_string();
TextProp(Rc::new(move || s.clone()))
}
}
impl<F> From<F> for TextProp
where
F: Fn() -> String + 'static,
{
fn from(s: F) -> Self {
TextProp(Rc::new(s))
}
}