Skip to content

Commit de1c217

Browse files
committed
feat(website): added plugins registry
1 parent f35ac4f commit de1c217

File tree

12 files changed

+198
-11
lines changed

12 files changed

+198
-11
lines changed

.github/workflows/website.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
with:
2020
fetch-depth: 0
2121
- run: cargo install bonnie wasm-pack
22-
- run: cargo install perseus-cli --version 0.3.0-beta.15
22+
- run: cargo install perseus-cli --version 0.3.0-beta.17
2323
- run: npm install
2424
working-directory: website
2525
- name: Build website

website/Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "perseus-website"
3-
version = "0.3.0-beta.16"
3+
version = "0.3.0-beta.17"
44
edition = "2018"
55
description = "The official website for the Perseus framework."
66
authors = ["arctic_hen7 <[email protected]>"]
@@ -13,7 +13,7 @@ readme = "./README.md"
1313

1414
[dependencies]
1515
# We use the current version of Perseus, not the local one
16-
perseus = { version = "0.3.0-beta.16", features = [ "translator-fluent" ] }
16+
perseus = { version = "0.3.0-beta.17", features = [ "translator-fluent" ] }
1717
sycamore = "0.6"
1818
sycamore-router = "0.6"
1919
serde = "1"

website/plugins/en-US/size_opt.json

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "Size Optimizations Plugin",
3+
"description": "Applies size optimizations automatically to make your app smaller and faster in production.",
4+
"author": "arctic-hen7",
5+
"url": "https://github.com/arctic-hen7/perseus-size-opt",
6+
"trusted": true
7+
}

website/src/components/container.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,20 @@ use perseus::{link, t};
22
use sycamore::prelude::Template as SycamoreTemplate;
33
use sycamore::prelude::*;
44

5+
// This is imported by all alternative containers as well
56
pub static COPYRIGHT_YEARS: &str = "2021";
67

78
#[component(NavLinks<G>)]
89
pub fn nav_links() -> SycamoreTemplate<G> {
910
template! {
1011
li(class = "m-3 p-1") {
11-
a(href = link!("/docs"), class = "px-2") { (t!("navlinks-docs")) }
12+
a(href = link!("/docs"), class = "px-2") { (t!("navlinks.docs")) }
1213
}
1314
li(class = "m-3 p-1") {
14-
a(href = link!("/comparisons"), class = "px-2") { (t!("navlinks-comparisons")) }
15+
a(href = link!("/comparisons"), class = "px-2") { (t!("navlinks.comparisons")) }
16+
}
17+
li(class = "m-3 p-1") {
18+
a(href = link!("/plugins"), class = "px-2") { (t!("navlinks.plugins")) }
1519
}
1620
}
1721
}

website/src/components/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod container;
33
pub mod features_list;
44
pub mod github_svg;
55
pub mod info_svg;
6+
pub mod trusted_svg;

website/src/components/trusted_svg.rs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub static TRUSTED_SVG: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="currentColor"><g><rect fill="none" height="24" width="24"/><rect fill="none" height="24" width="24"/></g><g><path d="M23,12l-2.44-2.79l0.34-3.69l-3.61-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,12l2.44,2.79l-0.34,3.7 l3.61,0.82L8.6,22.5l3.4-1.47l3.4,1.46l1.89-3.19l3.61-0.82l-0.34-3.69L23,12z M9.38,16.01L7,13.61c-0.39-0.39-0.39-1.02,0-1.41 l0.07-0.07c0.39-0.39,1.03-0.39,1.42,0l1.61,1.62l5.15-5.16c0.39-0.39,1.03-0.39,1.42,0l0.07,0.07c0.39,0.39,0.39,1.02,0,1.41 l-5.92,5.94C10.41,16.4,9.78,16.4,9.38,16.01z"/></g></svg>"#;

website/src/lib.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ define_app! {
99
templates: [
1010
templates::index::get_template::<G>(),
1111
templates::comparisons::get_template::<G>(),
12-
templates::docs::get_template::<G>()
12+
templates::docs::get_template::<G>(),
13+
templates::plugins::get_template::<G>()
1314
],
1415
error_pages: error_pages::get_error_pages(),
1516
locales: {

website/src/templates/docs/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
mod container;
2-
mod generation;
2+
pub mod generation; // This needs to be public so that we can reuse the `parse_md_to_html` function
33
mod get_file_at_version;
44
mod icons;
55
mod template;

website/src/templates/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod comparisons;
22
pub mod docs;
33
pub mod index;
4+
pub mod plugins;

website/src/templates/plugins.rs

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
// The structure of the `plugins` directory is to have a subdirectory for each locale, and then a list of plugins inside
2+
// Note that the same plugins must be defined for every locale
3+
4+
use crate::components::container::{Container, ContainerProps};
5+
use crate::components::trusted_svg::TRUSTED_SVG;
6+
use perseus::{t, RenderFnResultWithCause, Template};
7+
use serde::{Deserialize, Serialize};
8+
use std::fs;
9+
use sycamore::prelude::Template as SycamoreTemplate;
10+
use sycamore::prelude::*;
11+
use walkdir::WalkDir;
12+
use wasm_bindgen::JsCast;
13+
use web_sys::HtmlInputElement;
14+
15+
#[derive(Serialize, Deserialize)]
16+
struct PluginsPageProps {
17+
/// The list of plugins with minimal details. These will be displayed in cards on the
18+
/// index page.
19+
plugins: Vec<PluginDetails>,
20+
}
21+
/// The minimal amount of details for a plugin, which will be displayed in a card on the
22+
/// root page. This is a subset of `PluginDetails` (except for the `slug`). This needs to be `Eq` for Sycamore's keyed list diffing algorithm.
23+
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
24+
struct PluginDetails {
25+
/// The plugins's name.
26+
name: String,
27+
/// A short description of the plugin.
28+
description: String,
29+
/// The author of the plugin.
30+
author: String,
31+
/// Whether or not the plugin is trusted by the Perseus development team. Note that this is just a superficial measure, and it does not indicate
32+
/// security, audit status, or anything else of the like. It should NOT be relied on when deciding whether or not a plugin is secure!
33+
trusted: bool,
34+
/// The plugin's home URL, which the plugins registry will redirect the user to. This avoids developers having to update documentation in many places
35+
/// or ask for the website to be rebuilt every time their READMEs change.
36+
url: String,
37+
}
38+
39+
#[perseus::template(PluginsPage)]
40+
#[component(PluginsPage<G>)]
41+
fn plugins_page(props: PluginsPageProps) -> SycamoreTemplate<G> {
42+
let plugins = Signal::new(props.plugins);
43+
// This will store the plugins relevant to the user's search (all of them by
44+
// This stores the search that the user provides
45+
let filter = Signal::new(String::new());
46+
// A derived state that will filter the plugins that the user searches for
47+
let filtered_plugins = create_memo(cloned!((plugins, filter) => move || {
48+
plugins.get().iter().filter(|plugin| {
49+
let filter_text = &*filter.get().to_lowercase();
50+
plugin.name.to_lowercase().contains(filter_text) ||
51+
plugin.author.to_lowercase().contains(filter_text) ||
52+
plugin.description.to_lowercase().contains(filter_text)
53+
}).cloned().collect::<Vec<PluginDetails>>()
54+
}));
55+
// This renders a single plugin card
56+
let plugin_renderer = |plugin: PluginDetails| {
57+
let PluginDetails {
58+
name,
59+
description,
60+
author,
61+
trusted,
62+
url,
63+
} = plugin;
64+
template! {
65+
li(class = "inline-block align-top m-2") {
66+
a(
67+
class = "block text-left cursor-pointer rounded-xl shadow-md hover:shadow-2xl transition-shadow duration-100 p-8 max-w-sm dark:text-white",
68+
href = &url // This is an external link to the plugin's homepage
69+
) {
70+
p(class = "text-xl xs:text-2xl inline-flex") {
71+
(name)
72+
(if trusted {
73+
template! {
74+
div(class = "ml-1 self-center", dangerously_set_inner_html = TRUSTED_SVG)
75+
}
76+
} else {
77+
SycamoreTemplate::empty()
78+
})
79+
}
80+
p(class = "text-sm text-gray-500 dark:text-gray-300 mb-1") { (t!("plugin-card-author", { "author": author.clone() })) }
81+
p { (description) }
82+
}
83+
}
84+
}
85+
};
86+
87+
template! {
88+
Container(ContainerProps {
89+
title: t!("perseus"),
90+
children: template! {
91+
div(class = "mt-14 xs:mt-16 sm:mt-20 lg:mt-25 dark:text-white") {
92+
div(class = "w-full flex flex-col justify-center text-center") {
93+
h1(class = "text-5xl xs:text-7xl sm:text-8xl font-extrabold mb-5") { (t!("plugins-title")) }
94+
br()
95+
p(class = "mx-1") { (t!("plugins-desc")) }
96+
// TODO Remove `hidden` class once next Sycamore version is released and search bar works again
97+
input(class = "mx-2 sm:mx-4 md:mx-8 p-3 rounded-lg border-2 border-indigo-600 mb-3 dark:bg-navy hidden", on:input = cloned!((filter) => move |ev| {
98+
// This longwinded code gets the actual value that the user typed in
99+
let target: HtmlInputElement = ev.target().unwrap().unchecked_into();
100+
let new_input = target.value();
101+
filter.set(new_input);
102+
}), placeholder = t!("plugin-search.placeholder"))
103+
}
104+
div(class = "w-full flex justify-center") {
105+
ul(class = "text-center w-full max-w-7xl mx-2 mb-16") {
106+
Indexed(IndexedProps {
107+
iterable: filtered_plugins,
108+
template: plugin_renderer
109+
})
110+
}
111+
}
112+
}
113+
}
114+
})
115+
}
116+
}
117+
118+
#[perseus::head]
119+
fn head() -> SycamoreTemplate<SsrNode> {
120+
template! {
121+
title { (format!("{} | {}", t!("plugins-title"), t!("perseus"))) }
122+
link(rel = "stylesheet", href = ".perseus/static/styles/markdown.css")
123+
}
124+
}
125+
126+
pub fn get_template<G: GenericNode>() -> Template<G> {
127+
Template::new("plugins")
128+
.template(plugins_page)
129+
.head(head)
130+
.build_state_fn(get_build_state)
131+
}
132+
133+
#[perseus::autoserde(build_state)]
134+
async fn get_build_state(
135+
_path: String,
136+
locale: String,
137+
) -> RenderFnResultWithCause<PluginsPageProps> {
138+
// This is the root page, so we want a list of plugins and a small amount of information about each
139+
// This directory loop is relative to `.perseus/`
140+
let mut plugins = Vec::new();
141+
for entry in WalkDir::new(&format!("../plugins/{}", locale)) {
142+
let entry = entry?;
143+
let path = entry.path();
144+
// Ignore any empty directories or the like
145+
if path.is_file() {
146+
// Get the JSON contents and parse them as plugin details
147+
let contents = fs::read_to_string(&path)?;
148+
let details = serde_json::from_str::<PluginDetails>(&contents)?;
149+
150+
plugins.push(details);
151+
}
152+
}
153+
154+
Ok(PluginsPageProps { plugins })
155+
}

website/static/styles/markdown.css

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@
99
font-weight: 900;
1010
}
1111
.markdown a {
12-
color: #4f46e5;
12+
color: #4f46e5; /* Tailwind's `text-indigo-600` */
1313
text-decoration: underline;
1414
/* Animate between colors to and from the hover state */
1515
transition-property: background-color, border-color, color, fill, stroke;
1616
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1717
transition-duration: 100ms;
1818
}
1919
.markdown a:hover {
20-
color: #6366f1;
20+
color: #6366f1; /* Tailwind's `text-indigo-500` */
2121
}
2222
.markdown h1 {
2323
font-size: 2.25rem;

website/translations/en-US.ftl

+19-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
44
perseus = Perseus
55
sycamore = Sycamore
6-
navlinks-docs = Docs
7-
navlinks-comparisons = Comparisons
6+
navlinks =
7+
.docs = Docs
8+
.comparisons = Comparisons
9+
.plugins = Plugins
810
index-get-started = Get Started
911
index-github = GitHub
1012
index-desc = Perseus is a web development framework built entirely in Rust, designed to empower efficient development of the next generation of lightning-fast sites for the modern web. It comes with server-side rendering, static site generation, internationalization, and inferred routing built-in, letting you build even the most complex sites in a breeze.
@@ -101,3 +103,18 @@ docs-version-switcher =
101103
.beta = v{ $version } (beta)
102104
.stable = v{ $version } (stable)
103105
.outdated = v{ $version } (outdated)
106+
107+
plugins-title = Plugins
108+
plugins-title-full = Perseus Plugins
109+
plugin-card-author = By { $author }
110+
plugin-details =
111+
.repo_link = Repository:
112+
.crates_link = Crate page:
113+
.docs_link = Docs:
114+
.site_link = Site:
115+
.metadata_heading = Metadata
116+
.no_link_text = N/A
117+
plugin-search =
118+
.placeholder = Search
119+
.no_results = No results found.
120+
plugins-desc = These are all the public plugins for Perseus! If you've made a plugin, and you'd like it to be listed here, please open an issue on our repository!

0 commit comments

Comments
 (0)