[website] Añade la web que presenta PageTop

This commit is contained in:
Manuel Cillero 2024-12-25 09:48:05 +01:00
parent d447cd3e30
commit c5330036d6
38 changed files with 2030 additions and 46 deletions

6
.gitattributes vendored
View file

@ -1,6 +0,0 @@
# packages/pagetop-hljs
packages/pagetop-hljs/static/** linguist-vendored
# packages/pagetop-mdbook
packages/pagetop-mdbook/static/** linguist-vendored
packages/pagetop-mdbook/template/** linguist-vendored

11
Cargo.lock generated
View file

@ -2436,6 +2436,17 @@ dependencies = [
"url",
]
[[package]]
name = "pagetop-website"
version = "0.0.11"
dependencies = [
"pagetop",
"pagetop-build",
"pagetop-mdbook",
"serde",
"static-files",
]
[[package]]
name = "parking"
version = "2.2.1"

View file

@ -18,6 +18,7 @@ members = [
#"packages/pagetop-bootsier",
# App
"website",
"drust",
]
@ -28,7 +29,6 @@ license = "MIT OR Apache-2.0"
authors = ["Manuel Cillero <manuel@cillero.es>"]
[workspace.dependencies]
#include_dir = "0.7.4"
serde = { version = "1.0", features = ["derive"] }
static-files = "0.2.4"

View file

@ -96,16 +96,24 @@ El código se organiza en un *workspace* con los siguientes subproyectos:
integra [SeaORM](https://www.sea-ql.org/SeaORM) para trabajar con bases de datos en aplicaciones
`PageTop`.
* **[pagetop-mdbook](https://github.com/manuelcillero/pagetop/tree/latest/packages/pagetop-mdbook)**,
incluye contenido generado por [mdBook](https://rust-lang.github.io/mdBook/) en aplicaciones
desarrolladas con `PageTop`.
* **[pagetop-hljs](https://github.com/manuelcillero/pagetop/tree/latest/packages/pagetop-hljs)**,
integra [highlight.js](https://highlightjs.org) para mostrar fragmentos de código con resaltado
utiliza [HighlightJS](https://highlightjs.org) para mostrar fragmentos de código con resaltado
de sintaxis con `PageTop`.
## Aplicación
## Aplicaciones
* **[drust](https://github.com/manuelcillero/pagetop/tree/latest/drust)**, es una aplicación que
utiliza `PageTop` para crear un Sistema de Gestión de Contenidos (CMS) que permita construir
sitios web dinámicos, administrados y configurables.
* **[website](https://github.com/manuelcillero/pagetop/tree/latest/website)**, es la aplicación
web creada con el propio entorno `PageTop` para descubrir a la comunidad su ecosistema en
[pagetop.cillero.es](https://pagetop.cillero.es).
# 🚧 Advertencia

View file

@ -6,6 +6,7 @@ edition = "2021"
description = """\
Un Sistema de Gestión de Contenidos (CMS) basado en PageTop para compartir tu mundo.\
"""
default-run = "drust"
repository.workspace = true
homepage.workspace = true

1
packages/pagetop-hljs/.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
static/** linguist-vendored

View file

@ -0,0 +1,2 @@
static/** linguist-vendored
template/** linguist-vendored

View file

@ -14,7 +14,7 @@
## Descripción general
Integra los archivos generados por [mdBook](https://rust-lang.github.io/mdBook/) en aplicaciones
construidas con `PageTop` para crear documentación web de productos o APIs, tutoriales, material
desarrolladas con `PageTop` para crear documentación web de productos o APIs, tutoriales, material
para cursos o cualquier contenido que requiera una presentación navegable.
## Sobre PageTop

View file

@ -1,6 +1,41 @@
use pagetop::prelude::*;
pub mod util;
use std::path::Path;
const COMMON_RESOURCES: [&str; 28] = [
"css/chrome.css",
"css/general.css",
"css/print.css",
"css/variables.css",
"FontAwesome/css/font-awesome.css",
"FontAwesome/fonts/fontawesome-webfont.eot",
"FontAwesome/fonts/fontawesome-webfont.svg",
"FontAwesome/fonts/fontawesome-webfont.ttf",
"FontAwesome/fonts/fontawesome-webfont.woff",
"FontAwesome/fonts/fontawesome-webfont.woff2",
"FontAwesome/fonts/FontAwesome.ttf",
"fonts/fonts.css",
"fonts/OPEN-SANS-LICENSE.txt",
"fonts/open-sans-v17-all-charsets-300.woff2",
"fonts/open-sans-v17-all-charsets-300italic.woff2",
"fonts/open-sans-v17-all-charsets-600.woff2",
"fonts/open-sans-v17-all-charsets-600italic.woff2",
"fonts/open-sans-v17-all-charsets-700.woff2",
"fonts/open-sans-v17-all-charsets-700italic.woff2",
"fonts/open-sans-v17-all-charsets-800.woff2",
"fonts/open-sans-v17-all-charsets-800italic.woff2",
"fonts/open-sans-v17-all-charsets-italic.woff2",
"fonts/open-sans-v17-all-charsets-regular.woff2",
"fonts/SOURCE-CODE-PRO-LICENSE.txt",
"fonts/source-code-pro-v11-all-charsets-500.woff2",
"ayu-highlight.css",
"highlight.css",
"tomorrow-night.css",
];
pub fn except_common_resources(p: &Path) -> bool {
!COMMON_RESOURCES.iter().any(|f| p.ends_with(f))
}
include_files!(mdbook);

View file

@ -1,35 +0,0 @@
use std::path::Path;
pub fn except_mdbook_common_resources(p: &Path) -> bool {
let common_resources: [&str; 28] = [
"css/chrome.css",
"css/general.css",
"css/print.css",
"css/variables.css",
"FontAwesome/css/font-awesome.css",
"FontAwesome/fonts/fontawesome-webfont.eot",
"FontAwesome/fonts/fontawesome-webfont.svg",
"FontAwesome/fonts/fontawesome-webfont.ttf",
"FontAwesome/fonts/fontawesome-webfont.woff",
"FontAwesome/fonts/fontawesome-webfont.woff2",
"FontAwesome/fonts/FontAwesome.ttf",
"fonts/fonts.css",
"fonts/OPEN-SANS-LICENSE.txt",
"fonts/open-sans-v17-all-charsets-300.woff2",
"fonts/open-sans-v17-all-charsets-300italic.woff2",
"fonts/open-sans-v17-all-charsets-600.woff2",
"fonts/open-sans-v17-all-charsets-600italic.woff2",
"fonts/open-sans-v17-all-charsets-700.woff2",
"fonts/open-sans-v17-all-charsets-700italic.woff2",
"fonts/open-sans-v17-all-charsets-800.woff2",
"fonts/open-sans-v17-all-charsets-800italic.woff2",
"fonts/open-sans-v17-all-charsets-italic.woff2",
"fonts/open-sans-v17-all-charsets-regular.woff2",
"fonts/SOURCE-CODE-PRO-LICENSE.txt",
"fonts/source-code-pro-v11-all-charsets-500.woff2",
"ayu-highlight.css",
"highlight.css",
"tomorrow-night.css",
];
!common_resources.iter().any(|f| p.ends_with(f))
}

2
website/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Documentación generada
static

25
website/Cargo.toml Normal file
View file

@ -0,0 +1,25 @@
[package]
name = "pagetop-website"
version = "0.0.11"
edition = "2021"
description = """
Introduce el entorno de desarrollo PageTop a la comunidad.\
"""
repository.workspace = true
homepage.workspace = true
license.workspace = true
authors.workspace = true
[dependencies]
pagetop.workspace = true
pagetop-mdbook.workspace = true
#pagetop-bootsier.workspace = true
serde.workspace = true
static-files.workspace = true
[build-dependencies]
pagetop-build.workspace = true
pagetop-mdbook.workspace = true

44
website/README.md Normal file
View file

@ -0,0 +1,44 @@
<div align="center">
<h1>PageTop Website</h1>
<p>Introduce el entorno de desarrollo <strong>PageTop</strong> a la comunidad.</p>
[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](#-license)
[![Crates.io](https://img.shields.io/crates/v/pagetop-website.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-website)
[![Descargas](https://img.shields.io/crates/d/pagetop-website.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-website)
</div>
## Descripción general
Aplicación web creada con el propio entorno `PageTop` para descubrir a la comunidad su ecosistema en
[pagetop.cillero.es](https://pagetop.cillero.es). Se trata de un portal de entrada con documentación
completa para desarrolladores y usuarios.
## Sobre PageTop
[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web
clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y
configurables, basadas en HTML, CSS y JavaScript.
# 🚧 Advertencia
`PageTop` es un proyecto personal que hago por diversión para aprender cosas nuevas. Su API es
inestable y está sujeta a cambios frecuentes. No recomiendo su uso en producción, al menos mientras
no se libere una versión **1.0.0**.
# 📜 Licencia
El código está disponible bajo una doble licencia:
* **Licencia MIT**
([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT)
* **Licencia Apache, Versión 2.0**
([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0)
Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en
el ecosistema Rust.

176
website/build.rs Normal file
View file

@ -0,0 +1,176 @@
use pagetop_build::StaticFilesBundle;
use std::collections::HashMap;
use std::env;
use std::fs::{self, File};
use std::io::{self, BufRead, BufReader, Write};
use std::path::Path;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
const BUILD_STATE_FILE: &str = "./static/build_state.txt";
fn main() -> io::Result<()> {
// Reconstruye la documentación.
rebuild_doc()?;
// Crea la colección de archivos estáticos.
StaticFilesBundle::from_dir(
"./static/doc",
Some(pagetop_mdbook::except_common_resources),
)
.with_name("doc")
.build()
}
/// Reconstruye la documentación que ha cambiado para cada versión y sus idiomas.
fn rebuild_doc() -> io::Result<()> {
// Lee el estado de la última preparación.
let mut build_state = read_build_state()?;
// Define la ruta al directorio `doc`.
let project_dir = env::var("CARGO_MANIFEST_DIR").map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
format!("Environment variable error: {}", e),
)
})?;
let doc_dir = Path::new(&project_dir).join("doc");
// Itera sobre las versiones de la documentación en `doc`.
for entry in fs::read_dir(&doc_dir)? {
let entry = entry?;
let version_dir = entry.path();
if version_dir.is_dir() && version_dir.file_name().unwrap_or_default() != "theme" {
let version_name = version_dir
.file_name()
.unwrap()
.to_string_lossy()
.to_string();
// Itera sobre los idiomas dentro de la versión.
for lang_entry in fs::read_dir(&version_dir)? {
let lang_entry = lang_entry?;
let lang_dir = lang_entry.path();
if lang_dir.is_dir() {
let lang_name = lang_dir.file_name().unwrap().to_string_lossy().to_string();
// Obtiene la última marca de tiempo de cambios en archivos para el idioma.
let latest_change_time = get_latest_change_time(&lang_dir)?;
// Obtiene la última marca de tiempo registrada para esta versión e idioma.
let state_key = format!("{}/{}", version_name, lang_name);
let last_build_time =
build_state.get(&state_key).copied().unwrap_or(UNIX_EPOCH);
// Si hay cambios, construye el libro para este idioma.
if latest_change_time > last_build_time {
build_mdbook(&lang_dir)?;
build_state.insert(state_key, SystemTime::now());
}
}
}
}
}
// Guarda el estado actualizado.
write_build_state(&build_state)?;
Ok(())
}
/// Construye el libro mdBook para una versión específica.
fn build_mdbook(version_dir: &Path) -> io::Result<()> {
if !Command::new("mdbook")
.arg("build")
.current_dir(version_dir)
.status()?
.success()
{
return Err(io::Error::new(
io::ErrorKind::Other,
format!("Failed to build mdBook in {:?}", version_dir),
));
}
Ok(())
}
/// Lee el estado de construcción desde un archivo de texto plano.
fn read_build_state() -> io::Result<HashMap<String, SystemTime>> {
let mut build_state = HashMap::new();
let path = Path::new(BUILD_STATE_FILE);
if path.exists() {
let file = File::open(path)?;
let reader = BufReader::new(file);
for line in reader.lines() {
let line = line?;
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() == 2 {
if let Ok(timestamp) = parts[1].parse::<u64>() {
build_state.insert(
parts[0].to_string(),
UNIX_EPOCH + std::time::Duration::from_secs(timestamp),
);
}
}
}
}
Ok(build_state)
}
/// Escribe el estado de construcción en un archivo de texto plano.
fn write_build_state(build_state: &HashMap<String, SystemTime>) -> io::Result<()> {
let path = Path::new(BUILD_STATE_FILE);
// Crea el directorio `static` si no existe.
if let Some(parent) = path.parent() {
if !parent.exists() {
fs::create_dir_all(parent)?;
}
}
let mut file = File::create(path)?;
for (version, &time) in build_state {
let timestamp = time
.duration_since(UNIX_EPOCH)
.map_err(|e| {
io::Error::new(io::ErrorKind::Other, format!("Invalid system time: {}", e))
})?
.as_secs();
writeln!(file, "{} {}", version, timestamp)?;
}
Ok(())
}
/// Obtiene la marca de tiempo más reciente de los archivos de un directorio usando recursión.
fn get_latest_change_time(dir: &Path) -> io::Result<SystemTime> {
let mut latest = UNIX_EPOCH;
/// Función recursiva para recorrer directorios.
fn visit_dir(dir: &Path, latest: &mut SystemTime) -> io::Result<()> {
for entry in fs::read_dir(dir)? {
let entry = entry?;
let metadata = entry.metadata()?;
if metadata.is_dir() {
// Recurse en los subdirectorios.
visit_dir(&entry.path(), latest)?;
} else if let Ok(modified) = metadata.modified() {
// Actualiza la última marca de tiempo.
if modified > *latest {
*latest = modified;
}
}
}
Ok(())
}
visit_dir(dir, &mut latest)?;
Ok(latest)
}

View file

@ -0,0 +1,10 @@
[app]
name = "PageTop"
description = "An opinionated web framework to build modular Server-Side Rendering web solutions."
#theme = "Basic"
#theme = "Chassis"
#theme = "Inception"
#theme = "Bootsier"
[log]
rolling = "Daily"

655
website/doc/theme/book.js vendored Normal file
View file

@ -0,0 +1,655 @@
"use strict";
// Fix back button cache problem
window.onunload = function () { };
// Global variable, shared between modules
function playground_text(playground, hidden = true) {
let code_block = playground.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
return editor.getValue();
} else if (hidden) {
return code_block.textContent;
} else {
return code_block.innerText;
}
}
(function codeSnippets() {
function fetch_with_timeout(url, options, timeout = 6000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout))
]);
}
var playgrounds = Array.from(document.querySelectorAll(".playground"));
if (playgrounds.length > 0) {
fetch_with_timeout("https://play.rust-lang.org/meta/crates", {
headers: {
'Content-Type': "application/json",
},
method: 'POST',
mode: 'cors',
})
.then(response => response.json())
.then(response => {
// get list of crates available in the rust playground
let playground_crates = response.crates.map(item => item["id"]);
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
});
}
function handle_crate_list_update(playground_block, playground_crates) {
// update the play buttons after receiving the response
update_play_button(playground_block, playground_crates);
// and install on change listener to dynamically update ACE editors
if (window.ace) {
let code_block = playground_block.querySelector("code");
if (code_block.classList.contains("editable")) {
let editor = window.ace.edit(code_block);
editor.addEventListener("change", function (e) {
update_play_button(playground_block, playground_crates);
});
// add Ctrl-Enter command to execute rust code
editor.commands.addCommand({
name: "run",
bindKey: {
win: "Ctrl-Enter",
mac: "Ctrl-Enter"
},
exec: _editor => run_rust_code(playground_block)
});
}
}
}
// updates the visibility of play button based on `no_run` class and
// used crates vs ones available on https://play.rust-lang.org
function update_play_button(pre_block, playground_crates) {
var play_button = pre_block.querySelector(".play-button");
// skip if code is `no_run`
if (pre_block.querySelector('code').classList.contains("no_run")) {
play_button.classList.add("hidden");
return;
}
// get list of `extern crate`'s from snippet
var txt = playground_text(pre_block);
var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
var snippet_crates = [];
var item;
while (item = re.exec(txt)) {
snippet_crates.push(item[1]);
}
// check if all used crates are available on play.rust-lang.org
var all_available = snippet_crates.every(function (elem) {
return playground_crates.indexOf(elem) > -1;
});
if (all_available) {
play_button.classList.remove("hidden");
} else {
play_button.classList.add("hidden");
}
}
function run_rust_code(code_block) {
var result_block = code_block.querySelector(".result");
if (!result_block) {
result_block = document.createElement('code');
result_block.className = 'result hljs language-bash';
code_block.append(result_block);
}
let text = playground_text(code_block);
let classes = code_block.querySelector('code').classList;
let edition = "2015";
if(classes.contains("edition2018")) {
edition = "2018";
} else if(classes.contains("edition2021")) {
edition = "2021";
}
var params = {
version: "stable",
optimize: "0",
code: text,
edition: edition
};
if (text.indexOf("#![feature") !== -1) {
params.version = "nightly";
}
result_block.innerText = "Running...";
fetch_with_timeout("https://play.rust-lang.org/evaluate.json", {
headers: {
'Content-Type': "application/json",
},
method: 'POST',
mode: 'cors',
body: JSON.stringify(params)
})
.then(response => response.json())
.then(response => {
if (response.result.trim() === '') {
result_block.innerText = "No output";
result_block.classList.add("result-no-output");
} else {
result_block.innerText = response.result;
result_block.classList.remove("result-no-output");
}
})
.catch(error => result_block.innerText = "Playground Communication: " + error.message);
}
// Syntax highlighting Configuration
hljs.configure({
tabReplace: ' ', // 4 spaces
languages: [], // Languages used for auto-detection
});
let code_nodes = Array
.from(document.querySelectorAll('code'))
// Don't highlight `inline code` blocks in headers.
.filter(function (node) {return !node.parentElement.classList.contains("header"); });
if (window.ace) {
// language-rust class needs to be removed for editable
// blocks or highlightjs will capture events
code_nodes
.filter(function (node) {return node.classList.contains("editable"); })
.forEach(function (block) { block.classList.remove('language-rust'); });
code_nodes
.filter(function (node) {return !node.classList.contains("editable"); })
.forEach(function (block) { hljs.highlightBlock(block); });
} else {
code_nodes.forEach(function (block) { hljs.highlightBlock(block); });
}
// Adding the hljs class gives code blocks the color css
// even if highlighting doesn't apply
code_nodes.forEach(function (block) { block.classList.add('hljs'); });
Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) {
var lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return
if (!lines.length) { return; }
block.classList.add("hide-boring");
var buttons = document.createElement('div');
buttons.className = 'buttons';
buttons.innerHTML = "<button class=\"fa fa-eye\" title=\"Show hidden lines\" aria-label=\"Show hidden lines\"></button>";
// add expand button
var pre_block = block.parentNode;
pre_block.insertBefore(buttons, pre_block.firstChild);
pre_block.querySelector('.buttons').addEventListener('click', function (e) {
if (e.target.classList.contains('fa-eye')) {
e.target.classList.remove('fa-eye');
e.target.classList.add('fa-eye-slash');
e.target.title = 'Hide lines';
e.target.setAttribute('aria-label', e.target.title);
block.classList.remove('hide-boring');
} else if (e.target.classList.contains('fa-eye-slash')) {
e.target.classList.remove('fa-eye-slash');
e.target.classList.add('fa-eye');
e.target.title = 'Show hidden lines';
e.target.setAttribute('aria-label', e.target.title);
block.classList.add('hide-boring');
}
});
});
if (window.playground_copyable) {
Array.from(document.querySelectorAll('pre code')).forEach(function (block) {
var pre_block = block.parentNode;
if (!pre_block.classList.contains('playground')) {
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
var clipButton = document.createElement('button');
clipButton.className = 'fa fa-copy clip-button';
clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class=\"tooltiptext\"></i>';
buttons.insertBefore(clipButton, buttons.firstChild);
}
});
}
// Process playground code blocks
Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) {
// Add play button
var buttons = pre_block.querySelector(".buttons");
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
var runCodeButton = document.createElement('button');
runCodeButton.className = 'fa fa-play play-button';
runCodeButton.hidden = true;
runCodeButton.title = 'Run this code';
runCodeButton.setAttribute('aria-label', runCodeButton.title);
buttons.insertBefore(runCodeButton, buttons.firstChild);
runCodeButton.addEventListener('click', function (e) {
run_rust_code(pre_block);
});
if (window.playground_copyable) {
var copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'fa fa-copy clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard';
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
}
let code_block = pre_block.querySelector("code");
if (window.ace && code_block.classList.contains("editable")) {
var undoChangesButton = document.createElement('button');
undoChangesButton.className = 'fa fa-history reset-button';
undoChangesButton.title = 'Undo changes';
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
buttons.insertBefore(undoChangesButton, buttons.firstChild);
undoChangesButton.addEventListener('click', function () {
let editor = window.ace.edit(code_block);
editor.setValue(editor.originalCode);
editor.clearSelection();
});
}
});
})();
(function themes() {
var html = document.querySelector('html');
var themeToggleButton = document.getElementById('theme-toggle');
var themePopup = document.getElementById('theme-list');
var themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
var stylesheets = {
ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"),
tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"),
highlight: document.querySelector("[href$='highlight.css']"),
};
function showThemes() {
themePopup.style.display = 'block';
themeToggleButton.setAttribute('aria-expanded', true);
themePopup.querySelector("button#" + get_theme()).focus();
}
function updateThemeSelected() {
themePopup.querySelectorAll('.theme-selected').forEach(function (el) {
el.classList.remove('theme-selected');
});
themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected');
}
function hideThemes() {
themePopup.style.display = 'none';
themeToggleButton.setAttribute('aria-expanded', false);
themeToggleButton.focus();
}
function get_theme() {
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { }
if (theme === null || theme === undefined) {
return default_theme;
} else {
return theme;
}
}
function set_theme(theme, store = true) {
let ace_theme;
if (theme == 'coal' || theme == 'navy') {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = false;
stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night";
} else if (theme == 'ayu') {
stylesheets.ayuHighlight.disabled = false;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = true;
ace_theme = "ace/theme/tomorrow_night";
} else {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = false;
ace_theme = "ace/theme/dawn";
}
setTimeout(function () {
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
}, 1);
if (window.ace && window.editors) {
window.editors.forEach(function (editor) {
editor.setTheme(ace_theme);
});
}
var previousTheme = get_theme();
if (store) {
try { localStorage.setItem('mdbook-theme', theme); } catch (e) { }
}
html.classList.remove(previousTheme);
html.classList.add(theme);
updateThemeSelected();
}
// Set theme
var theme = get_theme();
set_theme(theme, false);
themeToggleButton.addEventListener('click', function () {
if (themePopup.style.display === 'block') {
hideThemes();
} else {
showThemes();
}
});
themePopup.addEventListener('click', function (e) {
var theme;
if (e.target.className === "theme") {
theme = e.target.id;
} else if (e.target.parentElement.className === "theme") {
theme = e.target.parentElement.id;
} else {
return;
}
set_theme(theme);
});
themePopup.addEventListener('focusout', function(e) {
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) {
hideThemes();
}
});
// Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628
document.addEventListener('click', function(e) {
if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) {
hideThemes();
}
});
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (!themePopup.contains(e.target)) { return; }
switch (e.key) {
case 'Escape':
e.preventDefault();
hideThemes();
break;
case 'ArrowUp':
e.preventDefault();
var li = document.activeElement.parentElement;
if (li && li.previousElementSibling) {
li.previousElementSibling.querySelector('button').focus();
}
break;
case 'ArrowDown':
e.preventDefault();
var li = document.activeElement.parentElement;
if (li && li.nextElementSibling) {
li.nextElementSibling.querySelector('button').focus();
}
break;
case 'Home':
e.preventDefault();
themePopup.querySelector('li:first-child button').focus();
break;
case 'End':
e.preventDefault();
themePopup.querySelector('li:last-child button').focus();
break;
}
});
})();
(function sidebar() {
var body = document.querySelector("body");
var sidebar = document.getElementById("sidebar");
var sidebarLinks = document.querySelectorAll('#sidebar a');
var sidebarToggleButton = document.getElementById("sidebar-toggle");
var sidebarResizeHandle = document.getElementById("sidebar-resize-handle");
var firstContact = null;
function showSidebar() {
body.classList.remove('sidebar-hidden')
body.classList.add('sidebar-visible');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', 0);
});
sidebarToggleButton.setAttribute('aria-expanded', true);
sidebar.setAttribute('aria-hidden', false);
try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { }
}
var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle');
function toggleSection(ev) {
ev.currentTarget.parentElement.classList.toggle('expanded');
}
Array.from(sidebarAnchorToggles).forEach(function (el) {
el.addEventListener('click', toggleSection);
});
function hideSidebar() {
body.classList.remove('sidebar-visible')
body.classList.add('sidebar-hidden');
Array.from(sidebarLinks).forEach(function (link) {
link.setAttribute('tabIndex', -1);
});
sidebarToggleButton.setAttribute('aria-expanded', false);
sidebar.setAttribute('aria-hidden', true);
try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { }
}
// Toggle sidebar
sidebarToggleButton.addEventListener('click', function sidebarToggle() {
if (body.classList.contains("sidebar-hidden")) {
var current_width = parseInt(
document.documentElement.style.getPropertyValue('--sidebar-width'), 10);
if (current_width < 150) {
document.documentElement.style.setProperty('--sidebar-width', '150px');
}
showSidebar();
} else if (body.classList.contains("sidebar-visible")) {
hideSidebar();
} else {
if (getComputedStyle(sidebar)['transform'] === 'none') {
hideSidebar();
} else {
showSidebar();
}
}
});
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
function initResize(e) {
window.addEventListener('mousemove', resize, false);
window.addEventListener('mouseup', stopResize, false);
body.classList.add('sidebar-resizing');
}
function resize(e) {
var viewportOffset = sidebar.getBoundingClientRect();
var pos = (e.clientX - sidebar.offsetLeft - viewportOffset.left);
if (pos < 20) {
hideSidebar();
} else {
if (body.classList.contains("sidebar-hidden")) {
showSidebar();
}
pos = Math.min(pos, window.innerWidth - 100);
document.documentElement.style.setProperty('--sidebar-width', pos + 'px');
}
}
//on mouseup remove windows functions mousemove & mouseup
function stopResize(e) {
body.classList.remove('sidebar-resizing');
window.removeEventListener('mousemove', resize, false);
window.removeEventListener('mouseup', stopResize, false);
}
document.addEventListener('touchstart', function (e) {
firstContact = {
x: e.touches[0].clientX,
time: Date.now()
};
}, { passive: true });
document.addEventListener('touchmove', function (e) {
if (!firstContact)
return;
var curX = e.touches[0].clientX;
var xDiff = curX - firstContact.x,
tDiff = Date.now() - firstContact.time;
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300))
showSidebar();
else if (xDiff < 0 && curX < 300)
hideSidebar();
firstContact = null;
}
}, { passive: true });
})();
(function chapterNavigation() {
document.addEventListener('keydown', function (e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; }
if (window.search && window.search.hasFocus()) { return; }
var html = document.querySelector('html');
function next() {
var nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
}
}
function prev() {
var previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
}
}
switch (e.key) {
case 'ArrowRight':
e.preventDefault();
if (html.dir == 'rtl') {
prev();
} else {
next();
}
break;
case 'ArrowLeft':
e.preventDefault();
if (html.dir == 'rtl') {
next();
} else {
prev();
}
break;
}
});
})();
(function clipboard() {
var clipButtons = document.querySelectorAll('.clip-button');
function hideTooltip(elem) {
elem.firstChild.innerText = "";
elem.className = 'fa fa-copy clip-button';
}
function showTooltip(elem, msg) {
elem.firstChild.innerText = msg;
elem.className = 'fa fa-copy tooltipped';
}
var clipboardSnippets = new ClipboardJS('.clip-button', {
text: function (trigger) {
hideTooltip(trigger);
let playground = trigger.closest("pre");
return playground_text(playground, false);
}
});
Array.from(clipButtons).forEach(function (clipButton) {
clipButton.addEventListener('mouseout', function (e) {
hideTooltip(e.currentTarget);
});
});
clipboardSnippets.on('success', function (e) {
e.clearSelection();
showTooltip(e.trigger, "Copied!");
});
clipboardSnippets.on('error', function (e) {
showTooltip(e.trigger, "Clipboard error!");
});
})();
(function scrollToTop () {
var menuTitle = document.querySelector('.menu-title');
menuTitle.addEventListener('click', function () {
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
});
})();
(function controllMenu() {
var menu = document.getElementById('menu-bar');
(function controllBorder() {
function updateBorder() {
if (menu.offsetTop === 0) {
menu.classList.remove('bordered');
} else {
menu.classList.add('bordered');
}
}
updateBorder();
document.addEventListener('scroll', updateBorder, { passive: true });
})();
})();

82
website/doc/theme/highlight.css vendored Normal file
View file

@ -0,0 +1,82 @@
/*
* An increased contrast highlighting scheme loosely based on the
* "Base16 Atelier Dune Light" theme by Bram de Haan
* (http://atelierbram.github.io/syntax-highlighting/atelier-schemes/dune)
* Original Base16 color scheme by Chris Kempson
* (https://github.com/chriskempson/base16)
*/
/* Comment */
.hljs-comment,
.hljs-quote {
color: #575757;
}
/* Red */
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-tag,
.hljs-name,
.hljs-regexp,
.hljs-link,
.hljs-name,
.hljs-selector-id,
.hljs-selector-class {
color: #d70025;
}
/* Orange */
.hljs-number,
.hljs-meta,
.hljs-built_in,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #b21e00;
}
/* Green */
.hljs-string,
.hljs-symbol,
.hljs-bullet {
color: #008200;
}
/* Blue */
.hljs-title,
.hljs-section {
color: #0030f2;
}
/* Purple */
.hljs-keyword,
.hljs-selector-tag {
color: #9d00ec;
}
.hljs {
display: block;
overflow-x: auto;
background: #f6f7f6;
color: #000;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-addition {
color: #22863a;
background-color: #f0fff4;
}
.hljs-deletion {
color: #b31d28;
background-color: #ffeef0;
}

