Initial commit of historian web application

This commit is contained in:
Captain Beyond
2024-08-03 06:10:53 -05:00
parent fa2c8ee85a
commit c18ae34880
18 changed files with 5354 additions and 0 deletions

2653
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

33
Cargo.toml Normal file
View File

@@ -0,0 +1,33 @@
[package]
name = "historian"
version = "0.0.1"
authors = ["Adrian Kuschelyagi Malacoda <adrian.malacoda@monarch-pass.net>"]
edition = "2021"
[dependencies]
pulldown-cmark = "0.9.2"
tera = "1.17.1"
toml = "0.4.2"
grep = "0.2"
regex = "1.10.5"
pathdiff = "0.2.1"
[dependencies.chrono]
version = "0.4.38"
features = ["serde"]
[dependencies.git2]
version = "0.19.0"
default-features = false
[dependencies.clap]
version = "4.0.29"
features = ["derive"]
[dependencies.rocket]
version = "0.5.0-rc.2"
features = ["json"]
[dependencies.serde]
version = "1.0"
features = ["derive"]

View File

@@ -1 +1,10 @@
# Historian
Historian is a hybrid wiki and static site generator.
More broadly, Historian aims to present a git repository of Markdown files as a web-editable wiki, which can be exported into a static site. As such, Historian tries not to introduce its own special syntax or conventions. A Historian wiki should be editable and previewable in any Markdown editor, and should be usable as a wiki or documentation site in e.g. a GitHub or Gitea repo.
That being said Historian does also include some helper tools to make management of such a wiki easier, for example, it includes a tool to resolve MediaWiki style [[wikilinks]] into a markdown links. These tools can be used both on the command line and in the Historian web interface.
The Historian web interface is a very simple wiki interface that provides an editor and a page history view. It does not currently do authentication; it's intended exclusively as an internal wiki.
A search functionality is planned but not implemented. Ideally it would be possible to include the search component as part of the statically built website, perhaps by compiling a search index along with the website contents.

40
guix.scm Normal file
View File

@@ -0,0 +1,40 @@
(define-module (historian)
#:use-module (guix packages)
#:use-module (guix download)
#:use-module (guix git-download)
#:use-module (guix build-system cargo)
#:use-module (guix licenses)
#:use-module (guix gexp)
#:use-module (gnu packages rust)
#:use-module (gnu packages crates-io)
#:use-module (gnu packages crates-web)
#:use-module ((guix licenses) #:prefix license:))
(define-public historian
(package
(name "historian")
(version "0.0.1")
(source
(local-file (dirname (current-filename))
#:recursive? #t))
(build-system cargo-build-system)
(arguments
`(#:cargo-inputs
(("rust-rocket" ,rust-rocket-0.4)
("rust-regex" ,rust-regex-1))
#:cargo-development-inputs
()))
; (arguments
; `(#:rust (rust-1.57)
; #:cargo-inputs
; (("rust-rocket" ,rust-rocket-0.4))
; #:cargo-development-inputs
; ()))
; (inputs)
; (native-inputs)
(home-page "https://forge.monarch-pass.net/malacoda/project-mushroom")
(synopsis "")
(description "")
(license license:gpl3+)))
historian

7
make-dev-environment Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env -S guix shell -D -f guix.scm rust-analyzer -- sh
##!/usr/bin/env -S guix shell -D -f guix.scm -- sh
#export LIBCLANG_PATH=$GUIX_ENVIRONMENT/lib/libclang.so
export CC=gcc
COMMAND=${1:-$SHELL}
ARGS=${@:2}
$COMMAND $ARGS

564
src/lib.rs Normal file
View File

@@ -0,0 +1,564 @@
static DEFAULT_INDEX_FILENAME: &'static str = "README.md";
static DEFAULT_TEMPLATES_PATH: &'static str = "templates";
static DEFAULT_TOML_FILENAME: &'static str = "Historian.toml";
extern crate pulldown_cmark;
extern crate tera;
extern crate serde;
extern crate git2;
extern crate regex;
extern crate pathdiff;
extern crate chrono;
#[macro_use] extern crate toml;
use std::fs;
use std::path::{Path, PathBuf};
use std::io::{Read, Write};
use std::collections::{HashMap, VecDeque};
use std::convert::From;
use std::sync::Mutex;
use std::ffi::OsStr;
use git2::{Repository, Signature, Commit};
use tera::Tera;
use toml::value::Table;
use serde::Serialize;
use regex::Regex;
use pathdiff::diff_paths;
use chrono::{DateTime, MappedLocalTime, TimeZone, Local};
pub struct Historian {
source_root: PathBuf,
index_filename: String,
pub repository: Option<Mutex<Repository>>,
pub site_config: Table
}
impl Historian {
pub fn new(root: String) -> Historian {
let source_root: PathBuf = root.into();
let toml_path = source_root.join(DEFAULT_TOML_FILENAME);
let site_config = match fs::metadata(&toml_path) {
Err(_) => Table::new(),
Ok(_) => {
let toml_data = fs::read(&toml_path).unwrap();
toml::de::from_slice(&toml_data).unwrap()
}
};
Historian {
source_root: source_root.clone(),
index_filename: DEFAULT_INDEX_FILENAME.to_owned(),
repository: Repository::open(source_root).ok().map(Mutex::new),
site_config
}
}
pub fn resolve_to_page(&self, name: &str) -> Option<Page> {
let mut file_path = self.source_root.clone().join(Path::new(name));
match fs::metadata(&file_path) {
Err(_) => None,
Ok(metadata) => {
let mut children = vec![];
if metadata.is_dir() {
for entry in fs::read_dir(&file_path).unwrap() {
let child = entry.unwrap().file_name().into_string().unwrap();
if !(child.starts_with(".") || child == self.index_filename || child == DEFAULT_TOML_FILENAME) {
children.push(child);
}
}
file_path.push(&self.index_filename);
}
let mut split_path = name.rsplitn(2, "/");
let base_name = split_path.next().unwrap().to_owned();
let parent_page = if name != "" {
self.resolve_to_page(split_path.next().unwrap_or(""))
.map(Box::new)
} else {
None
};
let mut url = name.to_owned();
if url != "" && metadata.is_dir() {
url.push('/');
}
Some(Page {
full_name: name.to_owned(),
name: base_name,
url,
parent: parent_page,
path: file_path,
is_directory: metadata.is_dir(),
children
})
}
}
}
// Creates a struct for a new page.
// The page is not saved until an edit is submitted.
pub fn start_page(&self, name: &str) -> Page {
let mut file_path = self.source_root.clone().join(Path::new(name));
let is_directory = file_path.extension().is_none();
let mut url = name.to_owned();
if is_directory {
file_path.push(&self.index_filename);
url.push('/');
}
let mut split_path = name.rsplitn(2, "/");
let base_name = split_path.next().unwrap().to_owned();
Page {
full_name: name.to_owned(),
name: base_name,
url,
parent: None,
path: file_path,
is_directory,
children: vec![]
}
}
pub fn submit_edit(&self, page: &Page, change: &Edit) {
// Create parent directories if necessary
if let Some(directory) = &page.path.parent() {
fs::create_dir_all(directory);
}
// write contents of file
let mut page_html_file = fs::File::create(&page.path).unwrap();
page_html_file.write_all(change.content.as_bytes()).unwrap();
// commit file to git repository
if let Some(repository_mutex) = &self.repository {
if let Ok(repository) = repository_mutex.lock() {
// add file to index
let mut index = repository.index().unwrap();
index.add_path(&page.path.strip_prefix(&self.source_root).unwrap()).unwrap();
index.write().unwrap();
let tree_oid = index.write_tree().unwrap();
let tree = repository.find_tree(tree_oid).unwrap();
// find parent commit
let parent_commit = if let Ok(commit) = repository.revparse_single("HEAD") {
commit.into_commit().ok()
} else {
None
};
let mut parents = Vec::new();
if parent_commit.is_some() {
parents.push(parent_commit.as_ref().unwrap());
}
// create commit
let signature = Signature::now(change.author.as_deref().unwrap_or("Historian"), "historian@local").unwrap();
repository.commit(
Some("HEAD"),
&signature,
&signature,
&change.summary,
&tree,
&parents[..],
).unwrap();
}
}
}
pub fn get_history_of_page(&self, page: &Page) -> Vec<Revision> {
let mut changes = Vec::new();
let page_path_in_repo = &page.path.strip_prefix(&self.source_root).unwrap();
if let Some(repository_mutex) = &self.repository {
if let Ok(repository) = repository_mutex.lock() {
let mut revwalk = repository.revwalk().unwrap();
revwalk.push_head().unwrap();
for result in revwalk {
if let Ok(rev) = result {
let commit = repository.find_commit(rev).unwrap();
if commit_includes_file(&repository, &commit, page_path_in_repo) {
changes.push(Revision {
id: commit.id().to_string(),
author: commit.author().name().unwrap().to_owned(),
summary: commit.summary().unwrap().to_owned(),
datetime: Local.timestamp_opt(commit.time().seconds(), 0).unwrap()
});
}
}
}
}
}
changes
}
pub fn get_revision_by_id(&self, id: &str) -> Option<Revision> {
if let Some(repository_mutex) = &self.repository {
if let Ok(repository) = repository_mutex.lock() {
let object = repository.revparse_single(id).ok()?;
let commit = object.as_commit()?;
return Some(Revision {
id: commit.id().to_string(),
author: commit.author().name().unwrap().to_owned(),
summary: commit.summary().unwrap().to_owned(),
datetime: Local.timestamp_opt(commit.time().seconds(), 0).unwrap()
});
}
}
None
}
// ref: https://github.com/rust-lang/git2-rs/issues/996
pub fn get_page_text_of_revision(&self, page: &Page, revision: &Revision) -> Option<String> {
let page_path_in_repo = &page.path.strip_prefix(&self.source_root).ok()?;
if let Some(repository_mutex) = &self.repository {
if let Ok(repository) = repository_mutex.lock() {
let object = repository.revparse_single(&revision.id).ok()?;
let tree = object.peel_to_tree().ok()?;
let rev_path = tree.get_path(page_path_in_repo).ok()?;
let path_object = rev_path.to_object(&repository).ok()?;
let blob = path_object.into_blob().ok()?;
return Some(std::str::from_utf8(blob.content()).ok()?.to_owned());
}
}
None
}
}
fn commit_includes_file(repository: &Repository, commit: &Commit, path: &Path) -> bool {
if commit.parent_count() != 1 {
return false;
}
let prev_commit = commit.parent(0).unwrap();
let tree = commit.tree().unwrap();
let prev_tree = prev_commit.tree().unwrap();
let diff = repository.diff_tree_to_tree(Some(&prev_tree), Some(&tree), None).unwrap();
for delta in diff.deltas() {
let file_path = delta.new_file().path().unwrap();
if file_path == path {
println!(" -- file path {:?}", file_path);
return true;
}
}
false
}
#[derive(Serialize)]
pub struct Page {
pub full_name: String,
pub name: String,
pub path: PathBuf,
pub url: String,
pub is_directory: bool,
pub parent: Option<Box<Page>>,
pub children: Vec<String>
}
#[derive(Serialize)]
pub struct Edit {
pub author: Option<String>,
pub content: String,
pub summary: String
}
#[derive(Serialize)]
pub struct Revision {
pub id: String,
pub author: String,
pub summary: String,
pub datetime: DateTime<Local>
}
pub struct PageRenderer {
template_root: PathBuf,
tera: Tera
}
fn render_markdown (content: &tera::Value, args: &HashMap<String, tera::Value>) -> tera::Result<tera::Value> {
let parser = pulldown_cmark::Parser::new(content.as_str().unwrap());
let mut html_output = String::new();
pulldown_cmark::html::push_html(&mut html_output, parser);
Ok(tera::Value::String(html_output))
}
impl PageRenderer {
pub fn new() -> PageRenderer {
let mut tera = tera::Tera::new("templates/**/*.html").unwrap();
tera.register_filter("markdown", render_markdown);
PageRenderer {
template_root: DEFAULT_TEMPLATES_PATH.into(),
tera
}
}
pub fn render_page(&self, historian: &Historian, page: &Page, options: &Table) -> String {
self.render_page_template("page.html", &historian, &page, &options)
}
pub fn render_page_template(&self, template: &str, historian: &Historian, page: &Page, options: &Table) -> String {
self.template(template)
.with_page(page)
.with_historian(historian)
.insert("options", options)
.render()
}
pub fn render_page_template_with_variables(&self, template: &str, historian: &Historian, page: &Page, variables: &Table) -> String {
self.template(template)
.with_page(page)
.with_historian(historian)
.insert_all(variables)
.render()
}
pub fn render_template(&self, template: &str, historian: &Historian, variables: &Table, options: &Table) -> String {
self.template(template)
.with_url("/")
.with_historian(historian)
.insert_all(variables)
.insert("options", options)
.render()
}
pub fn resource_names(&self) -> Vec<String> {
let mut children = vec![];
for entry in fs::read_dir(&self.template_root).unwrap() {
let child = entry.unwrap().file_name().into_string().unwrap();
if !child.ends_with(".html") {
children.push(child);
}
}
children
}
pub fn resolve_to_resource(&self, name: &str) -> Option<PathBuf> {
let file_path = self.template_root.clone().join(Path::new(name));
match fs::metadata(&file_path) {
Err(_) => None,
Ok(metadata) => {
if metadata.is_dir() {
None
} else {
Some(file_path)
}
}
}
}
pub fn template<'a>(&'a self, template: &'a str) -> Render<'a> {
Render::new(&self.tera, template)
}
}
pub struct Render<'a> {
tera: &'a Tera,
context: tera::Context,
template: &'a str
}
fn make_relative_root (url: &str) -> String {
let mut slash_count = 0;
for character in url.chars() {
if character == '/' {
slash_count = slash_count + 1;
}
}
"../".repeat(slash_count)
}
impl<'a> Render<'a> {
fn new(tera: &'a Tera, template: &'a str) -> Render<'a> {
Render {
tera,
context: tera::Context::new(),
template
}
}
pub fn insert<T: Serialize + ?Sized, S: Into<String>>(mut self, key: S, value: &'a T) -> Render {
self.context.insert(key, value);
self
}
pub fn insert_all(mut self, variables: &'a Table) -> Render {
for (key, value) in variables {
self.context.insert(key, value);
}
self
}
pub fn with_url(mut self, url: &'a str) -> Render {
self.context.insert("relative_root", &make_relative_root(url));
self
}
pub fn with_historian(mut self, historian: &'a Historian) -> Render {
self.context.insert("site", &historian.site_config);
self.context.insert("has_git", &historian.repository.is_some());
self
}
pub fn with_page(mut self, page: &'a Page) -> Render {
self.context.insert("page", &page);
let mut content = String::new();
if let Ok(mut file) = fs::File::open(&page.path) {
file.read_to_string(&mut content).unwrap();
}
self.context.insert("content", &content);
let mut ancestors: VecDeque<&Page> = VecDeque::new();
let mut ancestor = page.parent.as_ref();
while ancestor.is_some() {
ancestors.push_front(ancestor.unwrap().as_ref());
ancestor = ancestor.unwrap().parent.as_ref();
}
self.context.insert("ancestors", &ancestors);
let mut page_url = page.full_name.clone();
if page.full_name != "" && page.is_directory {
page_url.push('/');
}
self.context.insert("relative_root", &make_relative_root(&page.url));
self
}
pub fn render(self) -> String {
self.tera.render(&self.template, &self.context).unwrap()
}
}
impl From<Render<'_>> for String {
fn from(render: Render) -> Self {
render.render()
}
}
pub fn export_wiki(historian: &Historian, renderer: &PageRenderer, output_path: &str) {
fs::create_dir_all(&output_path).unwrap();
for resource_name in renderer.resource_names() {
let mut resource_output_path: PathBuf = output_path.into();
resource_output_path.push(&resource_name);
println!("export resource {} to {:?}", resource_name, resource_output_path);
fs::copy(renderer.resolve_to_resource(&resource_name).unwrap(), resource_output_path).unwrap();
}
export_wiki_page(historian, renderer, "", output_path);
}
fn export_wiki_page(historian: &Historian, renderer: &PageRenderer, name: &str, output_path: &str) {
if let Some(page) = historian.resolve_to_page(name) {
let page_path: PathBuf = page.full_name.to_owned().replace(".md", ".html").into();
let mut page_output_path: PathBuf = output_path.into();
page_output_path.push(&page_path);
if page.is_directory {
println!("create directory {:?}", page_output_path);
fs::create_dir_all(&page_output_path);
page_output_path.push("index.html");
}
println!("export: {} to {:?}", page.full_name, page_output_path);
let page_html = renderer.render_page_template("page.html", historian, &page, toml! {
dynamic = false
}.as_table().unwrap()).replace(".md", ".html");
let mut page_html_file = fs::File::create(page_output_path).unwrap();
page_html_file.write_all(page_html.as_bytes());
for child in page.children {
let child_path = page_path.join(child);
export_wiki_page(historian, renderer, child_path.to_str().unwrap(), output_path);
}
}
}
pub struct Linker<'a> {
historian: &'a Historian,
link_regex: Regex
}
impl<'a> Linker<'a> {
pub fn new(historian: &Historian) -> Linker {
Linker {
historian,
link_regex: Regex::new(r"\[\[([\w\s\|]+)\]\]").unwrap()
}
}
pub fn resolve_link(&self, link: &str) -> Option<String> {
let root = self.historian.resolve_to_page("")?;
let mut page_names = root.children;
loop {
let mut next_page_names: Vec<String> = vec![];
// loop through current list of page names to find match
for page_name in page_names {
let page = self.historian.resolve_to_page(&page_name)?;
if self.link_matches_path(link, &page_name) {
return Some(page.full_name.to_owned());
}
// Collect list of (fully qualified) subpages
for child in page.children {
let mut child_path = page.full_name.to_owned();
child_path.push('/');
child_path.push_str(&child);
next_page_names.push(child_path);
}
}
if next_page_names.is_empty() {
break;
}
//
page_names = next_page_names;
}
None
}
pub fn resolve_links(&self, page: &Page) -> String {
let mut content = String::new();
if let Ok(mut file) = fs::File::open(&page.path) {
file.read_to_string(&mut content).unwrap();
}
self.resolve_links_for_edit(&page, &Edit {
author: None,
content,
summary: String::new()
})
}
pub fn resolve_links_for_edit(&self, page: &Page, edit: &Edit) -> String {
let mut content = edit.content.to_owned();
for (link_full, [link]) in self.link_regex.captures_iter(&content.to_owned()).map(|c| c.extract()) {
if let Some(resolved_link) = self.resolve_link(link) {
let mut absolute_link_path = self.historian.source_root.to_owned();
absolute_link_path.push(&resolved_link);
let relative_link_path = diff_paths(&absolute_link_path, &page.path.parent().unwrap()).unwrap();
content = content.replace(link_full, &format!("[{}](<{}>)", link, relative_link_path.display()));
}
}
content
}
fn link_matches_path(&self, link: &str, path: &str) -> bool {
if let Some(page_name) = path.split('/').last() {
page_name == link || Path::new(page_name).file_stem() == Some(OsStr::new(link))
} else {
false
}
}
}

216
src/main.rs Normal file
View File

@@ -0,0 +1,216 @@
use std::path::PathBuf;
extern crate historian;
use historian::{Historian, Page, Edit, PageRenderer, export_wiki, Linker};
#[macro_use] extern crate rocket;
use rocket::serde::json::Json;
use rocket::Responder;
use rocket::response::{Redirect, content::{RawHtml, RawText}};
use rocket::form::Form;
use rocket::http::uri::Origin;
use rocket::fs::NamedFile;
use rocket::State;
extern crate clap;
use clap::Parser;
extern crate grep;
#[macro_use] extern crate toml;
#[derive(Responder)]
enum PageResponder {
Redirect(Redirect),
File(NamedFile),
Page(RawHtml<String>),
Text(RawText<String>)
}
#[get("/<path..>")]
async fn page<'r>(
path: PathBuf, origin: &Origin<'r>,
historian: &State<Historian>, renderer: &State<PageRenderer>
) -> Option<PageResponder> {
let path_str = path.to_str().unwrap();
if let Some(resource_path) = renderer.resolve_to_resource(path_str) {
Some(PageResponder::File(NamedFile::open(resource_path).await.unwrap()))
} else {
historian.resolve_to_page(path_str).map(|page| {
if let Some(query) = origin.query() {
for (key, value) in query.segments() {
if key == "action" {
if value == "edit" {
return PageResponder::Page(RawHtml(renderer.render_page_template("edit.html", &historian, &page, toml! {
dynamic = true
}.as_table().unwrap())))
} else if value == "history" {
return PageResponder::Page(RawHtml(renderer.template("history.html")
.with_historian(&historian)
.with_page(&page)
.insert("revisions", &historian.get_history_of_page(&page))
.insert("options", toml! {
dynamic = true
}.as_table().unwrap())
.render()))
}
}
if key == "revision" {
if let Some(revision) = historian.get_revision_by_id(value) {
return PageResponder::Page(RawHtml(renderer.template("revision.html")
.with_historian(&historian)
.with_page(&page)
.insert("revision", &revision)
.insert("content", &historian.get_page_text_of_revision(&page, &revision).unwrap())
.insert("options", toml! {
dynamic = true
}.as_table().unwrap())
.render()))
}
}
}
}
if page.is_directory && !origin.path().ends_with("/") {
PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned() + "/"))
} else {
PageResponder::Page(RawHtml(renderer.render_page(&historian, &page, toml! {
dynamic = true
}.as_table().unwrap())))
}
}).or_else(|| {
if let Some(query) = origin.query() {
for (key, value) in query.segments() {
if key == "action" && value == "edit" {
return Some(PageResponder::Page(RawHtml(renderer.template("edit.html")
.with_historian(&historian)
.with_url(&path_str)
.insert("page", &historian.start_page(path_str))
.insert("content", "")
.insert("ancestors", &Vec::<String>::with_capacity(0))
.insert("options", toml! {
dynamic = true
}.as_table().unwrap())
.render())))
}
}
None
} else {
Some(PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned() + "?action=edit")))
}
})
}
}
#[derive(FromForm)]
struct PageAction<'r> {
content: &'r str,
author: &'r str,
summary: &'r str,
subaction: &'r str
}
#[post("/<path..>", data = "<action>")]
fn action(
path: PathBuf, origin: &Origin,
historian: &State<Historian>, renderer: &State<PageRenderer>,
action: Form<PageAction<'_>>
) -> PageResponder {
let path_str = path.to_str().unwrap();
let page = historian.resolve_to_page(path_str).unwrap_or_else(|| historian.start_page(path_str));
if let Some(query) = origin.query() {
println!("{:?}", query);
for (key, value) in query.segments() {
if key == "action" {
if value == "edit" {
if action.subaction == "preview" {
return PageResponder::Page(RawHtml(renderer.template("edit.html")
.with_page(&page)
.with_historian(&historian)
.insert("content", action.content)
.insert("subaction", action.subaction)
.insert("summary", action.summary)
.insert("author", action.author)
.insert("options", toml! {
dynamic = true
}.as_table().unwrap())
.render()))
} else {
historian.submit_edit(&page, &Edit {
author: Some(action.author.to_owned()),
summary: action.summary.to_owned(),
content: action.content.to_owned()
});
return PageResponder::Redirect(Redirect::to(origin.path().as_str().to_owned()))
}
} else if value == "resolvelinks" {
let linker = Linker::new(&historian);
return PageResponder::Text(RawText(linker.resolve_links_for_edit(&page, &Edit {
author: None,
summary: action.summary.to_owned(),
content: action.content.to_owned()
})))
}
}
}
}
PageResponder::Page(RawHtml(renderer.render_template("message.html", &historian, toml! {
header = "Invalid Action"
message = "Action was invalid or misunderstood"
}.as_table().unwrap(), toml! {
dynamic = true
}.as_table().unwrap())))
}
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Args {
/// Path to wiki repository
wiki_path: String,
/// Render the wiki to a static website
#[arg(long)]
render_to: Option<String>,
/// Resolve a link to a page on the wiki
#[arg(long)]
resolve_link: Option<String>,
/// Resolve all wiki links in the given page, output the modified text
#[arg(long)]
resolve_links: Option<String>
}
#[rocket::main]
async fn main() {
let args = Args::parse();
let historian = Historian::new(args.wiki_path);
let renderer = PageRenderer::new();
let linker = Linker::new(&historian);
if let Some(resolve_link) = args.resolve_link {
println!("{}", linker.resolve_link(&resolve_link).unwrap());
return;
}
if let Some(resolve_links) = args.resolve_links {
let page = historian.resolve_to_page(&resolve_links).expect("failed to find page");
println!("{}", linker.resolve_links(&page));
return;
}
if let Some(render_to) = args.render_to {
export_wiki(&historian, &renderer, &render_to);
return;
}
rocket::build()
.manage(historian)
.manage(renderer)
.mount("/", routes![page, action])
.launch()
.await;
}

