Initial commit of historian web application
This commit is contained in:
2653
Cargo.lock
generated
Normal file
2653
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
33
Cargo.toml
Normal file
33
Cargo.toml
Normal 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"]
|
||||||
@@ -1 +1,10 @@
|
|||||||
# Historian
|
# 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
40
guix.scm
Normal 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
7
make-dev-environment
Executable 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
564
src/lib.rs
Normal 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
216
src/main.rs
Normal 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
134
templates/base.html
Normal 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> »
|
||||||
|
{% 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> © 2004 - 2007 John Zaitseff.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
18
templates/edit.css
Normal file
18
templates/edit.css
Normal 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
64
templates/edit.html
Normal 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
BIN
templates/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
22
templates/history.css
Normal file
22
templates/history.css
Normal 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
16
templates/history.html
Normal 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
5
templates/message.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<h2>{{ header }}</h2>
|
||||||
|
{{ message | safe }}
|
||||||
|
{% endblock content %}
|
||||||
4
templates/page.html
Normal file
4
templates/page.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
{{ content | markdown | safe }}
|
||||||
|
{% endblock content %}
|
||||||
14
templates/revision.html
Normal file
14
templates/revision.html
Normal 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 %}
|
||||||
705
templates/sinorcaish-print.css
Normal file
705
templates/sinorcaish-print.css
Normal 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 “ and ” 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 */
|
||||||
|
|
||||||
850
templates/sinorcaish-screen.css
Normal file
850
templates/sinorcaish-screen.css
Normal 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 “ and ” 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 */
|
||||||
|
|
||||||
Reference in New Issue
Block a user