♻️ Major code restructuring

This commit is contained in:
Manuel Cillero 2024-02-09 14:05:38 +01:00
parent a96e203bb3
commit fa66d628a0
221 changed files with 228 additions and 315 deletions

View file

@ -0,0 +1,18 @@
[package]
name = "pagetop-admin"
version = "0.0.16"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Module for PageTop that provides a common place for other modules to handle general site \
settings for administrators.\
"""
homepage = "https://pagetop.cillero.es"
repository = "https://github.com/manuelcillero/pagetop"
license = "MIT OR Apache-2.0"
[dependencies]
pagetop = { version = "0.0", path = "../../" }

View file

@ -0,0 +1,27 @@
Módulo para **PageTop** que proporciona a otros módulos un lugar común donde presentar a los
administradores sus opciones de configuración.
[PageTop](https://github.com/manuelcillero/pagetop/tree/main/pagetop), es un entorno de desarrollo
basado en algunos de los *crates* más estables y populares del ecosistema Rust para proporcionar
APIs, patrones de desarrollo y buenas prácticas para la creación de soluciones web SSR (*Server-Side
Rendering*).
# 🚧 Advertencia
**PageTop** sólo libera actualmente versiones de desarrollo. La API no es estable y los cambios son
constantes. No puede considerarse preparado hasta que se libere la versión **0.1.0**.
# 📜 Licencia
Este proyecto tiene licencia, de hecho tiene dos, puedes aplicar cualquiera de las siguientes a tu
elección:
* Licencia Apache versión 2.0
([LICENSE-APACHE](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-APACHE) o
[http://www.apache.org/licenses/LICENSE-2.0]).
* Licencia MIT
([LICENSE-MIT](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-MIT) o
[http://opensource.org/licenses/MIT]).

View file

@ -0,0 +1,40 @@
use pagetop::prelude::*;
static_locales!(LOCALES_ADMIN);
mod summary;
#[derive(AssignHandle)]
pub struct Admin;
impl PackageTrait for Admin {
fn name(&self) -> L10n {
L10n::t("package_name", &LOCALES_ADMIN)
}
fn description(&self) -> L10n {
L10n::t("package_description", &LOCALES_ADMIN)
}
fn actions(&self) -> Vec<Action> {
actions![
action::page::BeforePrepareBody::new(before_prepare_body),
action::component::BeforePrepareComponent::<Menu>::new(before_prepare_menu)
.filter_by_referer_id("admin-menu-test"),
]
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
scfg.service(
service::web::scope("/admin").route("", service::web::get().to(summary::summary)),
);
}
}
fn before_prepare_body(page: &mut Page) {
page.alter_body_classes(ClassesOp::Add, "test-admin");
}
fn before_prepare_menu(component: &mut Menu, _cx: &mut Context) {
component.alter_id("admin-menu-test-altered");
}

View file

@ -0,0 +1,2 @@
package_name = Admin module
package_description = Administration module.

View file

@ -0,0 +1,2 @@
package_name = Admin module
package_description = Módulo de administración.

View file

@ -0,0 +1,170 @@
use crate::LOCALES_ADMIN;
use pagetop::prelude::*;
pub async fn summary(request: service::HttpRequest) -> ResultPage<Markup, ErrorPage> {
let top_menu = Menu::new()
.with_id("admin-menu-test")
.add_item(menu::Item::label(L10n::t("package_name", &LOCALES_ADMIN)))
.add_item(menu::Item::label(L10n::n("Ejemplo \"Label\"")))
.add_item(menu::Item::link(L10n::n("Ejemplo \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Ejemplo \"LinkBlank\""),
|_| "https://www.google.es",
))
.add_item(menu::Item::submenu(
L10n::n("Ejemplo Submenú"),
menu::Submenu::new()
.with_title(L10n::n("Título submenú"))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
))
.add_item(menu::Item::submenu(
L10n::n("Otro submenú con un texto muy, pero que muy largo"),
menu::Submenu::new()
.add_item(menu::Item::label(L10n::n("Opción \"Label\"")))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
))
.add_item(menu::Item::label(L10n::n("Opción \"Label\""))),
))
.add_item(menu::Item::label(L10n::n("Opción \"Label\""))),
))
.add_item(menu::Item::megamenu(
L10n::n("Ejemplo Megamenú 1"),
menu::Megamenu::new()
.add_group(
menu::Group::new()
.add_element(menu::Element::submenu(
menu::Submenu::new()
.with_title(L10n::n("Título submenú"))
.add_item(menu::Item::label(L10n::n("Opción \"Label\"")))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
)),
))
.add_element(menu::Element::submenu(
menu::Submenu::new()
.with_title(L10n::n("Título submenú"))
.add_item(menu::Item::label(L10n::n("Opción \"Label\"")))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
)),
)),
)
.add_group(
menu::Group::new().add_element(menu::Element::submenu(
menu::Submenu::new()
.add_item(menu::Item::label(L10n::n("Opción \"Label\"")))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
))
.add_item(menu::Item::label(L10n::n("Opción \"Label\""))),
)),
)
.add_group(
menu::Group::new()
.add_element(menu::Element::submenu(
menu::Submenu::new()
.with_title(L10n::n("Título submenú"))
.add_item(menu::Item::label(L10n::n("Opción \"Label\"")))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
)),
))
.add_element(menu::Element::submenu(
menu::Submenu::new()
.with_title(L10n::n("Título submenú"))
.add_item(menu::Item::label(L10n::n("Opción \"Label\"")))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
)),
)),
)
.add_group(
menu::Group::new().add_element(menu::Element::submenu(
menu::Submenu::new()
.add_item(menu::Item::label(L10n::n("Opción \"Label\"")))
.add_item(menu::Item::link(L10n::n("Opción \"Link\""), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(
L10n::n("Opción \"LinkBlank\""),
|_| "https://www.google.es",
))
.add_item(menu::Item::label(L10n::n("Opción \"Label\""))),
)),
),
));
let side_menu = Menu::new()
.add_item(menu::Item::label(L10n::n("Opción 1")))
.add_item(menu::Item::link(L10n::n("Opción 2"), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::link_blank(L10n::n("Opción 3"), |_| {
"https://www.google.es"
}))
.add_item(menu::Item::submenu(
L10n::n("Submenú 1"),
menu::Submenu::new()
.add_item(menu::Item::label(L10n::n("Opción 1")))
.add_item(menu::Item::label(L10n::n("Opción 2"))),
)) /*
.add_item(menu::Item::separator()) */
.add_item(menu::Item::submenu(
L10n::n("Submenú 2"),
menu::Submenu::new()
.add_item(menu::Item::label(L10n::n("Opción 1")))
.add_item(menu::Item::label(L10n::n("Opción 2"))),
))
.add_item(menu::Item::label(L10n::n("Opción 4")));
Page::new(request)
//.with_context(ContextOp::Theme("Bootsier"))
.with_title(L10n::n("Admin"))
.with_component_in("top-menu", side_menu)
.with_component_in(
"content",
flex::Container::new()
.add_item(flex::Item::new().add_component(Html::with(html! {
p { "Columna 1"}
})))
.add_item(flex::Item::new().add_component(top_menu))
.add_item(flex::Item::new().add_component(Html::with(html! {
p { "Columna 3"}
}))),
)
.with_template("admin")
.render()
}

View file

@ -0,0 +1,21 @@
[package]
name = "pagetop-bootsier"
version = "0.0.13"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Theme for PageTop that uses the Bootstrap framework for page layout and components display.\
"""
homepage = "https://pagetop.cillero.es"
repository = "https://github.com/manuelcillero/pagetop"
license = "MIT OR Apache-2.0"
[dependencies]
pagetop = { version = "0.0", path = "../../" }
static-files = "0.2.3"
[build-dependencies]
pagetop-build = { version = "0.0", path = "../../helpers/pagetop-build" }

