Add configuration options
This commit is contained in:
parent
5d28a2e707
commit
270fed54f0
16 changed files with 272 additions and 83 deletions
|
|
@ -3,7 +3,7 @@ use axum::{
|
|||
http::{Response, StatusCode, header},
|
||||
};
|
||||
|
||||
use crate::handlers;
|
||||
use crate::{formats::populate_graph, handlers};
|
||||
|
||||
pub(in crate::handlers) fn by_code(
|
||||
code: Option<u16>,
|
||||
|
|
@ -26,6 +26,7 @@ fn make_body(code: Option<u16>, message: Option<&str>) -> String {
|
|||
|
||||
let out_code = code.unwrap_or(500);
|
||||
let out_message = &message.unwrap_or("Unknown error");
|
||||
let config = populate_graph().meta.config;
|
||||
|
||||
context.insert(
|
||||
"title",
|
||||
|
|
@ -36,6 +37,7 @@ fn make_body(code: Option<u16>, message: Option<&str>) -> String {
|
|||
|
||||
context.insert("message", out_message);
|
||||
context.insert("status_code", &out_code.to_string());
|
||||
context.insert("config", &config);
|
||||
|
||||
handlers::template::render(
|
||||
"error.html",
|
||||
|
|
|
|||
|
|
@ -1,25 +1,25 @@
|
|||
use axum::{body::Body, extract::Path, http::Response};
|
||||
use crate::syntax::content::elements::paragraph::Paragraph;
|
||||
use crate::syntax::content::parser;
|
||||
|
||||
use crate::{formats::populate_graph, handlers, types::Node};
|
||||
|
||||
pub async fn node(Path(id): Path<String>) -> Response<Body> {
|
||||
let mut context = tera::Context::new();
|
||||
|
||||
let graph = populate_graph();
|
||||
let empty_node = Node::new(Some(format!("Could not find node ID {id}.")));
|
||||
|
||||
let node: &Node = graph.nodes.get(&id).unwrap_or(&empty_node);
|
||||
|
||||
let mut context = tera::Context::new();
|
||||
context.insert("id", &id);
|
||||
context.insert("title", &node.title);
|
||||
context.insert("connections", &node.connections.clone());
|
||||
context.insert("incoming", &graph.incoming.get(&id));
|
||||
context.insert("config", &graph.meta.config);
|
||||
|
||||
let out_text = parser::read::<Paragraph>(&node.text);
|
||||
context.insert("text", &out_text);
|
||||
|
||||
let not_found = node.clone() == empty_node;
|
||||
let not_found = *node == empty_node;
|
||||
let template_name = "node.html".to_string();
|
||||
|
||||
handlers::template::by_filename(
|
||||
|
|
|
|||
|
|
@ -5,10 +5,16 @@ use axum::{
|
|||
Form,
|
||||
};
|
||||
|
||||
use crate::{formats::populate_graph, types::Node, handlers};
|
||||
use crate::{
|
||||
formats::populate_graph,
|
||||
handlers,
|
||||
syntax::content::parser,
|
||||
types::{Config, Node},
|
||||
syntax::content::elements::{span::Span, paragraph::Paragraph},
|
||||
};
|
||||
|
||||
#[expect(clippy::unused_async)]
|
||||
pub async fn nexus(template: &str) -> Response<Body> {
|
||||
pub async fn page(template: &str) -> Response<Body> {
|
||||
let mut context = tera::Context::new();
|
||||
let graph = populate_graph();
|
||||
let root_node = graph.get_root().unwrap_or_default();
|
||||
|
|
@ -17,6 +23,14 @@ pub async fn nexus(template: &str) -> Response<Body> {
|
|||
context.insert("nodes", &nodes);
|
||||
context.insert("root_node", &root_node);
|
||||
|
||||
let text_parsed_config = Config {
|
||||
footer_text: parser::read::<Span>(&graph.meta.config.footer_text),
|
||||
about_text: parser::read::<Paragraph>(&graph.meta.config.about_text),
|
||||
..graph.meta.config
|
||||
};
|
||||
|
||||
context.insert("config", &text_parsed_config);
|
||||
|
||||
handlers::template::by_filename(template, &context, 500, None, false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{backtrace, io, panic};
|
||||
|
||||
use en::{ONSET, syntax, dev};
|
||||
use en::{ONSET, dev, formats::populate_graph, syntax};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
|
|
@ -25,7 +25,8 @@ async fn main() -> io::Result<()> {
|
|||
}
|
||||
}));
|
||||
|
||||
let app = en::router::new();
|
||||
let graph = populate_graph();
|
||||
let app = en::router::new(&graph);
|
||||
|
||||
let listener =
|
||||
tokio::net::TcpListener::bind(&address).await.map_err(|e| {
|
||||
|
|
|
|||
|
|
@ -1,22 +1,14 @@
|
|||
use axum::{routing::get, Router};
|
||||
|
||||
use crate::{handlers, formats::Format};
|
||||
use crate::{formats::Format, handlers, types::Graph};
|
||||
|
||||
pub fn new() -> Router {
|
||||
Router::new()
|
||||
pub fn new(graph: &Graph) -> Router {
|
||||
let mut router = Router::new()
|
||||
.route(
|
||||
"/",
|
||||
get(|| handlers::navigation::nexus("index.html"))
|
||||
get(|| handlers::navigation::page("index.html"))
|
||||
.post(handlers::navigation::search),
|
||||
)
|
||||
.route(
|
||||
"/graph/toml",
|
||||
get(|| handlers::fixed::serial(&Format::Toml)),
|
||||
)
|
||||
.route(
|
||||
"/graph/json",
|
||||
get(|| handlers::fixed::serial(&Format::Json)),
|
||||
)
|
||||
.route(
|
||||
"/static/style.css",
|
||||
get(|| handlers::fixed::file("./static/style.css", "text/css")),
|
||||
|
|
@ -31,11 +23,32 @@ pub fn new() -> Router {
|
|||
"/node/{node_id}",
|
||||
get(handlers::graph::node).post(handlers::graph::node),
|
||||
)
|
||||
.route("/tree", get(|| handlers::navigation::nexus("tree.html")))
|
||||
.route("/about", get(|| handlers::template::fixed("about.html")))
|
||||
.route(
|
||||
"/acknowledgments",
|
||||
get(|| handlers::template::fixed("acknowledgments.html")),
|
||||
)
|
||||
.fallback(handlers::error::not_found)
|
||||
.fallback(handlers::error::not_found);
|
||||
|
||||
if graph.meta.config.about {
|
||||
router = router
|
||||
.route("/about", get(|| handlers::navigation::page("about.html")));
|
||||
}
|
||||
|
||||
if graph.meta.config.tree {
|
||||
router = router
|
||||
.route("/tree", get(|| handlers::navigation::page("tree.html")));
|
||||
}
|
||||
|
||||
if graph.meta.config.raw {
|
||||
if graph.meta.config.raw_json {
|
||||
router = router.route(
|
||||
"/graph/json",
|
||||
get(|| handlers::fixed::serial(&Format::Json)),
|
||||
);
|
||||
}
|
||||
if graph.meta.config.raw_toml {
|
||||
router = router.route(
|
||||
"/graph/toml",
|
||||
get(|| handlers::fixed::serial(&Format::Toml)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
router
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ impl Display for Level {
|
|||
}
|
||||
}
|
||||
|
||||
pub(in crate::syntax::content) struct Header {
|
||||
pub struct Header {
|
||||
level: Level,
|
||||
text: String,
|
||||
}
|
||||
|
|
@ -70,6 +70,7 @@ impl Parseable for Header {
|
|||
format!("<h{}>{}</h{0}>", &self.level, self.text)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Header {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Level {} Header: <{}>", &self.level, self.text)
|
||||
|
|
|
|||
28
src/syntax/content/elements/span.rs
Normal file
28
src/syntax/content/elements/span.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
use std::fmt::Display;
|
||||
use crate::syntax::content::{Parseable, Lexeme};
|
||||
|
||||
pub struct Span {
|
||||
text: String,
|
||||
}
|
||||
|
||||
impl Parseable for Span {
|
||||
fn probe(lexeme: &Lexeme) -> bool {
|
||||
!lexeme.raw.trim().is_empty()
|
||||
}
|
||||
|
||||
fn lex(lexeme: &Lexeme) -> Self {
|
||||
Self {
|
||||
text: lexeme.raw.trim().to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self) -> String {
|
||||
format!("<span>{}</span>", &self.text)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Span {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(f, "Span: <{}>", &self.text)
|
||||
}
|
||||
}
|
||||
83
src/types.rs
83
src/types.rs
|
|
@ -6,10 +6,10 @@ use serde::{Serialize, Deserialize};
|
|||
pub struct Graph {
|
||||
pub nodes: HashMap<String, Node>,
|
||||
pub root_node: String,
|
||||
#[serde(default)]
|
||||
pub messages: Vec<String>,
|
||||
#[serde(skip_deserializing)]
|
||||
pub incoming: HashMap<String, Vec<Edge>>,
|
||||
#[serde(default)]
|
||||
pub meta: Meta,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
||||
|
|
@ -37,16 +37,87 @@ pub struct Edge {
|
|||
pub detached: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
||||
pub struct Meta {
|
||||
pub config: Config,
|
||||
#[serde(default = "mkversion")]
|
||||
pub version: (u8, u8, u8),
|
||||
#[serde(default)]
|
||||
pub messages: Vec<String>,
|
||||
}
|
||||
|
||||
// See: https://github.com/serde-rs/serde/issues/368
|
||||
fn mkversion() -> (u8, u8, u8) {
|
||||
(0, 0, 0)
|
||||
}
|
||||
|
||||
#[expect(clippy::struct_excessive_bools)]
|
||||
#[derive(Serialize, Deserialize, Clone, Default, PartialEq, Eq, Debug)]
|
||||
pub struct Config {
|
||||
#[serde(default)]
|
||||
pub site_title: String,
|
||||
#[serde(default)]
|
||||
pub site_description: String,
|
||||
#[serde(default = "mktrue")]
|
||||
pub footer: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub footer_credits: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub footer_date: bool,
|
||||
#[serde(default)]
|
||||
pub footer_text: String,
|
||||
#[serde(default = "mktrue")]
|
||||
pub about: bool,
|
||||
#[serde(default)]
|
||||
pub about_text: String,
|
||||
#[serde(default = "mktrue")]
|
||||
pub tree: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub raw: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub raw_toml: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub raw_json: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub index_search: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub index_node_list: bool,
|
||||
#[serde(default = "mktrue")]
|
||||
pub tree_node_text: bool,
|
||||
}
|
||||
|
||||
// See: https://github.com/serde-rs/serde/issues/368
|
||||
fn mktrue() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl Graph {
|
||||
pub fn new(message: Option<String>) -> Graph {
|
||||
Self {
|
||||
nodes: HashMap::new(),
|
||||
root_node: "VoidNode".to_string(),
|
||||
incoming: HashMap::new(),
|
||||
messages: vec![
|
||||
message
|
||||
.unwrap_or("This graph is empty or in error".to_string()),
|
||||
],
|
||||
meta: Meta {
|
||||
config: Config {
|
||||
site_title: String::new(),
|
||||
site_description: String::new(),
|
||||
footer: true,
|
||||
footer_credits: true,
|
||||
footer_date: true,
|
||||
footer_text: String::new(),
|
||||
about: true,
|
||||
about_text: String::new(),
|
||||
tree: true,
|
||||
raw: true,
|
||||
raw_toml: true,
|
||||
raw_json: true,
|
||||
index_search: true,
|
||||
index_node_list: true,
|
||||
tree_node_text: true,
|
||||
},
|
||||
version: (0, 1, 0),
|
||||
messages: message.map_or(vec![], |m| vec![m]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -122,3 +122,23 @@ To learn more about TOML, you can visit its website at <toml.io>.
|
|||
|
||||
To see the TOML declaration that translates into the rendered graph you are reading right now, visit the "TOML Graph" link on the top navigation bar.
|
||||
"""
|
||||
|
||||
[nodes.Acknowledgments]
|
||||
text = """
|
||||
en is only possible thanks to a number of projects and people:
|
||||
|
||||
- [The Rust Programing Language](https://rust-lang.org/)
|
||||
- [Tokio](https://tokio.rs/)
|
||||
- [Axum](https://github.com/tokio-rs/axum)
|
||||
- [Tera](https://keats.github.io/tera/)
|
||||
- [Serde](https://serde.rs/) and the [toml crate](https://github.com/toml-rs/toml)
|
||||
- [Bacon](https://dystroy.org/bacon/config/)
|
||||
"""
|
||||
|
||||
[meta.config]
|
||||
footer_credits = false
|
||||
footer_text = """
|
||||
made by jutty • acknowledgements • source code
|
||||
"""
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
{%- block body %}
|
||||
<h1>About</h1>
|
||||
|
||||
{% if config.about_text %}
|
||||
{{ config.about_text | safe }}
|
||||
{% else %}
|
||||
<p>en is a program to create a connected collection of texts.</p>
|
||||
|
||||
<p>You define your graph using a plain-text configuration file, en reads this file and generates a website like the one you are browsing right now.</p>
|
||||
|
|
@ -17,5 +20,6 @@
|
|||
<li><a href="https://en.jutty.dev/node/Documentation">Documentation</a></li>
|
||||
<li><a href="https://codeberg.org/jutty/en">Source code repository</a></li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{%- endblock body %}
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Acknowledgments{% endblock title %}
|
||||
|
||||
{%- block body %}
|
||||
<h1>Acknowledgments</h1>
|
||||
|
||||
<p>en is only possible thanks to a number of projects and people:</p>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://rust-lang.org/">The Rust Programing Language</a></li>
|
||||
<li><a href="https://tokio.rs/">Tokio</a> and
|
||||
<a href="https://github.com/tokio-rs/axum">Axum</a></li>
|
||||
<li><a href="https://keats.github.io/tera/">Tera</a></li>
|
||||
<li><a href="https://serde.rs/">Serde</a> and the
|
||||
<a href="https://github.com/toml-rs/toml">toml crate</a></li>
|
||||
<li><a href="https://dystroy.org/bacon/config/">Bacon</a></li>
|
||||
</ul>
|
||||
|
||||
{%- endblock body %}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
{% if config.site_title %}
|
||||
<title>{% block title %}{% endblock title %} • {{config.site_title}}</title>
|
||||
{% else %}
|
||||
<title>{% block title %}{% endblock title %} • en</title>
|
||||
{% endif %}
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" >
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="/static/style.css" rel="stylesheet">
|
||||
|
|
@ -13,10 +17,20 @@
|
|||
<nav style="text-align: center;">
|
||||
<ul style="display: inline; padding-left: 0;">
|
||||
<li style="display: inline;"><a href="/">Index</a></li>
|
||||
{% if config.about %}
|
||||
<li style="display: inline;"><a href="/about">About</a></li>
|
||||
{% endif %}
|
||||
{% if config.tree %}
|
||||
<li style="display: inline;"><a href="/tree">Tree</a></li>
|
||||
{% endif %}
|
||||
{% if config.raw %}
|
||||
{% if config.raw_toml %}
|
||||
<li style="display: inline;"><a href="/graph/toml">TOML Graph</a></li>
|
||||
{% endif %}
|
||||
{% if config.raw_json %}
|
||||
<li style="display: inline;"><a href="/graph/json">JSON Graph</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
<hr>
|
||||
</nav>
|
||||
|
|
@ -24,22 +38,30 @@
|
|||
{% block body %}
|
||||
{% endblock body %}
|
||||
</main>
|
||||
{% if config.footer %}
|
||||
<footer>
|
||||
<hr>
|
||||
<div>
|
||||
<cite>made by <a href="https://jutty.dev">jutty</a></cite>
|
||||
•
|
||||
<a href="/acknowledgments">acknowledgments</a>
|
||||
•
|
||||
<a href="https://codeberg.org/jutty/en">source code</a>
|
||||
{% if config.footer_text %}{{ config.footer_text | safe }}{% endif %}
|
||||
{% if config.footer_text and (config.footer_credits or config.footer_date) %}
|
||||
<br/>
|
||||
{% endif %}
|
||||
{% if config.footer_credits %}
|
||||
<cite>made with <a href="https://en.jutty.dev">en</a></cite>, a non-linear writing instrument
|
||||
{% endif %}
|
||||
{% if config.footer_credits and config.footer_date %}
|
||||
<br/>
|
||||
{% endif %}
|
||||
{% if config.footer_date %}
|
||||
built
|
||||
<time>
|
||||
{{ now(utc=true) | date(format="%Y-%m-%d %H:%M") }} UTC
|
||||
</time>
|
||||
•
|
||||
<time>{{ now(timestamp=true) }} Unix Epoch</time>
|
||||
{% endif %}
|
||||
</div>
|
||||
</footer>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,2 +1,14 @@
|
|||
<p>There are no nodes. The graph is either empty or failed to parse.</p>
|
||||
<p>Check the <a href="/graph/toml">raw endpoints</a> for possible parsing errors.</p>
|
||||
{% if config.raw %}
|
||||
<p>Check the
|
||||
{% if config.raw_toml %}
|
||||
<a href="/graph/toml">
|
||||
{% elif config.raw_json %}
|
||||
<a href="/graph/json">
|
||||
{% endif %}
|
||||
raw endpoints
|
||||
{% if config.raw_toml or config.raw_json %}
|
||||
</a>
|
||||
{% endif %}
|
||||
for possible parsing errors.</p>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
fallen<br/>
|
||||
out of the circle<br/>
|
||||
you are welcome to climb<br/>
|
||||
back onto the <a href="/tree">tree</a>
|
||||
back onto the {% if config.tree %}<a href="/tree">tree</a>{% else %}tree{% endif %}
|
||||
</em>
|
||||
</div>
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -3,15 +3,24 @@
|
|||
{% block title %}Index{% endblock title %}
|
||||
|
||||
{%- block body %}
|
||||
<h1>en</h1>
|
||||
<h1>{%if config.site_title %}{{ config.site_title }}{% else %}en{%endif%}</h1>
|
||||
<p>
|
||||
<em>A non-linear writing instrument.</em>
|
||||
<em>
|
||||
{% if config.site_description %}
|
||||
{{config.site_description}}
|
||||
{% else %}
|
||||
A non-linear writing instrument.
|
||||
{% endif %}
|
||||
</em>
|
||||
{% if nodes %}
|
||||
{% if config.index_search %}
|
||||
<form method="post">
|
||||
<label for="node">Find by ID:</label>
|
||||
<input type="text" name="node" required/>
|
||||
<input type="submit" value="Submit"/>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if config.index_node_list %}
|
||||
<hr>
|
||||
<h2>Nodes</h2>
|
||||
<nav>
|
||||
|
|
@ -31,6 +40,7 @@
|
|||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<hr>
|
||||
{% include "empty.html" %}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
{% block title %}Tree{% endblock title %}
|
||||
|
||||
<!-- TODO Tera has macros, which could considerably simplify this template -->
|
||||
<!-- See: https://keats.github.io/tera/docs/#macros -->
|
||||
|
||||
{%- block body %}
|
||||
{% if nodes or root_node %}
|
||||
<h1>Tree</h1>
|
||||
|
|
@ -12,7 +15,9 @@
|
|||
<ul>
|
||||
<li>
|
||||
<a href="/node/{{root_node.id}}">{{root_node.title}}</a>
|
||||
{% if root_node.connections or config.tree_node_text %}
|
||||
<ul>
|
||||
{% if config.tree_node_text %}
|
||||
<li><strong>Text:</strong>
|
||||
<ul style="display: inline; padding-left: 0;">
|
||||
<li style="display: inline;">
|
||||
|
|
@ -24,16 +29,18 @@
|
|||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
{% if root_node.connections %}
|
||||
<li><strong>Connections</strong>
|
||||
<ul>
|
||||
{% for connection in root_node.connections %}
|
||||
<li><a href="/node/{{connection.to}}">{{connection.to}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if root_node.connections %}
|
||||
{% if config.tree_node_text %}<li><strong>Connections</strong>
|
||||
<ul>{% endif %}
|
||||
{% for connection in root_node.connections %}
|
||||
<li><a href="/node/{{connection.to}}">{{connection.to}}</a></li>
|
||||
{% endfor %}
|
||||
{% if config.tree_node_text %}</ul>
|
||||
</li>{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
|
@ -44,7 +51,9 @@
|
|||
{% for node in nodes %}
|
||||
<li>
|
||||
<a href="/node/{{node.id}}">{{node.title}}</a>
|
||||
{% if node.connections or config.tree_node_text %}
|
||||
<ul>
|
||||
{% if config.tree_node_text %}
|
||||
<li><strong>Text:</strong>
|
||||
<ul style="display: inline; padding-left: 0;">
|
||||
<li style="display: inline;">
|
||||
|
|
@ -56,18 +65,20 @@
|
|||
</details>
|
||||
</li>
|
||||
</ul>
|
||||
{% if node.connections %}
|
||||
<li><strong>Connections</strong>
|
||||
<ul>
|
||||
{% for connection in node.connections %}
|
||||
{% if not connection.detached %}
|
||||
<li><a href="/node/{{connection.to}}">{{connection.to}}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if node.connections %}
|
||||
{% if config.tree_node_text %}<li><strong>Connections</strong>
|
||||
<ul>{% endif %}
|
||||
{% for connection in node.connections %}
|
||||
{% if not connection.detached %}
|
||||
<li><a href="/node/{{connection.to}}">{{connection.to}}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if config.tree_node_text %}</ul>
|
||||
</li>{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue