canonrs-server 0.1.0

CanonRS server-side rendering support
#![allow(unreachable_pub, dead_code)]
use leptos::prelude::*;
use canonrs_core::primitives::structural::toc_item::{TocItemRowPrimitive, TocExpandIconPrimitive};
use canonrs_core::TocItem;
use canonrs_core::primitives::table_of_contents::*;
#[cfg(feature = "ssr")]
use canonrs_core::VisibilityState;

#[component]
pub fn TableOfContents(items: Vec<TocItem>, #[prop(default = TocMode::Simple)] mode: TocMode, #[prop(into, default = String::new())] class: String, #[prop(into, default = "On this page".to_string())] title: String) -> impl IntoView {
    #[cfg(feature = "ssr")]
    {
        view! {
            <TocPrimitive class=class mode=mode>
                <TocTitlePrimitive>{title}</TocTitlePrimitive>
                {match mode {
                    TocMode::Simple => render_simple(items).into_any(),
                    TocMode::Expand => render_expand(items).into_any(),
                    TocMode::Nested => render_nested(items).into_any(),
                }}
            </TocPrimitive>
        }.into_any()
    }
    #[cfg(not(feature = "ssr"))]
    {
        let _ = (items, mode, class, title);
        view! { <TocPrimitive class=String::new() mode=TocMode::Simple>{()}</TocPrimitive> }.into_any()
    }
}

#[cfg(feature = "ssr")]
fn render_simple(items: Vec<TocItem>) -> impl IntoView {
    view! {
        <TocListPrimitive>
            {items.into_iter().map(|item| view! {
                <TocItemPrimitive data_level=item.level.to_string() data_target=item.id.clone() state=TocItemState::Idle is_child=false has_children=false>
                    <TocLinkPrimitive href=format!("#{}", item.id)>{item.text}</TocLinkPrimitive>
                </TocItemPrimitive>
            }).collect::<Vec<_>>()}
        </TocListPrimitive>
    }
}

#[cfg(feature = "ssr")]
fn render_expand(items: Vec<TocItem>) -> impl IntoView {
    view! {
        <TocListPrimitive>
            {items.into_iter().map(|item| {
                let is_child = item.level > 1;
                view! {
                    <TocItemPrimitive data_level=item.level.to_string() data_target=item.id.clone() state=TocItemState::Idle is_child=is_child has_children=false>
                        <TocLinkPrimitive href=format!("#{}", item.id)>{item.text}</TocLinkPrimitive>
                    </TocItemPrimitive>
                }
            }).collect::<Vec<_>>()}
        </TocListPrimitive>
    }
}

#[cfg(feature = "ssr")]
fn render_nested(items: Vec<TocItem>) -> impl IntoView {
    let tree = build_tree(items);
    view! { <TocListPrimitive>{render_tree_nodes(tree)}</TocListPrimitive> }
}

#[derive(Clone)]
#[cfg(feature = "ssr")]
struct TocNode { item: TocItem, children: Vec<TocNode> }

#[cfg(feature = "ssr")]
fn build_tree(items: Vec<TocItem>) -> Vec<TocNode> {
    let flat: Vec<TocNode> = items.into_iter().map(|item| TocNode { item, children: Vec::new() }).collect();
    let n = flat.len();
    let mut parent: Vec<Option<usize>> = vec![None; n];
    let mut stack: Vec<(u8, usize)> = Vec::new();
    for i in 0..n {
        let level = flat[i].item.level;
        while stack.last().map(|(l, _)| *l >= level).unwrap_or(false) { stack.pop(); }
        if let Some(&(_, p)) = stack.last() { parent[i] = Some(p); }
        stack.push((level, i));
    }
    let mut nodes: Vec<Option<TocNode>> = flat.into_iter().map(Some).collect();
    for i in (0..n).rev() {
        if let Some(p) = parent[i] { let child = nodes[i].take().unwrap(); nodes[p].as_mut().unwrap().children.insert(0, child); }
    }
    nodes.into_iter().enumerate().filter(|(i, _)| parent[*i].is_none()).filter_map(|(_, n)| n).collect()
}

#[cfg(feature = "ssr")]
fn render_tree_nodes(nodes: Vec<TocNode>) -> Vec<AnyView> {
    nodes.into_iter().map(|node| -> AnyView {
        let has_children = !node.children.is_empty();
        let item = node.item;
        let children = node.children;
        view! {
            <TocItemPrimitive data_level=item.level.to_string() data_target=item.id.clone() state=TocItemState::Idle is_child=false has_children=has_children>
                <TocItemRowPrimitive>
                    {has_children.then(|| view! { <TocExpandButtonPrimitive><TocExpandIconPrimitive/></TocExpandButtonPrimitive> }.into_any())}
                    <TocLinkPrimitive href=format!("#{}", item.id)>{item.text}</TocLinkPrimitive>
                </TocItemRowPrimitive>
                {has_children.then(|| view! {
                    <TocSubtreePrimitive state=VisibilityState::Closed>{render_tree_nodes(children)}</TocSubtreePrimitive>
                }.into_any())}
            </TocItemPrimitive>
        }.into_any()
    }).collect()
}