View file

@ -0,0 +1,27 @@
Tema para **PageTop** que utiliza el *framework* [Bootstrap](https://getbootstrap.com/) para la
composición de páginas y visualización de componentes.
[PageTop](https://github.com/manuelcillero/pagetop/tree/main/pagetop), es un entorno de desarrollo
basado en algunos de los *crates* más estables y populares del ecosistema Rust para proporcionar
APIs, patrones de desarrollo y buenas prácticas para la creación de soluciones web SSR (*Server-Side
Rendering*).
# 🚧 Advertencia
**PageTop** sólo libera actualmente versiones de desarrollo. La API no es estable y los cambios son
constantes. No puede considerarse preparado hasta que se libere la versión **0.1.0**.
# 📜 Licencia
Este proyecto tiene licencia, de hecho tiene dos, puedes aplicar cualquiera de las siguientes a tu
elección:
* Licencia Apache versión 2.0
([LICENSE-APACHE](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-APACHE) o
[http://www.apache.org/licenses/LICENSE-2.0]).
* Licencia MIT
([LICENSE-MIT](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-MIT) o
[http://opensource.org/licenses/MIT]).

View file

@ -0,0 +1,7 @@
use pagetop_build::StaticFilesBundle;
fn main() -> std::io::Result<()> {
StaticFilesBundle::from_dir("./static")
.with_name("bootsier")
.build()
}

View file

@ -0,0 +1,239 @@
use pagetop::prelude::*;
static_locales!(LOCALES_BOOTSIER);
static_files!(bootsier);
#[derive(AssignHandle)]
pub struct Bootsier;
impl PackageTrait for Bootsier {
fn theme(&self) -> Option<ThemeRef> {
Some(&Bootsier)
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
service_for_static_files!(scfg, bootsier => "/bootsier");
}
}
impl ThemeTrait for Bootsier {
#[rustfmt::skip]
fn regions(&self) -> Vec<(&'static str, L10n)> {
vec![
("header", L10n::t("header", &LOCALES_BOOTSIER)),
("nav_branding", L10n::t("nav_branding", &LOCALES_BOOTSIER)),
("nav_main", L10n::t("nav_main", &LOCALES_BOOTSIER)),
("nav_additional", L10n::t("nav_additional", &LOCALES_BOOTSIER)),
("breadcrumb", L10n::t("breadcrumb", &LOCALES_BOOTSIER)),
("content", L10n::t("breadcrumb", &LOCALES_BOOTSIER)),
("sidebar_first", L10n::t("sidebar_first", &LOCALES_BOOTSIER)),
("sidebar_second", L10n::t("sidebar_second", &LOCALES_BOOTSIER)),
("footer", L10n::t("footer", &LOCALES_BOOTSIER)),
]
}
fn prepare_body(&self, page: &mut Page) -> Markup {
match page.template() {
"admin" => html! {
body class=[page.body_classes().get()] {
@for region in &[
"top-menu",
"side-menu",
"content"
] {
(self.prepare_region(page, region))
}
}
},
_ => html! {
body class=[page.body_classes().get()] {
(self.prepare_region(page, "header"))
(self.prepare_region(page, "nav_branding"))
(self.prepare_region(page, "nav_main"))
(self.prepare_region(page, "nav_additional"))
(self.prepare_region(page, "breadcrumb"))
(self.prepare_region(page, "content"))
(self.prepare_region(page, "sidebar_first"))
(self.prepare_region(page, "sidebar_second"))
(self.prepare_region(page, "footer"))
}
},
}
}
fn after_prepare_body(&self, page: &mut Page) {
page.alter_favicon(Some(Favicon::new().with_icon("/base/favicon.ico")))
.alter_context(ContextOp::AddStyleSheet(
StyleSheet::at("/bootsier/css/bootstrap.min.css")
.with_version("5.1.3")
.with_weight(-99),
))
.alter_context(ContextOp::AddJavaScript(
JavaScript::at("/bootsier/js/bootstrap.bundle.min.js")
.with_version("5.1.3")
.with_weight(-99),
))
.alter_context(ContextOp::AddBaseAssets)
.alter_context(ContextOp::AddStyleSheet(
StyleSheet::at("/bootsier/css/styles.css").with_version("0.0.1"),
));
}
fn before_prepare_component(&self, component: &mut dyn ComponentTrait, _cx: &mut Context) {
match component.handle() {
h if Icon::matches_handle(h) => {
if let Some(i) = component_as_mut::<Icon>(component) {
match i.font_size() {
FontSize::ExtraLarge => {
i.replace_classes(i.font_size().to_string(), "fs-1");
}
FontSize::XxLarge => {
i.replace_classes(i.font_size().to_string(), "fs-2");
}
FontSize::XLarge => {
i.replace_classes(i.font_size().to_string(), "fs-3");
}
FontSize::Large => {
i.replace_classes(i.font_size().to_string(), "fs-4");
}
FontSize::Medium => {
i.replace_classes(i.font_size().to_string(), "fs-5");
}
_ => {}
};
}
}
h if Button::matches_handle(h) => {
if let Some(b) = component_as_mut::<Button>(component) {
match b.style() {
ButtonStyle::Default => {
b.replace_classes(b.style().to_string(), "btn btn-primary");
}
ButtonStyle::Info => {
b.replace_classes(b.style().to_string(), "btn btn-info");
}
ButtonStyle::Success => {
b.replace_classes(b.style().to_string(), "btn btn-success");
}
ButtonStyle::Warning => {
b.replace_classes(b.style().to_string(), "btn btn-warning");
}
ButtonStyle::Danger => {
b.replace_classes(b.style().to_string(), "btn btn-danger");
}
ButtonStyle::Light => {
b.replace_classes(b.style().to_string(), "btn btn-light");
}
ButtonStyle::Dark => {
b.replace_classes(b.style().to_string(), "btn btn-dark");
}
ButtonStyle::Link => {
b.replace_classes(b.style().to_string(), "btn btn-link");
}
};
match b.font_size() {
FontSize::ExtraLarge => {
b.replace_classes(b.font_size().to_string(), "fs-1");
}
FontSize::XxLarge => {
b.replace_classes(b.font_size().to_string(), "fs-2");
}
FontSize::XLarge => {
b.replace_classes(b.font_size().to_string(), "fs-3");
}
FontSize::Large => {
b.replace_classes(b.font_size().to_string(), "fs-4");
}
FontSize::Medium => {
b.replace_classes(b.font_size().to_string(), "fs-5");
}
_ => {}
};
}
}
h if Heading::matches_handle(h) => {
if let Some(h) = component_as_mut::<Heading>(component) {
match h.size() {
HeadingSize::ExtraLarge => {
h.replace_classes(h.size().to_string(), "display-1");
}
HeadingSize::XxLarge => {
h.replace_classes(h.size().to_string(), "display-2");
}
HeadingSize::XLarge => {
h.replace_classes(h.size().to_string(), "display-3");
}
HeadingSize::Large => {
h.replace_classes(h.size().to_string(), "display-4");
}
HeadingSize::Medium => {
h.replace_classes(h.size().to_string(), "display-5");
}
_ => {}
};
}
}
h if Paragraph::matches_handle(h) => {
if let Some(p) = component_as_mut::<Paragraph>(component) {
match p.font_size() {
FontSize::ExtraLarge => {
p.replace_classes(p.font_size().to_string(), "fs-1");
}
FontSize::XxLarge => {
p.replace_classes(p.font_size().to_string(), "fs-2");
}
FontSize::XLarge => {
p.replace_classes(p.font_size().to_string(), "fs-3");
}
FontSize::Large => {
p.replace_classes(p.font_size().to_string(), "fs-4");
}
FontSize::Medium => {
p.replace_classes(p.font_size().to_string(), "fs-5");
}
_ => {}
};
}
}
_ => {}
}
}
fn render_component(&self, component: &dyn ComponentTrait, cx: &mut Context) -> Option<Markup> {
match component.handle() {
h if Error404::matches_handle(h) => Some(html! {
div class="jumbotron" {
div class="media" {
img
src="/bootsier/images/caution.png"
class="mr-4"
style="width: 20%; max-width: 188px"
alt="Caution!";
div class="media-body" {
h1 class="display-4" { ("RESOURCE NOT FOUND") }
p class="lead" {
(L10n::t("e404-description", &LOCALES_BOOTSIER)
.escaped(cx.langid()))
}
hr class="my-4";
p {
(L10n::t("e404-description", &LOCALES_BOOTSIER)
.escaped(cx.langid()))
}
a
class="btn btn-primary btn-lg"
href="/"
role="button"
{
(L10n::t("back-homepage", &LOCALES_BOOTSIER)
.escaped(cx.langid()))
}
}
}
}
}),
_ => None,
}
}
}

View file

@ -0,0 +1,6 @@
e404-description = Oops! Page Not Found
e404-message = The page you are looking for may have been removed, had its name changed, or is temporarily unavailable.
e500-description = Oops! Unexpected Error
e500-message = We're having an issue. Please report this error to an administrator.
back-homepage = Back to homepage

View file

@ -0,0 +1,9 @@
header = Header
nav_branding = Navigation branding region
nav_main = Main navigation region
nav_additional = Additional navigation region (eg search form, social icons, etc)
breadcrumb = Breadcrumb
content = Main content
sidebar_first = Sidebar first
sidebar_second = Sidebar second
footer = Footer

View file

@ -0,0 +1,5 @@
e404-description = ¡Vaya! Página No Encontrada
e404-message = La página que está buscando puede haber sido eliminada, cambiada de nombre o no está disponible temporalmente.
e500-description = ¡Vaya! Error Inesperado
e500-message = Está ocurriendo una incidencia. Por favor, informe de este error a un administrador.
back-homepage = Volver al inicio

View file

@ -0,0 +1,9 @@
header = Cabecera
nav_branding = Navegación y marca
nav_main = Navegación principal
nav_additional = Navegación adicional (p.e. formulario de búsqueda, iconos sociales, etc.)
breadcrumb = Ruta de posicionamiento
content = Contenido principal
sidebar_first = Barra lateral primera
sidebar_second = Barra lateral segunda
footer = Pie

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,13 @@
/* OVERRIDE COMPONENT STYLES */
/* Heading component */
.pt-heading__subtitle {
margin-top: calc(-1 * var(--pt-gap-0-35));
}
/* Button component */
.btn-link {
text-decoration: none;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,21 @@
[package]
name = "pagetop-bulmix"
version = "0.0.12"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Theme for PageTop that uses the Bulma framework for page layout and components display.\
"""
homepage = "https://pagetop.cillero.es"
repository = "https://github.com/manuelcillero/pagetop"
license = "MIT OR Apache-2.0"
[dependencies]
pagetop = { version = "0.0", path = "../../" }
static-files = "0.2.3"
[build-dependencies]
pagetop-build = { version = "0.0", path = "../../helpers/pagetop-build" }

View file

@ -0,0 +1,27 @@
Tema para **PageTop** que utiliza el *framework* [Bulma](https://bulma.io/) para la composición de
páginas y visualización de componentes.
[PageTop](https://github.com/manuelcillero/pagetop/tree/main/pagetop), es un entorno de desarrollo
basado en algunos de los *crates* más estables y populares del ecosistema Rust para proporcionar
APIs, patrones de desarrollo y buenas prácticas para la creación de soluciones web SSR (*Server-Side
Rendering*).
# 🚧 Advertencia
**PageTop** sólo libera actualmente versiones de desarrollo. La API no es estable y los cambios son
constantes. No puede considerarse preparado hasta que se libere la versión **0.1.0**.
# 📜 Licencia
Este proyecto tiene licencia, de hecho tiene dos, puedes aplicar cualquiera de las siguientes a tu
elección:
* Licencia Apache versión 2.0
([LICENSE-APACHE](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-APACHE) o
[http://www.apache.org/licenses/LICENSE-2.0]).
* Licencia MIT
([LICENSE-MIT](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-MIT) o
[http://opensource.org/licenses/MIT]).

View file

@ -0,0 +1,7 @@
use pagetop_build::StaticFilesBundle;
fn main() -> std::io::Result<()> {
StaticFilesBundle::from_dir("./static")
.with_name("bulmix")
.build()
}

View file

@ -0,0 +1,159 @@
use pagetop::prelude::*;
static_files!(bulmix);
#[derive(AssignHandle)]
pub struct Bulmix;
impl PackageTrait for Bulmix {
fn theme(&self) -> Option<ThemeRef> {
Some(&Bulmix)
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
service_for_static_files!(scfg, bulmix => "/bulmix");
}
}
impl ThemeTrait for Bulmix {
fn after_prepare_body(&self, page: &mut Page) {
page.alter_favicon(Some(Favicon::new().with_icon("/base/favicon.ico")))
.alter_context(ContextOp::AddStyleSheet(
StyleSheet::at("/bulmix/css/bulma.min.css")
.with_version("0.9.4")
.with_weight(-99),
))
.alter_context(ContextOp::AddBaseAssets)
.alter_context(ContextOp::AddStyleSheet(
StyleSheet::at("/bulmix/css/styles.css").with_version("0.0.1"),
));
}
fn before_prepare_component(&self, component: &mut dyn ComponentTrait, _cx: &mut Context) {
match component.handle() {
h if Icon::matches_handle(h) => {
if let Some(i) = component_as_mut::<Icon>(component) {
match i.font_size() {
FontSize::ExtraLarge => {
i.replace_classes(i.font_size().to_string(), "is-size-1");
}
FontSize::XxLarge => {
i.replace_classes(i.font_size().to_string(), "is-size-2");
}
FontSize::XLarge => {
i.replace_classes(i.font_size().to_string(), "is-size-3");
}
FontSize::Large => {
i.replace_classes(i.font_size().to_string(), "is-size-4");
}
FontSize::Medium => {
i.replace_classes(i.font_size().to_string(), "is-size-5");
}
_ => {}
};
}
}
h if Button::matches_handle(h) => {
if let Some(b) = component_as_mut::<Button>(component) {
match b.style() {
ButtonStyle::Default => {
b.replace_classes(b.style().to_string(), "button is-primary");
}
ButtonStyle::Info => {
b.replace_classes(b.style().to_string(), "button is-info");
}
ButtonStyle::Success => {
b.replace_classes(b.style().to_string(), "button is-success");
}
ButtonStyle::Warning => {
b.replace_classes(b.style().to_string(), "button is-warning");
}
ButtonStyle::Danger => {
b.replace_classes(b.style().to_string(), "button is-danger");
}
ButtonStyle::Light => {
b.replace_classes(b.style().to_string(), "button is-light");
}
ButtonStyle::Dark => {
b.replace_classes(b.style().to_string(), "button is-dark");
}
ButtonStyle::Link => {
b.replace_classes(b.style().to_string(), "button is-text");
}
};
match b.font_size() {
FontSize::ExtraLarge => {
b.replace_classes(b.font_size().to_string(), "is-size-1");
}
FontSize::XxLarge => {
b.replace_classes(b.font_size().to_string(), "is-size-2");
}
FontSize::XLarge => {
b.replace_classes(b.font_size().to_string(), "is-size-3");
}
FontSize::Large => {
b.replace_classes(b.font_size().to_string(), "is-size-4");
}
FontSize::Medium => {
b.replace_classes(b.font_size().to_string(), "is-size-5");
}
_ => {}
};
}
}
h if Heading::matches_handle(h) => {
if let Some(h) = component_as_mut::<Heading>(component) {
match h.size() {
HeadingSize::Subtitle => {
h.replace_classes(h.size().to_string(), "subtitle")
}
_ => h.add_classes("title"),
};
}
}
h if Paragraph::matches_handle(h) => {
if let Some(p) = component_as_mut::<Paragraph>(component) {
p.add_classes("block");
match p.font_size() {
FontSize::ExtraLarge => {
p.replace_classes(p.font_size().to_string(), "is-size-1");
}
FontSize::XxLarge => {
p.replace_classes(p.font_size().to_string(), "is-size-2");
}
FontSize::XLarge => {
p.replace_classes(p.font_size().to_string(), "is-size-3");
}
FontSize::Large => {
p.replace_classes(p.font_size().to_string(), "is-size-4");
}
FontSize::Medium => {
p.replace_classes(p.font_size().to_string(), "is-size-5");
}
_ => {}
};
}
}
_ => {}
}
}
fn render_component(
&self,
component: &dyn ComponentTrait,
_cx: &mut Context,
) -> Option<Markup> {
match component.handle() {
h if Icon::matches_handle(h) => {
if let Some(i) = component_as_ref::<Icon>(component) {
return match i.icon_name().get() {
None => None,
_ => Some(html! { span class="icon" { i class=[i.classes().get()] {} } }),
};
}
None
}
_ => None,
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,11 @@
html {
scroll-behavior: smooth;
}
/* OVERRIDE COMPONENT STYLES */
/* Button component */
.is-link {
text-decoration: none;
}

View file

@ -0,0 +1,21 @@
[package]
name = "pagetop-homedemo"
version = "0.0.12"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Module that shows a demo home page to present PageTop.\
"""
homepage = "https://pagetop.cillero.es"
repository = "https://github.com/manuelcillero/pagetop"
license = "MIT OR Apache-2.0"
[dependencies]
pagetop = { version = "0.0", path = "../../" }
static-files = "0.2.3"
[build-dependencies]
pagetop-build = { version = "0.0", path = "../../helpers/pagetop-build" }

View file

@ -0,0 +1,26 @@
Módulo que muestra una página de inicio de demostración para presentar **PageTop**.
[PageTop](https://github.com/manuelcillero/pagetop/tree/main/pagetop), es un entorno de desarrollo
basado en algunos de los *crates* más estables y populares del ecosistema Rust para proporcionar
APIs, patrones de desarrollo y buenas prácticas para la creación de soluciones web SSR (*Server-Side
Rendering*).
# 🚧 Advertencia
**PageTop** sólo libera actualmente versiones de desarrollo. La API no es estable y los cambios son
constantes. No puede considerarse preparado hasta que se libere la versión **0.1.0**.
# 📜 Licencia
Este proyecto tiene licencia, de hecho tiene dos, puedes aplicar cualquiera de las siguientes a tu
elección:
* Licencia Apache versión 2.0
([LICENSE-APACHE](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-APACHE) o
[http://www.apache.org/licenses/LICENSE-2.0]).
* Licencia MIT
([LICENSE-MIT](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-MIT) o
[http://opensource.org/licenses/MIT]).

View file

@ -0,0 +1,7 @@
use pagetop_build::StaticFilesBundle;
fn main() -> std::io::Result<()> {
StaticFilesBundle::from_dir("./static")
.with_name("homedemo")
.build()
}

View file

@ -0,0 +1,214 @@
use pagetop::prelude::*;
static_locales!(LOCALES_HOMEDEMO);
static_files!(homedemo);
#[derive(AssignHandle)]
pub struct HomeDemo;
impl PackageTrait for HomeDemo {
fn name(&self) -> L10n {
L10n::t("package_name", &LOCALES_HOMEDEMO)
}
fn description(&self) -> L10n {
L10n::t("package_description", &LOCALES_HOMEDEMO)
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
service_for_static_files!(scfg, homedemo => "/homedemo");
scfg.route("/", service::web::get().to(demo));
}
}
async fn demo(request: service::HttpRequest) -> ResultPage<Markup, ErrorPage> {
Page::new(request)
.with_title(L10n::t("page_title", &LOCALES_HOMEDEMO))
.with_context(ContextOp::AddStyleSheet(StyleSheet::at(
"/homedemo/css/styles.css",
)))
.with_body_classes(ClassesOp::Add, "default-homepage")
.with_component_in("content", hello_world())
.with_component_in("content", welcome())
.with_component_in("content", about_pagetop())
.with_component_in("content", promo_pagetop())
.with_component_in("content", reporting_issues())
.render()
}
fn hello_world() -> Wrapper {
Wrapper::header().with_id("hello-world").add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "hello-col-text")
.with_size(flex::ItemSize::Percent40)
.add_component(
Heading::h1(L10n::t("page_title", &LOCALES_HOMEDEMO))
.with_size(HeadingSize::Medium),
)
.add_component(
Paragraph::translated(L10n::t("hello_intro", &LOCALES_HOMEDEMO).with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name,
),
))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::translated(
L10n::t("hello_powered", &LOCALES_HOMEDEMO).with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://pagetop.cillero.es", "PageTop",
),
),
))
.add_component(
Button::anchor(
"https://github.com/manuelcillero/pagetop",
L10n::t("hello_code", &LOCALES_HOMEDEMO),
)
.with_target(ButtonTarget::Blank)
.with_left_icon(Some(Icon::with("git")))
.with_classes(ClassesOp::Add, "code-link")
.with_font_size(FontSize::Medium),
)
.add_component(
Button::anchor("#welcome", L10n::t("hello_welcome", &LOCALES_HOMEDEMO))
.with_style(ButtonStyle::Link)
.with_left_icon(Some(Icon::with("arrow-down-circle-fill")))
.with_classes(ClassesOp::Add, "welcome-link")
.with_font_size(FontSize::Medium),
),
)
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "hello-col-image")
.with_size(flex::ItemSize::Percent60)
.add_component(Image::with("/homedemo/images/header.svg")),
),
)
}
fn welcome() -> Wrapper {
Wrapper::section()
.with_id("welcome")
.with_classes(ClassesOp::Add, "welcome-col-text")
.add_component(Heading::h2(L10n::t("welcome_page", &LOCALES_HOMEDEMO)))
.add_component(
Heading::h3(L10n::t("welcome_subtitle", &LOCALES_HOMEDEMO).with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name
),
))
.with_size(HeadingSize::Subtitle),
)
.add_component(
Paragraph::translated(L10n::t("welcome_text1", &LOCALES_HOMEDEMO))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::translated(L10n::t(
"welcome_text2",
&LOCALES_HOMEDEMO,
)))
}
fn about_pagetop() -> Wrapper {
Wrapper::new().with_id("pagetop").add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::SM))
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "pagetop-col-image")
.with_size(flex::ItemSize::Percent40)
.add_component(Image::with("/homedemo/images/about.svg")),
)
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "pagetop-col-text")
.add_component(Heading::h2(L10n::t("pagetop_title", &LOCALES_HOMEDEMO)))
.add_component(
Paragraph::translated(L10n::t("pagetop_text1", &LOCALES_HOMEDEMO))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::translated(L10n::t(
"pagetop_text2",
&LOCALES_HOMEDEMO,
)))
.add_component(Paragraph::translated(
L10n::t("pagetop_text3", &LOCALES_HOMEDEMO)
.with_arg("href", "https://docs.rs/pagetop/latest/pagetop"),
)),
),
)
}
fn promo_pagetop() -> Wrapper {
Wrapper::new().with_id("promo").add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "promo-col-text")
.with_size(flex::ItemSize::Percent60)
.add_component(Heading::h2(L10n::t(
"pagetop_promo_title",
&LOCALES_HOMEDEMO,
)))
.add_component(
Paragraph::translated(
L10n::t("pagetop_promo_text1", &LOCALES_HOMEDEMO).with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://crates.io/crates/pagetop", "PageTop",
),
),
)
.with_font_size(FontSize::Medium),
),
)
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "promo-col-image")
.with_size(flex::ItemSize::Percent40)
.add_component(Image::with("/homedemo/images/pagetop.png")),
),
)
}
fn reporting_issues() -> Wrapper {
Wrapper::new().with_id("reporting").add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "reporting-col-image")
.add_component(Image::with("/homedemo/images/support.jpg")),
)
.add_item(
flex::Item::new()
.with_inner_classes(ClassesOp::Add, "reporting-col-text")
.with_size(flex::ItemSize::Percent50)
.add_component(Heading::h2(L10n::t(
"report_problems_title",
&LOCALES_HOMEDEMO,
)))
.add_component(
Paragraph::translated(L10n::t("report_problems_text1", &LOCALES_HOMEDEMO))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::translated(L10n::t(
"report_problems_text2",
&LOCALES_HOMEDEMO,
))),
),
)
}

