Commit 3694fcf8 authored by Sean Madsen's avatar Sean Madsen

Merge branch 'master' into book-home-pages

Conflicts:
	src/AppBundle/Resources/views/Read/book_list.html.twig
	src/AppBundle/Resources/views/Read/home.html.twig
parents 14a8f5a7 bc33e12c
# Learn more about services, parameters and containers at # Learn more about services, parameters and containers at
# http://symfony.com/doc/current/book/service_container.html # http://symfony.com/doc/current/book/service_container.html
parameters: parameters:
# parameter_name: value books_dir: %kernel.root_dir%/../books
publish_path_root: %kernel.root_dir%/../web
services: services:
library: library:
class: AppBundle\Model\Library class: AppBundle\Model\Library
arguments: arguments:
- %kernel.root_dir%/../books - %books_dir%
github.hook.processor: github.hook.processor:
class: AppBundle\Utils\GitHubHookProcessor class: AppBundle\Utils\GitHubHookProcessor
mkdocs:
class: AppBundle\Utils\MkDocs
arguments:
- '@filesystem'
- '@file_locator'
publisher: publisher:
class: AppBundle\Utils\Publisher class: AppBundle\Utils\Publisher
arguments: arguments:
...@@ -21,7 +28,8 @@ services: ...@@ -21,7 +28,8 @@ services:
- '@filesystem' - '@filesystem'
- '@library' - '@library'
- %publisher_repos_dir% - %publisher_repos_dir%
- %kernel.root_dir%/../web - %publish_path_root%
- '@mkdocs'
publish.logger: publish.logger:
class: Monolog\Logger class: Monolog\Logger
......
name: Developer guide name: Developer guide
weight: 10 weight: 10
description: For CiviCRM developers description: For CiviCRM developers
category: Core
langs: langs:
en: en:
repo: 'https://github.com/civicrm/civicrm-dev-docs' repo: 'https://github.com/civicrm/civicrm-dev-docs'
......
name: User guide name: User guide
weight: -100 weight: -100
description: Aimed at day to day users of CiviCRM. description: Aimed at day to day users of CiviCRM.
category: Core
langs: langs:
en: en:
repo: 'https://github.com/civicrm/civicrm-user-guide' repo: 'https://github.com/civicrm/civicrm-user-guide'
......
...@@ -11,9 +11,14 @@ class ReadController extends Controller { ...@@ -11,9 +11,14 @@ class ReadController extends Controller {
* @Route("/") * @Route("/")
*/ */
public function HomeAction() { public function HomeAction() {
/** @var \AppBundle\Model\Library $library */
$library = $this->get('library');
return $this->render( return $this->render(
'AppBundle:Read:home.html.twig', 'AppBundle:Read:home.html.twig', array(
array('library' => $this->get('library')) 'core_books' => $library->getBooksByCategory('Core'),
'extensions_books' => $library->getBooksByCategory('Extensions'),
)
); );
} }
......
...@@ -33,6 +33,12 @@ class Book { ...@@ -33,6 +33,12 @@ class Book {
*/ */
public $weight; public $weight;
/**
*
* @var string (e.g. "Core", "Extensions") Should be in sentence case
*/
public $category;
/** /**
* Creates a book based on a yaml conf file * Creates a book based on a yaml conf file
* *
...@@ -49,6 +55,8 @@ class Book { ...@@ -49,6 +55,8 @@ class Book {
foreach ($yaml['langs'] as $code => $languageData) { foreach ($yaml['langs'] as $code => $languageData) {
$this->languages[] = new Language($code, $languageData); $this->languages[] = new Language($code, $languageData);
} }
$category = isset($yaml['category']) ? $yaml['category'] : "Extensions";
$this->category = ucwords($category);
} }
/** /**
......
...@@ -99,6 +99,23 @@ class Library { ...@@ -99,6 +99,23 @@ class Library {
return $chosen; return $chosen;
} }
/**
* Gives an array of book objects which match a given category
*
* @param string $category
*
* @return array of Book objects
*/
public function getBooksByCategory($category) {
$books = array();
foreach ($this->books as $book) {
if ($book->category == $category) {
$books[] = $book;
}
}
return $books;
}
/** /**
* See which books/languages are using a given repository. * See which books/languages are using a given repository.
* *
......
$( document ).ready(function() {
$("dt").wrapInner("<span class='inner'></span>");
});
/* make header non-fixed */
.md-container,
.md-main__inner {
padding-top: 0;
}
.md-header {
position: static;
height: auto;
}
.md-sidebar[data-md-state=lock] {
top: 0;
}
/* Header tweaks */
header a.md-source:hover {
text-decoration: none;
}
/* Logo */
header.md-header .civi-logo {
float: left;
padding: 5px;
}
header.md-header .civi-logo a {
margin: 0;
padding: 0;
}
/* Nav for CiviCRM / Documentation */
header.md-header .civi-header {
padding-top: 0.8rem;
padding-left: 2rem;
font-size: 1.4rem;
font-style: italic;
}
header.md-header .civi-header span {
color: white;
}
header.md-header .edition, header.md-header .other-editions {
color: #A5CCDC !important;
}
header.md-header .other-editions {
padding-left: 2rem;
font-style: normal;
}
/* Custom colors */
button[data-md-color-primary=indigo]{background-color:#3387ac}
[data-md-color-primary=indigo] .md-typeset a{color:#3387ac}
[data-md-color-primary=indigo] .md-header{background-color:#3387ac}
[data-md-color-primary=indigo] .md-nav__item--active > .md-nav__link,[data-md-color-primary=indigo] .md-nav__link:active{color:#3387ac}
button[data-md-color-accent=green]{background-color:#81c459}
[data-md-color-accent=green] .md-typeset a:active,[data-md-color-accent=green] .md-typeset a:hover{color:#81c459}
[data-md-color-accent=green] .md-typeset .codehilite::-webkit-scrollbar-thumb:hover,[data-md-color-accent=green] .md-typeset pre::-webkit-scrollbar-thumb:hover{background-color:#81c459}
[data-md-color-accent=green] .md-nav__link:hover,[data-md-color-accent=green] .md-typeset .footnote li:hover .footnote-backref:hover,[data-md-color-accent=green] .md-typeset .footnote li:target .footnote-backref,[data-md-color-accent=green] .md-typeset [id] .headerlink:focus,[data-md-color-accent=green] .md-typeset [id]:hover .headerlink:hover,[data-md-color-accent=green] .md-typeset [id]:target .headerlink{color:#81c459}
[data-md-color-accent=green] .md-search__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#81c459}
[data-md-color-accent=green] .md-search-result__link:hover{background-color:rgba(129,196,89,.1)}
[data-md-color-accent=green] .md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover{background-color:#81c459}
@media only screen and (max-width:59.9375em){[data-md-color-primary=indigo] .md-nav__source{background-color:rgba(51,135,172,.9675)}}
@media only screen and (max-width:76.1875em){html [data-md-color-primary=indigo] .md-nav--primary .md-nav__title--site{background-color:#3387ac}}
@media only screen and (min-width:60em){[data-md-color-primary=indigo] .md-nav--secondary{border-left:.4rem solid #3387ac}}
/* definition lists */
dt .inner {
border-bottom: solid 5px #B6D8E6;
}
/* hyperlinks */
a:hover {text-decoration: underline; }
{% extends "base.html" %}
{% block htmltitle %}
{% if page.title %}
<title>{{ page.title }} - {{ config.site_name }} - CiviCRM documentation</title>
{% elif config.site_description %}
<title>{{ config.site_name }} - {{ config.site_description }} - CiviCRM documentation</title>
{% else %}
<title>{{ config.site_name }} - CiviCRM documentation</title>
{% endif %}
{% endblock %}
{% block extrahead %}
<link rel="stylesheet" href="{{ base_url }}/assets/stylesheets/extra.css">
<script src="/static/js/jquery-3.1.1.min.js"></script>
<script src="{{ base_url }}/assets/javascripts/extra.js"></script>
{% endblock %}
{% import "partials/language.html" as lang %}
<footer class="md-footer">
{% if page.previous_page or page.next_page %}
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
{% if page.previous_page %}
<a href="{{ page.previous_page.url }}" title="{{ page.previous_page.title }}" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
{{ lang.t('footer.previous') }}
</span>
{{ page.previous_page.title }}
</span>
</div>
</a>
{% endif %}
{% if page.next_page %}
<a href="{{ page.next_page.url }}" title="{{ page.next_page.title }}" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
{{ lang.t('footer.next') }}
</span>
{{ page.next_page.title }}
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
{% endif %}
</nav>
</div>
{% endif %}
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
{% if config.copyright %}
<div class="md-footer-copyright__highlight">
{{ config.copyright }}
</div>
{% endif %}
<a href="https://civicrm.org">CiviCRM</a>
&raquo;
<a href="https://docs.civicrm.org">Documentation</a>
</div>
{% block social %}
{% include "partials/social.html" %}
{% endblock %}
</div>
</div>
</footer>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex civi-logo">
<div class="md-flex__cell md-flex__cell--shrink">
<a class="md-logo md-header-nav__button" href="https://civicrm.org" title="CiviCRM home">
<img src="/static/images/logo.svg" height="60" alt="logo" />
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="drawer"></label>
</div>
</div>
<div class="md-flex civi-header">
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-header-nav__parent">
<a href="https://civicrm.org">CiviCRM</a>
</span>
<span class="md-header-nav__parent">
<a href="/">Documentation</a>
</span>
<a href="{{ base_url }}">
{{ config.site_name }}
<span class="edition">({{ config.extra.edition }})</span>
</a>
<a href="{{ config.extra.book_home }}" class="other-editions">
(Other editions)
</a>
</div>
</div>
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--stretch">
<span class="md-flex__ellipsis md-header-nav__title">
{% block site_name %}
{% if page %}
{% for parent in page.ancestors %}
<span class="md-header-nav__parent">
{{ parent.title }}
</span>
{% endfor %}
{% endif %}
{{ page.title | default(config.site_name, true) }}
{% endblock %}
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
{% block search_box %}
<label class="md-icon md-icon--search md-header-nav__button" for="search"></label>
{% include "partials/search.html" %}
{% endblock %}
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
{% if config.repo_url %}
{% include "partials/source.html" %}
{% endif %}
</div>
</div>
</div>
</nav>
</header>
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
<span class="other-editions"> <span class="other-editions">
<a href="/{{ book.slug }}">(Other editions)</a> <a href="/{{ book.slug }}">(Other editions)</a>
</span> </span>
</a>
</div> </div>
<div class="description"> <div class="description">
{{ book.description }} {{ book.description }}
......
...@@ -15,8 +15,12 @@ ...@@ -15,8 +15,12 @@
installation and upgrades</a> installation and upgrades</a>
is still on the wiki.</em></p> is still on the wiki.</em></p>
<h2>Books</h2> <h2>Core Books</h2>
{% include 'AppBundle:Read:book_list.html.twig' with {books: library.books} %} {% include 'AppBundle:Read:book_list.html.twig' with {books: core_books} %}
<h2>Extensions Books</h2>
{% include 'AppBundle:Read:book_list.html.twig' with {books: extensions_books} %}
{% endblock %} {% endblock %}
<?php
namespace AppBundle\Utils;
use Symfony\Component\Yaml\Parser;
use Symfony\Component\Yaml\Dumper;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Config\FileLocatorInterface as FileLocator;
use Symfony\Component\Process\Process;
class MkDocs {
/**
* @var Filesystem
*/
private $fs;
/**
* @var FileLocator
*/
private $fileLocator;
/**
* @var string The full filesystem path to the directory containing the
* markdown files
*/
private $sourcePath;
/**
* @var string $destinationPath The full filesystem path to the directory
* where we want the published content to go
*/
private $destinationPath;
/**
* @var string The full filesystem path to the directory which stores
* different possible theme customizations. Within this directory,
* separate directories should exist, per theme, for the
* customizations, named with the same name as the theme.
*/
private $themeCustomPathRoot;
/**
* @var string The full filesystem path to the directory
*/
private $themeCustomPath;
/**
* @var array A associative array with config values to put in the 'extra'
* setting when building the book
*/
private $extraConfig;
/**
* @var string The full filesystem location of the mkdocs.yml config file to
* use when building the book. This is the file as it's stored
* after adjustments we make to it.
*/
private $configFile;
/**
* @param Filesystem $fs
* @param FileLocator $fileLocator
*/
public function __construct(Filesystem $fs, FileLocator $fileLocator) {
$this->fs = $fs;
$this->fileLocator = $fileLocator;
$this->themeCustomPathRoot = $this->fileLocator->locate(
'@AppBundle/Resources/theme-customizations');
}
/**
* Reads the mkdocs.yml config file from the book source. Makes some
* customizations to it, and the write the file into the directory where
* we're going to publish the book. Why put it there? It doesn't need to be in
* the publish destination, but it seems like as good a place as any. It just
* needs to be stored somewhere so that mkdocs can read it while building the
* book.
*/
private function customizeConfig() {
// Read config in
$inFile = "{$this->sourcePath}/mkdocs.yml";
$parser = new Parser();
$config = $parser->parse(file_get_contents($inFile));
// If we have a theme-cumstomization directory which matches the theme used
// in the book, then use these theme customizations when building.
$theme = $config['theme'];
$this->themeCustomPath = "{$this->themeCustomPathRoot}/$theme";
if ($this->fs->exists($this->themeCustomPath)) {
$config['theme_dir'] = $this->themeCustomPath;
}
// Set extra config which was passed into build()
foreach ($this->extraConfig as $key => $val) {
$config['extra'][$key] = $val;
}
// Set up custom config for our Material theme extension
if ($theme == 'material') {
$config['extra']['palette']['primary'] = 'indigo';
$config['extra']['palette']['accent'] = 'green';
if (!isset($config['extra']['edition'])) {
$config['extra']['edition'] = "English / Latest";
}
if (!isset($config['extra']['book_home'])) {
$config['extra']['book_home'] = "/";
}
}
// Dump config out
$dumper = new Dumper();
$this->configFile = dirname($this->destinationPath) . "/"
. basename($this->destinationPath) . "-mkdocs.yml";
$this->fs->dumpFile($this->configFile, $dumper->dump($config, 4));
}
private function getOptions() {
// discard existing build files -- build site from scratch
$opts[] = "--clean";
// abort the build if any errors occur
$opts[] = "--strict";
// use our customized config file
$opts[] = "--config-file {$this->configFile}";
// this is where the finished site should go
$opts[] = "--site-dir {$this->destinationPath}";
return implode(" ", $opts);
}
/**
* Run MkDocs to build a book
*
* @param string $sourcePath The full filesystem path to the directory
* containing the markdown files
*
* @param string $destinationPath The full filesystem path to the directory
* where we want the published content to go
*
* @param array $extraConfig A associative array with config values to put in
* the 'extra' setting when building the book
*/
public function build($sourcePath, $destinationPath, $extraConfig = array()) {
$this->sourcePath = $sourcePath;
$this->destinationPath = $destinationPath;
$this->extraConfig = $extraConfig;
$this->customizeConfig();
$buildCommand = "mkdocs build " . $this->getOptions();
$mkdocs = new Process($buildCommand, $this->sourcePath);
$mkdocs->run();
if (!$mkdocs->isSuccessful()) {
throw new \Exception("MkDocs was unable to build the book. "
. "MkDocs command output: "
. $mkdocs->getErrorOutput());
}
}
}
...@@ -102,6 +102,11 @@ class Publisher { ...@@ -102,6 +102,11 @@ class Publisher {
*/ */
public $repoURL; public $repoURL;
/**
* @var \AppBundle\Utils\MkDocs
*/
private $mkDocs;
/** /**
* *
* @param RequestStack $requestStack * @param RequestStack $requestStack
...@@ -110,6 +115,7 @@ class Publisher { ...@@ -110,6 +115,7 @@ class Publisher {
* @param Library $library * @param Library $library
* @param string $reposPathRoot * @param string $reposPathRoot
* @param string $publishPathRoot * @param string $publishPathRoot
* @param \AppBundle\Utils\MkDocs $mkDocs
*/ */
public function __construct( public function __construct(
$requestStack, $requestStack,
...@@ -117,12 +123,14 @@ class Publisher { ...@@ -117,12 +123,14 @@ class Publisher {
$fs, $fs,
$library, $library,
$reposPathRoot, $reposPathRoot,
$publishPathRoot) { $publishPathRoot,
$mkDocs) {
$this->logger = $logger; $this->logger = $logger;
$this->fs = $fs; $this->fs = $fs;
$this->library = $library; $this->library = $library;
$this->repoPathRoot = realpath($reposPathRoot); $this->repoPathRoot = realpath($reposPathRoot);
$this->publishPathRoot = realpath($publishPathRoot); $this->publishPathRoot = realpath($publishPathRoot);
$this->mkDocs = $mkDocs;
if ($requestStack->getCurrentRequest()) { if ($requestStack->getCurrentRequest()) {
$this->publishURLBase $this->publishURLBase
= $requestStack->getCurrentRequest()->getUriForPath(''); = $requestStack->getCurrentRequest()->getUriForPath('');
...@@ -170,7 +178,7 @@ class Publisher { ...@@ -170,7 +178,7 @@ class Publisher {
try { try {
$this->book->validate(); $this->book->validate();
} }
catch (Exception $e) { catch (\Exception $e) {
$this->addMessage('CRITICAL', "The book settings for {$this->book->name}" $this->addMessage('CRITICAL', "The book settings for {$this->book->name}"
. "failed validation. Validation error is: " . $e->getMessage()); . "failed validation. Validation error is: " . $e->getMessage());
return FALSE; return FALSE;
...@@ -343,22 +351,16 @@ class Publisher { ...@@ -343,22 +351,16 @@ class Publisher {
* @return boolean TRUE if success * @return boolean TRUE if success
*/ */
private function build() { private function build() {
$buildCommand = "mkdocs build " $extraConfig['edition']
. "--clean --strict --site-dir {$this->publishPath}"; = "{$this->language->nativeName()} / {$this->version->name}";
$this->addMessage('INFO', "Running '{$buildCommand}'"); $extraConfig['book_home'] = "/{$this->book->slug}";
$mkdocs = new Process($buildCommand, $this->repoPath); try {
$mkdocs->run(); $this->mkDocs->build($this->repoPath, $this->publishPath, $extraConfig);
$mkdocsLogMessages = explode("\n", trim($mkdocs->getErrorOutput())); }
$this->addMessage('INFO', "mkdocs output: '{$mkdocs->getErrorOutput()}'"); catch (\Exception $e) {
$mkdocsErrors = FALSE;
foreach ($mkdocsLogMessages as $mkdocsLogMessage) {
if (substr($mkdocsLogMessage, 0, 4) != 'INFO') {
$mkdocsErrors = TRUE;
}
}
if ($mkdocsErrors) {
$this->addMessage('CRITICAL', $this->addMessage('CRITICAL',
"MkDocs build errors encountered. Book not published."); "Build errors encountered. Book not published. Build error message: "
. $e->getMessage());
return FALSE; return FALSE;
} }
$this->addMessage('INFO', "Book published successfully at " $this->addMessage('INFO', "Book published successfully at "
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment