mirror of
https://github.com/silverstripe/silverstripe-docsviewer
synced 2024-10-22 11:05:56 +02:00
FEATURE: added support to register versions and modules manually and disable the automatic includsion. FEATURE: added support for multiple versions and languages in the documentation. ENHANCEMENT: added toolbox to view module docs on pages and lots of other new templates
This commit is contained in:
parent
e11837763b
commit
a07855109f
182
code/DocumentationEntity.php
Normal file
182
code/DocumentationEntity.php
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* A wrapper for a documentation entity which is created when registering the
|
||||
* path with {@link DocumentationService::register()}. This refers to a whole package
|
||||
* rather than a specific page but if we need page options we may need to introduce
|
||||
* a class for that.
|
||||
*
|
||||
* @package sapphiredocs
|
||||
*/
|
||||
|
||||
class DocumentationEntity extends ViewableData {
|
||||
|
||||
static $casting = array(
|
||||
'Name' => 'Text'
|
||||
);
|
||||
|
||||
/**
|
||||
* @var String $module folder name
|
||||
*/
|
||||
private $moduleFolder;
|
||||
|
||||
/**
|
||||
* @var String $title nice title
|
||||
*/
|
||||
private $title;
|
||||
|
||||
/**
|
||||
* @var Array $version version numbers and the paths to each
|
||||
*/
|
||||
private $versions = array();
|
||||
|
||||
/**
|
||||
* @var Array $langs a list of available langauges
|
||||
*/
|
||||
private $langs = array();
|
||||
|
||||
/**
|
||||
* Constructor. You do not need to pass the langs to this as
|
||||
* it will work out the languages from the filesystem
|
||||
*
|
||||
* @param String $module name of module
|
||||
* @param String $version version of this module
|
||||
* @param String $path path to this module
|
||||
*/
|
||||
function __construct($module, $version = '', $path, $title = false) {
|
||||
$this->addVersion($version, $path);
|
||||
$this->title = (!$title) ? $this->module : $title;
|
||||
$this->moduleFolder = $module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the languages which are available
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public function getLanguages() {
|
||||
return $this->langs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether this entity has a given language
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasLanguage($lang) {
|
||||
return (in_array($lang, $this->langs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a langauge or languages to the entity
|
||||
*
|
||||
* @param Array|String languages
|
||||
*/
|
||||
public function addLanguage($language) {
|
||||
if(is_array($language)) {
|
||||
$this->langs = array_unique(array_merge($this->langs, $language));
|
||||
}
|
||||
else {
|
||||
$this->langs[] = $language;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the folder name of this module
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public function getModuleFolder() {
|
||||
return $this->moduleFolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title of this module
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public function getTitle() {
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the versions which are available
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public function getVersions() {
|
||||
return array_keys($this->versions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether we have a given version of this entity
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasVersion($version) {
|
||||
return (isset($this->versions[$version]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether we have any versions at all0
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasVersions() {
|
||||
return (sizeof($this->versions) > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add another version to this entity
|
||||
*
|
||||
* @param Float $version Version number
|
||||
* @param String $path path to folder
|
||||
*/
|
||||
public function addVersion($version = '', $path) {
|
||||
// determine the langs in this path
|
||||
|
||||
$langs = scandir($path);
|
||||
|
||||
$available = array();
|
||||
|
||||
if($langs) {
|
||||
foreach($langs as $key => $lang) {
|
||||
if(!is_dir($path . $lang) || strlen($lang) > 2 || in_array($lang, DocumentationService::get_ignored_files(), true))
|
||||
$lang = 'en';
|
||||
|
||||
if(!in_array($lang, $available))
|
||||
$available[] = $lang;
|
||||
}
|
||||
}
|
||||
|
||||
$this->addLanguage($available);
|
||||
$this->versions[$version] = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a version from this entity
|
||||
*
|
||||
* @param Float $version
|
||||
*/
|
||||
public function removeVersion($version = '') {
|
||||
if(isset($this->versions[$version])) {
|
||||
unset($this->versions[$version]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the path to this documentation entity
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public function getPath($version = false, $lang = false) {
|
||||
|
||||
if(!$version) $version = '';
|
||||
if(!$lang) $lang = 'en';
|
||||
|
||||
if(!$this->hasVersion($version)) $path = array_pop($this->versions);
|
||||
else $path = $this->versions[$version];
|
||||
|
||||
return $path . $lang .'/';
|
||||
}
|
||||
}
|
153
code/DocumentationParser.php
Normal file
153
code/DocumentationParser.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Wrapper for MarkdownUltra parsing in the template and related functionality for
|
||||
* parsing paths and documents
|
||||
*
|
||||
* @package sapphiredocs
|
||||
*/
|
||||
|
||||
class DocumentationParser {
|
||||
|
||||
/**
|
||||
* Parse a given path to the documentation for a file. Performs a case insensitive
|
||||
* lookup on the file system. Automatically appends the file extension to one of the markdown
|
||||
* extensions as well so /install/ in a web browser will match /install.md or /INSTALL.md
|
||||
*
|
||||
* @param String $module path to a module
|
||||
* @param Array path of urls. Should be folders, last one is a page
|
||||
*
|
||||
* @return HTMLText
|
||||
*/
|
||||
public static function parse($module, $path) {
|
||||
require_once('../sapphiredocs/thirdparty/markdown.php');
|
||||
|
||||
if($content = self::find_page($module, $path)) {
|
||||
$content = Markdown(file_get_contents($content));
|
||||
|
||||
return DBField::create('HTMLText', $content);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a documentation page given a path and a file name. It ignores the extensions
|
||||
* and simply compares the title.
|
||||
*
|
||||
* Name may also be a path /install/foo/bar.
|
||||
*
|
||||
* @param String $entity path to the entity
|
||||
* @param Array $path path to the file in the entity
|
||||
*
|
||||
* @return String|false - File path
|
||||
*/
|
||||
private static function find_page($entity, $path) {
|
||||
return self::find_page_recursive($entity, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive function for finding the goal
|
||||
*/
|
||||
private static function find_page_recursive($base, $goal) {
|
||||
$handle = opendir($base);
|
||||
|
||||
$name = strtolower(array_shift($goal));
|
||||
|
||||
if(!$name) $name = 'index';
|
||||
|
||||
if($handle) {
|
||||
$extensions = DocumentationService::get_valid_extensions();
|
||||
|
||||
while (false !== ($file = readdir($handle))) {
|
||||
if(in_array($file, DocumentationService::get_valid_extensions())) continue;
|
||||
|
||||
$formatted = strtolower($file);
|
||||
|
||||
// if the name has a . then take the substr
|
||||
$formatted = ($pos = strrpos($formatted, '.')) ? substr($formatted, 0, $pos) : $formatted;
|
||||
$name = ($dot = strrpos($formatted, '.')) ? substr($name, 0, $dot) : $name;
|
||||
|
||||
// the folder is the one that we are looking for.
|
||||
if($name == $formatted) {
|
||||
|
||||
if(is_dir($base . $file)) {
|
||||
// if this is a directory check that there is any more states to get
|
||||
// to in the goal. If none then what we want is the 'index.md' file
|
||||
if(count($goal) > 0) {
|
||||
return self::find_page_recursive($base . $file, $goal);
|
||||
}
|
||||
else {
|
||||
// recurse but check for an index.md file next time around
|
||||
return self::find_page_recursive($base . $file, array('index'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
// goal state. End of recursion
|
||||
$result = $base .'/'. $file;
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
}
|
||||
|
||||
/**
|
||||
* String helper for cleaning a file name to a readable version.
|
||||
*
|
||||
* @param String $name to convert
|
||||
*
|
||||
* @return String $name output
|
||||
*/
|
||||
public static function clean_page_name($name) {
|
||||
// remove dashs and _
|
||||
$name = str_ireplace(array('-', '_'), ' ', $name);
|
||||
|
||||
// remove extension
|
||||
$hasExtension = strpos($name, '.');
|
||||
|
||||
if($hasExtension !== false && $hasExtension > 0) {
|
||||
$name = substr($name, 0, $hasExtension);
|
||||
}
|
||||
|
||||
// convert first letter
|
||||
return ucfirst($name);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the children from a given module. Used for building the tree of the page
|
||||
*
|
||||
* @param String module name
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
public static function get_pages_from_folder($folder) {
|
||||
$handle = opendir($folder);
|
||||
$output = new DataObjectSet();
|
||||
|
||||
if($handle) {
|
||||
$extensions = DocumentationService::get_valid_extensions();
|
||||
$ignore = DocumentationService::get_ignored_files();
|
||||
|
||||
while (false !== ($file = readdir($handle))) {
|
||||
if(!in_array($file, $ignore)) {
|
||||
$file = strtolower($file);
|
||||
|
||||
$clean = ($pos = strrpos($file, '.')) ? substr($file, 0, $pos) : $file;
|
||||
|
||||
$output->push(new ArrayData(array(
|
||||
'Title' => self::clean_page_name($file),
|
||||
'Filename' => $clean,
|
||||
'Path' => $folder . $file .'/'
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
342
code/DocumentationService.php
Normal file
342
code/DocumentationService.php
Normal file
@ -0,0 +1,342 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DocumentationService
|
||||
*
|
||||
* Handles the management of the documentation services delivered by the module.
|
||||
* Includes registering which components to document and handles the entities being
|
||||
* documented
|
||||
*
|
||||
* @todo - unregistering a lang / version from site does not update the registered_* arrays
|
||||
* - handle modules (rather than core) differently
|
||||
* @package sapphiredocs
|
||||
*/
|
||||
|
||||
class DocumentationService {
|
||||
|
||||
/**
|
||||
* A mapping of know / popular languages to nice titles.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
private static $language_mapping = array(
|
||||
'en' => 'English',
|
||||
'fr' => 'French',
|
||||
'de' => 'German'
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* Files to ignore from any documentation listing.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
private static $ignored_files = array('.', '..', '.DS_Store', '.svn', '.git', 'assets', 'themes');
|
||||
|
||||
/**
|
||||
* Set the ignored files list
|
||||
*
|
||||
* @param Array
|
||||
*/
|
||||
public function set_ignored_files($files) {
|
||||
self::$ignored_files = $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of files which are ignored
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public function get_ignored_files() {
|
||||
return self::$ignored_files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Case insenstive values to use as extensions on markdown pages.
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
public static $valid_markdown_extensions = array('.md', '.txt', '.markdown');
|
||||
|
||||
/**
|
||||
* Return the allowed extensions
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public static function get_valid_extensions() {
|
||||
return self::$valid_markdown_extensions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registered modules to include in the documentation. Either pre-filled by the
|
||||
* automatic filesystem parser or via {@link DocumentationService::register()}. Stores
|
||||
* {@link DocumentEntity} objects which contain the languages and versions of each module.
|
||||
*
|
||||
* You can remove registered modules using {@link DocumentationService::unregister()}
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
private static $registered_modules = array();
|
||||
|
||||
/**
|
||||
* Major Versions store. We don't want to register all versions of every module in
|
||||
* the documentation but for sapphire/cms and overall we need to register major
|
||||
* versions via {@link DocumentationService::register}
|
||||
*
|
||||
* @var Array
|
||||
*/
|
||||
private static $major_versions = array();
|
||||
|
||||
/**
|
||||
* Return the major versions
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public static function get_major_versions() {
|
||||
return self::$major_versions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a given language is registered in the system
|
||||
*
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_registered_language($lang) {
|
||||
$langs = self::get_registered_languages();
|
||||
|
||||
return (isset($langs[$lang]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the registered languages. Optionally limited to a module. Includes
|
||||
* the nice titles
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
public static function get_registered_languages($module = false) {
|
||||
$langs = array();
|
||||
|
||||
if($module) {
|
||||
if(isset(self::$registered_modules[$module])) {
|
||||
$langs = self::$registered_modules[$module]->getLanguages();
|
||||
}
|
||||
}
|
||||
else if($modules = self::get_registered_modules()) {
|
||||
|
||||
foreach($modules as $module) {
|
||||
$langs = array_unique(array_merge($langs, $module->getLanguages()));
|
||||
}
|
||||
}
|
||||
|
||||
$output = array();
|
||||
foreach($langs as $lang) {
|
||||
$output[$lang] = self::get_language_title($lang);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all the registered versions in the system. Optionally only
|
||||
* include versions from a module.
|
||||
*
|
||||
* @param String $module module to check for versions
|
||||
* @return array
|
||||
*/
|
||||
public static function get_registered_versions($module = false) {
|
||||
if($module) {
|
||||
if(isset($registered_modules[$module])) {
|
||||
return $registered_modules[$module]->getVersions();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return self::$major_versions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should generation of documentation categories be automatic. If this
|
||||
* is set to true then it will generate documentation sections (modules) from
|
||||
* the filesystem. This can be slow and also some projects may want to restrict
|
||||
* to specific project folders (rather than everything).
|
||||
*
|
||||
* You can also disable or remove a given folder from registration using
|
||||
* {@link DocumentationService::unregister()}
|
||||
*
|
||||
* @see DocumentationService::$registered_modules
|
||||
* @see DocumentationService::set_automatic_registration();
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $automatic_registration = true;
|
||||
|
||||
/**
|
||||
* Set automatic registration of modules and documentation folders
|
||||
*
|
||||
* @see DocumentationService::$automatic_registration
|
||||
* @param bool
|
||||
*/
|
||||
public static function set_automatic_registration($bool = true) {
|
||||
self::$automatic_registration = $bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is automatic registration of modules enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function automatic_registration_enabled() {
|
||||
return self::$automatic_registration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the modules which are listed for documentation. Optionally only get
|
||||
* modules which have a version or language given
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_registered_modules($version = false, $lang = false) {
|
||||
$output = array();
|
||||
|
||||
if($modules = self::$registered_modules) {
|
||||
if($version || $lang) {
|
||||
foreach($modules as $module) {
|
||||
if(self::is_registered_module($module->getModuleFolder(), $version, $lang)) {
|
||||
$output[] = $module;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$output = $modules;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if a module is registered with the documenter
|
||||
*
|
||||
* @param String $module module name
|
||||
* @param String $version version
|
||||
* @param String $lang language
|
||||
*
|
||||
* @return DocumentationEntity $module the registered module
|
||||
*/
|
||||
public static function is_registered_module($module, $version = false, $lang = false) {
|
||||
|
||||
if(isset(self::$registered_modules[$module])) {
|
||||
$module = self::$registered_modules[$module];
|
||||
|
||||
if($lang && !$module->hasLanguage($lang)) return false;
|
||||
|
||||
if($version && !$module->hasVersion($version)) return false;
|
||||
|
||||
return $module;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a module to be included in the documentation. To unregister a module
|
||||
* use {@link DocumentationService::unregister()}. Must include the trailing slash
|
||||
*
|
||||
* @param String $module Name of module to register
|
||||
* @param String $path Path to documentation root.
|
||||
* @param Float $version Version of module.
|
||||
* @param String $title Nice title to use
|
||||
* @param bool $major is this a major release
|
||||
*/
|
||||
public static function register($module, $path, $version = '', $title = false, $major = false) {
|
||||
|
||||
// add the module to the registered array
|
||||
if(!isset(self::$registered_modules[$module])) {
|
||||
// module is completely new
|
||||
$entity = new DocumentationEntity($module, $version, $path, $title);
|
||||
|
||||
self::$registered_modules[$module] = $entity;
|
||||
}
|
||||
else {
|
||||
// module exists so add the version to it
|
||||
$entity = self::$registered_modules[$module];
|
||||
|
||||
$entity->addVersion($version, $path);
|
||||
}
|
||||
|
||||
if($major) {
|
||||
if(!$version) $version = '';
|
||||
|
||||
if(!in_array($version, self::$major_versions)) {
|
||||
self::$major_versions[] = $version;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a module from being included in the documentation. Useful
|
||||
* for keeping {@link DocumentationService::$automatic_registration} enabled
|
||||
* but disabling modules which you do not want to show. Combined with a
|
||||
* {@link Director::isLive()} you can hide modules you don't want a client to see.
|
||||
*
|
||||
* If no version or lang specified then the whole module is removed. Otherwise only
|
||||
* the specified version of the documentation.
|
||||
*
|
||||
* @param String $module
|
||||
* @param String $version
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function unregister($module, $version = '') {
|
||||
if(isset(self::$registered_modules[$module])) {
|
||||
$module = self::$registered_modules[$module];
|
||||
|
||||
if($version) {
|
||||
$module->removeVersion($version);
|
||||
}
|
||||
else {
|
||||
// only given a module so unset the whole module
|
||||
unset(self::$registered_modules[$module]);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the docs from off a file system if automatic registration is turned on.
|
||||
*/
|
||||
public static function load_automatic_registration() {
|
||||
if(self::automatic_registration_enabled()) {
|
||||
$modules = scandir(BASE_PATH);
|
||||
|
||||
if($modules) {
|
||||
foreach($modules as $key => $module) {
|
||||
if(is_dir(BASE_PATH .'/'. $module) && !in_array($module, self::$ignored_files, true)) {
|
||||
// check to see if it has docs
|
||||
$docs = BASE_PATH .'/'. $module .'/docs/';
|
||||
|
||||
if(is_dir($docs)) {
|
||||
self::register($module, $docs, '', $module, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a language code to a 'nice' text string. Uses the
|
||||
* {@link self::$language_mapping} array combined with translatable.
|
||||
*
|
||||
* @param String $code code
|
||||
*/
|
||||
public static function get_language_title($lang) {
|
||||
return (isset(self::$language_mapping[$lang])) ? _t("DOCUMENTATIONSERVICE.LANG-$lang", self::$language_mapping[$lang]) : $lang;
|
||||
}
|
||||
}
|
@ -3,277 +3,420 @@
|
||||
/**
|
||||
* Documentation Viewer.
|
||||
*
|
||||
* Reads the bundled markdown files from docs/ folders and displays output in a formatted page at /dev/docs/.
|
||||
* For more documentation on how to use this class see the documentation online in /dev/docs/ or in the
|
||||
* /sapphiredocs/docs folder
|
||||
* Reads the bundled markdown files from documentation folders and displays the output (either
|
||||
* via markdown or plain text)
|
||||
*
|
||||
* For more documentation on how to use this class see the documentation in /sapphiredocs/docs folder
|
||||
*
|
||||
* To view the documentation in the browser use:
|
||||
*
|
||||
* http://yoursite.com/dev/docs/ Which is locked to ADMIN only
|
||||
*
|
||||
* @todo - Add ability to have docs on the front end as the main site.
|
||||
* - Fix Language Selector (enabling it troubles the handleRequest when submitting)
|
||||
* - SS_HTTPRequest when we ask for 10 params it gives us 10. Could be 10 blank ones.
|
||||
* It would mean I could save alot of code if it only gave back an array of size X
|
||||
* up to a maximum of 10...
|
||||
*
|
||||
* @author Will Rossiter <will@silverstripe.com>
|
||||
* @package sapphiredocs
|
||||
*/
|
||||
|
||||
class DocumentationViewer extends Controller {
|
||||
|
||||
static $url_handlers = array(
|
||||
'' => 'index',
|
||||
'$Module/$Page/$OtherPage' => 'parse'
|
||||
|
||||
static $allowed_actions = array(
|
||||
'LanguageForm',
|
||||
'doLanguageForm',
|
||||
'handleRequest',
|
||||
'fr', // better way of handling this?
|
||||
'en'
|
||||
);
|
||||
|
||||
/**
|
||||
* An array of files to ignore from the listing
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $ignored_files = array('.', '..', '.DS_Store', '.svn', '.git', 'assets', 'themes');
|
||||
static $casting = array(
|
||||
'Version' => 'Text',
|
||||
'Lang' => 'Text',
|
||||
'Module' => 'Text',
|
||||
'LanguageTitle' => 'Text'
|
||||
);
|
||||
|
||||
/**
|
||||
* An array of case insenstive values to use as readmes
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
static $readme_files = array('readme', 'readme.md', 'readme.txt', 'readme.markdown');
|
||||
|
||||
function init() {
|
||||
parent::init();
|
||||
|
||||
$canAccess = (Director::isDev() || Director::is_cli() || Permission::check("ADMIN"));
|
||||
|
||||
|
||||
/**
|
||||
* Main documentation page
|
||||
*/
|
||||
function index() {
|
||||
return $this->customise(array(
|
||||
'DocumentedModules' => $this->DocumentedModules()
|
||||
))->renderWith(array('DocumentationViewer_index', 'DocumentationViewer'));
|
||||
if(!$canAccess) return Security::permissionFailure($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Individual documentation page
|
||||
* Handle the url parsing for the documentation. In order to make this
|
||||
* user friendly this does some tricky things..
|
||||
*
|
||||
* @param HTTPRequest
|
||||
* The urls which should work
|
||||
* / - index page
|
||||
* /en/sapphire - the index page of sapphire (shows versions)
|
||||
* /2.4/en/sapphire - the docs for 2.4 sapphire.
|
||||
* /2.4/en/sapphire/installation/
|
||||
*
|
||||
* @return SS_HTTPResponse
|
||||
*/
|
||||
function parse($request) {
|
||||
require_once('../sapphiredocs/thirdparty/markdown.php');
|
||||
public function handleRequest(SS_HTTPRequest $request) {
|
||||
|
||||
$this->Version = $request->shift();
|
||||
$this->Lang = $request->shift();
|
||||
|
||||
$this->Remaining = $request->shift(10);
|
||||
|
||||
DocumentationService::load_automatic_registration();
|
||||
|
||||
if(isset($this->Version)) {
|
||||
// check to see if its a valid version. If its not a float then its not actually a version
|
||||
// its actually a language and it needs to change. So this means we support 2 structures
|
||||
// /2.4/en/sapphire/page and
|
||||
// /en/sapphire/page which is a link to the latest one
|
||||
|
||||
$page = $request->param('Page');
|
||||
$module = $request->param('Module');
|
||||
|
||||
$path = BASE_PATH .'/'. $module .'/docs';
|
||||
|
||||
if($content = $this->findPage($path, $page)) {
|
||||
$title = $page;
|
||||
$content = Markdown(file_get_contents($content));
|
||||
if(!is_numeric($this->Version)) {
|
||||
// not numeric so /en/sapphire/folder/page
|
||||
if(isset($this->Lang) && $this->Lang)
|
||||
array_unshift($this->Remaining, $this->Lang);
|
||||
|
||||
$this->Lang = $this->Version;
|
||||
$this->Version = null;
|
||||
}
|
||||
else {
|
||||
// if(!DocumentationService::is_registered_version($this->Version)) {
|
||||
// $this->httpError(404, 'The requested version could not be found.');
|
||||
// }
|
||||
}
|
||||
}
|
||||
if(isset($this->Lang)) {
|
||||
// check to see if its a valid language
|
||||
// if(!DocumentationService::is_registered_language($this->Lang)) {
|
||||
// $this->httpError(404, 'The requested language could not be found.');
|
||||
// }
|
||||
}
|
||||
else {
|
||||
$title = 'Page not Found';
|
||||
$content = false;
|
||||
$this->Lang = 'en';
|
||||
}
|
||||
|
||||
return $this->customise(array(
|
||||
'Title' => $title,
|
||||
'Content' => $content
|
||||
))->renderWith('DocumentationViewer');
|
||||
return parent::handleRequest($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the modules installed. Currently to determine if a module is
|
||||
* installed look at all the folders and check is a _config file.
|
||||
*
|
||||
* @return array
|
||||
* Custom templates for each of the sections.
|
||||
*/
|
||||
function getModules() {
|
||||
$modules = scandir(BASE_PATH);
|
||||
function getViewer($action) {
|
||||
// count the number of parameters after the language, version are taken
|
||||
// into account. This automatically includes ' ' so all the counts
|
||||
// are 1 more than what you would expect
|
||||
|
||||
if($modules) {
|
||||
foreach($modules as $key => $module) {
|
||||
if(!is_dir(BASE_PATH .'/'. $module) || in_array($module, self::$ignored_files, true) || !file_exists(BASE_PATH . '/'. $module .'/_config.php')) {
|
||||
unset($modules[$key]);
|
||||
}
|
||||
if($this->Remaining) {
|
||||
$params = count(array_unique($this->Remaining));
|
||||
|
||||
switch($params) {
|
||||
case '1':
|
||||
return parent::getViewer('home');
|
||||
case '2':
|
||||
return parent::getViewer('folder');
|
||||
default:
|
||||
if($module = $this->getModule()) {
|
||||
$params = $this->Remaining;
|
||||
array_shift($params);
|
||||
|
||||
$path = implode('/', array_unique($params));
|
||||
}
|
||||
|
||||
if(is_dir($module->getPath() . $path)) return parent::getViewer('folder');
|
||||
}
|
||||
}
|
||||
|
||||
return $modules;
|
||||
|
||||
return parent::getViewer($action);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a set of modules for the home page
|
||||
* Return all the available languages. Optionally the languages which are
|
||||
* available for a given module
|
||||
*
|
||||
* @param String - The name of the module
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function DocumentedModules() {
|
||||
function getLanguages($module = false) {
|
||||
$output = new DataObjectSet();
|
||||
|
||||
$modules = new DataObjectSet();
|
||||
|
||||
// include sapphire first
|
||||
$modules->push(new ArrayData(array(
|
||||
'Title' => 'sapphire',
|
||||
'Content' => $this->generateNestedTree('sapphire'),
|
||||
'Readme' => $this->readmeExists('sapphire')
|
||||
)));
|
||||
|
||||
$extra_ignore = array('sapphire');
|
||||
|
||||
foreach($this->getModules() as $module) {
|
||||
if(!in_array($module, $extra_ignore) && $this->moduleHasDocs($module)) {
|
||||
$modules->push(new ArrayData(array(
|
||||
'Title' => $module,
|
||||
'Content' => $this->generateNestedTree($module),
|
||||
'Readme' => $this->readmeExists($module)
|
||||
)));
|
||||
if($module) {
|
||||
// lookup the module for the available languages
|
||||
|
||||
// @todo
|
||||
}
|
||||
else {
|
||||
$languages = DocumentationService::get_registered_languages();
|
||||
|
||||
if($languages) {
|
||||
foreach($languages as $key => $lang) {
|
||||
|
||||
if(stripos($_SERVER['REQUEST_URI'], '/'. $this->Lang .'/') === false) {
|
||||
// no language is in the URL currently. It needs to insert the language
|
||||
// into the url like /sapphire/install to /en/sapphire/install
|
||||
//
|
||||
// @todo
|
||||
}
|
||||
|
||||
$link = str_ireplace('/'.$this->Lang .'/', '/'. $lang .'/', $_SERVER['REQUEST_URI']);
|
||||
|
||||
$output->push(new ArrayData(array(
|
||||
'Title' => $lang,
|
||||
'Link' => $link
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $modules;
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a list of modules (folder which has a _config) which have no /docs/ folder
|
||||
* Get all the versions loaded into the module. If the project is only displaying from
|
||||
* the filesystem then they are loaded under the 'Current' namespace.
|
||||
*
|
||||
* @todo Only show 'core' versions (2.3, 2.4) versions of the modules are going
|
||||
* to spam this
|
||||
*
|
||||
* @param String $module name of module to limit it to eg sapphire
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function UndocumentedModules() {
|
||||
$modules = $this->getModules();
|
||||
$undocumented = array();
|
||||
function getVersions($module = false) {
|
||||
$versions = DocumentationService::get_registered_versions($module);
|
||||
$output = new DataObjectSet();
|
||||
|
||||
if($modules) {
|
||||
foreach($modules as $module) {
|
||||
if(!$this->moduleHasDocs($module)) $undocumented[] = $module;
|
||||
}
|
||||
foreach($versions as $key => $version) {
|
||||
// work out the link to this version of the documentation.
|
||||
//
|
||||
// @todo Keep the user on their given page rather than redirecting to module.
|
||||
// @todo Get links working
|
||||
$linkingMode = ($this->Version == $version) ? 'current' : 'link';
|
||||
|
||||
if(!$version) $version = 'Current';
|
||||
$major = (in_array($version, DocumentationService::get_major_versions())) ? true : false;
|
||||
|
||||
$output->push(new ArrayData(array(
|
||||
'Title' => $version,
|
||||
'Link' => $_SERVER['REQUEST_URI'],
|
||||
'LinkingMode' => $linkingMode,
|
||||
'MajorRelease' => $major
|
||||
)));
|
||||
}
|
||||
|
||||
return implode(', ', $undocumented);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to determine whether a module has documentation
|
||||
*
|
||||
* @param String - Module folder name
|
||||
* @return bool - Has docs folder
|
||||
*/
|
||||
function moduleHasDocs($module) {
|
||||
return is_dir(BASE_PATH .'/'. $module .'/docs/');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Work out if a module contains a readme
|
||||
*
|
||||
* @param String - Module to check
|
||||
* @return bool|String - of path
|
||||
*/
|
||||
private function readmeExists($module) {
|
||||
$children = scandir(BASE_PATH.'/'.$module);
|
||||
|
||||
$readmeOptions = self::$readme_files;
|
||||
|
||||
if($children) {
|
||||
foreach($children as $i => $file) {
|
||||
if(in_array(strtolower($file), $readmeOptions)) return $file;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a documentation page within a given module.
|
||||
*
|
||||
* @param String - Path to Module
|
||||
* @param String - Name of doc page
|
||||
*
|
||||
* @return String|false - File path
|
||||
*/
|
||||
private function findPage($path, $name) {
|
||||
|
||||
// open docs folder
|
||||
$handle = opendir($path);
|
||||
|
||||
if($handle) {
|
||||
while (false !== ($file = readdir($handle))) {
|
||||
$newpath = $path .'/'. $file;
|
||||
|
||||
if(!in_array($file, self::$ignored_files)) {
|
||||
|
||||
if(is_dir($newpath)) return $this->findPage($newpath, $name);
|
||||
|
||||
elseif(strtolower($this->formatStringForTitle($file)) == strtolower($name)) {
|
||||
return $newpath;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a nested tree for a given folder via recursion
|
||||
*
|
||||
* @param String - module to generate
|
||||
*/
|
||||
private function generateNestedTree($module) {
|
||||
$path = BASE_PATH . '/'. $module .'/docs/';
|
||||
|
||||
return (is_dir($path)) ? $this->recursivelyGenerateTree($path, $module) : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursive method to generate the tree
|
||||
*
|
||||
* @param String - folder to work through
|
||||
* @param String - module we're working through
|
||||
*/
|
||||
private function recursivelyGenerateTree($path, $module, $output = '') {
|
||||
$output .= "<ul class='tree'>";
|
||||
$handle = opendir($path);
|
||||
|
||||
if($handle) {
|
||||
while (false !== ($file = readdir($handle))) {
|
||||
if(!in_array($file, self::$ignored_files)) {
|
||||
$newPath = $path.'/'.$file;
|
||||
|
||||
// if the file is a dir nest the pages
|
||||
if(is_dir($newPath)) {
|
||||
|
||||
// if this has a number
|
||||
$output .= "<li class='folder'>". $this->formatStringForTitle($file) ."</li>";
|
||||
|
||||
$output = $this->recursivelyGenerateTree($newPath, $module, $output);
|
||||
|
||||
}
|
||||
else {
|
||||
$offset = (strpos($file,'-') > 0) ? strpos($file,'-') + 1 : 0;
|
||||
|
||||
$file = substr(str_ireplace('.md', '', $file), $offset);
|
||||
|
||||
$output .= "<li class='page'><a href='". Director::absoluteBaseURL() . 'dev/docs/' . $module .'/'. $file . "'>". $this->formatStringForTitle($file) ."</a></li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir($handle);
|
||||
$output .= "</ul>";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a file name and generate a 'nice' title for it.
|
||||
* Generate the module which are to be documented. It filters
|
||||
* the list based on the current head version. It displays the contents
|
||||
* from the index.md file on the page to use.
|
||||
*
|
||||
* example. 01-Getting-Started -> Getting Started
|
||||
*
|
||||
* @param String - raw title
|
||||
* @return String - nicely formatted one
|
||||
*/
|
||||
private function formatStringForTitle($title) {
|
||||
// remove numbers if used.
|
||||
if(substr($title, 2, 1) == '-') $title = substr($title, 3);
|
||||
* @return DataObject
|
||||
*/
|
||||
function getModules($version = false, $lang = false) {
|
||||
if(!$version) $version = $this->Version;
|
||||
if(!$lang) $lang = $this->Lang;
|
||||
|
||||
// change - to spaces
|
||||
$title = str_ireplace('-', ' ', $title);
|
||||
$modules = DocumentationService::get_registered_modules($version, $lang);
|
||||
$output = new DataObjectSet();
|
||||
|
||||
// remove extension
|
||||
$title = str_ireplace(array('.md', '.markdown'), '', $title);
|
||||
if($modules) {
|
||||
foreach($modules as $module) {
|
||||
// build the dataset. Load the $Content from an index.md
|
||||
$output->push(new ArrayData(array(
|
||||
'Title' => $module->getTitle(),
|
||||
'Code' => $module,
|
||||
'Content' => DocumentationParser::parse($module->getPath(), array('index'))
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
return $title;
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently accessed entity from the site.
|
||||
*
|
||||
* @return false|DocumentationEntity
|
||||
*/
|
||||
function getModule() {
|
||||
if($this->Remaining && is_array($this->Remaining)) {
|
||||
return DocumentationService::is_registered_module($this->Remaining[0], $this->Version, $this->Lang);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the related pages to this module and the children to those pages
|
||||
*
|
||||
* @todo this only handles 2 levels. Could make it recursive
|
||||
*
|
||||
* @return false|DataObjectSet
|
||||
*/
|
||||
function getModulePages() {
|
||||
if($module = $this->getModule()) {
|
||||
$pages = DocumentationParser::get_pages_from_folder($module->getPath());
|
||||
|
||||
if($pages) {
|
||||
foreach($pages as $page) {
|
||||
$linkParts = array($module->getModuleFolder());
|
||||
|
||||
// don't include the 'index in the url
|
||||
if($page->Title != "Index") $linkParts[] = $page->Filename;
|
||||
|
||||
$page->Link = $this->Link($linkParts);
|
||||
|
||||
$page->LinkingMode = 'link';
|
||||
$page->Children = false;
|
||||
|
||||
if(isset($this->Remaining[1])) {
|
||||
if(strtolower($this->Remaining[1]) == $page->Filename) {
|
||||
$page->LinkingMode = 'current';
|
||||
|
||||
if(is_dir($page->Path)) {
|
||||
$children = DocumentationParser::get_pages_from_folder($page->Path);
|
||||
$segments = array($module->getModuleFolder(), $this->Remaining[1]);
|
||||
|
||||
foreach($children as $child) {
|
||||
$child->Link = $this->Link(array_merge($segments, array($child->Filename)));
|
||||
}
|
||||
|
||||
$page->Children = $children;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Return the content for the page. If its an actual documentation page then
|
||||
* display the content from the page, otherwise display the contents from
|
||||
* the index.md file if its a folder
|
||||
*
|
||||
* @return HTMLText
|
||||
*/
|
||||
function getContent() {
|
||||
if($module = $this->getModule()) {
|
||||
// name of the module. Throw it away since we already have the module path.
|
||||
$filepath = $this->Remaining;
|
||||
array_shift($filepath);
|
||||
|
||||
return DocumentationParser::parse($module->getPath(), $filepath);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a list of breadcrumbs for the user. Based off the remaining params
|
||||
* in the url
|
||||
*
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
function getBreadcrumbs() {
|
||||
$pages = $this->Remaining;
|
||||
|
||||
$output = new DataObjectSet();
|
||||
$output->push(new ArrayData(array(
|
||||
'Title' => ($this->Version) ? $this->Version : _t('DocumentationViewer.DOCUMENTATION', 'Documentation'),
|
||||
'Link' => $this->Link()
|
||||
)));
|
||||
|
||||
if($pages) {
|
||||
$path = array();
|
||||
|
||||
foreach($pages as $page => $title) {
|
||||
if($title) {
|
||||
$path[] = $title;
|
||||
|
||||
$output->push(new ArrayData(array(
|
||||
'Title' => DocumentationParser::clean_page_name($title),
|
||||
'Link' => $this->Link($path)
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base link to this documentation location
|
||||
*
|
||||
* @todo Make this work on non /dev/
|
||||
* @return String
|
||||
*/
|
||||
public function Link($path = false) {
|
||||
$base = Director::absoluteBaseURL();
|
||||
|
||||
// @todo
|
||||
$loc = 'dev/docs/';
|
||||
|
||||
$version = ($this->Version) ? $this->Version . '/' : false;
|
||||
$lang = ($this->Lang) ? $this->Lang .'/' : false;
|
||||
|
||||
$action = '';
|
||||
if(is_string($path)) $action = $path . '/';
|
||||
|
||||
if(is_array($path)) {
|
||||
foreach($path as $key => $value) {
|
||||
if($value) {
|
||||
$action .= $value .'/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $base . $loc . $version . $lang . $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the language dropdown.
|
||||
*
|
||||
* @todo do this on a page by page rather than global
|
||||
*
|
||||
* @return Form
|
||||
*/
|
||||
function LanguageForm() {
|
||||
if($module = $this->getModule()) {
|
||||
$langs = DocumentationService::get_registered_languages($module->getModuleFolder());
|
||||
}
|
||||
else {
|
||||
$langs = DocumentationService::get_registered_languages();
|
||||
}
|
||||
|
||||
$fields = new FieldSet(
|
||||
$dropdown = new DropdownField(
|
||||
'LangCode',
|
||||
_t('DocumentationViewer.LANGUAGE', 'Language'),
|
||||
$langs,
|
||||
$this->Lang
|
||||
)
|
||||
);
|
||||
|
||||
$actions = new FieldSet(
|
||||
new FormAction('doLanguageForm', _t('DocumentationViewer.CHANGE', 'Change'))
|
||||
);
|
||||
|
||||
$dropdown->setDisabled(true);
|
||||
|
||||
return new Form($this, 'LanguageForm', $fields, $actions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the language change
|
||||
*
|
||||
*/
|
||||
function doLanguageForm($data, $form) {
|
||||
$this->Lang = (isset($data['LangCode'])) ? $data['LangCode'] : 'en';
|
||||
|
||||
return $this->redirect($this->Link());
|
||||
}
|
||||
}
|
@ -1,83 +1,84 @@
|
||||
/**
|
||||
* Documentation Viewer Styles.
|
||||
*
|
||||
* @author Will Rossiter <will@silverstripe.com>
|
||||
*/
|
||||
|
||||
/* Reset */
|
||||
body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{ margin:0;padding: 0;}
|
||||
|
||||
/* Core */
|
||||
html { background: #f4f4f4;}
|
||||
body { font: 14px/1.1 Arial,sans-serif; color: #444; }
|
||||
|
||||
a { color: #1389ce; text-decoration: none; }
|
||||
a:hover { text-decoration: underline;}
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
a:hover,
|
||||
a:focus { text-decoration: underline;}
|
||||
|
||||
p { font-size: 14px; line-height: 22px; margin-bottom: 22px; }
|
||||
|
||||
ul { margin: 8px 16px 20px 20px; }
|
||||
li { font-size: 12px; line-height: 18px; margin-bottom: 8px;}
|
||||
ul { margin: 11px 0 22px 20px; }
|
||||
li { font-size: 12px; line-height: 13px; margin-bottom: 8px;}
|
||||
|
||||
|
||||
h1 { font-size: 30px; margin-bottom: 18px; color: #111; }
|
||||
h2 { font-size: 24px; margin-bottom: 16px; color: #111; }
|
||||
h3 { font-size: 18px; margin-bottom: 16px; color: #111; }
|
||||
h4 { font-size: 16px; margin-bottom: 6px; line-height: 16px;}
|
||||
h5 { font-size: 14px; line-height: 18px; margin-bottom: 6px;}
|
||||
h1 { font-size: 33px; line-height: 33px; margin-bottom: 22px; color: #111; letter-spacing: -1px;}
|
||||
h2 { font-size: 24px; line-height: 33px; margin-bottom: 11px; color: #111; }
|
||||
h3 { font-size: 18px; line-height: 22px; margin-bottom: 11px; color: #111; }
|
||||
h4 { font-size: 16px; margin-bottom: 11px; line-height: 22px;}
|
||||
h5 { font-size: 14px; line-height: 22px; margin-bottom: 11px;}
|
||||
|
||||
pre {
|
||||
margin-bottom: 18px;
|
||||
margin-bottom: 22px;
|
||||
font-family:'Bitstream Vera Sans Mono',Monaco, 'Courier New', monospace;
|
||||
border-left: 4px solid #eee;
|
||||
background: #f4f4f4;
|
||||
padding: 12px;
|
||||
padding: 11px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
#container { width: 960px; margin: 20px auto; padding: 20px; background: #fff; overflow: hidden; }
|
||||
/* Forms */
|
||||
fieldset { border: none; }
|
||||
|
||||
/* Container */
|
||||
#container { width: 960px; margin: 44px auto 22px auto; padding: 22px 30px; background: #fff; overflow: hidden;
|
||||
-webkit-box-shadow: 0 0 20px #ccc; -moz-box-shadow: 0 0 20px #ccc;}
|
||||
|
||||
#header { border-bottom: 3px solid #535360; padding-top: 10px; margin-bottom: 30px; }
|
||||
#header h1 { margin-bottom: 9px;}
|
||||
#header h1 a { text-decoration: none; font-size: 30px; color: #333; letter-spacing: -1px;}
|
||||
/* Header */
|
||||
#header { padding: 11px 0 0 0; margin-bottom: 22px; }
|
||||
#header h1 { margin-bottom: 0px; line-height: 33px;}
|
||||
#header h1 a { text-decoration: none; font-size: 30px; color: #121929; letter-spacing: -1px;}
|
||||
|
||||
#header .breadcrumbs { font-size: 12px; }
|
||||
#left-column {
|
||||
width: 640px;
|
||||
float: left;
|
||||
}
|
||||
#header #breadcrumbs p { font-size: 11px; margin: 0 0 10px 0; color: #798D85;}
|
||||
#header #breadcrumbs p a { color: #798D85;}
|
||||
|
||||
/* Language Bar */
|
||||
#language { position: absolute; top: 12px; left: 50%; margin-left: -480px; width: 960px; }
|
||||
#language label { float: left; width: 830px; line-height: 19px; text-align: right; font-size: 11px; color: #999;}
|
||||
#language select { float: right; width: 120px;}
|
||||
#language input.action { float: right; margin-top: 4px;}
|
||||
|
||||
/* Footer */
|
||||
#footer { width: 960px; margin: 22px auto; }
|
||||
#footer p { font-size: 11px; line-height: 11px; color: #798D85;}
|
||||
#footer p a { color: #798D85;}
|
||||
|
||||
/* Content */
|
||||
#layout { }
|
||||
#content { }
|
||||
|
||||
/* Versions */
|
||||
#versions-nav { background: #121929; margin: 0 0 44px; padding: 10px 0 0 10px; overflow: hidden;}
|
||||
#versions-nav h2 { font-size: 11px; color: #fff; font-weight: normal; float: left; margin-right: 5px;}
|
||||
#versions-nav ul { margin: 0; padding: 0; float: left;}
|
||||
#versions-nav li { list-style: none; }
|
||||
#versions-nav li a { display: block; float: left; margin-left: 4px; padding: 11px; font-size: 14px;}
|
||||
#versions-nav li a.current { background: #fff;}
|
||||
#left-column { width: 640px; float: left; }
|
||||
|
||||
#right-column {
|
||||
width: 260px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
#home #left-column { width: 500px; }
|
||||
#home #right-column { width: 340px; }
|
||||
#home .box {
|
||||
margin: 0 12px 12px 0px;
|
||||
border: 1px solid #d8d8d8;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
}
|
||||
#home .box h2 {
|
||||
background: #535360;
|
||||
border: 1px solid #535360;
|
||||
-moz-border-top-radius: 4px;
|
||||
-webkit-border-top-left-radius: 4px;
|
||||
-webkit-border-top-right-radius: 4px;
|
||||
padding: 6px 8px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
}
|
||||
#home .box h2 a {
|
||||
background: url(../images/readme.png) no-repeat right center;
|
||||
padding: 2px 20px 0 0;
|
||||
font-size: 11px;
|
||||
color: #fff;
|
||||
display: block;
|
||||
float: right;
|
||||
}
|
||||
#right-column .box {
|
||||
margin: 0 12px 12px 0;
|
||||
}
|
||||
@ -103,19 +104,27 @@ pre {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.module { margin: 10px -; }
|
||||
|
||||
/**
|
||||
* TOC
|
||||
*/
|
||||
|
||||
ul#toc {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
.sidebar-box {
|
||||
margin: 0 0 11px 0;
|
||||
padding: 11px 15px;
|
||||
background: #f4f4f4;
|
||||
width: 220px;
|
||||
}
|
||||
ul#toc h4 { font-size: 12px; margin-bottom: 0px;}
|
||||
ul#toc li { list-style: none; margin: 0 0 4px 0; }
|
||||
ul#toc li.h1 { margin-top: 10px; font-weight: bold;}
|
||||
ul#toc li.h2 { margin: 0 0 0px 10px; font-size: 11px;}
|
||||
ul#toc li.h3 { margin-left: 20px; font-size: 10px; }
|
||||
ul#toc li.h4 { margin-right: 30px; font-size: 10px; }
|
||||
.sidebar-box ul { margin: 0; padding: 0;}
|
||||
.sidebar-box h4 { font-size: 12px; margin-bottom: 11px;}
|
||||
.sidebar-box ul li { list-style: none; }
|
||||
.sidebar-box ul li .current { font-weight: bold;}
|
||||
.sidebar-box ul li.h1 { margin-top: 11px; font-weight: bold;}
|
||||
.sidebar-box ul li.h2,
|
||||
.sidebar-box ul ul { margin-top: 8px;}
|
||||
.sidebar-box ul li li { font-size: 11px; margin-left: 10px;}
|
||||
.sidebar-box ul li.h3,
|
||||
.sidebar-box ul li li li { margin-left: 20px; font-size: 10px; margin-left: 20px;}
|
||||
.sidebar-box ul li.h4,
|
||||
.sidebar-box ul li li li li { margin-right: 30px; font-size: 10px; margin-left: 20px; }
|
@ -1,28 +0,0 @@
|
||||
# Writing Documentation #
|
||||
|
||||
|
||||
Your documentation needs to go in the specific modules doc folder which it refers mostly too. For example if you want to document
|
||||
a feature of your custom module 'MyModule' you need to create markdown files in mymodule/doc/.
|
||||
|
||||
The files have to end with the __.md__ extension. The documentation viewer will automatically replace hyphens (-) with spaces (since you cannot
|
||||
have spaces easily in some file systems).
|
||||
|
||||
## Syntax ##
|
||||
This uses a customized markdown extra parser. To view the syntax for page formatting check out [http://daringfireball.net/projects/markdown/syntax][Daring Fireball]
|
||||
|
||||
|
||||
## Creating Hierarchy ##
|
||||
|
||||
The document viewer supports folder structure. There is no limit on depth or number of sub categories you can create.
|
||||
|
||||
## Customizing Page Order ##
|
||||
|
||||
Sometimes you will have pages which you want at the top of the documentation viewer summary. Pages like Getting-Started will come after Advanced-Usage
|
||||
due to the default alphabetical ordering.
|
||||
|
||||
To handle this you can use a number prefix for example __01-My-First-Folder__ which would be the first folder in the list.
|
||||
|
||||
DocumentationViewer will remove the __01-__ from the name as well so you don't need to worry about labels for your folders with numbers. It will be
|
||||
outputted in the front end as __My First Folder__
|
||||
|
||||
|
22
docs/en/Configuration-Options.md
Normal file
22
docs/en/Configuration-Options.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Helpful Configuration Options
|
||||
|
||||
DocumentationService::set_ignored_files(array());
|
||||
|
||||
If you want to ignore (hide) certain file types from being included.
|
||||
|
||||
DocumentationService::set_automatic_registration(false);
|
||||
|
||||
By default the documentation system will parse all the directories in your project and
|
||||
include the documentation. If you want to only specify a few folders you can disable it
|
||||
with the above.
|
||||
|
||||
DocumentationService::register($module, $path, $version = 'current', $lang = 'en', $major_release = false)
|
||||
|
||||
Registers a module to be included in the system (if automatic registration is off or you need
|
||||
to load a module outside a documentation path).
|
||||
|
||||
DocumentationService::unregister($module, $version = false, $lang = false)
|
||||
|
||||
Unregister a module (removes from documentation list). You can specify the module, the version
|
||||
and the lang. If no version is specified then all folders of that lang are removed. If you do
|
||||
not specify a version or lang the whole module will be removed from the documentation.
|
32
docs/en/Writing-Documentation.md
Normal file
32
docs/en/Writing-Documentation.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Writing Documentation #
|
||||
|
||||
Your documentation needs to go in the specific modules docs folder which it refers mostly too. For example if you want to document
|
||||
a feature of your custom module 'MyModule' you need to create markdown files in mymodule/docs/.
|
||||
|
||||
The files have to end with the __.md__ extension. The documentation viewer will automatically replace hyphens (-) with spaces (since you cannot
|
||||
have spaces web / file systems).
|
||||
|
||||
Also docs folder should be localized. Even if you do not plan on using multiple languages you should at least write your documentation
|
||||
in a 'en' subfolder
|
||||
|
||||
/module/docs/en/
|
||||
|
||||
## Syntax ##
|
||||
|
||||
This uses a customized markdown extra parser. To view the syntax for page formatting check out [http://daringfireball.net/projects/markdown/syntax][Daring Fireball]
|
||||
|
||||
## Creating Hierarchy ##
|
||||
|
||||
The document viewer supports folder structure. There is a 9 folder limit on depth / number of sub categories you can create.
|
||||
Each level deep it will generate the nested urls.
|
||||
|
||||
## Directory Listing ##
|
||||
|
||||
Each folder you create should also contain a __index.md__ file (see sapphiredocs/doc/en/index.md) which contains an overview of the
|
||||
module and related links.
|
||||
|
||||
## Table of Contents ##
|
||||
|
||||
The table of contents on each module page is generated
|
||||
|
||||
|
11
docs/en/index.md
Normal file
11
docs/en/index.md
Normal file
@ -0,0 +1,11 @@
|
||||
### Sapphire Documentation Module
|
||||
|
||||
This module has been developed to read and display content from markdown files in webbrowser. It is an easy
|
||||
way to bundle end user documentation within a SilverStripe installation.
|
||||
|
||||
See <a href="dev/docs/en/sapphiredocs/writing-documentation">Writing Documentation</a> for more information on how to write markdown files which
|
||||
are available here.
|
||||
|
||||
To include your docs file here create a __docs/en/index.md__ file. You can also include custom paths and versions. To configure the documentation system the configuration information is available on the <a href="dev/docs/en/sapphiredocs/configuration-options">Configurations</a>
|
||||
page.
|
||||
|
@ -21,5 +21,16 @@
|
||||
|
||||
$('#table-of-contents').prepend(toc);
|
||||
}
|
||||
|
||||
/** ---------------------------------------------
|
||||
* LANGAUGE SELECTER
|
||||
*
|
||||
* Hide the change button and do it onclick
|
||||
*/
|
||||
$("#Form_LanguageForm .Actions").hide();
|
||||
|
||||
$("#Form_LanguageForm select").change(function() {
|
||||
$("#Form_LanguageForm").submit();
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
|
@ -15,12 +15,39 @@
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header">
|
||||
<h1><a href="dev/docs/">SilverStripe Documentation</a></h1>
|
||||
|
||||
$Breadcrumbs
|
||||
</div>
|
||||
<h1><a href="$Link"><% _t('SILVERSTRIPEDOCUMENTATION', 'SilverStripe Documentation') %></a></h1>
|
||||
|
||||
$Layout
|
||||
<div id="language">
|
||||
$LanguageForm
|
||||
</div>
|
||||
|
||||
<div id="breadcrumbs">
|
||||
<% include DocBreadcrumbs %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="layout">
|
||||
<div id="versions-nav">
|
||||
<h2>Versions:</h2>
|
||||
|
||||
<ul>
|
||||
<% control Versions %>
|
||||
<% if MajorRelease %>
|
||||
<li class="major-release"><a href="$Link" class="$LinkingMode">$Title</a></li>
|
||||
<% else %>
|
||||
<li class="module-only"><a href="$Link" class="$LinkingMode">$Title</a></li>
|
||||
<% end_if %>
|
||||
<% end_control %>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="content">
|
||||
$Layout
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer">
|
||||
<p>Documentation powered by <a href="http://www.silverstripe.org">SilverStripe</a>. Found a typo? <a href="http://github.com/chillu/silverstripe-doc-restructuring">Contribute to the Documentation Project</a>.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
5
templates/Includes/DocBreadcrumbs.ss
Normal file
5
templates/Includes/DocBreadcrumbs.ss
Normal file
@ -0,0 +1,5 @@
|
||||
<p>
|
||||
<% control Breadcrumbs %>
|
||||
<a href="$Link">$Title</a> <% if Last %><% else %>›<% end_if %>
|
||||
<% end_control %>
|
||||
</p>
|
18
templates/Includes/DocInThisModule.ss
Normal file
18
templates/Includes/DocInThisModule.ss
Normal file
@ -0,0 +1,18 @@
|
||||
<div id="in-this-module" class="sidebar-box">
|
||||
<h4>In this module</h4>
|
||||
|
||||
<ul>
|
||||
<% control ModulePages %>
|
||||
<li>
|
||||
<a href="$Link" class="$LinkingMode">$Title</a>
|
||||
<% if Children %>
|
||||
<ul>
|
||||
<% control Children %>
|
||||
<li><a href="$Link" class="$LinkingMode">$Title</a></li>
|
||||
<% end_control %>
|
||||
</ul>
|
||||
<% end_if %>
|
||||
</li>
|
||||
<% end_control %>
|
||||
</ul>
|
||||
</div>
|
1
templates/Includes/DocTableOfContents.ss
Normal file
1
templates/Includes/DocTableOfContents.ss
Normal file
@ -0,0 +1 @@
|
||||
<div id="table-of-contents" class="sidebar-box"></div>
|
@ -1,11 +1,14 @@
|
||||
<div id="left-column">
|
||||
<% if Content %>
|
||||
$Content
|
||||
<% else %>
|
||||
<p>Woops no documentation for this page</p>
|
||||
<% end_if %>
|
||||
</div>
|
||||
<div id="documentation-page">
|
||||
<div id="left-column">
|
||||
<% if Content %>
|
||||
$Content
|
||||
<% else %>
|
||||
<p>Woops page not found</p>
|
||||
<% end_if %>
|
||||
</div>
|
||||
|
||||
<div id="right-column">
|
||||
<div id="table-of-contents"></div>
|
||||
<div id="right-column">
|
||||
<% include DocTableOfContents %>
|
||||
<% include DocInThisModule %>
|
||||
</div>
|
||||
</div>
|
15
templates/Layout/DocumentationViewer_folder.ss
Normal file
15
templates/Layout/DocumentationViewer_folder.ss
Normal file
@ -0,0 +1,15 @@
|
||||
<div id="module-home">
|
||||
|
||||
<div id="left-column">
|
||||
<% if Content %>
|
||||
$Content
|
||||
<% else %>
|
||||
frs
|
||||
<h2>$Title</h2>
|
||||
<% end_if %>
|
||||
</div>
|
||||
|
||||
<div id="right-column">
|
||||
<% include DocInThisModule %>
|
||||
</div>
|
||||
</div>
|
10
templates/Layout/DocumentationViewer_home.ss
Executable file
10
templates/Layout/DocumentationViewer_home.ss
Executable file
@ -0,0 +1,10 @@
|
||||
<div id="home">
|
||||
<% control Modules %>
|
||||
<% if Content %>
|
||||
<div class="module">
|
||||
|
||||
$Content
|
||||
</div>
|
||||
<% end_if %>
|
||||
<% end_control %>
|
||||
</div>
|
@ -1,29 +0,0 @@
|
||||
<div id="home">
|
||||
<% control DocumentedModules %>
|
||||
<% if First %>
|
||||
<div id="left-column">
|
||||
<div class="box">
|
||||
<h2>$Title $Readme</h2>
|
||||
|
||||
$Content
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="right-column">
|
||||
<% else %>
|
||||
<div class="box">
|
||||
<h2>$Title $Readme</h2>
|
||||
|
||||
$Content
|
||||
</div>
|
||||
<% end_if %>
|
||||
|
||||
<% end_control %>
|
||||
</div>
|
||||
|
||||
<% if UndocumentedModules %>
|
||||
<div class='undocumented-modules'>
|
||||
<p>Undocumented Modules: $UndocumentedModules</p>
|
||||
</div>
|
||||
<% end_if %>
|
||||
</div>
|
11
tests/DocumentTests.yml
Normal file
11
tests/DocumentTests.yml
Normal file
@ -0,0 +1,11 @@
|
||||
Permission:
|
||||
admin:
|
||||
Code: ADMIN
|
||||
Group:
|
||||
admins:
|
||||
Code: admins
|
||||
Permissions: =>Permission.admin
|
||||
Member:
|
||||
admin:
|
||||
Email: admin@test.com
|
||||
Groups: =>Group.admins
|
52
tests/DocumentationViewerTests.php
Normal file
52
tests/DocumentationViewerTests.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Some of these tests are simply checking that pages load. They should not assume
|
||||
* somethings working.
|
||||
*
|
||||
* @package sapphiredocs
|
||||
*/
|
||||
|
||||
class DocumentationViewerTests extends FunctionalTest {
|
||||
|
||||
static $fixture_file = 'sapphiredocs/tests/DocumentTests.yml';
|
||||
|
||||
function testCleanPageNames() {
|
||||
$names = array(
|
||||
'documentation-Page',
|
||||
'documentation_Page',
|
||||
'documentation.md',
|
||||
'documentation.pdf',
|
||||
'documentation.file.txt',
|
||||
'.hidden'
|
||||
);
|
||||
|
||||
$should = array(
|
||||
'Documentation Page',
|
||||
'Documentation Page',
|
||||
'Documentation',
|
||||
'Documentation',
|
||||
'Documentation',
|
||||
'.hidden' // don't display something without a title
|
||||
);
|
||||
|
||||
foreach($names as $key => $value) {
|
||||
$this->assertEquals(DocumentationParser::clean_page_name($value), $should[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
function testDocumentationEntityAccessing() {
|
||||
$entity = new DocumentationEntity('docs', '1.0', '../sapphiredocs/tests/docs/', 'My Test');
|
||||
|
||||
$this->assertEquals($entity->getTitle(), 'My Test');
|
||||
$this->assertEquals($entity->getVersions(), array('1.0'));
|
||||
$this->assertEquals($entity->getLanguages(), array('en', 'de'));
|
||||
$this->assertEquals($entity->getModuleFolder(), 'docs');
|
||||
|
||||
$this->assertTrue($entity->hasVersion('1.0'));
|
||||
$this->assertFalse($entity->hasVersion('2.0'));
|
||||
|
||||
$this->assertTrue($entity->hasLanguage('en'));
|
||||
$this->assertFalse($entity->hasLanguage('fr'));
|
||||
}
|
||||
}
|
5
tests/docs-2/en/index.md
Normal file
5
tests/docs-2/en/index.md
Normal file
@ -0,0 +1,5 @@
|
||||
## english test
|
||||
|
||||
index
|
||||
|
||||
2.0
|
5
tests/docs/de/index.md
Normal file
5
tests/docs/de/index.md
Normal file
@ -0,0 +1,5 @@
|
||||
## german test
|
||||
|
||||
index
|
||||
|
||||
1.0
|
5
tests/docs/de/test.md
Normal file
5
tests/docs/de/test.md
Normal file
@ -0,0 +1,5 @@
|
||||
## german test
|
||||
|
||||
test
|
||||
|
||||
1.0
|
5
tests/docs/en/index.md
Normal file
5
tests/docs/en/index.md
Normal file
@ -0,0 +1,5 @@
|
||||
## english test
|
||||
|
||||
index
|
||||
|
||||
1.0
|
5
tests/docs/en/test.md
Normal file
5
tests/docs/en/test.md
Normal file
@ -0,0 +1,5 @@
|
||||
## english test
|
||||
|
||||
test
|
||||
|
||||
1.0
|
Loading…
Reference in New Issue
Block a user