View file

@ -0,0 +1,26 @@
package_name = Default homepage
package_description = Displays a demo homepage when none is configured.
page_title = Hello world!
hello_intro = This page is used to test the proper operation of { $app } after installation.
hello_powered = This web solution is powered by { $pagetop }.
hello_code = Code
hello_welcome = Welcome
welcome_page = Welcome page
welcome_subtitle = Are you user of { $app }?
welcome_text1 = If you don't know what this page is about, this probably means that the site is either experiencing problems or is undergoing routine maintenance.
welcome_text2 = If the problem persists, please contact your system administrator.
pagetop_title = About PageTop
pagetop_text1 = If you can read this page, it means that the PageTop server is working properly, but has not yet been configured.
pagetop_text2 = PageTop defines an interface for the most stable and popular Rust packages to build modular, extensible and configurable web solutions.
pagetop_text3 = For more information on PageTop please visit the <a href="{ $href }" target="_blank">technical documentation</a>.
pagetop_promo_title = Promoting PageTop
pagetop_promo_text1 = You are free to use the image below on applications powered by { $pagetop }. Thanks for using PageTop!
report_problems_title = Reporting problems
report_problems_text1 = Please use the GitHub tool to report bugs in PageTop. However, check "existing bug reports" before reporting a new bug.
report_problems_text2 = Please report bugs specific to modules (such as admin, and others) to respective repositories, not to PageTop itself.