53
website/doc/theme/highlight.js vendored Normal file

File diff suppressed because one or more lines are too long

292
website/doc/theme/index.hbs vendored Normal file
View file

@ -0,0 +1,292 @@
<!-- Lang: {{ language }} -->
<!-- Title: {{ title }} -->
<!-- Print: {{#if print_enable}}enabled{{else}}disabled{{/if}} -->
<!-- MathJax: {{#if mathjax_support}}supported{{else}}unsupported{{/if}} -->
<!-- mdBook -->
<!-- Provide site root to javascript -->
<script>
var path_to_root = "{{ path_to_root }}";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "{{ preferred_dark_theme }}" : "{{ default_theme }}";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('{{ default_theme }}')
html.classList.add(theme);
var body = document.querySelector('body');
body.classList.remove('no-js')
body.classList.add('js');
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
var body = document.querySelector('body');
var sidebar = null;
var sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
body.classList.remove('sidebar-visible');
body.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
{{#toc}}{{/toc}}
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<!-- Track and set sidebar scroll position -->
<script>
var sidebarScrollbox = document.querySelector('#sidebar .sidebar-scrollbox');
sidebarScrollbox.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
sessionStorage.setItem('sidebar-scroll', sidebarScrollbox.scrollTop);
}
}, { passive: true });
var sidebarScrollTop = sessionStorage.getItem('sidebar-scroll');
sessionStorage.removeItem('sidebar-scroll');
if (sidebarScrollTop) {
// preserve sidebar scroll position when navigating via links within sidebar
sidebarScrollbox.scrollTop = sidebarScrollTop;
} else {
// scroll sidebar to current active section when navigating via "next/previous chapter" buttons
var activeSection = document.querySelector('#sidebar .active');
if (activeSection) {
activeSection.scrollIntoView({ block: 'center' });
}
}
</script>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
{{> header}}
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
{{#if search_enabled}}
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
{{/if}}
</div>
<h1 class="menu-title">{{ book_title }}</h1>
<div class="right-buttons">
{{#if print_enable}}
<a href="{{ path_to_root }}print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
{{/if}}
{{#if git_repository_url}}
<a href="{{git_repository_url}}" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa {{git_repository_icon}}"></i>
</a>
{{/if}}
{{#if git_repository_edit_url}}
<a href="{{git_repository_edit_url}}" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
{{/if}}
</div>
</div>
{{#if search_enabled}}
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
{{/if}}
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
{{{ content }}}
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
{{#previous}}
<a rel="prev" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
{{#previous}}
<a rel="prev" href="{{ path_to_root }}{{link}}" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
{{/previous}}
{{#next}}
<a rel="next prefetch" href="{{ path_to_root }}{{link}}" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
{{/next}}
</nav>
</div>
{{#if live_reload_endpoint}}
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "{{{live_reload_endpoint}}}";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
{{/if}}
{{#if google_analytics}}
<!-- Google Analytics Tag -->
<script>
var localAddrs = ["localhost", "127.0.0.1", ""];
// make sure we don't activate google analytics if the developer is
// inspecting the book locally...
if (localAddrs.indexOf(document.location.hostname) === -1) {
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', '{{google_analytics}}', 'auto');
ga('send', 'pageview');
}
</script>
{{/if}}
{{#if playground_line_numbers}}
<script>
window.playground_line_numbers = true;
</script>
{{/if}}
{{#if playground_copyable}}
<script>
window.playground_copyable = true;
</script>
{{/if}}
{{#if playground_js}}
<script src="{{ path_to_root }}ace.js"></script>
<script src="{{ path_to_root }}editor.js"></script>
<script src="{{ path_to_root }}mode-rust.js"></script>
<script src="{{ path_to_root }}theme-dawn.js"></script>
<script src="{{ path_to_root }}theme-tomorrow_night.js"></script>
{{/if}}
{{#if search_js}}
<script src="{{ path_to_root }}elasticlunr.min.js"></script>
<script src="{{ path_to_root }}mark.min.js"></script>
<script src="{{ path_to_root }}searcher.js"></script>
{{/if}}
<script src="{{ path_to_root }}clipboard.min.js"></script>
<script src="{{ path_to_root }}highlight.js"></script>
<script src="{{ path_to_root }}book.js"></script>
<!-- Custom JS scripts -->
{{#each additional_js}}
<script src="{{ ../path_to_root }}{{this}}"></script>
{{/each}}
{{#if is_print}}
{{#if mathjax_support}}
<script>
window.addEventListener('load', function() {
MathJax.Hub.Register.StartupHook('End', function() {
window.setTimeout(window.print, 100);
});
});
</script>
{{else}}
<script>
window.addEventListener('load', function() {
window.setTimeout(window.print, 100);
});
</script>
{{/if}}
{{/if}}

View file

@ -0,0 +1,11 @@
[book]
authors = ["Manuel Cillero"]
language = "en"
src = "src"
title = "PageTop Guides"
[build]
build-dir = "../../../static/doc/v0.0/en"
[output.html]
theme = "../../theme"

View file

@ -0,0 +1,3 @@
# Document not found (404)
This URL is invalid, sorry. Try the search instead!

View file

@ -0,0 +1,3 @@
# Summary
[Introduction](intro.md)

View file

@ -0,0 +1,3 @@
<div style="border: 1px solid black; padding: 10px; text-align: center;">
<strong>This website is under construction</strong>.<br>If you're curious, you can read the <a href="/doc/latest/es">latest version of this guide in Spanish</a>.
</div>

View file

@ -0,0 +1,11 @@
[book]
authors = ["Manuel Cillero"]
language = "es"
src = "src"
title = "Guías de PageTop"
[build]
build-dir = "../../../static/doc/v0.0/es"
[output.html]
theme = "../../theme"

View file

@ -0,0 +1,3 @@
# Documento no encontrado (404)
Esta URL no es válida, lo sentimos. ¡Intenta buscar en su lugar!

View file

@ -0,0 +1,17 @@
# Summary
[Introducción](intro.md)
# Inicio rápido
- [Comenzando](getting-started.md)
- [Prepara tu entorno](configuration.md)
- [Aplicaciones](apps.md)
- [Paquetes](packages.md)
- [Componentes](components.md)
- [Acciones](actions.md)
- [Temas](themes.md)
# Tutoriales
- [Cómo crear un paquete desde cero](tutorials/create-a-package-from-scratch.md)

View file

@ -0,0 +1 @@
# Acciones

View file

@ -0,0 +1,61 @@
# Aplicaciones
Los programas de PageTop se denominan [Aplicaciones](https://docs.rs/pagetop/latest/pagetop/app/struct.Application.html). La aplicación PageTop más simple luce así:
```rust
use pagetop::prelude::*;
#[pagetop::main]
async fn main() -> std::io::Result<()> {
Application::new().run()?.await
}
```
La línea `use pagetop::prelude::*;` sirve para importar la API esencial de PageTop. Por brevedad, esta guía podría omitirla en ejemplos posteriores.
Ahora sólo tienes que copiar el código anterior en tu archivo `main.rs` y desde la carpeta del proyecto ejecutar:
```bash
cargo run
```
Si todo ha ido bien, después de compilar el código se ejecutará la aplicación. El terminal quedará en espera mostrando el *nombre* y *lema* predefinidos.
Ahora abre un navegador en el mismo equipo y escribe `http://localhost:8088` en la barra de direcciones. Y ya está, ¡la página de presentación de PageTop te dará la bienvenida!
Sin embargo, aún no hemos indicado a nuestra aplicación qué hacer.
# Extendiendo PageTop
La [API de PageTop](https://docs.rs/pagetop) ofrece cuatro instrumentos esenciales para construir una aplicación:
- [**Paquetes**](packages.md), que añaden, amplían o personalizan funcionalidades interactuando con la API de PageTop o las APIs de paquetes de terceros.
- [**Componentes**](components.md), para encapsular HTML, CSS y JavaScript en unidades funcionales, configurables y bien definidas.
- [**Acciones**](actions.md), alteran el comportamiento interno de otros elementos de PageTop interceptando su flujo de ejecución.
- [**Temas**](themes.md), son *paquetes* que permiten a los desarrolladores cambiar la apariencia de páginas y componentes sin afectar su funcionalidad.
Si quieres saber más sobre el funcionamiento interno de las aplicaciones, continúa leyendo. Si no, puedes saltar a la siguiente página y empezar a añadir lógica a nuestra primera aplicación.
# ¿Qué hace una aplicación?
Como hemos visto arriba, primero se instancia la [Aplicación](https://docs.rs/pagetop/latest/pagetop/app/struct.Application.html). Podemos hacerlo usando [`new()`](https://docs.rs/pagetop/latest/pagetop/app/struct.Application.html#method.new), como en el ejemplo, o con [`prepare()`](https://docs.rs/pagetop/latest/pagetop/app/struct.Application.html#method.prepare), que veremos en la siguiente página. Ambos se encargan de iniciar los diferentes subsistemas de PageTop por este orden:
1. Inicializa la traza de mensajes de registro y eventos.
2. Valida el identificador global de idioma.
3. Conecta con la base de datos.
4. Registra los paquetes de la aplicación según sus dependencias internas.
5. Registra las acciones de los paquetes.
6. Inicializa los paquetes.
7. Ejecuta las actualizaciones pendientes de la base de datos.
Pero no ejecuta la aplicación. Para eso se usa el método [`run()`](https://docs.rs/pagetop/latest/pagetop/app/struct.Application.html#method.run), que arranca el servidor web para empezar a responder las peticiones desde cualquier navegador.
Hablaremos más de todos estos subsistemas en próximas páginas. Mientras tanto, ¡vamos a añadir algo de lógica a nuestra aplicación creando un paquete con un nuevo servicio web!

View file

@ -0,0 +1 @@
# Componentes

View file

@ -0,0 +1,90 @@
# Prepara tu entorno
PageTop está escrito en Rust. Antes de empezar a crear tu aplicación web, es importante dedicar un tiempo a preparar tu entorno de desarrollo con Rust.
## Instalación de Rust
PageTop depende en gran medida de las mejoras que se aplican en el lenguaje y el compilador Rust. Procura tener instalada "*la última versión estable*" para admitir la Versión Mínima de Rust Soportada (MSRV) por PageTop.
Puedes instalar Rust siguiendo la [Guía de Inicio Rápido de Rust](https://www.rust-lang.org/learn/get-started).
Una vez completada la instalación, tendrás disponibles en tu sistema el compilador `rustc` y `cargo` para la construcción y gestión de paquetes (*crates*) de Rust.
## Recursos para aprender Rust
El objetivo de esta guía es aprender a programar con PageTop rápidamente, por lo que no te va a servir como material de aprendizaje de Rust. Si deseas saber más sobre el [lenguaje de programación Rust](https://www.rust-lang.org), consulta los siguientes recursos:
* **[El Libro de Rust](https://doc.rust-lang.org/book/)**: el mejor lugar para aprender Rust desde cero.
* **[Rust con Ejemplos](https://doc.rust-lang.org/rust-by-example/)**: aprende Rust programando ejemplos de todo tipo.
* **[Rustlings](https://github.com/rust-lang/rustlings)**: una serie de ejercicios divertidos e interactivos para conocer Rust.
## Editor de código / IDE
Puedes usar tu editor de código preferido, pero se recomienda uno que permita instalar la extensión de [rust-analyzer](https://github.com/rust-lang/rust-analyzer). Aunque aún está en desarrollo, proporciona autocompletado y una inteligencia de código avanzada. [Visual Studio Code](https://code.visualstudio.com/) tiene una [extensión de rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) oficialmente soportada.
# Tu primer proyecto PageTop
¡Ha llegado el momento de programar con PageTop! Inicialmente PageTop es sólo una dependencia más en tu proyecto. Puedes añadirlo a un proyecto ya existente o crear uno nuevo. Para ser completos, asumiremos que estás empezando desde cero.
## Crea un nuevo proyecto de ejecutable Rust
Primero, navega a una carpeta donde quieras crear tu nuevo proyecto. Luego, ejecuta el siguiente comando para crear una nueva carpeta que contenga nuestro proyecto de ejecutable Rust:
```bash
cargo new my_pagetop_app
cd my_pagetop_app
```
Ahora ejecuta `cargo run` para compilar y ejecutar tu proyecto. Deberías ver el texto "Hello, world!" en tu terminal. Abre la carpeta "my_pagetop_app" en tu editor de código preferido y tómate un tiempo para revisar los archivos.
`main.rs` es el punto de entrada de tu programa:
```rust
fn main() {
println!("Hello, world!");
}
```
`Cargo.toml` es tu "archivo de proyecto". Contiene metadatos sobre tu proyecto, como su nombre, dependencias y configuración para compilarlo.
```toml
[package]
name = "my_pagetop_app"
version = "0.1.0"
edition = "2021"
[dependencies]
```
## Añade PageTop como dependencia
PageTop está disponible como [biblioteca en crates.io](https://crates.io/crates/pagetop), el repositorio oficial de paquetes (*crates*) Rust.
La forma más fácil de incorporarlo en tu proyecto es usar `cargo add`:
```bash
cargo add pagetop
```
O puedes añadirlo manualmente en el archivo `Cargo.toml` del proyecto escribiendo:
```toml
[package]
name = "my_pagetop_app"
version = "0.1.0"
edition = "2021" # debe ser 2021, o necesitarás configurar "resolver=2"
[dependencies]
pagetop = "0.0.X" # siendo X la última versión de desarrollo
```
Asegúrate de que se añade la última versión disponible de PageTop:
[![Crates.io](https://img.shields.io/crates/v/pagetop.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop)
## Construye PageTop
Ahora ejecuta `cargo run` nuevamente. Las dependencias de PageTop deberían comenzar a compilarse. Tomará algo de tiempo ya que es la primera compilación de tu proyecto con PageTop. Esto sólo ocurrirá la primera vez. ¡Cada compilación después de esta será más rápida!
Ahora que tenemos nuestro proyecto PageTop preparado, ¡estamos listos para programar nuestra primera aplicación PageTop!

View file

@ -0,0 +1,40 @@
# Comenzando
Esta sección te ayudará a conocer PageTop de la manera más rápida posible. Te enseñará a preparar un entorno de desarrollo apropiado para crear una aplicación web sencilla usando PageTop.
# Inicio rápido
Si quieres entrar de lleno en el código de PageTop y ya cuentas con un entorno de Rust operativo puedes seguir leyendo este apartado de "inicio rápido".
En otro caso puedes pasar a la siguiente página para preparar un entorno de Rust desde cero y empezar a programar tu primera aplicación web con PageTop.
<!-- Nota: la configuración para "compilaciones rápidas" se encuentra en la próxima página, por lo que podrías querer leer esa sección primero. -->
## Empieza con los ejemplos
1. Clona el [repositorio de PageTop](https://github.com/manuelcillero/pagetop):
```bash
git clone https://github.com/manuelcillero/pagetop
```
2. Cambia a la carpeta recién creada "pagetop":
```bash
cd pagetop
```
3. Asegurate de que trabajas con la última versión de PageTop (ya que por defecto se descarga la rama principal de git):
```bash
git checkout latest
```
4. Prueba los ejemplos de la [carpeta de ejemplos](https://github.com/manuelcillero/pagetop/tree/latest/examples):
```bash
cargo run --example hello-world
```
Recuerda que cada ejecución pone en marcha un servidor web. Tendrás que abrir un navegador y acceder a la dirección `http://localhost:8088` (según configuración predeterminada) para comprobar el funcionamiento de los servicios web ofrecidos por cada ejemplo. Para detener la ejecución del servidor bastará con pulsar `Ctrl-C` en el terminal.

View file

@ -0,0 +1,42 @@
# Introducción
Si quieres aprender a construir soluciones web que rescaten la esencia de los orígenes usando HTML, CSS y JavaScript para crear páginas web ofrecidas desde un servidor, pero con la potencia de un lenguaje de programación rápido y seguro como Rust, entonces... ¡has llegado a buen puerto!
# ¿Qué es PageTop?
**PageTop** es un marco de desarrollo web que proporciona herramientas y patrones de diseño predefinidos para el desarrollo de soluciones web seguras, modulares y personalizables con *Renderizado desde el Servidor* ([SSR](#ssr)).
PageTop está desarrollado en el [lenguaje de programación Rust](https://www.rust-lang.org/) y se apoya sobre los hombros de auténticos gigantes, porque utiliza algunas de las librerías (*crates*) más estables y reconocidas del [ecosistema Rust](https://lib.rs) como:
- [Actix Web](https://github.com/actix/actix-web), para la gestión de los servicios y del servidor web.
- [Tracing](https://github.com/tokio-rs/tracing), para el sistema de diagnóstico y mensajes de registro estructurados.
- [Fluent templates](https://github.com/XAMPPRocky/fluent-templates), que incorpora [Fluent](https://projectfluent.org/) para internacionalizar los proyectos.
- [SeaORM](https://github.com/SeaQL/sea-orm), que usa [SQLx](https://github.com/launchbadge/sqlx) para modelar el acceso a bases de datos.
- Integra versiones *ad hoc* de [config-rs](https://github.com/mehcode/config-rs) y [Maud](https://github.com/lambda-fairy/maud) en su código.
- Y usa otras librerías que puedes ver en el archivo [`Cargo.toml`](https://github.com/manuelcillero/pagetop/blob/latest/Cargo.toml) de PageTop.
# SSR
El *Renderizado desde el Servidor* (SSR) es una técnica de desarrollo web en la que el contenido HTML se genera en el servidor antes de enviarlo al navegador del usuario, donde CSS y JavaScript añaden la interactividad necesaria. PageTop encapsula todos estos elementos en **componentes** unitarios que pueden mantenerse de forma independiente y ser extendidos o modificados por otras librerías.
Esto contrasta con la *Renderización desde el Cliente* (CSR), donde es el navegador el que genera el contenido HTML tras recibir el código WebAssembly o JavaScript necesario desde el servidor.
PageTop usa SSR como una solución robusta para la creación de soluciones web complejas. Pero también presenta desafíos, como ciclos de desarrollo más lentos por la necesidad de recompilar cada cambio en el código Rust. No obstante, ofrece excelentes tiempos de carga iniciales, mejora en el SEO, y unifica el desarrollo en cliente y servidor bajo un mismo lenguaje.
# Contribuciones
PageTop [empezó como un proyecto personal](https://manuel.cillero.es/blog/aprendiendo-rust-presentando-pagetop/) para aprender a programar con Rust. Es [libre y de código abierto](https://github.com/manuelcillero/pagetop#-license), para siempre. Y puedes contribuir aumentando su versatilidad, documentando, traduciendo o corrigiendo errores. Pero también puedes crear tus propios paquetes o temas que otros desarrolladores podrán utilizar en sus proyectos.
# Advertencia
PageTop está aún en las primeras etapas de desarrollo. Faltan características importantes y otras no funcionan como deberían. Y la documentación es escasa. Sólo se liberan versiones de desarrollo con cambios importantes en la API que desaconseja su uso en producción. Úsalo si estás interesado en conocerlo o quieres contribuir.
Si necesitas un entorno *fullstack* estable y robusto para tu próximo proyecto, puedes mirar [Perseus](https://github.com/framesurge/perseus) basado en la excelente librería [Sycamore](https://github.com/sycamore-rs/sycamore), también te entusiasmará [Rocket](https://github.com/rwf2/Rocket), sin descartar [MoonZoon](https://github.com/MoonZoon/MoonZoon) o [Percy](https://github.com/chinedufn/percy). Y puedes crear tu propio *framework* combinando soluciones como [Yew](https://yew.rs/), [Leptos](https://leptos.dev/) o [Dioxus](https://dioxuslabs.com/) con el servidor [Axum](https://github.com/tokio-rs/axum) y el ORM [Diesel](https://github.com/diesel-rs/diesel) para construir increíbles aplicaciones [SSR](https://en.wikipedia.org/wiki/Server-side_scripting).
Si aún sigues por aquí, ¡ha llegado el momento de empezar a aprender algo de PageTop!
La guía de [Inicio Rápido](getting-started.html) te enseñará a probar los ejemplos. También te ayudará con la [configuración](configuration.html) de tu entorno de desarrollo y te orientará con los próximos pasos a seguir.

View file

@ -0,0 +1,56 @@
# Paquetes
Una de las características más poderosas de PageTop es su extensibilidad mediante el uso de [Paquetes](https://docs.rs/pagetop/latest/pagetop/core/package/index.html). Los paquetes añaden, amplían o personalizan funcionalidades para nuestra aplicación.
Un paquete es una [estructura unitaria](https://stackoverflow.com/questions/67689613/what-is-a-real-world-example-of-using-a-unit-struct) (*unit struct*) que implementa el *trait* [`PackageTrait`](https://docs.rs/pagetop/latest/pagetop/core/package/trait.PackageTrait.html). Los métodos de [`PackageTrait`](https://docs.rs/pagetop/latest/pagetop/core/package/trait.PackageTrait.html) tienen un funcionamiento predefinido que se puede personalizar.
Los paquetes tienen acceso a puntos de nuestra aplicación donde PageTop permite que el código de terceros haga ciertas cosas.
## ¡Hola mundo!
Para añadir lógica a nuestra [aplicación](apps.html) puedes crear un paquete en tu archivo `main.rs` sustituyendo el código de ejemplo por este nuevo código:
```rust
use pagetop::prelude::*;
struct HelloWorld;
impl PackageTrait for HelloWorld {
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
scfg.route("/", service::web::get().to(hello_world));
}
}
async fn hello_world(request: HttpRequest) -> ResultPage<Markup, ErrorPage> {
Page::new(request)
.with_component(Html::with(html! { h1 { "Hello World!" } }))
.render()
}
#[pagetop::main]
async fn main() -> std::io::Result<()> {
Application::prepare(&HelloWorld).run()?.await
}
```
La función `main()` instancia la aplicación usando el método `prepare()` con una referencia ([`PackageRef`](https://docs.rs/pagetop/latest/pagetop/core/package/type.PackageRef.html)) al paquete `HelloWorld`. Así se indica a PageTop que debe incluirlo en su registro interno de paquetes.
`HelloWorld` configura un servicio en la ruta de inicio ("/") que se implementa en `hello_world()`. Esta función devuelve una página web con un componente que directamente renderiza código HTML para mostrar un título con el texto *Hello World!*.
Ahora si compilamos y ejecutamos nuestra aplicación con `cargo run` y en el navegador volvemos a cargar la dirección `http://localhost:8088`, veremos el saludo esperado.
## Librerías
Los paquetes en PageTop son *crates* de biblioteca, usualmente publicados en [crates.io](https://crates.io/search?q=pagetop), que puedes usar como dependencias en tu aplicación.
# Seguridad
Los paquetes ajenos a PageTop contienen código desarrollado por terceros y, dado que pueden hacer básicamente lo que quieran, pueden representar un serio riesgo para la seguridad de tu sistema. Por ejemplo, un paquete podría indicar que está analizando la entrada del usuario y realmente está descargando ransomware en tu computadora.
Cualquier sospecha sobre paquetes malintencionados debe ser reportado confidencialmente al administrador de PageTop para ser analizado por la comunidad.
<!--
## El registro de paquetes
En este sitio web, se mantiene un registro de todos los paquetes conocidos. El ecosistema es joven. Los paquetes respaldados por la comunidad de PageTop tendrán una marca de verificación, aunque PageTop no se responsabiliza de ningún modo por paquetes malintencionados al ser código de terceros. Puedes añadir tus propios paquetes al registro siguiendo las instrucciones en nuestro sistema de reporte de issues, que te guiará a través del proceso.
-->

View file

@ -0,0 +1 @@
# Temas

View file

@ -0,0 +1,156 @@
# Cómo crear un paquete desde cero
Este tutorial describe cómo se ha creado el módulo `pagetop-jquery` para incluir la librería [jQuery](https://jquery.com) en las páginas web generadas por otros módulos o aplicaciones desarrolladas con **PageTop**.
# Primeros pasos
Para este tutorial se suponen conocimientos de programación con [Rust](https://www.rust-lang.org), del gestor de paquetes [`cargo`](https://doc.rust-lang.org/stable/cargo), así como de la [API de PageTop](https://docs.rs/pagetop). Los ejemplos están pensados para entornos Linux pero no deberían ser muy diferentes en otros sistemas operativos.
## Crear el proyecto
La forma más sencilla de empezar es rompiendo el hielo con el gestor de paquetes `cargo` siguiendo los mismos pasos que en cualquier otro proyecto Rust:
```bash
cargo new --lib pagetop-jquery
```
Accede al proyecto recién creado y añade la dependiencia a **PageTop**:
```bash
cd pagetop-jquery
cargo add pagetop
```
## Soporte multilingüe
La [API de localización](https://docs.rs/pagetop/latest/pagetop/locale/index.html) de **PageTop** proporciona soporte multilingüe a los módulos. Primero crea la estructura de carpetas para los archivos de los textos en inglés y español (únicos idiomas soportados actualmente):
```bash
mkdir src/locale
mkdir src/locale/en-US
mkdir src/locale/es-ES
```
Crea el archivo `src\locale\en-US\module.flt` con las siguientes asignaciones para identificar y describir el módulo:
```ini
package_name = jQuery support
package_description = Integrate the jQuery library into web pages generated by other modules.
```
Y su archivo equivalente `src\locale\es-ES\module.flt` para las mismas asignaciones en español:
```ini
package_name = Soporte a jQuery
package_description = Incorpora la librería jQuery en páginas web generadas por otros módulos.
```
Estas primeras asignaciones suelen ser habituales en todos los módulos.
## Iniciar el módulo
Para desarrollar un módulo **PageTop** hay que implementar los métodos necesarios del *trait* [`ModuleTrait`](https://docs.rs/pagetop/latest/pagetop/core/module/trait.ModuleTrait.html) sobre una estructura vacía que se puede inicializar en el archivo `src/lib.rs`:
```rust
use pagetop::prelude::*;
static_locales!(LOCALES_JQUERY);
#[derive(AssignHandle)]
pub struct JQuery;
impl ModuleTrait for JQuery {
fn name(&self) -> L10n {
L10n::t("package_name", &LOCALES_JQUERY)
}
fn description(&self) -> L10n {
L10n::t("package_description", &LOCALES_JQUERY)
}
}
```
La función [`handle()`](https://docs.rs/pagetop/latest/pagetop/core/module/trait.ModuleTrait.html#method.handle) es la única que obligatoriamente debe implementarse porque permite asignar al módulo un identificador único creado previamente con la macro [`create_handle!()`](https://docs.rs/pagetop/latest/pagetop/macro.create_handle.html).
El soporte multilingüe se incorpora con la macro [`static_locales!()`](https://docs.rs/pagetop/latest/pagetop/macro.static_locales.html) asignando un identificador a la ruta de los archivos de localización (que puede omitirse si la ruta es `src/locale`).
Las funciones [`name()`](https://docs.rs/pagetop/latest/pagetop/core/module/trait.ModuleTrait.html#method.name) y [`description()`](https://docs.rs/pagetop/latest/pagetop/core/module/trait.ModuleTrait.html#method.description) son opcionales, aunque se recomienda su implementación para identificar y describir adecuadamente el módulo. Hacen uso del componente [`L10n`](https://docs.rs/pagetop/latest/pagetop/base/component/struct.L10n.html) y de los archivos de localización creados en el apartado anterior para devolver los textos en el idioma del contexto.
# Archivos estáticos
Seguimos en el directorio del proyecto, al mismo nivel de `src`. Es buena práctica crear una carpeta de nombre `static` para los archivos estáticos. En ella descargaremos los archivos `jquery.min.js` y `jquery.min.map` de la librería **jQuery**:
```bash
mkdir static
```
## Crear el archivo build.rs
Para que estos archivos estáticos formen parte del binario de la aplicación hay que añadir dos nuevas dependencias al proyecto:
```bash
cargo add static-files
cargo add pagetop-build --build
```
Y crear un archivo `build.rs` con el siguiente código para incorporar el directorio `./static` en los recursos de compilación del proyecto:
```rust
use pagetop_build::StaticFilesBundle;
fn main() -> std::io::Result<()> {
StaticFilesBundle::from_dir("./static")
.with_name("jquery")
.build()
}
```
En este momento el proyecto tiene la siguiente estructura de directorios y archivos:
```bash
pagetop-jquery/
├── src/
│ ├── locale/
│ │ ├── en-ES/
│ │ │ └── module.flt
│ │ └── es-ES/
│ │ └── module.flt
│ └── lib.rs
├── static/
│ ├── jquery.min.js
│ └── jquery.min.map
└── Cargo.toml
```
## Declarar los archivos en el módulo
En `src/lib.rs` incorpora los recursos estáticos con `static_files!()` usando como identificador el nombre proporcionado al *bundle* de archivos en `build.rs` con `.with_name("jquery")`, pero ahora sin las comillas dobles:
```rust
static_files!(jquery);
```
Y en la implementación de `JQuery` añade la función `configure_service()` para configurar el servicio web que responderá a las peticiones cuando el *path* comience por `/jquery/*` usando la macro [`configure_service_for_static_files!()`]():
```rust
impl ModuleTrait for JQuery {
...
fn configure_service(&self, cfg: &mut service::web::ServiceConfig) {
configure_service_for_static_files!(cfg, jquery => "/jquery");
}
...
}
```
De esta forma, a la petición `/jquery/jquery.min.js` el servidor web responderá devolviendo el archivo estático `./static/jquery.min.js`.
# La API del módulo
Este módulo proporciona funciones públicas para añadir o quitar del contexto los recursos de **jQuery**:
--------------------
***Notas***
* Puede que el código del módulo no sea el mismo que aquí se reproduce. El sentido de este tutorial es proporcionar una explicación sencilla de los principales elementos de un módulo.

View file

@ -0,0 +1,10 @@
app_name = PageTop Landing Page
app_description = Discover PageTop with helpful user guides, tutorials, and practical examples.
app_slogan = Modular web solutions made simple
menu_home = Home
menu_documentation = Documentation
menu_api = API
menu_code = Code
under_construction = <strong>This website is under construction</strong>. If you're curious, you can read the <a href="/doc/latest/es">latest Spanish version of the PageTop documentation</a>.

View file

@ -0,0 +1,10 @@
app_name = Presentación de PageTop
app_description = Descubre PageTop con guías útiles para el usuario, tutoriales y ejemplos prácticos.
app_slogan = Crea fácilmente soluciones web modulares
menu_home = Inicio
menu_documentation = Documentación
menu_api = API
menu_code = Código
under_construction = <strong>Este sitio web está en construcción</strong>. Si tienes curiosidad, puedes leer la <a href="/doc/latest/es">versión más reciente de la documentación de PageTop en español</a>.

109
website/src/main.rs Normal file
View file

@ -0,0 +1,109 @@
use pagetop::prelude::*;
include_files!(BUNDLE_DOC => doc);
include_locales!(LOCALES_WEBSITE);
struct PageTopWebSite;
impl PackageTrait for PageTopWebSite {
fn name(&self) -> L10n {
L10n::t("app_name", &LOCALES_WEBSITE)
}
fn description(&self) -> L10n {
L10n::t("app_description", &LOCALES_WEBSITE)
}
fn dependencies(&self) -> Vec<PackageRef> {
vec![
// Packages.
&pagetop_mdbook::MdBook,
// Theme.
//&pagetop_bootsier::Bootsier,
]
}
fn init(&self) {
/*
let branding = Branding::new()
.with_logo(Some(Image::pagetop()))
.with_slogan(L10n::t("app_slogan", &LOCALES_WEBSITE))
.with_frontpage(|cx| match cx.langid().language.as_str() {
"es" => "/es",
_ => "/",
});
let menu = Menu::new()
.add_item(menu::Item::link(
L10n::t("menu_home", &LOCALES_WEBSITE),
|cx| match cx.langid().language.as_str() {
"es" => "/es",
_ => "/",
},
))
.add_item(menu::Item::link(
L10n::t("menu_documentation", &LOCALES_WEBSITE),
|cx| match cx.langid().language.as_str() {
"es" => "/doc/latest/es",
_ => "/doc/latest/en",
},
))
.add_item(menu::Item::link_blank(
L10n::t("menu_api", &LOCALES_WEBSITE),
|_| "https://docs.rs/pagetop",
))
.add_item(menu::Item::link_blank(
L10n::t("menu_code", &LOCALES_WEBSITE),
|_| "https://github.com/manuelcillero/pagetop",
))
.add_item(menu::Item::html(Html::with(html! {
select id="select-lang" {
option value="en" { "EN" }
option value="es" { "ES" }
}
script {
r###"
var selectLang=document.getElementById('select-lang');
selectLang.value=document.documentElement.lang;
selectLang.addEventListener('change',function(){window.location.href='/'+selectLang.value;});
"###
}
})));
InRegion::Named("header").add(AnyComponent::with(
flex::Container::new()
.with_direction(flex::Direction::Row(BreakPoint::None))
.with_justify(flex::Justify::SpaceBetween)
.with_align(flex::Align::End)
.add_item(flex::Item::with(branding))
.add_item(flex::Item::with(menu)),
));
InRegion::Named("pagetop").add(AnyComponent::with(
Block::new()
.with_style(StyleBase::Info)
.add_component(Paragraph::fluent(L10n::t(
"under_construction",
&LOCALES_WEBSITE,
))),
));
InRegion::Named("footer").add(AnyComponent::with(PoweredBy::new()));
*/
}
fn configure_service(&self, scfg: &mut service::web::ServiceConfig) {
scfg.route("/doc/latest/{lang}", service::web::get().to(doc_latest));
pagetop_mdbook::MdBook::mdbook_service(scfg, "/doc", &BUNDLE_DOC);
}
}
async fn doc_latest(path: service::web::Path<String>) -> service::HttpResponse {
match path.into_inner().as_str() {
"es" => Redirect::see_other("/doc/v0.0/es/index.html"),
_ => Redirect::see_other("/doc/v0.0/en/index.html"),
}
}
#[pagetop::main]
async fn main() -> std::io::Result<()> {
Application::prepare(&PageTopWebSite).run()?.await
}