tangled
alpha
login
or
join now
nonbinary.computer
/
weaver
atproto blogging
24
fork
atom
overview
issues
2
pulls
pipelines
ssr options for stuff
Orual
2 months ago
23946661
6b7587e4
+288
-134
9 changed files
expand all
collapse all
unified
split
crates
weaver-app
.env-example
.env-prod
src
components
css.rs
identity.rs
data.rs
env.rs
main.rs
views
home.rs
notebook.rs
-10
crates/weaver-app/.env-example
···
1
-
WEAVER_APP_ENV="dev"
2
-
WEAVER_APP_HOST="http://localhost"
3
-
WEAVER_APP_DOMAIN=""
4
-
WEAVER_PORT=8080
5
-
WEAVER_APP_SCOPES="atproto transition:generic"
6
-
WEAVER_CLIENT_NAME="Weaver"
7
-
8
-
WEAVER_LOGO_URI=""
9
-
WEAVER_TOS_URI=""
10
-
WEAVER_PRIVACY_POLICY_URI=""
···
0
0
0
0
0
0
0
0
0
0
+10
crates/weaver-app/.env-prod
···
0
0
0
0
0
0
0
0
0
0
···
1
+
WEAVER_APP_ENV="prod"
2
+
WEAVER_APP_HOST="https://alpha.weaver.sh"
3
+
WEAVER_APP_DOMAIN="https://alpha.weaver.sh"
4
+
WEAVER_PORT=8080
5
+
WEAVER_APP_SCOPES="atproto transition:generic"
6
+
WEAVER_CLIENT_NAME="Weaver"
7
+
8
+
WEAVER_LOGO_URI=""
9
+
WEAVER_TOS_URI=""
10
+
WEAVER_PRIVACY_POLICY_URI=""
+5
-5
crates/weaver-app/src/components/css.rs
···
1
#[allow(unused_imports)]
2
use crate::fetch;
3
#[allow(unused_imports)]
4
-
use dioxus::{prelude::*, CapturedError};
5
6
#[cfg(feature = "fullstack-server")]
7
use dioxus::fullstack::response::Response;
···
20
use dioxus::fullstack::get_server_url;
21
rsx! {
22
document::Stylesheet {
23
-
href: "{get_server_url()}/{ident}/{notebook}/css"
24
}
25
}
26
}
···
30
pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element {
31
use jacquard::client::AgentSessionExt;
32
use jacquard::types::ident::AtIdentifier;
33
-
use jacquard::{from_data, CowStr};
34
use weaver_api::sh_weaver::notebook::book::Book;
35
use weaver_renderer::css::{generate_base_css, generate_syntax_css};
36
use weaver_renderer::theme::{default_resolved_theme, resolve_theme};
···
87
}
88
89
#[cfg(feature = "fullstack-server")]
90
-
#[get("/{ident}/{notebook}/css", fetcher: Extension<Arc<fetch::CachedFetcher>>)]
91
pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<Response> {
92
use dioxus::fullstack::http::header::CONTENT_TYPE;
93
use jacquard::client::AgentSessionExt;
94
use jacquard::types::ident::AtIdentifier;
95
-
use jacquard::{from_data, CowStr};
96
97
use weaver_api::sh_weaver::notebook::book::Book;
98
use weaver_renderer::css::{generate_base_css, generate_syntax_css};
···
1
#[allow(unused_imports)]
2
use crate::fetch;
3
#[allow(unused_imports)]
4
+
use dioxus::{CapturedError, prelude::*};
5
6
#[cfg(feature = "fullstack-server")]
7
use dioxus::fullstack::response::Response;
···
20
use dioxus::fullstack::get_server_url;
21
rsx! {
22
document::Stylesheet {
23
+
href: "{get_server_url()}/css/{ident}/{notebook}"
24
}
25
}
26
}
···
30
pub fn NotebookCss(ident: SmolStr, notebook: SmolStr) -> Element {
31
use jacquard::client::AgentSessionExt;
32
use jacquard::types::ident::AtIdentifier;
33
+
use jacquard::{CowStr, from_data};
34
use weaver_api::sh_weaver::notebook::book::Book;
35
use weaver_renderer::css::{generate_base_css, generate_syntax_css};
36
use weaver_renderer::theme::{default_resolved_theme, resolve_theme};
···
87
}
88
89
#[cfg(feature = "fullstack-server")]
90
+
#[get("/css/{ident}/{notebook}", fetcher: Extension<Arc<fetch::CachedFetcher>>)]
91
pub async fn css(ident: SmolStr, notebook: SmolStr) -> Result<Response> {
92
use dioxus::fullstack::http::header::CONTENT_TYPE;
93
use jacquard::client::AgentSessionExt;
94
use jacquard::types::ident::AtIdentifier;
95
+
use jacquard::{CowStr, from_data};
96
97
use weaver_api::sh_weaver::notebook::book::Book;
98
use weaver_renderer::css::{generate_base_css, generate_syntax_css};
+22
-26
crates/weaver-app/src/components/identity.rs
···
1
-
use crate::{Route, fetch};
2
use dioxus::prelude::*;
3
use jacquard::{smol_str::SmolStr, types::ident::AtIdentifier};
4
use weaver_api::com_atproto::repo::strong_ref::StrongRef;
···
20
pub fn RepositoryIndex(ident: ReadSignal<AtIdentifier<'static>>) -> Element {
21
use crate::components::ProfileDisplay;
22
23
-
let fetcher = use_context::<fetch::CachedFetcher>();
24
-
25
-
// Fetch notebooks for this specific DID
26
-
let notebooks = use_resource(move || {
27
-
let fetcher = fetcher.clone();
28
-
async move { fetcher.fetch_notebooks_for_did(&ident()).await }
29
-
});
30
31
rsx! {
32
document::Stylesheet { href: NOTEBOOK_CARD_CSS }
···
40
// Main content area
41
main { class: "repository-main",
42
div { class: "notebooks-list",
43
-
match notebooks() {
44
-
Some(Ok(notebook_list)) => rsx! {
45
-
for notebook in notebook_list.iter() {
46
-
{
47
-
let view = ¬ebook.0;
48
-
let entries = ¬ebook.1;
49
-
rsx! {
50
-
div {
51
-
key: "{view.cid}",
52
-
NotebookCard {
53
-
notebook: view.clone(),
54
-
entry_refs: entries.clone()
0
0
55
}
56
}
57
}
58
}
0
0
0
59
}
60
-
},
61
-
Some(Err(_)) => rsx! {
62
-
div { "Error loading notebooks" }
63
-
},
64
-
None => rsx! {
65
-
div { "Loading notebooks..." }
66
}
0
0
67
}
68
}
69
}
···
1
+
use crate::{Route, data, fetch};
2
use dioxus::prelude::*;
3
use jacquard::{smol_str::SmolStr, types::ident::AtIdentifier};
4
use weaver_api::com_atproto::repo::strong_ref::StrongRef;
···
20
pub fn RepositoryIndex(ident: ReadSignal<AtIdentifier<'static>>) -> Element {
21
use crate::components::ProfileDisplay;
22
23
+
// Fetch notebooks for this specific DID with SSR support
24
+
let notebooks = data::use_notebooks_for_did(ident()).ok();
0
0
0
0
0
25
26
rsx! {
27
document::Stylesheet { href: NOTEBOOK_CARD_CSS }
···
35
// Main content area
36
main { class: "repository-main",
37
div { class: "notebooks-list",
38
+
if let Some(notebooks_memo) = ¬ebooks {
39
+
match &*notebooks_memo.read_unchecked() {
40
+
Some(notebook_list) => rsx! {
41
+
for notebook in notebook_list.iter() {
42
+
{
43
+
let view = ¬ebook.0;
44
+
let entries = ¬ebook.1;
45
+
rsx! {
46
+
div {
47
+
key: "{view.cid}",
48
+
NotebookCard {
49
+
notebook: view.clone(),
50
+
entry_refs: entries.clone()
51
+
}
52
}
53
}
54
}
55
}
56
+
},
57
+
None => rsx! {
58
+
div { "Loading notebooks..." }
59
}
0
0
0
0
0
0
60
}
61
+
} else {
62
+
div { "Loading notebooks..." }
63
}
64
}
65
}
+187
-1
crates/weaver-app/src/data.rs
···
18
};
19
#[allow(unused_imports)]
20
use std::sync::Arc;
21
-
use weaver_api::sh_weaver::notebook::{BookEntryView, entry::Entry};
22
// ============================================================================
23
// Wrapper Hooks (feature-gated)
24
// ============================================================================
···
358
.await
359
.ok()
360
.map(|notebooks| notebooks.iter().map(|arc| (*arc).clone()).collect())
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
361
}
362
}));
363
Ok(use_memo(move || {
···
18
};
19
#[allow(unused_imports)]
20
use std::sync::Arc;
21
+
use weaver_api::sh_weaver::notebook::{BookEntryView, NotebookView, entry::Entry};
22
// ============================================================================
23
// Wrapper Hooks (feature-gated)
24
// ============================================================================
···
358
.await
359
.ok()
360
.map(|notebooks| notebooks.iter().map(|arc| (*arc).clone()).collect())
361
+
}
362
+
}));
363
+
Ok(use_memo(move || {
364
+
r.read_unchecked().as_ref().and_then(|v| v.clone())
365
+
}))
366
+
}
367
+
368
+
/// Fetches notebooks from UFOS with SSR support in fullstack mode
369
+
#[cfg(feature = "fullstack-server")]
370
+
pub fn use_notebooks_from_ufos() -> Result<
371
+
Memo<Option<Vec<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>>,
372
+
RenderError,
373
+
> {
374
+
let fetcher = use_context::<crate::fetch::CachedFetcher>();
375
+
let res = use_server_future(move || {
376
+
let fetcher = fetcher.clone();
377
+
async move {
378
+
fetcher
379
+
.fetch_notebooks_from_ufos()
380
+
.await
381
+
.ok()
382
+
.map(|notebooks| {
383
+
notebooks
384
+
.iter()
385
+
.map(|arc| serde_json::to_value(arc.as_ref()).ok())
386
+
.collect::<Option<Vec<_>>>()
387
+
})
388
+
.flatten()
389
+
}
390
+
})?;
391
+
Ok(use_memo(move || {
392
+
if let Some(Some(values)) = &*res.read_unchecked() {
393
+
values
394
+
.iter()
395
+
.map(|v| {
396
+
jacquard::from_json_value::<(
397
+
NotebookView,
398
+
Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef>,
399
+
)>(v.clone())
400
+
.ok()
401
+
})
402
+
.collect::<Option<Vec<_>>>()
403
+
} else {
404
+
None
405
+
}
406
+
}))
407
+
}
408
+
409
+
/// Fetches notebooks from UFOS client-side only (no SSR)
410
+
#[cfg(not(feature = "fullstack-server"))]
411
+
pub fn use_notebooks_from_ufos() -> Result<
412
+
Memo<Option<Vec<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>>,
413
+
RenderError,
414
+
> {
415
+
let fetcher = use_context::<crate::fetch::CachedFetcher>();
416
+
let r = use_resource(move || {
417
+
let fetcher = fetcher.clone();
418
+
async move {
419
+
fetcher
420
+
.fetch_notebooks_from_ufos()
421
+
.await
422
+
.ok()
423
+
.map(|notebooks| notebooks.iter().map(|arc| (*arc).clone()).collect())
424
+
}
425
+
});
426
+
Ok(use_memo(move || {
427
+
r.read_unchecked().as_ref().and_then(|v| v.clone())
428
+
}))
429
+
}
430
+
431
+
/// Fetches notebook metadata with SSR support in fullstack mode
432
+
#[cfg(feature = "fullstack-server")]
433
+
pub fn use_notebook(
434
+
ident: AtIdentifier<'static>,
435
+
book_title: SmolStr,
436
+
) -> Result<
437
+
Memo<Option<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>,
438
+
RenderError,
439
+
> {
440
+
let fetcher = use_context::<crate::fetch::CachedFetcher>();
441
+
let ident = use_signal(|| ident);
442
+
let book_title = use_signal(|| book_title);
443
+
let res = use_server_future(move || {
444
+
let fetcher = fetcher.clone();
445
+
async move {
446
+
fetcher
447
+
.get_notebook(ident(), book_title())
448
+
.await
449
+
.ok()
450
+
.flatten()
451
+
.map(|arc| serde_json::to_value(arc.as_ref()).ok())
452
+
.flatten()
453
+
}
454
+
})?;
455
+
Ok(use_memo(move || {
456
+
if let Some(Some(value)) = &*res.read_unchecked() {
457
+
jacquard::from_json_value::<(
458
+
NotebookView,
459
+
Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef>,
460
+
)>(value.clone())
461
+
.ok()
462
+
} else {
463
+
None
464
+
}
465
+
}))
466
+
}
467
+
468
+
/// Fetches notebook metadata client-side only (no SSR)
469
+
#[cfg(not(feature = "fullstack-server"))]
470
+
pub fn use_notebook(
471
+
ident: AtIdentifier<'static>,
472
+
book_title: SmolStr,
473
+
) -> Result<
474
+
Memo<Option<(NotebookView<'static>, Vec<weaver_api::com_atproto::repo::strong_ref::StrongRef<'static>>)>>,
475
+
RenderError,
476
+
> {
477
+
let fetcher = use_context::<crate::fetch::CachedFetcher>();
478
+
let r = use_resource(use_reactive!(|(ident, book_title)| {
479
+
let fetcher = fetcher.clone();
480
+
async move {
481
+
fetcher
482
+
.get_notebook(ident, book_title)
483
+
.await
484
+
.ok()
485
+
.flatten()
486
+
.map(|arc| (*arc).clone())
487
+
}
488
+
}));
489
+
Ok(use_memo(move || {
490
+
r.read_unchecked().as_ref().and_then(|v| v.clone())
491
+
}))
492
+
}
493
+
494
+
/// Fetches notebook entries with SSR support in fullstack mode
495
+
#[cfg(feature = "fullstack-server")]
496
+
pub fn use_notebook_entries(
497
+
ident: AtIdentifier<'static>,
498
+
book_title: SmolStr,
499
+
) -> Result<Memo<Option<Vec<BookEntryView<'static>>>>, RenderError> {
500
+
let fetcher = use_context::<crate::fetch::CachedFetcher>();
501
+
let ident = use_signal(|| ident);
502
+
let book_title = use_signal(|| book_title);
503
+
let res = use_server_future(move || {
504
+
let fetcher = fetcher.clone();
505
+
async move {
506
+
fetcher
507
+
.list_notebook_entries(ident(), book_title())
508
+
.await
509
+
.ok()
510
+
.flatten()
511
+
.map(|entries| {
512
+
entries
513
+
.iter()
514
+
.map(|e| serde_json::to_value(e).ok())
515
+
.collect::<Option<Vec<_>>>()
516
+
})
517
+
.flatten()
518
+
}
519
+
})?;
520
+
Ok(use_memo(move || {
521
+
if let Some(Some(values)) = &*res.read_unchecked() {
522
+
values
523
+
.iter()
524
+
.map(|v| jacquard::from_json_value::<BookEntryView>(v.clone()).ok())
525
+
.collect::<Option<Vec<_>>>()
526
+
} else {
527
+
None
528
+
}
529
+
}))
530
+
}
531
+
532
+
/// Fetches notebook entries client-side only (no SSR)
533
+
#[cfg(not(feature = "fullstack-server"))]
534
+
pub fn use_notebook_entries(
535
+
ident: AtIdentifier<'static>,
536
+
book_title: SmolStr,
537
+
) -> Result<Memo<Option<Vec<BookEntryView<'static>>>>, RenderError> {
538
+
let fetcher = use_context::<crate::fetch::CachedFetcher>();
539
+
let r = use_resource(use_reactive!(|(ident, book_title)| {
540
+
let fetcher = fetcher.clone();
541
+
async move {
542
+
fetcher
543
+
.list_notebook_entries(ident, book_title)
544
+
.await
545
+
.ok()
546
+
.flatten()
547
}
548
}));
549
Ok(use_memo(move || {
+3
-3
crates/weaver-app/src/env.rs
···
1
// This file is automatically generated by build.rs
2
3
#[allow(unused)]
4
-
pub const WEAVER_APP_ENV: &'static str = "prod";
5
#[allow(unused)]
6
-
pub const WEAVER_APP_HOST: &'static str = "https://alpha.weaver.sh";
7
#[allow(unused)]
8
-
pub const WEAVER_APP_DOMAIN: &'static str = "https://alpha.weaver.sh";
9
#[allow(unused)]
10
pub const WEAVER_PORT: &'static str = "8080";
11
#[allow(unused)]
···
1
// This file is automatically generated by build.rs
2
3
#[allow(unused)]
4
+
pub const WEAVER_APP_ENV: &'static str = "dev";
5
#[allow(unused)]
6
+
pub const WEAVER_APP_HOST: &'static str = "http://localhost";
7
#[allow(unused)]
8
+
pub const WEAVER_APP_DOMAIN: &'static str = "";
9
#[allow(unused)]
10
pub const WEAVER_PORT: &'static str = "8080";
11
#[allow(unused)]
+9
-15
crates/weaver-app/src/main.rs
···
136
// Enable incremental rendering
137
.incremental(
138
dioxus::server::IncrementalRendererConfig::new()
139
-
.static_dir(
140
-
std::env::current_exe()
141
-
.unwrap()
142
-
.parent()
143
-
.unwrap()
144
-
.join("public"),
145
-
)
146
.clear_cache(false),
147
)
148
.enable_out_of_order_streaming(),
···
260
}
261
}
262
263
-
#[server(endpoint = "static_routes", output = server_fn::codec::Json)]
264
-
async fn static_routes() -> Result<Vec<String>, ServerFnError> {
265
-
// The `Routable` trait has a `static_routes` method that returns all static routes in the enum
266
-
Ok(Route::static_routes()
267
-
.iter()
268
-
.map(ToString::to_string)
269
-
.collect())
270
-
}
···
136
// Enable incremental rendering
137
.incremental(
138
dioxus::server::IncrementalRendererConfig::new()
139
+
.pre_render(true)
0
0
0
0
0
0
140
.clear_cache(false),
141
)
142
.enable_out_of_order_streaming(),
···
254
}
255
}
256
257
+
// #[server(endpoint = "static_routes", output = server_fn::codec::Json)]
258
+
// async fn static_routes() -> Result<Vec<String>, ServerFnError> {
259
+
// // The `Routable` trait has a `static_routes` method that returns all static routes in the enum
260
+
// Ok(Route::static_routes()
261
+
// .iter()
262
+
// .map(ToString::to_string)
263
+
// .collect())
264
+
// }
+22
-26
crates/weaver-app/src/views/home.rs
···
1
-
use crate::{Route, components::identity::NotebookCard, fetch};
2
use dioxus::prelude::*;
3
use jacquard::types::aturi::AtUri;
4
···
7
/// The Home page component that will be rendered when the current route is `[Route::Home]`
8
#[component]
9
pub fn Home() -> Element {
10
-
let fetcher = use_context::<fetch::CachedFetcher>();
11
-
12
-
// Fetch notebooks from UFOS
13
-
let notebooks = use_resource(move || {
14
-
let fetcher = fetcher.clone();
15
-
async move { fetcher.fetch_notebooks_from_ufos().await }
16
-
});
17
let navigator = use_navigator();
18
let mut uri_input = use_signal(|| String::new());
19
···
49
}
50
}
51
div { class: "notebooks-list",
52
-
match notebooks() {
53
-
Some(Ok(notebook_list)) => rsx! {
54
-
for notebook in notebook_list.iter() {
55
-
{
56
-
let view = ¬ebook.0;
57
-
let entries = ¬ebook.1;
58
-
rsx! {
59
-
div {
60
-
key: "{view.cid}",
61
-
NotebookCard {
62
-
notebook: view.clone(),
63
-
entry_refs: entries.clone()
0
0
64
}
65
}
66
}
67
}
0
0
0
68
}
69
-
},
70
-
Some(Err(_)) => rsx! {
71
-
div { "Error loading notebooks" }
72
-
},
73
-
None => rsx! {
74
-
div { "Loading notebooks..." }
75
}
0
0
76
}
77
}
78
}
···
1
+
use crate::{Route, components::identity::NotebookCard, data};
2
use dioxus::prelude::*;
3
use jacquard::types::aturi::AtUri;
4
···
7
/// The Home page component that will be rendered when the current route is `[Route::Home]`
8
#[component]
9
pub fn Home() -> Element {
10
+
// Fetch notebooks from UFOS with SSR support
11
+
let notebooks = data::use_notebooks_from_ufos().ok();
0
0
0
0
0
12
let navigator = use_navigator();
13
let mut uri_input = use_signal(|| String::new());
14
···
44
}
45
}
46
div { class: "notebooks-list",
47
+
if let Some(notebooks_memo) = ¬ebooks {
48
+
match &*notebooks_memo.read_unchecked() {
49
+
Some(notebook_list) => rsx! {
50
+
for notebook in notebook_list.iter() {
51
+
{
52
+
let view = ¬ebook.0;
53
+
let entries = ¬ebook.1;
54
+
rsx! {
55
+
div {
56
+
key: "{view.cid}",
57
+
NotebookCard {
58
+
notebook: view.clone(),
59
+
entry_refs: entries.clone()
60
+
}
61
}
62
}
63
}
64
}
65
+
},
66
+
None => rsx! {
67
+
div { "Loading notebooks..." }
68
}
0
0
0
0
0
0
69
}
70
+
} else {
71
+
div { "Loading notebooks..." }
72
}
73
}
74
}
+30
-48
crates/weaver-app/src/views/notebook.rs
···
1
use crate::{
2
Route,
3
components::{EntryCard, NotebookCover, NotebookCss},
4
-
fetch,
5
};
6
use dioxus::prelude::*;
7
use jacquard::{
···
28
ident: ReadSignal<AtIdentifier<'static>>,
29
book_title: ReadSignal<SmolStr>,
30
) -> Element {
31
-
let fetcher = use_context::<fetch::CachedFetcher>();
32
-
// Fetch full notebook to get author count
33
-
let data_fetcher = fetcher.clone();
34
-
let notebook_data = use_resource(move || {
35
-
let fetcher = data_fetcher.clone();
36
-
async move {
37
-
fetcher
38
-
.get_notebook(ident(), book_title())
39
-
.await
40
-
.ok()
41
-
.flatten()
42
-
}
43
-
});
44
45
-
// Also fetch entries
46
-
let entry_fetcher = fetcher.clone();
47
-
let entries_resource = use_resource(move || {
48
-
let fetcher = entry_fetcher.clone();
49
-
async move {
50
-
fetcher
51
-
.list_notebook_entries(ident(), book_title())
52
-
.await
53
-
.ok()
54
-
.flatten()
55
-
}
56
-
});
57
58
rsx! {
59
document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS }
60
61
-
match (&*notebook_data.read_unchecked(), &*entries_resource.read_unchecked()) {
62
-
(Some(Some(data)), Some(Some(entries))) => {
63
-
let (notebook_view, _) = data.as_ref();
64
-
let author_count = notebook_view.authors.len();
0
65
66
-
rsx! {
67
-
div { class: "notebook-layout",
68
-
aside { class: "notebook-sidebar",
69
-
NotebookCover {
70
-
notebook: notebook_view.clone(),
71
-
title: book_title().to_string()
0
72
}
73
-
}
74
75
-
main { class: "notebook-main",
76
-
div { class: "entries-list",
77
-
for entry in entries {
78
-
EntryCard {
79
-
entry: entry.clone(),
80
-
book_title: book_title(),
81
-
author_count
0
82
}
83
}
84
}
85
}
86
}
87
-
}
88
-
},
89
-
(Some(None), _) | (_, Some(None)) => rsx! { div { class: "error", "Notebook or entries not found" } },
90
-
_ => rsx! { div { class: "loading", "Loading..." } }
0
91
}
92
}
93
}
···
1
use crate::{
2
Route,
3
components::{EntryCard, NotebookCover, NotebookCss},
4
+
data,
5
};
6
use dioxus::prelude::*;
7
use jacquard::{
···
28
ident: ReadSignal<AtIdentifier<'static>>,
29
book_title: ReadSignal<SmolStr>,
30
) -> Element {
31
+
// Fetch full notebook metadata with SSR support
32
+
let notebook_data = data::use_notebook(ident(), book_title()).ok();
0
0
0
0
0
0
0
0
0
0
0
33
34
+
// Fetch entries with SSR support
35
+
let entries_resource = data::use_notebook_entries(ident(), book_title()).ok();
0
0
0
0
0
0
0
0
0
0
36
37
rsx! {
38
document::Link { rel: "stylesheet", href: ENTRY_CARD_CSS }
39
40
+
if let (Some(notebook_memo), Some(entries_memo)) = (¬ebook_data, &entries_resource) {
41
+
match (&*notebook_memo.read_unchecked(), &*entries_memo.read_unchecked()) {
42
+
(Some(data), Some(entries)) => {
43
+
let (notebook_view, _) = data;
44
+
let author_count = notebook_view.authors.len();
45
46
+
rsx! {
47
+
div { class: "notebook-layout",
48
+
aside { class: "notebook-sidebar",
49
+
NotebookCover {
50
+
notebook: notebook_view.clone(),
51
+
title: book_title().to_string()
52
+
}
53
}
0
54
55
+
main { class: "notebook-main",
56
+
div { class: "entries-list",
57
+
for entry in entries {
58
+
EntryCard {
59
+
entry: entry.clone(),
60
+
book_title: book_title(),
61
+
author_count
62
+
}
63
}
64
}
65
}
66
}
67
}
68
+
},
69
+
_ => rsx! { div { class: "loading", "Loading..." } }
70
+
}
71
+
} else {
72
+
div { class: "loading", "Loading..." }
73
}
74
}
75
}