View file

@ -0,0 +1,26 @@
package_name = Página de inicio predeterminada
package_description = Muestra una página de demostración predeterminada cuando no hay ninguna configurada.
page_title = ¡Hola mundo!
hello_intro = Esta página se utiliza para comprobar el correcto funcionamiento de { $app } después de la instalación.
hello_powered = Esta solución web funciona con { $pagetop }.
hello_code = Código
hello_welcome = Bienvenida
welcome_page = Página de bienvenida
welcome_subtitle = ¿Eres usuario de { $app }?
welcome_text1 = Si no sabes de qué trata esta página, probablemente significa que el sitio está experimentando problemas o está pasando por un mantenimiento de rutina.
welcome_text2 = Si el problema persiste, póngase en contacto con el administrador del sistema.
pagetop_title = Sobre PageTop
pagetop_text1 = Si puedes leer esta página, significa que el servidor PageTop funciona correctamente, pero aún no se ha configurado.
pagetop_text2 = PageTop define una interfaz para los paquetes Rust más estables y populares para crear soluciones web modulares, extensibles y configurables.
pagetop_text3 = Para más información sobre PageTop, por favor visita la <a href="{ $href }" target="_blank">documentación técnica</a>.
pagetop_promo_title = Promociona PageTop
pagetop_promo_text1 = Eres libre de usar la siguiente imagen en aplicaciones desarrolladas con { $pagetop }. ¡Gracias por usar PageTop!
report_problems_title = Informando problemas
report_problems_text1 = Utiliza la herramienta GitHub para informar errores en PageTop. Sin embargo, comprueba los "informes de errores existentes" antes de informar de un nuevo error.
report_problems_text2 = Informa de errores específicos de los módulos (como admin y otros) en sus repositorios respectivos, no a PageTop en sí.

View file

@ -0,0 +1,109 @@
body.default-homepage span.app-name {
font-weight: 400;
color: inherit;
}
#hello-world,
#welcome,
#pagetop,
#promo,
#reporting {
padding: 2rem 5%;
}
body.default-homepage [class$="-col-text"] {
padding: 0 5%;
text-align: center;
}
body.default-homepage [class$="-col-image"] {
padding: 1rem 5%;
}
/*
#hello-world a {
margin: .25rem;
}
#hello-world a.code-link {
padding-left: 2rem;
padding-right: 2rem;
border-radius: 1.5rem;
font-size: 1.125rem;
}
#hello-world a.welcome-link {
text-decoration: none;
display: inline-block;
vertical-align: middle;
}
#hello-world .hello-col-image {
padding-top: 3em;
}
*/
#welcome > div.container {
padding: 2rem 1rem;
border-radius: 28px;
background: url("/homedemo/images/welcome.jpg") center center no-repeat;
background-size: auto;
background-size: cover;
color: #fff;
}
#welcome > div.container > h2 {
color: #fff;
}
#welcome > div.container > h3 {
color: #ccc;
}
#reporting .reporting-col-image img {
border-radius: 40px;
}
/* BREAKPOINTS */
/* MD - Applies <= 768px */
@media screen and (min-width: 48em) {
body.default-homepage [class$="-col-image"] {
padding-top: 5%;
}
#reporting .reporting-col-text {
padding-left: 1rem;
text-align: left;
}
}
/* LG - Applies >= 992px */
@media screen and (min-width: 62em) {
#hello-world .hello-col-text {
padding-top: 2rem;
padding-left: 10%;
text-align: left;
}
#welcome {
padding-left: 10%;
padding-right: 10%;
}
#welcome > div.container {
padding: 2rem 8rem;
}
#promo .promo-col-text {
padding-top: 1rem;
padding-right: 0;
text-align: right;
}
}
/* XL - Applies >= 1280px */
@media screen and (min-width: 80em) {
body.default-homepage [class$="-col-image"] {
padding-top: 1rem;
}
#hello-world .hello-col-text {
padding-top: 4rem;
}
#pagetop .pagetop-col-text {
padding-top: 3rem;
padding-left: 3rem;
text-align: left;
}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View file

