1#![allow(clippy::must_use_candidate)]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![recursion_limit = "256"]
4
5#[cfg(any(
6 all(feature = "ssr", feature = "hydrate", not(feature = "docs-only")),
7 not(any(feature = "ssr", feature = "hydrate"))
8))]
9compile_error!("exactly one of the features \"ssr\" or \"hydrate\" must be enabled");
10
11pub mod math;
12mod module_picker;
13
14use flams_router_base::maybe_lazy;
15use flams_router_content::Views;
16use ftml_backend::{FtmlBackend, GlobalBackend};
17use ftml_dom::{FtmlViews, utils::css::CssExt};
18use ftml_ontology::{
19 domain::{HasDeclarations, declarations::AnyDeclarationRef, modules::ModuleLike},
20 utils::Css,
21};
22use ftml_uris::{DocumentUri, Id, ModuleUri, SymbolUri};
23use leptos::prelude::*;
24
25maybe_lazy!(FloDownEditor = flodown_editor());
26
27pub fn flodown_editor() -> AnyView {
29 #[cfg(feature = "hydrate")]
30 math::TeXClient::provide();
31
32 Css::Link("/rustex.css".to_string().into_boxed_str()).inject();
33 Css::Link(
34 "https://fonts.googleapis.com/css2?family=STIX+Two+Text"
35 .to_string()
36 .into_boxed_str(),
37 )
38 .inject();
39
40 let modules = RwSignal::new(rustc_hash::FxHashSet::default());
41 let symbols = RwSignal::new(rustc_hash::FxHashMap::default());
42 let action = Action::new(move |v: &rustc_hash::FxHashSet<_>| {
43 let v = v.iter().cloned().collect();
44 async move {
45 let syms = get_symbols(v).await;
46 symbols.set(syms);
47 }
48 });
49 let _ = Effect::new(move || {
50 modules.with(|modules| {
51 action.dispatch(modules.clone());
52 });
53 });
54
55 view! {
56 {editor(symbols)}
57 <div style="width:100%;display:flex;flex-direction:row;">
58 <div style="width:45%;border:1px solid black;">
59 <strong>Symbols:</strong>
60 <span>
61 {move ||
62 symbols.with(|s| {
63 let mut s = s.iter().collect::<Vec<_>>();
64 s.sort_by_key(|(a,_)| *a);
65 ftml_components::components::content::CommaSep("",
66 s.into_iter().map(|(id,uri)| ftml_components::components::content::symbol_uri(id.to_string(), uri))
67 ).into_view().attr("style", "display:inline;")
68 } )
69 }
70 </span>
71 </div>
72 <div style="width:45%">
73 {module_picker::picker(modules)}
74 </div>
75 </div>
76 }.into_any()
77}
78
79async fn get_symbols(mut todos: Vec<ModuleUri>) -> rustc_hash::FxHashMap<Id, SymbolUri> {
80 let mut dones = rustc_hash::FxHashSet::default();
81 let mut ret = rustc_hash::FxHashMap::default();
82 while let Some(next) = todos.pop() {
83 if dones.contains(&next) {
84 continue;
85 }
86 dones.insert(next.clone());
87 if let Ok(ModuleLike::Module(m)) = flams_router_content::backend::FtmlBackend::get()
88 .get_module(next)
89 .await
90 {
91 for d in m.declarations() {
92 match d {
93 AnyDeclarationRef::Symbol(s) => {
94 ret.insert(
95 unsafe { s.uri.name().as_ref().parse().unwrap_unchecked() },
96 s.uri.clone(),
97 );
98 if let Some(mac) = &s.data.macroname {
99 ret.insert(mac.clone(), s.uri.clone());
100 }
101 }
102 AnyDeclarationRef::MathStructure(s) => {
103 ret.insert(
104 unsafe { s.uri.name().as_ref().parse().unwrap_unchecked() },
105 s.uri.clone(),
106 );
107 if let Some(mac) = &s.macroname {
108 ret.insert(mac.clone(), s.uri.clone());
109 }
110 }
111 AnyDeclarationRef::Import { uri: m, .. } => {
112 todos.push(m.clone());
113 }
114 _ => (),
115 }
116 }
117 }
118 }
119 ret
120}
121
122fn editor(symbols: RwSignal<rustc_hash::FxHashMap<Id, SymbolUri>>) -> AnyView {
123 let csr = RwSignal::new(false);
124 #[cfg(feature = "hydrate")]
125 let _ = Effect::new(move || csr.set(true));
126 let checked = RwSignal::new(false);
127 let text = RwSignal::new(DEMO.to_string());
128
129 Views::setup_document(
131 DocumentUri::no_doc().clone(),
132 ftml_components::SidebarPosition::None,
133 false,
134 ftml_dom::toc::TocSource::None,
135 move || {
136 view! {
137 <div><input type="checkbox" on:change:target=move |ev| {
138 checked.set(ev.target().checked());
139 }/>"LaTeX"</div>
140 <div style="width:100%;display:flex;flex-direction:row;">
141 <textarea
142 style="width:48%;max-width:48%;min-width:48%;min-height:200px;"
143 on:input:target=move |ev| {
144 text.set(ev.target().value());
145 }
146 prop:value=text
147 />
148 <div
149 style="width:48%;max-width:48%;min-width:48%;text-align:left;border:1px solid black"
150 >
152 {move ||
153 if csr.get() {
154 Some(if checked.get() {
155 view!(<pre>{text.with(|txt| flodown::to_latex(txt))}</pre>).into_any()
156 } else {
157 md_html(text,symbols)
158 })
159 } else {
160 None
161 }
162 }
163 </div>
164 </div>
165 }.into_any()
166 },
167 )
168}
169
170fn md_html(
171 md: RwSignal<String>,
172 symbols: RwSignal<rustc_hash::FxHashMap<Id, SymbolUri>>,
173) -> AnyView {
174 let owner = leptos::prelude::Owner::current().expect("not in a reactive context");
175
176 let actual = RwSignal::new(String::new());
177 let signals = RwSignal::new(Vec::<(usize, RwSignal<Option<Result<String, String>>>)>::new());
178 Effect::new(move || {
179 #[cfg(feature = "hydrate")]
180 {
181 signals.update_untracked(Vec::clear);
182 math::TeXClient::reset();
183 let s = md.with(|txt| {
184 symbols.with(|symbols| {
185 flodown::to_html_with_math_and_symbols(
186 txt,
187 symbols,
188 |s, out| {
189 use std::fmt::Write;
190 let (i, rs) = owner.with(|| math::TeXClient::inline_math(s));
191 signals.update_untracked(|v| v.push((i, rs)));
192 let _ = write!(out, "<!--math{i}--> ...");
193 },
194 |s, out| {
195 use std::fmt::Write;
196 let (i, rs) = owner.with(|| math::TeXClient::block_math(s));
197 signals.update_untracked(|v| v.push((i, rs)));
198 let _ = write!(out, "<!--math{i}--> ...");
199 },
200 )
201 })
202 });
203 actual.set(s);
204 signals.notify();
205 }
206 });
207 Effect::new(move || {
208 #[cfg(feature = "hydrate")]
209 {
210 signals.with(|v| {
211 for (i, sig) in v {
212 if let Some(v) = sig.get() {
213 match v {
214 Ok(s) => actual.update_untracked(|a| {
215 *a = a.replace(&format!("<!--math{i}--> ..."), &s)
216 }),
217 Err(e) => actual.update_untracked(|a| {
218 *a = a.replace(
219 &format!("<!--math{i}--> ..."),
220 &format!("<span style=\"background-color:red;\">{e}</span>"),
221 );
222 }),
223 }
224 }
225 }
226 });
227 actual.notify();
228 }
229 });
230 (move || actual.with(|txt| Views::render_ftml(txt.clone(), None))).into_any()
231}
232
233static DEMO: &str = r#"
234::: definition title="Gödel's Incompleteness Theorem"
235 foo @[sym](CS) @[sym](CS,computer science)
236 @[def](uncertain)
237:::
238
239@[definition](this is a @[sym](mind) inline definition for @[def](CS,computer science))
240
241a *b* **blubb** \*bla\* blubb _bla_ __blubb__ ~bla~ ^blubb^ ~~bla~~ ==blubb==
242$inline math$ and $$block math$$ and such, and `inline code` and
243```javascript
244some
245 code blocks
246 with
247 indentation
248```
249
250go visit [mathhub](https://mathhub.info)
251
252- foo
253 - bar
254
255- blubb
256
2571. btw
2581. this works
259 1. too
260 1. and this
2611) and this
262
263foo bar
264"#;