From 2cb1f133a18c488c3ced525aecf46494620d7936 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:10:40 -0700 Subject: [PATCH 1/8] add search to blog landing page --- pgml-dashboard/src/api/cms.rs | 88 ++++++++++- .../cards/blog/article_preview/mod.rs | 16 ++ .../navigation/navbar/marketing/template.html | 4 +- .../pages/blog/blog_search/call/call.scss | 2 + .../blog/blog_search/call/call_controller.js | 20 +++ .../pages/blog/blog_search/call/mod.rs | 14 ++ .../pages/blog/blog_search/call/template.html | 15 ++ .../components/pages/blog/blog_search/mod.rs | 10 ++ .../pages/blog/blog_search/response/mod.rs | 137 +++++++++++++++++ .../blog/blog_search/response/response.scss | 2 + .../response/response_controller.js | 14 ++ .../blog/blog_search/response/template.html | 9 ++ .../components/pages/blog/landing_page/mod.rs | 142 +----------------- .../pages/blog/landing_page/template.html | 24 +-- .../src/components/pages/blog/mod.rs | 3 + .../src/components/search/button/button.scss | 7 - .../components/search/button/template.html | 2 +- pgml-dashboard/static/css/modules.scss | 2 + .../static/css/scss/components/_buttons.scss | 11 +- .../templates/components/search_modal.html | 1 + 20 files changed, 355 insertions(+), 168 deletions(-) create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/call/template.html create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/mod.rs create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js create mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/response/template.html diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index 3f49a934f..0e8d2a3ff 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -21,6 +21,9 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::fmt; +use sailfish::TemplateOnce; +use crate::components::cards::blog::article_preview; + lazy_static! { pub static ref BLOG: Collection = Collection::new( "Blog", @@ -119,6 +122,24 @@ impl Document { Document { ..Default::default() } } + pub async fn from_url(url: &str) -> anyhow::Result { + let doc_type = match url.split('/').collect::>().get(1) { + Some(&"blog") => Some(DocType::Blog), + Some(&"docs") => Some(DocType::Docs), + Some(&"careers") => Some(DocType::Careers), + _ => None, + }; + + let path = match doc_type { + Some(DocType::Blog) => BLOG.url_to_path(url), + Some(DocType::Docs) => DOCS.url_to_path(url), + Some(DocType::Careers) => CAREERS.url_to_path(url), + _ => PathBuf::new(), + }; + + Document::from_path(&path).await + } + pub async fn from_path(path: &PathBuf) -> anyhow::Result { let doc_type = match path.strip_prefix(config::cms_dir()) { Ok(path) => match path.into_iter().next() { @@ -669,6 +690,34 @@ async fn search(query: &str, site_search: &State", rank = 20)] +async fn search_blog(query: &str, site_search: &State) -> ResponseOk { + let results = if query.len() > 0 { + site_search.search(query, Some(DocType::Blog)).await.expect("Error performing search") + .into_iter() + .map(|document| article_preview::DocMeta::from_document(document)) + .collect::>() + + } else { + let mut results = Vec::new(); + + for url in BLOG.get_all_urls() { + let doc = Document::from_url(&url).await.unwrap(); + + results.push(article_preview::DocMeta::from_document(doc)); + } + + results + }; + + ResponseOk( + crate::components::pages::blog::blog_search::Response::new() + .pattern(results, query.to_string()) + .render_once() + .unwrap() + ) +} + #[get("/blog/.gitbook/assets/", rank = 10)] pub async fn get_blog_asset(path: &str) -> Option { BLOG.get_asset(path).await @@ -747,11 +796,43 @@ async fn blog_landing_page(cluster: &Cluster) -> Result>(); + + Ok(ResponseOk( layout.render( crate::components::pages::blog::LandingPage::new(cluster) - .index(&BLOG) - .await, + .featured_cards(featured_cards) ), )) } @@ -802,7 +883,8 @@ pub fn routes() -> Vec { get_docs, get_docs_asset, get_user_guides, - search + search, + search_blog ] } diff --git a/pgml-dashboard/src/components/cards/blog/article_preview/mod.rs b/pgml-dashboard/src/components/cards/blog/article_preview/mod.rs index f222ce46f..1885d46fd 100644 --- a/pgml-dashboard/src/components/cards/blog/article_preview/mod.rs +++ b/pgml-dashboard/src/components/cards/blog/article_preview/mod.rs @@ -17,6 +17,22 @@ pub struct DocMeta { pub path: String, } +impl DocMeta { + pub fn from_document(doc: Document) -> DocMeta { + DocMeta { + description: doc.description, + author: doc.author, + author_image: doc.author_image, + featured: false, + date: doc.date, + tags: doc.tags, + image: doc.image, + title: doc.title, + path: doc.url, + } + } +} + #[derive(TemplateOnce)] #[template(path = "cards/blog/article_preview/template.html")] pub struct ArticlePreview { diff --git a/pgml-dashboard/src/components/navigation/navbar/marketing/template.html b/pgml-dashboard/src/components/navigation/navbar/marketing/template.html index 9902fc527..af250f339 100644 --- a/pgml-dashboard/src/components/navigation/navbar/marketing/template.html +++ b/pgml-dashboard/src/components/navigation/navbar/marketing/template.html @@ -42,7 +42,7 @@
@@ -88,7 +88,7 @@ <% } %> diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss b/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss new file mode 100644 index 000000000..576da71d6 --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss @@ -0,0 +1,2 @@ +div[data-controller="pages-blog-blog-search-call"] { +} diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js b/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js new file mode 100644 index 000000000..61c8c9142 --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js @@ -0,0 +1,20 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = [ + 'searchFrame', + 'searchInput' + ] + static outlets = [] + + connect() { + this.timer + } + + search() { + clearTimeout(this.timer) + this.timer = setTimeout(() => { + this.searchFrameTarget.src = `/search_blog?query=${this.searchInputTarget.value}` + }, 250) + } +} diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs b/pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs new file mode 100644 index 000000000..8f4e41c34 --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs @@ -0,0 +1,14 @@ +use sailfish::TemplateOnce; +use pgml_components::component; + +#[derive(TemplateOnce, Default)] +#[template(path = "pages/blog/blog_search/call/template.html")] +pub struct Call {} + +impl Call { + pub fn new() -> Call { + Call {} + } +} + +component!(Call); diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html new file mode 100644 index 000000000..066ae0f60 --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html @@ -0,0 +1,15 @@ +
+
+
+ + + +
+
+ + + +
diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/mod.rs b/pgml-dashboard/src/components/pages/blog/blog_search/mod.rs new file mode 100644 index 000000000..a58656acc --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/mod.rs @@ -0,0 +1,10 @@ +// This file is automatically generated. +// You shouldn't modify it manually. + +// src/components/pages/blog/blog_search/call +pub mod call; +pub use call::Call; + +// src/components/pages/blog/blog_search/response +pub mod response; +pub use response::Response; diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs b/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs new file mode 100644 index 000000000..09778194c --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs @@ -0,0 +1,137 @@ +use sailfish::TemplateOnce; +use pgml_components::component; +use crate::utils::markdown::SearchResult; +use crate::components::cards::blog::article_preview::{ArticlePreview, DocMeta}; +use crate::api::cms::Document; + +#[derive(TemplateOnce, Default)] +#[template(path = "pages/blog/blog_search/response/template.html")] +pub struct Response { + html: Vec, +} + +impl Response { + pub fn new() -> Response { + Response { + html: Vec::new(), + } + } + + pub fn pattern(mut self, mut articles: Vec, query: String) -> Response { + let mut cycle = 0; + let mut html: Vec = Vec::new(); + + // Apply special layout if the user did not specify a query. + // Blogs are in cms Summary order, make the first post the big card and second long card. + if query.len() == 0 { + let big_index = articles.remove(0); + let long_index = articles.remove(0); + let small_image_index = articles.remove(0); + articles.insert(1, long_index); + articles.insert(2, big_index); + articles.insert(6, small_image_index); + } + + let (layout, repeat) = if query.len() != 0 { + ( + Vec::from([ + Vec::from(["default", "default", "default"]), + Vec::from(["default", "default", "default"]), + Vec::from(["default", "default", "default"]), + Vec::from(["default", "default", "default"]), + ]), + 2, + ) + } else { + ( + Vec::from([ + Vec::from(["default", "long"]), + Vec::from(["big", "default", "default"]), + Vec::from(["default", "show_image", "default"]), + Vec::from(["default", "default", "default"]), + Vec::from(["long", "default"]), + Vec::from(["default", "default", "default"]), + Vec::from(["default", "long"]), + Vec::from(["default", "default", "default"]), + ]), + 4, + ) + }; + + articles.reverse(); + while articles.len() > 0 { + // Get the row pattern or repeat the last two row patterns. + let pattern = match layout.get(cycle) { + Some(pattern) => pattern, + _ => { + let a = cycle - layout.len() + repeat; + &layout[layout.len() - repeat + (a % repeat)] + } + }; + + // if there is enough items to complete the row pattern make the row otherwise just add default cards. + if articles.len() > pattern.len() { + let mut row = Vec::new(); + for _ in 0..pattern.len() { + row.push(articles.pop()) + } + + if pattern[0] != "big" { + for (i, doc) in row.into_iter().enumerate() { + let template = pattern[i]; + html.push( + ArticlePreview::new(&doc.unwrap()) + .card_type(template) + .render_once() + .unwrap(), + ) + } + } else { + html.push(format!( + r#" +
+ {} +
+ {} + {} +
+
+ +
+ {} +
+
+ {} +
+
+ {} +
+ "#, + ArticlePreview::new(&row[0].clone().unwrap()) + .big() + .render_once() + .unwrap(), + ArticlePreview::new(&row[1].clone().unwrap()).render_once().unwrap(), + ArticlePreview::new(&row[2].clone().unwrap()).render_once().unwrap(), + ArticlePreview::new(&row[0].clone().unwrap()).render_once().unwrap(), + ArticlePreview::new(&row[1].clone().unwrap()).render_once().unwrap(), + ArticlePreview::new(&row[2].clone().unwrap()).render_once().unwrap() + )) + } + } else { + html.push( + ArticlePreview::new(&articles.pop().unwrap()) + .card_type("default") + .render_once() + .unwrap(), + ) + } + cycle += 1; + } + + self.html = html; + self + } +} + +component!(Response); diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss b/pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss new file mode 100644 index 000000000..8ce0c8a1f --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss @@ -0,0 +1,2 @@ +div[data-controller="pages-blog-blog-search-response"] { +} diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js b/pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js new file mode 100644 index 000000000..0bc078f09 --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js @@ -0,0 +1,14 @@ +import { Controller } from '@hotwired/stimulus' + +export default class extends Controller { + static targets = [] + static outlets = [] + + initialize() { + console.log('Initialized pages-blog-blog-search-response') + } + + connect() {} + + disconnect() {} +} diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html new file mode 100644 index 000000000..20b499b00 --- /dev/null +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html @@ -0,0 +1,9 @@ + +
+
+ <% for item in html { %> + <%- item %> + <% } %> +
+
+
diff --git a/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs b/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs index ec472607b..2e6ab1c7e 100644 --- a/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs +++ b/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs @@ -11,155 +11,21 @@ use sailfish::TemplateOnce; #[template(path = "pages/blog/landing_page/template.html")] pub struct LandingPage { feature_banner: FeatureBanner, - index: Vec, - is_search: bool, + featured_cards: Vec, } impl LandingPage { pub fn new(context: &Cluster) -> LandingPage { LandingPage { feature_banner: FeatureBanner::from_notification(Notification::next_feature(Some(context))), - index: Vec::new(), - is_search: false, + featured_cards: Vec::new(), } } - pub async fn index(mut self, collection: &Collection) -> Self { - let urls = collection.get_all_urls(); - - for url in urls { - let file = collection.url_to_path(url.as_ref()); - - let doc = crate::api::cms::Document::from_path(&file).await.unwrap(); - - let meta = DocMeta { - description: doc.description, - author: doc.author, - author_image: doc.author_image, - date: doc.date, - image: doc.image, - featured: doc.featured, - tags: doc.tags, - title: doc.title, - path: doc.url, - }; - - self.index.push(meta) - } + pub fn featured_cards(mut self, docs: Vec) -> Self { + self.featured_cards = docs; self } - - pub fn pattern(mut index: Vec, is_search: bool) -> Vec { - let mut cycle = 0; - let mut html: Vec = Vec::new(); - - // blogs are in cms Summary order, make the first post the big card and second long card. - let big_index = index.remove(0); - let long_index = index.remove(0); - let small_image_index = index.remove(0); - index.insert(1, long_index); - index.insert(2, big_index); - index.insert(6, small_image_index); - - let (layout, repeat) = if is_search { - ( - Vec::from([ - Vec::from(["default", "show_image", "default"]), - Vec::from(["default", "default", "default"]), - Vec::from(["show_image", "default", "default"]), - Vec::from(["default", "default", "default"]), - ]), - 2, - ) - } else { - ( - Vec::from([ - Vec::from(["default", "long"]), - Vec::from(["big", "default", "default"]), - Vec::from(["default", "show_image", "default"]), - Vec::from(["default", "default", "default"]), - Vec::from(["long", "default"]), - Vec::from(["default", "default", "default"]), - Vec::from(["default", "long"]), - Vec::from(["default", "default", "default"]), - ]), - 4, - ) - }; - - index.reverse(); - while index.len() > 0 { - // Get the row pattern or repeat the last two row patterns. - let pattern = match layout.get(cycle) { - Some(pattern) => pattern, - _ => { - let a = cycle - layout.len() + repeat; - &layout[layout.len() - repeat + (a % repeat)] - } - }; - - // if there is enough items to complete the row pattern make the row otherwise just add default cards. - if index.len() > pattern.len() { - let mut row = Vec::new(); - for _ in 0..pattern.len() { - row.push(index.pop()) - } - - if pattern[0] != "big" { - for (i, doc) in row.into_iter().enumerate() { - let template = pattern[i]; - html.push( - ArticlePreview::new(&doc.unwrap()) - .card_type(template) - .render_once() - .unwrap(), - ) - } - } else { - html.push(format!( - r#" -
- {} -
- {} - {} -
-
- -
- {} -
-
- {} -
-
- {} -
- "#, - ArticlePreview::new(&row[0].clone().unwrap()) - .big() - .render_once() - .unwrap(), - ArticlePreview::new(&row[1].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[2].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[0].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[1].clone().unwrap()).render_once().unwrap(), - ArticlePreview::new(&row[2].clone().unwrap()).render_once().unwrap() - )) - } - } else { - html.push( - ArticlePreview::new(&index.pop().unwrap()) - .card_type("default") - .render_once() - .unwrap(), - ) - } - cycle += 1; - } - - html - } } component!(LandingPage); diff --git a/pgml-dashboard/src/components/pages/blog/landing_page/template.html b/pgml-dashboard/src/components/pages/blog/landing_page/template.html index 2182ab0b8..b3b2e4188 100644 --- a/pgml-dashboard/src/components/pages/blog/landing_page/template.html +++ b/pgml-dashboard/src/components/pages/blog/landing_page/template.html @@ -3,17 +3,13 @@ use crate::components::cards::blog::ArticlePreview; use crate::components::pages::blog::LandingPage; use crate::components::sections::common_resources::{Cards, CommonResources}; + use crate::components::pages::blog::blog_search::call::Call as BlogSearchCall; - let featured_cards = index - .clone() - .into_iter() - .filter(|x| x - .featured) - .map(|x| ArticlePreview::new(&x) - .featured() - .render_once() - .unwrap()) - .collect::>(); + + + let cards = featured_cards.iter().map(|card| { + ArticlePreview::new(card).featured().render_once().unwrap() + }).collect::>(); %>
@@ -34,15 +30,11 @@

PostgresML Blog

- <%+ Carousel::new(featured_cards) %> + <%+ Carousel::new(cards) %>
-
- <% for doc in LandingPage::pattern(index.clone(), is_search) {%> - <%- doc %> - <% } %> -
+ <%+ BlogSearchCall::new() %>
diff --git a/pgml-dashboard/src/components/pages/blog/mod.rs b/pgml-dashboard/src/components/pages/blog/mod.rs index 4cfb933ea..26eb7f93a 100644 --- a/pgml-dashboard/src/components/pages/blog/mod.rs +++ b/pgml-dashboard/src/components/pages/blog/mod.rs @@ -1,6 +1,9 @@ // This file is automatically generated. // You shouldn't modify it manually. +// src/components/pages/blog/blog_search +pub mod blog_search; + // src/components/pages/blog/landing_page pub mod landing_page; pub use landing_page::LandingPage; diff --git a/pgml-dashboard/src/components/search/button/button.scss b/pgml-dashboard/src/components/search/button/button.scss index 51f36b250..7d61d95b7 100644 --- a/pgml-dashboard/src/components/search/button/button.scss +++ b/pgml-dashboard/src/components/search/button/button.scss @@ -1,9 +1,2 @@ div[data-controller="search-button"] { - .input { - background: linear-gradient(265deg, #212224 20.41%, #17181A 83.75%); - } - - .input-text { - color: #{$gray-300}; - } } diff --git a/pgml-dashboard/src/components/search/button/template.html b/pgml-dashboard/src/components/search/button/template.html index 0c1fc646f..2add2f5e9 100644 --- a/pgml-dashboard/src/components/search/button/template.html +++ b/pgml-dashboard/src/components/search/button/template.html @@ -1,5 +1,5 @@
-
From 5d59fcf9e3329ce14fc0cd23613c4eec78a5c688 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:03:22 -0700 Subject: [PATCH 3/8] add search --- pgml-dashboard/src/api/cms.rs | 71 +++++++++++-------- .../pages/blog/blog_search/call/call.scss | 21 +++++- .../blog/blog_search/call/call_controller.js | 42 ++++++++++- .../pages/blog/blog_search/call/mod.rs | 2 +- .../pages/blog/blog_search/call/template.html | 26 +++++-- .../pages/blog/blog_search/response/mod.rs | 34 ++++----- .../blog/blog_search/response/template.html | 12 +++- .../components/pages/blog/landing_page/mod.rs | 2 - .../pages/blog/landing_page/template.html | 1 - pgml-dashboard/src/utils/markdown.rs | 2 +- 10 files changed, 146 insertions(+), 67 deletions(-) diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index 6fdeddc74..27622de01 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -21,8 +21,8 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::fmt; -use sailfish::TemplateOnce; use crate::components::cards::blog::article_preview; +use sailfish::TemplateOnce; lazy_static! { pub static ref BLOG: Collection = Collection::new( @@ -694,32 +694,47 @@ async fn search(query: &str, site_search: &State", rank = 20)] -async fn search_blog(query: &str, site_search: &State) -> ResponseOk { - let results = if query.len() > 0 { - site_search.search(query, Some(DocType::Blog)).await.expect("Error performing search") - .into_iter() - .map(|document| article_preview::DocMeta::from_document(document)) - .collect::>() +#[get("/search_blog?&", rank = 20)] +async fn search_blog(query: &str, tag: &str, site_search: &State) -> ResponseOk { + let tag = if tag.len() > 0 { + Some(Vec::from([tag.to_string()])) + } else { + None + }; + + // If user is not makign a search return all blogs in default design. + let results = if query.len() > 0 || tag.clone().is_some() { + let results = site_search.search(query, Some(DocType::Blog), tag.clone()).await; + + let results = match results { + Ok(results) => results + .into_iter() + .map(|document| article_preview::DocMeta::from_document(document)) + .collect::>(), + Err(_) => Vec::new(), + }; - } else { - let mut results = Vec::new(); + results + } else { + let mut results = Vec::new(); - for url in BLOG.get_all_urls() { - let doc = Document::from_url(&url).await.unwrap(); + for url in BLOG.get_all_urls() { + let doc = Document::from_url(&url).await.unwrap(); - results.push(article_preview::DocMeta::from_document(doc)); - } + results.push(article_preview::DocMeta::from_document(doc)); + } - results - }; + results + }; + + let is_search = query.len() > 0 || tag.is_some(); ResponseOk( - crate::components::pages::blog::blog_search::Response::new() - .pattern(results, query.to_string()) - .render_once() - .unwrap() - ) + crate::components::pages::blog::blog_search::Response::new() + .pattern(results, is_search) + .render_once() + .unwrap(), + ) } #[get("/blog/.gitbook/assets/", rank = 10)] @@ -800,7 +815,6 @@ async fn blog_landing_page(cluster: &Cluster) -> Result Result>(); - - Ok(ResponseOk( - layout.render( - crate::components::pages::blog::LandingPage::new(cluster) - .featured_cards(featured_cards) - ), - )) + Ok(ResponseOk(layout.render( + crate::components::pages::blog::LandingPage::new(cluster).featured_cards(featured_cards), + ))) } #[get("/docs")] diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss b/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss index 9a9c9708e..139c824eb 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss @@ -1,7 +1,26 @@ div[data-controller="pages-blog-blog-search-call"] { - button { + .btn-primary { @include media-breakpoint-down(md) { padding: 12px 16px; } } + + .btn-tag { + border: 2px solid #{$gray-200}; + background-color: transparent; + color: #{$gray-200}; + + &.selected{ + background-color: #{$gray-100}; + border-color: #{$gray-100}; + color: #{$gray-900}; + } + + &:hover:not(.all-tags), &:hover:not(.selected):is(.all-tags) { + background-color: transparent; + color: #{$gray-100}; + border-color: #{$gray-100}; + @include bold_by_shadow(var(#{$gray-100})); + } + } } diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js b/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js index 562de72e5..0075fab59 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/call_controller.js @@ -3,19 +3,55 @@ import { Controller } from '@hotwired/stimulus' export default class extends Controller { static targets = [ 'searchFrame', - 'searchInput' + 'searchInput', + 'tagLink', + 'removeTags' ] + + static classes = ["selected"] + static outlets = [] connect() { this.timer + this.tags = "" } search() { clearTimeout(this.timer) this.timer = setTimeout(() => { - this.searchFrameTarget.src = `/search_blog?query=${this.searchInputTarget.value}` - this.searchInputTarget.value = "" + this.searchFrameTarget.src = `/search_blog?query=${this.searchInputTarget.value}&tag=${this.tags}` }, 250) } + + tag(e) { + if( e.target.classList.contains(this.selectedClass) ) { + e.target.classList.remove(this.selectedClass) + this.tags = "" + this.removeTagsTarget.classList.add(this.selectedClass) + } else { + e.target.classList.add(this.selectedClass) + this.tags = e.params.tag + this.removeTagsTarget.classList.remove(this.selectedClass) + } + + for( let tag of this.tagLinkTargets) { + if( tag != e.target) { + tag.classList.remove(this.selectedClass) + } + } + + this.search() + } + + removeTags() { + for( let tag of this.tagLinkTargets) { + tag.classList.remove(this.selectedClass) + } + + this.removeTagsTarget.classList.add(this.selectedClass) + + this.tags = "" + this.search() + } } diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs b/pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs index 8f4e41c34..abb15fd14 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/mod.rs @@ -1,5 +1,5 @@ -use sailfish::TemplateOnce; use pgml_components::component; +use sailfish::TemplateOnce; #[derive(TemplateOnce, Default)] #[template(path = "pages/blog/blog_search/call/template.html")] diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html index 06b4c5ba2..70691b407 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html @@ -1,6 +1,24 @@ -
-
-
+<% + // leave out Company and Customer Stories for until tags are consistently used in blog posts + let tag_links = Vec::from([ + "Engineering", + "Product", + // "Company", + // "Customer Stories", + ]); + + let selected_class = "selected"; +%> + +
+
+
+ + <% for tag in tag_links {%> + + <% } %> +
+
- +
diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs b/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs index 09778194c..ac8a89af1 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/mod.rs @@ -1,8 +1,6 @@ -use sailfish::TemplateOnce; -use pgml_components::component; -use crate::utils::markdown::SearchResult; use crate::components::cards::blog::article_preview::{ArticlePreview, DocMeta}; -use crate::api::cms::Document; +use pgml_components::component; +use sailfish::TemplateOnce; #[derive(TemplateOnce, Default)] #[template(path = "pages/blog/blog_search/response/template.html")] @@ -12,27 +10,14 @@ pub struct Response { impl Response { pub fn new() -> Response { - Response { - html: Vec::new(), - } + Response { html: Vec::new() } } - pub fn pattern(mut self, mut articles: Vec, query: String) -> Response { + pub fn pattern(mut self, mut articles: Vec, is_search: bool) -> Response { let mut cycle = 0; let mut html: Vec = Vec::new(); - // Apply special layout if the user did not specify a query. - // Blogs are in cms Summary order, make the first post the big card and second long card. - if query.len() == 0 { - let big_index = articles.remove(0); - let long_index = articles.remove(0); - let small_image_index = articles.remove(0); - articles.insert(1, long_index); - articles.insert(2, big_index); - articles.insert(6, small_image_index); - } - - let (layout, repeat) = if query.len() != 0 { + let (layout, repeat) = if is_search { ( Vec::from([ Vec::from(["default", "default", "default"]), @@ -43,6 +28,15 @@ impl Response { 2, ) } else { + // Apply special layout if the user did not specify a query. + // Blogs are in cms Summary order, make the first post the big card and second long card. + let big_index = articles.remove(0); + let long_index = articles.remove(0); + let small_image_index = articles.remove(0); + articles.insert(1, long_index); + articles.insert(2, big_index); + articles.insert(6, small_image_index); + ( Vec::from([ Vec::from(["default", "long"]), diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html index 20b499b00..595e1501c 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html @@ -1,8 +1,14 @@
-
- <% for item in html { %> - <%- item %> +
+ <% if html.len() > 0 {%> + <% for item in html { %> + <%- item %> + <% } %> + <% } else {%> +
+
No blogs satisfy that search
+
<% } %>
diff --git a/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs b/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs index 2e6ab1c7e..3b37769c0 100644 --- a/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs +++ b/pgml-dashboard/src/components/pages/blog/landing_page/mod.rs @@ -1,6 +1,4 @@ -use crate::api::cms::Collection; use crate::components::cards::blog::article_preview::DocMeta; -use crate::components::cards::blog::ArticlePreview; use crate::components::notifications::marketing::FeatureBanner; use crate::guards::Cluster; use crate::Notification; diff --git a/pgml-dashboard/src/components/pages/blog/landing_page/template.html b/pgml-dashboard/src/components/pages/blog/landing_page/template.html index b3b2e4188..cdabe3541 100644 --- a/pgml-dashboard/src/components/pages/blog/landing_page/template.html +++ b/pgml-dashboard/src/components/pages/blog/landing_page/template.html @@ -1,7 +1,6 @@ <% use crate::components::Carousel; use crate::components::cards::blog::ArticlePreview; - use crate::components::pages::blog::LandingPage; use crate::components::sections::common_resources::{Cards, CommonResources}; use crate::components::pages::blog::blog_search::call::Call as BlogSearchCall; diff --git a/pgml-dashboard/src/utils/markdown.rs b/pgml-dashboard/src/utils/markdown.rs index 6872c509e..424dc81e0 100644 --- a/pgml-dashboard/src/utils/markdown.rs +++ b/pgml-dashboard/src/utils/markdown.rs @@ -21,7 +21,7 @@ use std::sync::Mutex; use url::Url; // Excluded paths in the pgml-cms directory -const EXCLUDED_DOCUMENT_PATHS: [&str; 1] = ["blog/README.md"]; +const EXCLUDED_DOCUMENT_PATHS: [&str; 2] = ["blog/README.md", "blog/SUMMARY.md"]; pub struct MarkdownHeadings { header_map: Arc>>, From 5007ffb3b8317b13e2131b77ff73d63769db71ec Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:30:49 -0700 Subject: [PATCH 4/8] add active state for tag buttons --- pgml-dashboard/src/api/cms.rs | 4 +++- .../src/components/pages/blog/blog_search/call/call.scss | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index 27622de01..b647bef2e 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -123,6 +123,7 @@ impl Document { Document { ..Default::default() } } + // make a document from a uri of form /< path and file name > pub async fn from_url(url: &str) -> anyhow::Result { let doc_type = match url.split('/').collect::>().get(1) { Some(&"blog") => Some(DocType::Blog), @@ -971,8 +972,9 @@ This is the end of the markdown async fn rocket() -> Rocket { dotenv::dotenv().ok(); + rocket::build() - // .manage(crate::utils::markdown::SearchIndex::open().unwrap()) + // .manage(crate::utils::markdown::SiteSearch::new().await.expect("Error initializing site search")) .mount("/", crate::api::cms::routes()) } diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss b/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss index 139c824eb..96ed0721d 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/call.scss @@ -22,5 +22,11 @@ div[data-controller="pages-blog-blog-search-call"] { border-color: #{$gray-100}; @include bold_by_shadow(var(#{$gray-100})); } + + &:active:not(.all-tags), &:active:not(.selected):is(.all-tags){ + background-color: #{$gray-200}; + border-color: #{$gray-200}; + color: #{$gray-900}; + } } } From ac05a0dad9ed773236fa5d505318c5f14f5da46b Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:56:21 -0700 Subject: [PATCH 5/8] remove dead code, clean up --- pgml-dashboard/src/api/cms.rs | 18 ++---------------- .../article_preview_controller.js | 12 ------------ .../cards/blog/article_preview/mod.rs | 15 ++------------- .../response/response_controller.js | 14 -------------- 4 files changed, 4 insertions(+), 55 deletions(-) delete mode 100644 pgml-dashboard/src/components/cards/blog/article_preview/article_preview_controller.js delete mode 100644 pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index b647bef2e..da0a91e85 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -821,22 +821,8 @@ async fn blog_landing_page(cluster: &Cluster) -> Result ArticlePreview { let doc = Document::from_path(&PathBuf::from(path)).await.unwrap(); - - let meta = DocMeta { - description: doc.description, - author: doc.author, - author_image: doc.author_image, - featured: false, - date: doc.date, - tags: doc.tags, - image: doc.image, - title: doc.title, - path: doc.url, - }; + let meta = DocMeta::from_document(doc); ArticlePreview::new(&meta) } } diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js b/pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js deleted file mode 100644 index 0bc078f09..000000000 --- a/pgml-dashboard/src/components/pages/blog/blog_search/response/response_controller.js +++ /dev/null @@ -1,14 +0,0 @@ -import { Controller } from '@hotwired/stimulus' - -export default class extends Controller { - static targets = [] - static outlets = [] - - initialize() { - console.log('Initialized pages-blog-blog-search-response') - } - - connect() {} - - disconnect() {} -} From 4dc139e8fbfae4740a0344c15f4f729c93ee45a2 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:24:41 -0700 Subject: [PATCH 6/8] move loading to dashboard, use loading for search --- .../src/components/loading/dots/dots.scss | 37 +++++++++++++++++++ .../src/components/loading/dots/mod.rs | 14 +++++++ .../src/components/loading/dots/template.html | 8 ++++ .../components/loading/message/message.scss | 1 + .../src/components/loading/message/mod.rs | 23 ++++++++++++ .../components/loading/message/template.html | 5 +++ pgml-dashboard/src/components/loading/mod.rs | 10 +++++ pgml-dashboard/src/components/mod.rs | 3 ++ .../pages/blog/blog_search/call/template.html | 6 ++- .../blog/blog_search/response/response.scss | 21 +++++++++++ .../blog/blog_search/response/template.html | 11 +++++- pgml-dashboard/static/css/modules.scss | 2 + 12 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 pgml-dashboard/src/components/loading/dots/dots.scss create mode 100644 pgml-dashboard/src/components/loading/dots/mod.rs create mode 100644 pgml-dashboard/src/components/loading/dots/template.html create mode 100644 pgml-dashboard/src/components/loading/message/message.scss create mode 100644 pgml-dashboard/src/components/loading/message/mod.rs create mode 100644 pgml-dashboard/src/components/loading/message/template.html create mode 100644 pgml-dashboard/src/components/loading/mod.rs diff --git a/pgml-dashboard/src/components/loading/dots/dots.scss b/pgml-dashboard/src/components/loading/dots/dots.scss new file mode 100644 index 000000000..ad1e4c6ad --- /dev/null +++ b/pgml-dashboard/src/components/loading/dots/dots.scss @@ -0,0 +1,37 @@ +div { + @mixin loading-dot($delay, $initial) { + width: 30px; + height: 30px; + opacity: $initial; + border-radius: 30px; + background-color: #{$gray-100}; + animation: opacity 3s infinite linear; + animation-delay: $delay; + } + + .loading-dot-1 { + @include loading-dot(0s, 0.1); + } + + .loading-dot-2 { + @include loading-dot(0.5s, 0.2); + } + + .loading-dot-3 { + @include loading-dot(1s, 0.3); + } + + @keyframes opacity { + 0% { + opacity: 0.1; + } + + 75% { + opacity: 1; + } + + 100% { + opacity: 0.1; + } + } +} diff --git a/pgml-dashboard/src/components/loading/dots/mod.rs b/pgml-dashboard/src/components/loading/dots/mod.rs new file mode 100644 index 000000000..096fe857d --- /dev/null +++ b/pgml-dashboard/src/components/loading/dots/mod.rs @@ -0,0 +1,14 @@ +use pgml_components::component; +use sailfish::TemplateOnce; + +#[derive(TemplateOnce, Default)] +#[template(path = "loading/dots/template.html")] +pub struct Dots {} + +impl Dots { + pub fn new() -> Dots { + Dots {} + } +} + +component!(Dots); diff --git a/pgml-dashboard/src/components/loading/dots/template.html b/pgml-dashboard/src/components/loading/dots/template.html new file mode 100644 index 000000000..be10399d6 --- /dev/null +++ b/pgml-dashboard/src/components/loading/dots/template.html @@ -0,0 +1,8 @@ +
+
+
+
+
+
+
+
diff --git a/pgml-dashboard/src/components/loading/message/message.scss b/pgml-dashboard/src/components/loading/message/message.scss new file mode 100644 index 000000000..af1916ba3 --- /dev/null +++ b/pgml-dashboard/src/components/loading/message/message.scss @@ -0,0 +1 @@ +div[data-controller="loading-message"] {} diff --git a/pgml-dashboard/src/components/loading/message/mod.rs b/pgml-dashboard/src/components/loading/message/mod.rs new file mode 100644 index 000000000..eabb3ea2a --- /dev/null +++ b/pgml-dashboard/src/components/loading/message/mod.rs @@ -0,0 +1,23 @@ +use sailfish::TemplateOnce; +use pgml_components::component; + +#[derive(TemplateOnce, Default)] +#[template(path = "loading/message/template.html")] +pub struct Message { + message: String, +} + +impl Message { + pub fn new() -> Message { + Message { + message: String::from("Loading..."), + } + } + + pub fn message(mut self, message: &str) -> Message { + self.message = String::from(message); + self + } +} + +component!(Message); diff --git a/pgml-dashboard/src/components/loading/message/template.html b/pgml-dashboard/src/components/loading/message/template.html new file mode 100644 index 000000000..5784628d6 --- /dev/null +++ b/pgml-dashboard/src/components/loading/message/template.html @@ -0,0 +1,5 @@ +<% use crate::components::loading::Dots; %> +
+ <%+ Dots::new() %> +
<%- message %>
+
diff --git a/pgml-dashboard/src/components/loading/mod.rs b/pgml-dashboard/src/components/loading/mod.rs new file mode 100644 index 000000000..cb7c6ca4d --- /dev/null +++ b/pgml-dashboard/src/components/loading/mod.rs @@ -0,0 +1,10 @@ +// This file is automatically generated. +// You shouldn't modify it manually. + +// src/components/loading/dots +pub mod dots; +pub use dots::Dots; + +// src/components/loading/message +pub mod message; +pub use message::Message; diff --git a/pgml-dashboard/src/components/mod.rs b/pgml-dashboard/src/components/mod.rs index aa845f074..b11068a79 100644 --- a/pgml-dashboard/src/components/mod.rs +++ b/pgml-dashboard/src/components/mod.rs @@ -52,6 +52,9 @@ pub use left_nav_menu::LeftNavMenu; // src/components/lists pub mod lists; +// src/components/loading +pub mod loading; + // src/components/modal pub mod modal; pub use modal::Modal; diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html index 70691b407..33005f15b 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html @@ -1,4 +1,6 @@ <% + use crate::components::loading::Message as Loading; + // leave out Company and Customer Stories for until tags are consistently used in blog posts let tag_links = Vec::from([ "Engineering", @@ -28,6 +30,8 @@
- + + + <%+ Loading::new().message("Fetching all blogs") %>
diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss b/pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss index 8ce0c8a1f..3290b6734 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/response.scss @@ -1,2 +1,23 @@ div[data-controller="pages-blog-blog-search-response"] { + } + +turbo-frame.blog-frame { + .loading { + display: none; + } + + .content { + display: block; + } +} + +turbo-frame[aria-busy="true"].blog-frame { + .loading { + display: block; + } + .content { + display: none; + } +} + diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html index 595e1501c..7078d0739 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html @@ -1,5 +1,8 @@ - -
+<% + use crate::components::loading::Message as Loading; +%> + +
<% if html.len() > 0 {%> <% for item in html { %> @@ -12,4 +15,8 @@
No blogs satisfy that search
<% } %>
+ +
+ <%+ Loading::new().message("Searching ...") %> +
diff --git a/pgml-dashboard/static/css/modules.scss b/pgml-dashboard/static/css/modules.scss index e170a8d0e..71d508af8 100644 --- a/pgml-dashboard/static/css/modules.scss +++ b/pgml-dashboard/static/css/modules.scss @@ -17,6 +17,8 @@ @import "../../src/components/layouts/docs/docs.scss"; @import "../../src/components/layouts/marketing/base/base.scss"; @import "../../src/components/left_nav_menu/left_nav_menu.scss"; +@import "../../src/components/loading/dots/dots.scss"; +@import "../../src/components/loading/message/message.scss"; @import "../../src/components/modal/modal.scss"; @import "../../src/components/navigation/dropdown_link/dropdown_link.scss"; @import "../../src/components/navigation/left_nav/docs/docs.scss"; From 8e699cf52192bc0178d95cd37dd3b699ac032fe2 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Tue, 5 Mar 2024 16:38:28 -0700 Subject: [PATCH 7/8] fmt --- pgml-dashboard/src/api/cms.rs | 2 +- pgml-dashboard/templates/components/search_modal.html | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pgml-dashboard/src/api/cms.rs b/pgml-dashboard/src/api/cms.rs index da0a91e85..1b1978b05 100644 --- a/pgml-dashboard/src/api/cms.rs +++ b/pgml-dashboard/src/api/cms.rs @@ -703,7 +703,7 @@ async fn search_blog(query: &str, tag: &str, site_search: &State 0 || tag.clone().is_some() { let results = site_search.search(query, Some(DocType::Blog), tag.clone()).await; diff --git a/pgml-dashboard/templates/components/search_modal.html b/pgml-dashboard/templates/components/search_modal.html index e60555450..15d148b25 100644 --- a/pgml-dashboard/templates/components/search_modal.html +++ b/pgml-dashboard/templates/components/search_modal.html @@ -14,4 +14,3 @@
- \ No newline at end of file From b3f4146e5b9f5d5e17fc862cb2e713efc3ea29bc Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:52:40 -0700 Subject: [PATCH 8/8] add some spacing to blog loading --- .../src/components/pages/blog/blog_search/call/template.html | 4 +++- .../components/pages/blog/blog_search/response/template.html | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html index 33005f15b..b81ac297c 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html +++ b/pgml-dashboard/src/components/pages/blog/blog_search/call/template.html @@ -32,6 +32,8 @@ - <%+ Loading::new().message("Fetching all blogs") %> +
+ <%+ Loading::new().message("Fetching all blogs") %> +
diff --git a/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html index 7078d0739..66c39402a 100644 --- a/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html +++ b/pgml-dashboard/src/components/pages/blog/blog_search/response/template.html @@ -17,6 +17,8 @@
No blogs satisfy that search
- <%+ Loading::new().message("Searching ...") %> +
+ <%+ Loading::new().message("Searching ...") %> +