@ -0,0 +1,18 @@
[package]
name = "pagetop-node"
version = "0.0.16"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Module for PageTop to create, extend or customize the types of content that a website can \
handle.\
"""
homepage = "https://pagetop.cillero.es"
repository = "https://github.com/manuelcillero/pagetop"
license = "MIT OR Apache-2.0"
[dependencies]
pagetop = { version = "0.0", path = "../../", features = ["database"], default-features = false }

View file

@ -0,0 +1,27 @@
Módulo para **PageTop** para crear, extender o personalizar los tipos de contenido que puede
administrar un sitio web.
[PageTop](https://github.com/manuelcillero/pagetop/tree/main/pagetop), es un entorno de desarrollo
basado en algunos de los *crates* más estables y populares del ecosistema Rust para proporcionar
APIs, patrones de desarrollo y buenas prácticas para la creación de soluciones web SSR (*Server-Side
Rendering*).
# 🚧 Advertencia
**PageTop** sólo libera actualmente versiones de desarrollo. La API no es estable y los cambios son
constantes. No puede considerarse preparado hasta que se libere la versión **0.1.0**.
# 📜 Licencia
Este proyecto tiene licencia, de hecho tiene dos, puedes aplicar cualquiera de las siguientes a tu
elección:
* Licencia Apache versión 2.0
([LICENSE-APACHE](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-APACHE) o
[http://www.apache.org/licenses/LICENSE-2.0]).
* Licencia MIT
([LICENSE-MIT](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-MIT) o
[http://opensource.org/licenses/MIT]).

View file

@ -0,0 +1,44 @@
use pagetop::prelude::*;
static_locales!(LOCALES_NODE);
//mod entity;
mod migration;
#[derive(AssignHandle)]
pub struct Node;
impl PackageTrait for Node {
fn name(&self) -> L10n {
L10n::t("package_name", &LOCALES_NODE)
}
fn description(&self) -> L10n {
L10n::t("package_description", &LOCALES_NODE)
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
scfg.route("/node", service::web::get().to(node));
}
fn actions(&self) -> Vec<Action> {
actions![action::page::BeforePrepareBody::new(before_prepare_body).with_weight(-1)]
}
fn migrations(&self) -> Vec<MigrationItem> {
migrations![
m20220316_000001_create_table_node_type,
m20220316_000002_create_table_node,
m20220316_000003_create_table_node_access,
m20220316_000004_create_table_node_revision,
]
}
}
async fn node(request: service::HttpRequest) -> ResultPage<Markup, ErrorPage> {
Page::new(request).with_title(L10n::n("Nodo")).render()
}
fn before_prepare_body(page: &mut Page) {
page.alter_body_classes(ClassesOp::Add, "test-node");
}

View file

@ -0,0 +1,2 @@
package_name = Node
package_description = Allows content to be submitted to the site and displayed on pages.

View file

@ -0,0 +1,2 @@
package_name = Nodo
package_description = Permite enviar contenidos al sitio y mostrarlos en páginas.

View file

@ -0,0 +1,4 @@
pub mod m20220316_000001_create_table_node_type;
pub mod m20220316_000002_create_table_node;
pub mod m20220316_000003_create_table_node_access;
pub mod m20220316_000004_create_table_node_revision;

View file

@ -0,0 +1,59 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum NodeType {
Table, // node_type: Stores information about all defined Node types.
Type, // The machine-readable name of this type.
Name, // The human-readable name of this type.
Description, // Descripción breve del tipo.
Help, // Help information shown to the user when creating a Node of this type.
HasTitle, // Boolean indicating whether this type uses the Node.Title field.
TitleLabel, // The label displayed for the title field on the edit form.
Custom, // A boolean indicating whether this type is defined by a module (FALSE) or
// by a user via Add content type (TRUE).
Locked, // A boolean indicating whether the administrator can change the machine
// name of this type.
Disabled, // A boolean indicating whether the node type is disabled.
OrigType, // The original machine-readable name of this node type, this may be
// different from the current type name if the locked field is 0.
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(NodeType::Table)
.if_not_exists()
.col(
ColumnDef::new(NodeType::Type)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(NodeType::Name).string().not_null())
.col(ColumnDef::new(NodeType::Description).string().not_null())
.col(ColumnDef::new(NodeType::Help).string().not_null())
.col(ColumnDef::new(NodeType::HasTitle).string().not_null())
.col(ColumnDef::new(NodeType::TitleLabel).string().not_null())
.col(ColumnDef::new(NodeType::Custom).string().not_null())
.col(ColumnDef::new(NodeType::Locked).string().not_null())
.col(ColumnDef::new(NodeType::Disabled).string().not_null())
.col(ColumnDef::new(NodeType::OrigType).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(NodeType::Table).to_owned())
.await
}
}

View file

@ -0,0 +1,70 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum Node {
Table, // node: The base table for nodes.
Nid, // The primary identifier for a node.
Vid, // The current NodeRevision.vid version identifier.
Type, // The NodeType.type of this node.
Language, // The {languages}.language of this node.
Title, // The title of this node, always treated as non-markup plain text.
Uid, // The User.uid that owns this node; initially, this is the user that
// created it.
Status, // Boolean indicating whether the node is published (visible to
// non-administrators).
Created, // The Unix timestamp when the node was created.
Changed, // The Unix timestamp when the node was most recently saved.
Comment, // Whether comments are allowed on this node: 0 = no, 1 = closed (read
// only), 2 = open (read/write).
Promote, // Boolean indicating whether the node should be displayed on the front
// page.
Sticky, // Boolean indicating whether the node should be displayed at the top of
// lists in which it appears.
Tnid, // The translation set id for this node, which equals the node id of the
// source post in each set.
Translate, // A boolean indicating whether this translation page needs to be updated.
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Node::Table)
.if_not_exists()
.col(
ColumnDef::new(Node::Nid)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(Node::Vid).string().not_null())
.col(ColumnDef::new(Node::Type).string().not_null())
.col(ColumnDef::new(Node::Language).string().not_null())
.col(ColumnDef::new(Node::Title).string().not_null())
.col(ColumnDef::new(Node::Uid).string().not_null())
.col(ColumnDef::new(Node::Status).string().not_null())
.col(ColumnDef::new(Node::Created).string().not_null())
.col(ColumnDef::new(Node::Changed).string().not_null())
.col(ColumnDef::new(Node::Comment).string().not_null())
.col(ColumnDef::new(Node::Promote).string().not_null())
.col(ColumnDef::new(Node::Sticky).string().not_null())
.col(ColumnDef::new(Node::Tnid).string().not_null())
.col(ColumnDef::new(Node::Translate).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Node::Table).to_owned())
.await
}
}

View file

@ -0,0 +1,54 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum NodeAccess {
Table, // node_access: Identifies which realm/grant pairs a user must possess in
// order to view, update, or delete specific nodes.
Nid, // The Node.nid this record affects.
Gid, // The grant ID a user must possess in the specified realm to gain this
// row's privileges on the node.
Realm, // The realm in which the user must possess the grant ID. Each node access
// node can define one or more realms.
GrantView, // Boolean indicating whether a user with the realm/grant pair can view this
// node.
GrantUpdate, // Boolean indicating whether a user with the realm/grant pair can edit this
// node.
GrantDelete, // Boolean indicating whether a user with the realm/grant pair can delete
// this node.
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(NodeAccess::Table)
.if_not_exists()
.col(
ColumnDef::new(NodeAccess::Nid)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(NodeAccess::Gid).string().not_null())
.col(ColumnDef::new(NodeAccess::Realm).string().not_null())
.col(ColumnDef::new(NodeAccess::GrantView).string().not_null())
.col(ColumnDef::new(NodeAccess::GrantUpdate).string().not_null())
.col(ColumnDef::new(NodeAccess::GrantDelete).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(NodeAccess::Table).to_owned())
.await
}
}

View file

@ -0,0 +1,60 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum NodeRevision {
Table, // node_revision: Stores information about each saved version of a Node.
Nid, // The Node this version belongs to.
Vid, // The primary identifier for this version.
Uid, // The User.uid that created this version.
Title, // The title of this version.
Log, // The log entry explaining the changes in this version.
Timestamp, // A Unix timestamp indicating when this version was created.
Status, // Boolean indicating whether the node (at the time of this revision) is
// published (visible to non-administrators).
Comment, // Whether comments are allowed on this node (at the time of this revision):
// 0 = no, 1 = closed (read only), 2 = open (read/write).
Promote, // Boolean indicating whether the node (at the time of this revision) should
// be displayed on the front page.
Sticky, // Boolean indicating whether the node (at the time of this revision) should
// be displayed at the top of lists in which it appears.
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(NodeRevision::Table)
.if_not_exists()
.col(
ColumnDef::new(NodeRevision::Nid)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(NodeRevision::Vid).string().not_null())
.col(ColumnDef::new(NodeRevision::Uid).string().not_null())
.col(ColumnDef::new(NodeRevision::Title).string().not_null())
.col(ColumnDef::new(NodeRevision::Log).string().not_null())
.col(ColumnDef::new(NodeRevision::Timestamp).string().not_null())
.col(ColumnDef::new(NodeRevision::Status).string().not_null())
.col(ColumnDef::new(NodeRevision::Comment).string().not_null())
.col(ColumnDef::new(NodeRevision::Promote).string().not_null())
.col(ColumnDef::new(NodeRevision::Sticky).string().not_null())
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(NodeRevision::Table).to_owned())
.await
}
}

View file

@ -0,0 +1,19 @@
[package]
name = "pagetop-user"
version = "0.0.14"
edition = "2021"
authors = [
"Manuel Cillero <manuel@cillero.es>"
]
description = """\
Module to add user management, roles, permissions and sessions in applications developed with \
PageTop.\
"""
homepage = "https://pagetop.cillero.es"
repository = "https://github.com/manuelcillero/pagetop"
license = "MIT OR Apache-2.0"
[dependencies]
pagetop = { version = "0.0", path = "../../", features = ["database"], default-features = false }
serde = { version = "1.0", features = ["derive"] }

View file

@ -0,0 +1,27 @@
Módulo para añadir gestión de usuarios, roles, permisos y sesiones en aplicaciones desarrolladas con
**PageTop**.
[PageTop](https://github.com/manuelcillero/pagetop/tree/main/pagetop), es un entorno de desarrollo
basado en algunos de los *crates* más estables y populares del ecosistema Rust para proporcionar
APIs, patrones de desarrollo y buenas prácticas para la creación de soluciones web SSR (*Server-Side
Rendering*).
# 🚧 Advertencia
**PageTop** sólo libera actualmente versiones de desarrollo. La API no es estable y los cambios son
constantes. No puede considerarse preparado hasta que se libere la versión **0.1.0**.
# 📜 Licencia
Este proyecto tiene licencia, de hecho tiene dos, puedes aplicar cualquiera de las siguientes a tu
elección:
* Licencia Apache versión 2.0
([LICENSE-APACHE](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-APACHE) o
[http://www.apache.org/licenses/LICENSE-2.0]).
* Licencia MIT
([LICENSE-MIT](https://github.com/manuelcillero/pagetop/blob/main/LICENSE-MIT) o
[http://opensource.org/licenses/MIT]).

View file

@ -0,0 +1,65 @@
use pagetop::prelude::*;
static_locales!(LOCALES_USER);
mod migration;
#[derive(AssignHandle)]
pub struct User;
impl PackageTrait for User {
fn name(&self) -> L10n {
L10n::t("package_name", &LOCALES_USER)
}
fn description(&self) -> L10n {
L10n::t("package_description", &LOCALES_USER)
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
scfg.route("/user/login", service::web::get().to(login));
}
fn migrations(&self) -> Vec<MigrationItem> {
migrations![
m20220312_000001_create_table_role,
m20220312_000002_create_table_role_permission,
m20220312_000003_create_table_user,
m20220312_000004_create_table_user_role,
]
}
}
async fn login(request: service::HttpRequest) -> ResultPage<Markup, ErrorPage> {
Page::new(request)
.with_title(L10n::n("Identificación del usuario"))
.with_component_in(
"content",
Wrapper::new()
.with_id("welcome")
.add_component(form_login()),
)
.render()
}
fn form_login() -> Form {
Form::new()
.with_id("user-login")
.with_element(
form::Input::textfield()
.with_name("name")
.with_label(L10n::t("username", &LOCALES_USER))
.with_help_text(
L10n::t("username_help", &LOCALES_USER)
.with_arg("app", config::SETTINGS.app.name.to_owned()),
)
.with_autofocus(true),
)
.with_element(
form::Input::password()
.with_name("pass")
.with_label(L10n::t("password", &LOCALES_USER))
.with_help_text(L10n::t("password_help", &LOCALES_USER)),
)
.with_element(form::ActionButton::submit().with_value(L10n::t("login", &LOCALES_USER)))
}

View file

@ -0,0 +1,8 @@
package_name = User
package_description = Manages the user registration and login system.
username = User name
password = Password
username_help = Enter your { $app } username.
password_help = Enter the password that accompanies your username.
login = Log in

View file

@ -0,0 +1,8 @@
package_name = Usuario
package_description = Gestiona el registro de usuarios y el sistema de accesos.
username = Nombre de usuario
password = Contraseña
username_help = Introduzca su nombre de usuario en { $app }.
password_help = Introduzca la contraseña asociada a su nombre de usuario.
login = Iniciar sesión

View file

@ -0,0 +1,4 @@
pub mod m20220312_000001_create_table_role;
pub mod m20220312_000002_create_table_role_permission;
pub mod m20220312_000003_create_table_user;
pub mod m20220312_000004_create_table_user_role;

View file

@ -0,0 +1,71 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum Role {
Table, // role: Store user roles.
Rid, // Primary Key: Unique role ID.
Name, // Unique role name.
Weight, // The weight of this role in listings and the user interface.
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(Role::Table)
.if_not_exists()
.col(
ColumnDef::new(Role::Rid)
.unsigned()
.not_null()
.auto_increment()
.primary_key(),
)
.col(
ColumnDef::new(Role::Name)
.string_len(64)
.not_null()
.unique_key(),
)
.col(
ColumnDef::new(Role::Weight)
.integer()
.not_null()
.default(10),
)
// INDEXES.
.index(
Index::create()
.name("weight-name")
.col(Role::Weight)
.col(Role::Name),
)
.to_owned(),
)
.await?;
// Built-in roles.
db::exec::<InsertStatement>(
Query::insert()
.into_table(Role::Table)
.columns(vec![Role::Name, Role::Weight])
.values_panic(vec!["anonymous".into(), "1".into()])
.values_panic(vec!["authenticated".into(), "2".into()])
.values_panic(vec!["administrator".into(), "3".into()]),
)
.await
.map(|_| ())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Role::Table).to_owned())
.await
}
}

View file

@ -0,0 +1,64 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum RolePermission {
Table, // role_permission: Stores the permissions assigned to user roles.
Rid, // Foreign Key: Role::Rid.
Permission, // A single permission granted to the role identified by Rid.
}
#[derive(Iden)]
enum Role {
Table,
Rid,
/* ... */
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(RolePermission::Table)
.if_not_exists()
.col(ColumnDef::new(RolePermission::Rid).unsigned().not_null())
.col(
ColumnDef::new(RolePermission::Permission)
.string_len(128)
.not_null(),
)
// INDEXES.
.primary_key(
Index::create()
.col(RolePermission::Rid)
.col(RolePermission::Permission),
)
.index(
Index::create()
.name("permission")
.col(RolePermission::Permission),
)
.foreign_key(
ForeignKey::create()
.name("fk_role_permission-rid")
.from(RolePermission::Table, RolePermission::Rid)
.to(Role::Table, Role::Rid)
.on_delete(ForeignKeyAction::Restrict)
.on_update(ForeignKeyAction::Restrict),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(RolePermission::Table).to_owned())
.await
}
}

View file

@ -0,0 +1,60 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum User {
Table, // user: Stores user data.
Uid, // Primary Key: Unique user ID.
Name, // Unique user name.
Pass, // User's password (hashed).
Mail, // User's e-mail address.
Created, // Timestamp for when user was created.
Changed, // Timestamp for when user was changed.
Access, // Timestamp for previous time user accessed the site.
Login, // Timestamp for user's last login.
Status, // Whether the user is active(1) or blocked(0).
Timezone, // User's time zone.
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(User::Table)
.if_not_exists()
.col(
ColumnDef::new(User::Uid)
.unsigned()
.not_null()
.primary_key(),
)
.col(
ColumnDef::new(User::Name)
.string_len(60)
.not_null()
.unique_key(),
)
.col(ColumnDef::new(User::Pass).string_len(128).not_null())
.col(ColumnDef::new(User::Mail).string_len(255))
.col(ColumnDef::new(User::Created).timestamp().not_null())
.col(ColumnDef::new(User::Changed).timestamp().not_null())
.col(ColumnDef::new(User::Access).timestamp().not_null())
.col(ColumnDef::new(User::Login).timestamp().not_null())
.col(ColumnDef::new(User::Status).boolean().not_null())
.col(ColumnDef::new(User::Timezone).string_len(32))
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(User::Table).to_owned())
.await
}
}

View file

@ -0,0 +1,66 @@
use pagetop::prelude::*;
#[rustfmt::skip]
#[derive(Iden)]
enum UserRole {
Table, // user_role: Maps users to roles.
Uid, // Foreign Key: User::Uid for user.
Rid, // Foreign Key: Role::Rid for role.
}
#[derive(Iden)]
enum User {
Table,
Uid,
/* ... */
}
#[derive(Iden)]
enum Role {
Table,
Rid,
/* ... */
}
new_migration!(Migration);
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(UserRole::Table)
.if_not_exists()
.col(ColumnDef::new(UserRole::Uid).unsigned().not_null())
.col(ColumnDef::new(UserRole::Rid).unsigned().not_null())
// INDEXES.
.primary_key(Index::create().col(UserRole::Uid).col(UserRole::Rid))
.foreign_key(
ForeignKey::create()
.name("fk_user_role-uid")
.from(UserRole::Table, UserRole::Uid)
.to(User::Table, User::Uid)
.on_delete(ForeignKeyAction::Restrict)
.on_update(ForeignKeyAction::Restrict),
)
.foreign_key(
ForeignKey::create()
.name("fk_user_role-rid")
.from(UserRole::Table, UserRole::Rid)
.to(Role::Table, Role::Rid)
.on_delete(ForeignKeyAction::Restrict)
.on_update(ForeignKeyAction::Restrict),
)
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(UserRole::Table).to_owned())
.await
}
}