134
templates/base.html Normal file
View File

@@ -0,0 +1,134 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>{{ page.name }}</title>
<meta name="Author" content="John Zaitseff, J.Zaitseff@zap.org.au" />
<meta name="Description" content="A minimal template page to use with the Sinorcaish CSS stylesheet" />
<meta name="Copyright" content="This file may be redistributed and/or modified without limitation" />
<meta name="CVSInfo" content="$ZAPGroupID: sinorcaish/template.html,v 1.8 2004/11/18 02:17:49 john Exp $" />
<meta name="Language" content="en" />
<meta name="Generator" content="Hand-written XHTML using Emacs under Debian GNU/Linux" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta http-equiv="Content-Language" content="en" />
<link rev="made" href="mailto:J.Zaitseff@zap.org.au" />
{% block styles %}
<link rel="StyleSheet" href="{{ relative_root | safe }}sinorcaish-screen.css" type="text/css" media="screen" />
<link rel="StyleSheet" href="{{ relative_root | safe }}sinorcaish-print.css" type="text/css" media="print" />
{% endblock styles %}
</head>
<body>
<!-- For non-visual or non-stylesheet-capable user agents -->
<div id="mainlink"><a href="#main">Skip to main content.</a></div>
<!-- ======== Header ======== -->
<div id="header">
<div class="left">
<p>{{ site.header | default(value="Header") | safe }}</p>
</div>
<div class="right">
{% if options.dynamic %}
<span class="hidden">Useful links:</span>
<a href="{{ relative_root | safe }}{{ page.url }}?action=edit">Edit</a>
{% if has_git %}
|
<a href="{{ relative_root | safe }}{{ page.url }}?action=history">History</a>
{% endif %}
<div>
<form action="{{ relative_root | safe }}{{ page.url }}" method="get">
<script type="text/javascript">
var EmptySearchFieldDone = 0;
function EmptySearchField(elem) {
if (EmptySearchFieldDone == 0) {
elem.value = "";
EmptySearchFieldDone = 1;
}
return true;
}
</script>
<div>
<input type="text" name="q" value="Search" onfocus="EmptySearchField(this);" size="15" maxlength="250" />
<input type="image" name="submit" src="search.png" alt="Search" />
</div>
</form>
</div>
{% endif %}
</div>
<div class="subheader">
{% if site.navigation %}
<p>
<span class="hidden">Navigation:</span>
{% for entry in site.navigation %}
<a href="{{ entry.url }}">{{ entry.title }}</a> |
{% endfor %}
</p>
{% endif %}
</div>
</div>
<!-- ======== Left Sidebar ======== -->
<div id="sidebar">
{% if page.parent %}
<div>
<p class="title"><a href="{{ relative_root | safe }}{{ page.parent.url }}">{{ page.parent.name }}</a></p>
<ul>
{% for child in page.parent.children %}
<li{% if child == page.name %} class="highlight"{% endif %}><a href="{% if page.is_directory %}../{% endif %}{{ child }}">{{ child }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if page.is_directory %}
<div>
<p class="title"><a href="{{ relative_root | safe }}{{ page.url }}">{{ page.name }}</a></p>
<ul>
{% for child in page.children %}
<li><a href="{{ child }}">{{ child }}</a></li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<!-- ======== Main Content ======== -->
<div id="main">
<div id="navhead">
<hr />
<span class="hidden">Path to this page:</span>
{% for ancestor in ancestors %}
<a href="{{ relative_root | safe }}{{ ancestor.url }}">{% if ancestor.name %} {{ ancestor.name }} {% else %} Home {% endif %}</a> &raquo;
{% endfor %}
<a href="{{ relative_root | safe }}{{ page.url }}">{% if page.name %} {{ page.name }} {% else %} Home {% endif %}</a>
</div>
{% block content %}
{% endblock content %}
<br id="endmain" />
</div>
<!-- ======== Footer ======== -->
<div id="footer">
<hr />
This web page was generated by Historian 0.0.1.
<br />
<a href="https://www.zap.org.au/documents/styles/sinorcaish/">Sinorcaish style sheet and template</a> &copy; 2004 - 2007 John Zaitseff.
</div>
</body>
</html>

18
templates/edit.css Normal file
View File

@@ -0,0 +1,18 @@
#edit-area {
width: 100%;
height: 450px;
}
#edit-summary-label, #edit-author-label {
margin-top: 5px;
display: block;
margin-bottom: 5px;
}
#edit-summary, #edit-author {
width: 500px;
}
button {
padding: 5px 10px 5px 10px;
}

64
templates/edit.html Normal file
View File

@@ -0,0 +1,64 @@
{% extends "base.html" %}
{% block styles %}
{{ super() }}
<link rel="StyleSheet" href="/edit.css" type="text/css" media="screen" />
{% endblock styles %}
{% block content %}
{% if subaction and subaction == "preview" %}
<div id="preview">{{ content | markdown | safe }}</div>
{% endif %}
<form method="post">
<button id="resolve-button" name="subaction" value="resolvelinks">Resolve Wikilinks</button>
<textarea id="edit-area" name="content">{{ content }}</textarea>
<label id="edit-author-remember-label">Remember author name: <input type="checkbox" id="edit-author-remember" name="author-remember" /></label>
<label id="edit-author-label">Author name: <input type="text" id="edit-author" name="author" value="{{ author | default(value="") }}" /></label>
<label id="edit-summary-label">Edit summary: <input type="text" id="edit-summary" name="summary" value="{{ summary | default(value="") }}" /></label>
<div id="edit-buttons">
<button name="subaction" value="preview">Preview</button>
<button name="subaction" value="submit">Submit</button>
</div>
</form>
<script type="text/javascript">
// save author name if enabled
const authorName = document.getElementById("edit-author");
const rememberAuthor = document.getElementById("edit-author-remember");
function saveAuthorName () {
document.cookie = `author=${encodeURIComponent(rememberAuthor.checked ? authorName.value : '')}; path=/`
}
rememberAuthor.addEventListener("change", saveAuthorName);
authorName.addEventListener("change", saveAuthorName);
// load author name
const authorCookieValue = document.cookie
.split("; ")
.find((row) => row.startsWith("author="))
?.split("=")[1];
if (authorCookieValue) {
rememberAuthor.checked = true;
authorName.value = decodeURIComponent(authorCookieValue);
}
const editArea = document.getElementById("edit-area");
document.getElementById("resolve-button").addEventListener("click", event => {
let formData = new FormData();
formData.append("content", editArea.value);
formData.append("summary", "");
formData.append("subaction", "");
formData.append("author", "");
fetch("?action=resolvelinks", {
method: 'POST',
body: formData
}).then(result => result.text())
.then(body => editArea.value = body);
event.preventDefault();
});
</script>
{% endblock content %}

BIN
templates/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

22
templates/history.css Normal file
View File

@@ -0,0 +1,22 @@
#revision-details {
text-align: center;
padding: 4px;
color: rgb(112, 182, 255);
background-color: rgb(24, 51, 111);
border: 1px solid rgb(112, 182, 255);
margin-top: 10px;
margin-bottom: 10px;
}
#revision-summary {
display: block;
font-style: italic;
}
#revision-author {
font-weight: bold;
}
#revision-datetime {
font-weight: bold;
}

16
templates/history.html Normal file
View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block styles %}
{{ super() }}
<link rel="StyleSheet" href="/history.css" type="text/css" media="screen" />
{% endblock styles %}
{% block content %}
<ul id="revisions">
{% for revision in revisions %}
<li><a href="{{ relative_root | safe }}{{ page.url }}?revision={{ revision.id }}">{{ revision.datetime }} {{ revision.summary }} ({{ revision.author }})</a></li>
{% endfor %}
</ul>
{% endblock content %}

5
templates/message.html Normal file
View File

@@ -0,0 +1,5 @@
{% extends "base.html" %}
{% block content %}
<h2>{{ header }}</h2>
{{ message | safe }}
{% endblock content %}

4
templates/page.html Normal file
View File

@@ -0,0 +1,4 @@
{% extends "base.html" %}
{% block content %}
{{ content | markdown | safe }}
{% endblock content %}

14
templates/revision.html Normal file
View File

@@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block styles %}
{{ super() }}
<link rel="StyleSheet" href="/history.css" type="text/css" media="screen" />
{% endblock styles %}
{% block content %}
<div id="revision-details">
Revision <span id="revision-datetime">{{ revision.datetime }}</span> by <span id="revision-author">{{ revision.author }}</span>
<span id="revision-summary">{{ revision.summary }}</span>
</div>
{{ content | markdown | safe }}
{% endblock content %}

View File

@@ -0,0 +1,705 @@
/************************************************************************
* *
* Sinorcaish Print-based Style Sheet *
* Copyright (C) 2004-07, John Zaitseff *
* *
************************************************************************/
/* Author: John Zaitseff <J.Zaitseff@zap.org.au>
Version: 1.3
$Id: sinorcaish-print.css 192 2007-03-22 02:10:24Z john $
This file provides the Sinorcaish style sheet for printing HTML pages
to paper. This file conforms to the Cascading Style Sheets 2.1
specification.
The design of Sinorcaish is influenced by Sinorca (available from the
Open Source Web Design site, http://www.oswd.org/), which in turn was
based on the Acronis company web site (http://www.acronis.com/). You
can find more information about this design from its home page on the
ZAP Group web site, http://www.zap.org.au/documents/styles/sinorcaish/.
This file may be redistributed and/or modified on the condition that
the original copyright notice is retained.
*/
/********** Global Styles **********/
@page {
margin: 0.5in;
}
body {
font-family: "Lucida Bright", "Georgia", "DejaVu Serif", "Bitstream Vera Serif", "Times New Roman", TimesNR, Times, Roman, serif;
font-size: 11pt;
color: black;
background: white;
margin: 0;
padding: 0;
border: none;
orphans: 2;
widows: 2;
}
.hidden { /* Used for content that should be displayed */
/* by non-stylesheet-aware browsers */
display: none !important;
}
.notprinted { /* Used for content that should not be */
/* printed to paper */
display: none !important;
}
/* Headings */
h1, /* Headings (H1-H6) should only be used in */
h2, /* the main content area */
h3,
h4,
h5,
h6,
.title {
font-family: "Lucida Sans", "DejaVu Sans", "Bitstream Vera Sans", Verdana, Arial, Helvetica, sans-serif;
}
h1,
h2,
h3 {
font-weight: bold;
text-align: left;
margin: 1.5em 0 0 0;
padding: 0;
page-break-after: avoid;
page-break-inside: avoid;
}
h4,
h5,
h6 {
font-weight: bold;
text-align: left;
margin: 1.25em 0 0 0;
padding: 0;
page-break-after: avoid;
page-break-inside: avoid;
}
h1 { font-size: 175% }
h2 { font-size: 145% }
h3 { font-size: 120% }
h4 { font-size: 105% }
h5 { font-size: 80% }
h6 { font-size: 65% }
/* Anchors */
a:link {
text-decoration: none;
color: black;
background: transparent;
}
a:visited {
text-decoration: none;
color: black;
background: transparent;
}
a:hover,
a:active {
text-decoration: none;
}
/* Inline elements and classes */
/* This style sheet assumes B, BIG, EM, I, */
/* SMALL, STRONG, SUB and SUP are defined */
/* by the browser as per the HTML4 sample */
/* style sheet. */
code,
kbd,
samp,
tt {
font-family: "Courier New", Courier, monospace;
font-size: 100%;
}
kbd,
code.markup, /* HTML/CSS markup */
span.markup, /* Alternative form for HTML/CSS markup */
.title { /* Title in floating boxes / left sidebar */
font-weight: bolder;
}
.title {
page-break-after: avoid;
page-break-inside: avoid;
}
abbr,
acronym {
font: inherit; /* Don't use small-caps, etc. */
}
.tooltip {
border: none;
}
abbr[title],
acronym[title] {
border: none;
}
cite,
dfn,
var,
.fn, /* Filename */
.url, /* Uniform Resource Locator */
.email { /* E-mail address */
font-style: italic;
}
.program, /* Command-line name of a computer program */
.window, /* Window or dialog box name */
.menu, /* Menu item in a computer program */
.gui, /* Generic GUI element in a computer program */
.key { /* Keypress in a computer program */
font-weight: bolder;
}
.clearboxes { /* Clear navboxes and floatboxes */
clear: right;
}
.unicode {
font-family: "Arial Unicode MS", "Lucida Sans Unicode", Verdana, "DejaVu Sans", "Bitstream Vera Sans", "Lucida Sans", Arial, Helvetica, sans-serif;
}
/* Block-inline elements and classes */
img {
vertical-align: baseline;
margin: 0;
padding: 0;
border: none;
}
img.icon16 { /* For 16x16 file-type icons */
vertical-align: -2px;
}
del,
del * { /* Required for Mozilla */
text-decoration: line-through;
}
ins,
ins * { /* Required for Mozilla */
text-decoration: underline;
}
.floatleft { /* Left-floating images and spans */
margin: 0.5em 1.5em 0.5em 0;
float: left;
}
.floatright { /* Right-floating images and spans */
margin: 0.5em 0 0.5em 1.5em;
float: right;
}
.nowrap {
white-space: nowrap;
}
/* Block elements */
p {
margin: 1em 0;
padding: 0;
}
blockquote { /* Should only be used in main content area, */
/* floating boxes or left sidebar. */
margin: 1em 2.5em;
padding: 0;
}
pre { /* Should only be used in main content area */
/* and floating boxes. */
font-family: "Courier New", Courier, monospace;
font-size: 100%;
line-height: 1.2;
margin: 1em 2.5em;
padding: 0;
}
hr {
color: black;
background: transparent;
height: 1pt; /* Required for IE/Win */
margin: 0;
padding: 0;
border-color: black;
border-width: 1pt;
border-style: none none solid none;
}
hr.lighter {
display: none;
}
/* Lists */
ol {
list-style: decimal outside;
margin: 1em 0;
padding: 0 0 0 2.5em;
}
ol.alpha {
list-style-type: lower-alpha;
}
ol.number {
list-style-type: decimal;
}
ul {
list-style: square outside;
margin: 1em 0;
padding: 0 0 0 2.5em;
}
ol ol,
ol ul,
ul ol,
ul ul {
margin-top: 0;
margin-bottom: 0;
}
ol ul, /* Override possible browser styles */
ol ol ul,
ol ul ul,
ul ul,
ul ol ul,
ul ul ul {
list-style: square outside;
}
li {
margin: 0;
padding: 0;
}
dl {
margin: 1em 0;
padding: 0;
}
dt {
font: inherit; /* Don't make the text bold by default */
margin: 1em 0 0.25em 0;
padding: 0;
page-break-after: avoid;
page-break-inside: avoid;
}
dd {
margin: 0 0 1em 2.5em;
padding: 0;
}
/* Tables */
/* Tables should never be used for visual */
/* formatting: that is the role of CSS! */
table.simple {
color: inherit;
background: inherit; /* Don't make tables transparent */
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
margin: 0.5em 2.5em;
padding: 0;
border: 1pt solid black;
}
table.simple caption {
text-align: center;
caption-side: top;
margin: 0 2.5em 0.75em;
padding: 0;
border: none;
}
table.simple td,
table.simple th {
text-align: center;
vertical-align: middle;
margin: 0;
padding: 0.25em 0.5em;
border: 1pt solid black;
}
table.simple th,
table.simple td.highlight,
table.simple th.highlight {
font-weight: bold;
color: inherit;
background: inherit;
}
table.simple td.lighter,
table.simple th.lighter {
color: inherit;
background: inherit;
}
table.simple td.left,
table.simple th.left {
text-align: left;
}
table.simple td.center,
table.simple th.center {
text-align: center;
}
table.simple td.right,
table.simple th.right {
text-align: right;
}
thead {
page-break-after: avoid;
page-break-inside: avoid;
}
tfoot {
page-break-before: avoid;
page-break-inside: avoid;
}
/* Forms */
form {
margin: 1em 0;
padding: 0;
border: none;
}
input,
button,
select,
fieldset,
legend {
font-size: 95%;
color: black;
background: inherit;
vertical-align: middle;
}
input,
button,
select {
font-family: Verdana, "DejaVu Sans", "Bitstream Vera Sans", "Lucida Sans", Arial, Helvetica, sans-serif;
}
fieldset,
legend {
font-family: "Lucida Bright", "Georgia", "DejaVu Serif", "Bitstream Vera Serif", "Times New Roman", TimesNR, Times, Roman, serif;
}
textarea {
font-family: "Courier New", Courier, monospace;
font-size: 100%;
color: black;
background: inherit;
vertical-align: middle;
}
fieldset {
font-size: 100%;
margin: 1em 0;
border: 1pt solid black;
}
legend {
font-size: 100%;
margin: 0 0.5em;
padding: 0 0.25em;
border: none;
}
table.formtable {
color: inherit;
background: inherit;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
margin: 0;
padding: 0;
border: none;
}
table.formtable td,
table.formtable th {
text-align: justify;
vertical-align: middle;
margin: 0;
padding: 0.25em 0.5em;
border: none;
}
table.formtable th {
text-align: center;
font-weight: bold;
}
table.formtable td.label,
table.formtable th.label {
text-align: right;
vertical-align: top;
}
table.formtable td.vertspace,
table.formtable th.vertspace {
empty-cells: show;
margin: 0;
padding: 0.5em 0;
height: 1em; /* Required for IE/Win */
}
table.formtable fieldset {
margin: 0;
}
table.formtable fieldset td,
table.formtable fieldset th {
margin: 0.25em 0.5em;
}
.reqfield {
font-family: Verdana, "DejaVu Sans", "Bitstream Vera Sans", "Lucida Sans", Arial, Helvetica, sans-serif;
color: black;
background: transparent;
font-weight: bolder;
}
.info {
color: black;
background: transparent;
font-style: italic;
font-size: 90%;
}
/* The following HTML elements should NOT be used in documents using this
style sheet:
address - use the #footer style instead
q - use &ldquo; and &rdquo; instead
*/
/********** Styles for Main Content **********/
#main {
text-align: justify;
line-height: 1.3;
color: black;
background: white;
margin: 0;
padding: 0;
}
#main h1 { /* Should be used once, following navhead */
margin: 0 0 0.5em 0;
}
#main .highlight { /* Highlight box (for warnings, etc) */
color: inherit;
background: inherit;
margin: 1em 0;
padding: 1em 2.5em;
border: 1pt solid black;
page-break-inside: avoid;
}
#main .totop { /* For "Top ^" lines in FAQs, etc */
display: none;
}
#main table.simple td.highlight, /* Else "#main .highlight" will override */
#main table.simple th.highlight {
margin: 0;
padding: 0.25em 0.5em;
}
/* Other styles related to the main content */
#mainlink { /* "Skip to main content" link */
display: none !important;
}
#navhead { /* "Path to this page" information */
display: none !important;
}
#endmain {
visibility: hidden;
clear: both; /* Doesn't always work under IE/Win */
}
/********** Styles for Floating Boxes **********/
/* "navbox" is used to provide intra/inter- */
/* page links; it is NOT printed out on */
/* paper. "floatbox" is used to provide */
/* floating boxes that may appear anywhere */
/* in the main content; they ARE printed. */
.floatbox,
.navbox {
overflow: visible;
font-size: 95%;
line-height: 1.25;
margin: 0 0 0.75em 1.5em;
padding: 0.5em 1em;
border: 1pt solid black;
float: right;
clear: right;
page-break-inside: avoid;
}
.floatbox {
color: black;
background: white;
width: 35%;
}
.navbox {
display: none !important;
}
.floatbox hr, /* Used for non-stylesheet-aware browsers */
.navbox hr {
display: none !important;
}
.floatbox p,
.navbox p {
margin: 0.75em 0;
padding: 0;
}
.floatbox ol,
.floatbox ul {
margin: 0.75em 0;
padding: 0 0 0 1.5em;
}
.navbox ol,
.navbox ul {
margin: 0.5em 0;
padding: 0 0 0 1.5em;
}
.floatbox blockquote {
margin: 0.75em 1.5em;
padding: 0;
}
.floatbox pre {
font-size: 95%;
margin: 0.75em 1.5em;
padding: 0;
}
.floatbox dt {
margin: 0.75em 0;
padding: 0;
}
.floatbox dt {
margin: 0.75em 0 0.25em 0;
padding: 0;
}
.floatbox dd {
margin: 0 0 0.75em 1.5em;
padding: 0;
}
#main .floatbox .highlight {
color: inherit;
background: inherit;
margin: 0.75em 0;
padding: 0.75em 1.5em;
}
#main .floatbox table.simple {
margin: 0.75em 0;
}
#main .floatbox table.simple th,
#main .floatbox table.simple td.highlight,
#main .floatbox table.simple th.highlight {
color: inherit;
background: inherit;
margin: 0;
padding: 0.25em 0.5em;
}
/********** Styles for Header **********/
#header {
display: none !important;
}
/********** Styles for Left Sidebar **********/
#sidebar {
display: none !important;
}
/********** Styles for Footer **********/
#footer {
font-size: 90%;
text-align: left;
color: black;
background: white;
margin: 0;
padding: 0.5em 0 0 0;
border-top: 1pt solid black;
clear: both;
page-break-before: avoid;
page-break-inside: avoid;
}
#footer hr {
display: none !important;
}
/* End of the Sinorcaish style sheet */

View File

@@ -0,0 +1,850 @@
/************************************************************************
* *
* Sinorcaish Screen-based Style Sheet *
* Copyright (C) 2004-07, John Zaitseff *
* *
************************************************************************/
/* Author: John Zaitseff <J.Zaitseff@zap.org.au>
Version: 1.3
$Id: sinorcaish-screen.css 189 2007-03-22 01:35:44Z john $
This file provides the Sinorcaish style sheet for screen-based user
agents (ie, for ordinary Web browsers). This file conforms to the
Cascading Style Sheets 2.1 specification.
The design of Sinorcaish is influenced by Sinorca (available from the
Open Source Web Design site, http://www.oswd.org/), which in turn was
based on the Acronis company web site (http://www.acronis.com/). You
can find more information about this design from its home page on the
ZAP Group web site, http://www.zap.org.au/documents/styles/sinorcaish/.
This file may be redistributed and/or modified on the condition that
the original copyright notice is retained.
*/
/********** Global Styles **********/
/* The global font size is set to 90% as */
/* most browsers' normal font size is too */
/* large, at least when using Verdana */
body {
font-family: Verdana, "DejaVu Sans", "Bitstream Vera Sans", "Lucida Sans", Arial, Helvetica, sans-serif;
font-size: 90%; /* Allow IE/Win to resize the document */
color: black;
background: #F0F0F0;
margin: 0;
padding: 0;
border: none;
}
.hidden { /* Used for content that should be displayed */
/* by non-stylesheet-aware browsers */
display: none !important;
}
.notprinted { /* Used for content that should not be */
} /* printed to paper */
/* Headings */
h1, /* Headings (H1-H6) should only be used in */
h2, /* the main content area */
h3 {
font-weight: bold;
text-align: left;
margin: 1.5em 0 0 0;
padding: 0;
}
h4,
h5,
h6 {
font-weight: bold;
text-align: left;
margin: 1.25em 0 0 0;
padding: 0;
}
h1 { font-size: 175% }
h2 { font-size: 145% }
h3 { font-size: 120% }
h4 { font-size: 105% }
h5 { font-size: 80% }
h6 { font-size: 65% }
/* Anchors */
a:link {
text-decoration: none;
color: #0066CC;
background: transparent;
}
a:visited {
text-decoration: none;
color: #003399;
background: transparent;
}
a:hover,
a:active {
text-decoration: underline;
}
/* Inline elements and classes */
/* This style sheet assumes B, BIG, EM, I, */
/* SMALL, STRONG, SUB and SUP are defined */
/* by the browser as per the HTML4 sample */
/* style sheet. */
code,
kbd,
samp,
tt {
font-family: "Courier New", Courier, monospace;
font-size: 115%; /* Courier tends to be a little too small */
line-height: 1.0; /* Otherwise lines tend to spread out */
}
kbd,
code.markup, /* HTML/CSS markup */
span.markup, /* Alternative form for HTML/CSS markup */
.title { /* Title in floating boxes / left sidebar */
font-weight: bolder;
}
abbr,
acronym {
font: inherit; /* Don't use small-caps, etc. */
}
.tooltip {
cursor: help;
border-bottom: 1px dotted #CCCCCC;
}
abbr[title],
acronym[title] {
cursor: help;
border-bottom: 1px dotted #CCCCCC;
}
cite,
dfn,
var,
.fn, /* Filename */
.url, /* Uniform Resource Locator */
.email { /* E-mail address */
font-style: italic;
}
.program, /* Command-line name of a computer program */
.window, /* Window or dialog box name */
.menu, /* Menu item in a computer program */
.gui, /* Generic GUI element in a computer program */
.key { /* Keypress in a computer program */
font-weight: bolder;
}
.clearboxes { /* Clear navboxes and floatboxes */
clear: right;
}
.unicode {
font-family: "Arial Unicode MS", "Lucida Sans Unicode", Verdana, "DejaVu Sans", "Bitstream Vera Sans", "Lucida Sans", Arial, Helvetica, sans-serif;
}
/* Block-inline elements and classes */
img {
vertical-align: baseline;
margin: 0;
padding: 0;
border: none;
}
img.icon16 { /* For 16x16 file-type icons */
vertical-align: -2px;
}
del,
del * { /* Required for Mozilla */
text-decoration: line-through;
}
ins,
ins * { /* Required for Mozilla */
text-decoration: underline;
}
.floatleft { /* Left-floating images and spans */
margin: 0.5em 1.5em 0.5em 0;
float: left;
}
.floatright { /* Right-floating images and spans */
margin: 0.5em 0 0.5em 1.5em;
float: right;
}
.nowrap {
white-space: nowrap;
}
/* Block elements */
p {
margin: 1em 0;
padding: 0;
}
blockquote { /* Should only be used in main content area, */
/* floating boxes or left sidebar. */
margin: 1em 2.5em;
padding: 0;
}
pre { /* Should only be used in main content area */
/* and floating boxes. */
font-family: "Courier New", Courier, monospace;
font-size: 115%; /* Courier tends to be a little too small */
line-height: 1.2;
margin: 1em 2.5em;
padding: 0;
}
pre code,
pre kbd,
pre samp,
pre tt {
font-size: 100%; /* PRE is already enlarged above */
line-height: 1.2; /* Use same value as for PRE above */
}
hr {
color: #999999;
background: transparent;
height: 1px; /* Required for IE/Win */
margin: 0;
padding: 0;
border-color: #999999;
border-width: 1px;
border-style: none none solid none;
}
hr.lighter { /* Warning: not printed out on paper */
color: #F0F0F0;
background: transparent;
border-color: #F0F0F0;
}
/* Lists */
ol {
list-style: decimal outside;
margin: 1em 0;
padding: 0 0 0 2.5em;
}
ol.alpha {
list-style-type: lower-alpha;
}
ol.number {
list-style-type: decimal;
}
ul {
list-style: square outside;
margin: 1em 0;
padding: 0 0 0 2.5em;
}
ol ol,
ol ul,
ul ol,
ul ul {
margin-top: 0;
margin-bottom: 0;
}
ol ul, /* Override possible browser styles */
ol ol ul,
ol ul ul,
ul ul,
ul ol ul,
ul ul ul {
list-style: square outside;
}
li {
margin: 0;
padding: 0;
}
dl {
margin: 1em 0;
padding: 0;
}
dt {
font: inherit; /* Don't make the text bold by default */
margin: 1em 0 0.25em 0;
padding: 0;
}
dd {
margin: 0 0 1em 2.5em;
padding: 0;
}
/* Tables */
/* Tables should never be used for visual */
/* formatting: that is the role of CSS! */
table.simple {
color: inherit;
background: inherit; /* Don't make tables transparent */
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
margin: 0.5em 2.5em;
padding: 0;
border: 1px solid #999999;
}
table.simple caption {
text-align: center;
caption-side: top;
margin: 0 2.5em 0.75em;
padding: 0;
border: none;
}
table.simple td,
table.simple th {
text-align: center;
vertical-align: middle;
margin: 0;
padding: 0.25em 0.5em;
border: 1px solid #999999;
}
table.simple th,
table.simple td.highlight,
table.simple th.highlight {
font-weight: bold;
color: inherit;
background: #F0F0F0;
}
table.simple td.lighter,
table.simple th.lighter {
color: inherit;
background: #F8F8F8;
}
table.simple td.left,
table.simple th.left {
text-align: left;
}
table.simple td.center,
table.simple th.center {
text-align: center;
}
table.simple td.right,
table.simple th.right {
text-align: right;
}
/* Forms */
form {
margin: 1em 0;
padding: 0;
border: none;
}
input,
button,
select,
fieldset,
legend {
font-family: Verdana, "DejaVu Sans", "Bitstream Vera Sans", "Lucida Sans", Arial, Helvetica, sans-serif;
font-size: 95%;
color: black;
background: inherit;
vertical-align: middle;
}
textarea {
font-family: "Courier New", Courier, monospace;
font-size: 100%;
color: black;
background: inherit;
vertical-align: middle;
}
fieldset {
font-size: 100%;
margin: 1em 0;
border: 1px solid #999999;
}
legend {
font-size: 100%;
margin: 0 0.5em;
padding: 0 0.25em;
border: none;
}
table.formtable {
color: inherit;
background: inherit;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
margin: 0;
padding: 0;
border: none;
}
table.formtable td,
table.formtable th {
text-align: justify;
vertical-align: middle;
margin: 0;
padding: 0.25em 0.5em;
border: none;
}
table.formtable th {
text-align: center;
font-weight: bold;
}
table.formtable td.label,
table.formtable th.label {
text-align: right;
vertical-align: top;
}
table.formtable td.vertspace,
table.formtable th.vertspace {
empty-cells: show;
margin: 0;
padding: 0.5em 0;
height: 1em; /* Required for IE/Win */
}
table.formtable fieldset {
margin: 0;
}
table.formtable fieldset td,
table.formtable fieldset th {
margin: 0.25em 0.5em;
}
.reqfield {
color: red;
background: transparent;
font-weight: bolder;
}
.info {
color: gray;
background: transparent;
font-size: 90%;
}
/* The following HTML elements should NOT be used in documents using this
style sheet:
address - use the #footer style instead
q - use &ldquo; and &rdquo; instead
*/
/********** Styles for Main Content **********/
#main {
text-align: justify;
line-height: 1.5;
color: black;
background: white;
margin: 0 0 0 12.5em;
padding: 0.25em 1.5em 0.5em 1em;
border-left: 1px solid #999999;
}
#main h1 { /* Should be used once, following navhead */
color: #999999;
background: transparent;
margin: 0 0 0.5em 0;
}
#main .highlight { /* Highlight box (for warnings, etc) */
color: inherit;
background: #F0F0F0;
margin: 1em 0;
padding: 1em 2.5em;
border: 1px solid #999999;
}
#main .totop { /* For "Top ^" lines in FAQs, etc */
font-size: 90%;
text-align: right;
margin: -0.75em 0 1em 0;
padding: 0 0 0.25em 0;
border-bottom: 1px solid #F0F0F0;
}
#main table.simple td.highlight, /* Else "#main .highlight" will override */
#main table.simple th.highlight {
margin: 0;
padding: 0.25em 0.5em;
}
/* Other styles related to the main content */
#mainlink { /* "Skip to main content" link */
display: none !important;
}
#navhead { /* "Path to this page" information */
/* Warning: not printed out on paper */
font-size: 90%;
}
#navhead hr {
display: none;
}
#endmain {
visibility: hidden;
clear: both; /* Doesn't always work under IE/Win */
}
/********** Styles for Floating Boxes **********/
/* "navbox" is used to provide intra/inter- */
/* page links; it is NOT printed out on */
/* paper. "floatbox" is used to provide */
/* floating boxes that may appear anywhere */
/* in the main content; they ARE printed. */
.floatbox,
.navbox {
overflow: visible;
font-size: 95%;
line-height: 1.25;
margin: 0 0 0.75em 1.5em;
padding: 0.5em 1em;
border: 1px solid #999999;
float: right;
clear: right;
}
.floatbox {
color: black;
background: #F0F0F0;
width: 35%;
}
.navbox {
text-align: left;
color: black;
background: white;
width: 12.5em;
}
.floatbox hr, /* Used for non-stylesheet-aware browsers */
.navbox hr {
display: none !important;
}
.floatbox p,
.navbox p {
margin: 0.75em 0;
padding: 0;
}
.floatbox ol,
.floatbox ul {
margin: 0.75em 0;
padding: 0 0 0 1.5em;
}
.navbox ol,
.navbox ul {
margin: 0.5em 0;
padding: 0 0 0 1.5em;
}
.floatbox blockquote {
margin: 0.75em 1.5em;
padding: 0;
}
.floatbox pre {
font-size: 95%;
margin: 0.75em 1.5em;
padding: 0;
}
.floatbox dt {
margin: 0.75em 0;
padding: 0;
}
.floatbox dt {
margin: 0.75em 0 0.25em 0;
padding: 0;
}
.floatbox dd {
margin: 0 0 0.75em 1.5em;
padding: 0;
}
#main .floatbox .highlight {
color: inherit;
background: white;
margin: 0.75em 0;
padding: 0.75em 1.5em;
}
#main .floatbox table.simple {
margin: 0.75em 0;
}
#main .floatbox table.simple th,
#main .floatbox table.simple td.highlight,
#main .floatbox table.simple th.highlight {
color: inherit;
background: white;
margin: 0;
padding: 0.25em 0.5em;
}
/********** Styles for Header **********/
/* In this style sheet, headers are composed */
/* of three parts: left, right and subheader */
/* Left part is ideally an image. */
#header { /* Warning: not printed out on paper */
color: #003399;
background: #8CA8E6;
}
#header a:link,
#header a:visited {
color: #003399;
background: transparent;
}
#header .highlight,
#header a.highlight:link,
#header a.highlight:visited {
color: white;
background: transparent;
}
/* Left part of header (ideally an image but may be a link) */
#header div.left {
float: left;
clear: left;
margin: 0;
padding: 0;
}
#header div.left img {
display: block; /* Otherwise IMG is an inline, causing gaps */
}
#header div.left,
#header div.left a:link,
#header div.left a:visited {
font-size: 200%;
font-weight: bold;
text-decoration: none;
color: white;
background: transparent;
}
#header div.left p {
margin: 0 0 0 0.25em;
padding: 0;
}
#header div.left .alt {
color: #FF9800;
background: transparent;
}
/* Right part of header is for external/global links, search, etc */
#header div.right {
font-size: 90%;
text-align: right;
margin: 0;
padding: 0.5em 1.17em 0 1em;
float: right;
clear: right;
}
#header div.right a:link,
#header div.right a:visited {
margin: 0;
padding: 0 0.5em;
}
#header div.right form {
margin: 0;
padding: 0.25em 0.5em 0 0;
}
#header div.right form input {
font-size: 95%;
vertical-align: middle;
}
/* Subheader for global links */
#header div.subheader {
color: white;
background: #003399;
margin: 0;
padding: 0;
border: 1px solid #003399; /* Required for IE/Win */
clear: both;
}
#header div.subheader p { /* To overcome an IE/Win unwanted padding */
/* bug, still present in IE7. */
margin: 0;
padding: 0.5em;
}
#header div.subheader a:link,
#header div.subheader a:visited {
font-weight: bolder;
color: white;
background: transparent;
margin: 0;
padding: 0 0.5em;
}
#header div.subheader .highlight,
#header div.subheader a.highlight:link,
#header div.subheader a.highlight:visited {
color: #FDA05E;
background: transparent;
}
/********** Styles for Left Sidebar **********/
#sidebar { /* Warning: not printed out on paper */
width: 12.5em;
border-right: 1px solid #999999;
float: left;
clear: both;
}
#sidebar div {
font-size: 95%;
text-align: left;
margin: 0;
padding: 0.5em 1em;
border-bottom: 1px solid #999999;
}
#sidebar div.lighter {
color: inherit;
background: white;
}
#sidebar p {
margin: 0.5em 0;
}
#sidebar .title a:link,
#sidebar .title a:visited {
color: black;
background: transparent;
}
#sidebar ul {
list-style: none outside;
margin: 0.5em 0;
padding: 0;
}
#sidebar ul li {
margin: 0;
padding: 0.125em 0;
}
#sidebar ul li.highlight {
color: inherit;
background: white;
margin-left: -1em;
margin-right: -1em;
padding-left: 1em;
border-top: 1px solid #999999;
border-bottom: 1px solid #999999;
}
#sidebar ul li.highlight a:link,
#sidebar ul li.highlight a:visited {
color: black;
background: transparent;
}
/********** Styles for Footer **********/
#footer {
font-size: 90%;
text-align: left;
color: white;
background: #6381DC;
margin: 0;
padding: 0.5em 1.67em 0.5em 15.25em;
clear: both;
}
#footer a:link,
#footer a:visited {
text-decoration: underline;
color: white;
background: transparent;
}
#footer hr {
display: none !important;
}
/* End of the Sinorcaish style sheet */