diff --git a/resources/layouts/_default/page.html.twig b/resources/layouts/_default/page.html.twig
index f88b30d57..cc5ad475f 100644
--- a/resources/layouts/_default/page.html.twig
+++ b/resources/layouts/_default/page.html.twig
@@ -52,6 +52,22 @@
nav a:hover:not(main) {
text-decoration: underline;
}
+ nav.breadcrumb ol {
+ display: flex;
+ flex-wrap: wrap;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ }
+ nav.breadcrumb li {
+ margin: 0;
+ font-size: 0.875rem;
+ }
+ nav.breadcrumb li:not(:last-child)::after {
+ margin: .25rem;
+ content: "›";
+ opacity: .5;
+ }
main header {
background: unset;
border-bottom: unset;
@@ -157,6 +173,7 @@
{%- endblock header ~%}
+ {%- include 'partials/breadcrumb.html.twig' with {page: page} only ~%}
{%- block content ~%}
{{ page.content }}
{%- endblock content ~%}
diff --git a/resources/layouts/partials/breadcrumb.html.twig b/resources/layouts/partials/breadcrumb.html.twig
new file mode 100644
index 000000000..f53e014b1
--- /dev/null
+++ b/resources/layouts/partials/breadcrumb.html.twig
@@ -0,0 +1,19 @@
+ {%- if page.type != 'homepage' and page.ancestors|default([])|length > 0 ~%}
+
+ {%- endif ~%}
\ No newline at end of file
diff --git a/src/Collection/Page/Page.php b/src/Collection/Page/Page.php
index 53d142aa1..1126352a2 100644
--- a/src/Collection/Page/Page.php
+++ b/src/Collection/Page/Page.php
@@ -71,6 +71,9 @@ class Page extends Item
/** @var \Cecil\Collection\Taxonomy\Vocabulary Terms of a vocabulary. */
protected $terms;
+ /** @var self Parent page of a PAGE page or a SECTION page */
+ protected $parent;
+
/** @var Slugify */
private static $slugifier;
@@ -118,20 +121,18 @@ public static function slugify(string $path): string
*/
public static function createIdFromFile(SplFileInfo $file): string
{
- $relativePath = self::slugify(str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()));
- $basename = self::slugify(PrefixSuffix::subPrefix($file->getBasename('.' . $file->getExtension())));
- // if file is "README.md", ID is "index"
- $basename = (string) str_ireplace('readme', 'index', $basename);
- // if file is section's index: "section/index.md", ID is "section"
+ $relativePath = self::slugify(self::getFileComponents($file)['path']);
+ $basename = self::slugify(PrefixSuffix::subPrefix(self::getFileComponents($file)['name']));
+ // if file is a section's index: "/index.md", "" is the ID
if (!empty($relativePath) && PrefixSuffix::sub($basename) == 'index') {
- // case of a localized section's index: "section/index.fr.md", ID is "fr/section"
+ // case of a localized section's index: "/index.fr.md", "" is the ID
if (PrefixSuffix::hasSuffix($basename)) {
return PrefixSuffix::getSuffix($basename) . '/' . $relativePath;
}
return $relativePath;
}
- // localized page
+ // localized page: ".fr.md" -> "fr/"
if (PrefixSuffix::hasSuffix($basename)) {
return trim(Util::joinPath(PrefixSuffix::getSuffix($basename), $relativePath, PrefixSuffix::sub($basename)), '/');
}
@@ -163,15 +164,8 @@ public function setFile(SplFileInfo $file): self
/*
* File path components
*/
- $fileRelativePath = str_replace(DIRECTORY_SEPARATOR, '/', $this->file->getRelativePath());
- $fileExtension = $this->file->getExtension();
- $fileName = $this->file->getBasename('.' . $fileExtension);
- // renames "README" to "index"
- $fileName = (string) str_ireplace('readme', 'index', $fileName);
- // case of "index" = home page
- if (empty($this->file->getRelativePath()) && PrefixSuffix::sub($fileName) == 'index') {
- $this->setType(Type::HOMEPAGE->value);
- }
+ $fileRelativePath = self::getFileComponents($file)['path'];
+ $fileName = self::getFileComponents($file)['name'];
/*
* Set page properties and variables
*/
@@ -184,9 +178,16 @@ public function setFile(SplFileInfo $file): self
'updated' => (new \DateTime())->setTimestamp($this->file->getMTime()),
'filepath' => $this->file->getRelativePathname(),
]);
- /*
- * Set specific variables
- */
+ // is a section?
+ if (PrefixSuffix::sub($fileName) == 'index') {
+ $this->setType(Type::SECTION->value);
+ $this->setVariable('title', ucfirst(explode('/', $fileRelativePath)[\count(explode('/', $fileRelativePath)) - 1]));
+ // is the home page?
+ if (empty($this->getFolder())) {
+ $this->setType(Type::HOMEPAGE->value);
+ $this->setVariable('title', 'Homepage');
+ }
+ }
// is file has a prefix?
if (PrefixSuffix::hasPrefix($fileName)) {
$prefix = PrefixSuffix::getPrefix($fileName);
@@ -336,29 +337,22 @@ public function getSlug(): string
public function setPath(string $path): self
{
$path = trim($path, '/');
-
// case of homepage
if ($path == 'index') {
$this->path = '';
-
return $this;
}
-
// case of custom sections' index (ie: section/index.md -> section)
if (substr($path, -6) == '/index') {
$path = substr($path, 0, \strlen($path) - 6);
}
$this->path = $path;
-
$lastslash = strrpos($this->path, '/');
-
// case of root/top-level pages
if ($lastslash === false) {
$this->slug = $this->path;
-
return $this;
}
-
// case of sections' pages: set section
if (!$this->virtual && $this->getSection() === null) {
$this->section = explode('/', $this->path)[0];
@@ -366,7 +360,6 @@ public function setPath(string $path): self
// set/update folder and slug
$this->folder = substr($this->path, 0, $lastslash);
$this->slug = substr($this->path, -(\strlen($this->path) - $lastslash - 1));
-
return $this;
}
@@ -665,6 +658,46 @@ public function getFmVariables(): array
return $this->fmVariables;
}
+ /**
+ * Set parent page.
+ */
+ public function setParent(self $page): self
+ {
+ $this->parent = $page;
+
+ return $this;
+ }
+
+ /**
+ * Returns parent page if exists.
+ */
+ public function getParent(): ?self
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Returns array of ancestors pages.
+ */
+ public function getAncestors(): array
+ {
+ $ancestors = [];
+ $currentPage = $this;
+ while ($currentPage->getParent() !== null) {
+ $ancestors[] = $currentPage = $currentPage->getParent();
+ }
+
+ return $ancestors;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setId(string $id): self
+ {
+ return parent::setId($id);
+ }
+
/**
* Cast "boolean" string (or array of strings) to boolean.
*
@@ -683,10 +716,20 @@ private function filterBool(&$value)
}
/**
- * {@inheritdoc}
- */
- public function setId(string $id): self
- {
- return parent::setId($id);
+ * Get file components.
+ *
+ * [
+ * path => relative path,
+ * name => name,
+ * ext => extension,
+ * ]
+ */
+ private static function getFileComponents(SplFileInfo $file): array
+ {
+ return [
+ 'path' => str_replace(DIRECTORY_SEPARATOR, '/', $file->getRelativePath()),
+ 'name' => (string) str_ireplace('readme', 'index', $file->getBasename('.' . $file->getExtension())),
+ 'ext' => $file->getExtension(),
+ ];
}
}
diff --git a/src/Generator/Section.php b/src/Generator/Section.php
index 5b78bff3f..0dca399d5 100644
--- a/src/Generator/Section.php
+++ b/src/Generator/Section.php
@@ -30,16 +30,21 @@ public function generate(): void
{
$sections = [];
- // identifying sections from all pages
+ // identifying sections from pages collection
/** @var Page $page */
foreach ($this->builder->getPages() as $page) {
// top level (root) sections
if ($page->getSection()) {
- // do not add "not published" and "not excluded" pages to its section
+ // do not add "draft" and "excluded" pages to its section
if ($page->getVariable('published') !== true || $page->getVariable('exclude')) {
continue;
}
+ // $sections[section][language][] = $page
$sections[$page->getSection()][$page->getVariable('language', $this->config->getLanguageDefault())][] = $page;
+ // nested sections
+ /*if ($page->getParent() !== null) {
+ $sections[$page->getParent()->getId()][$page->getVariable('language', $this->config->getLanguageDefault())][] = $page;
+ }*/
}
}
diff --git a/src/Step/Pages/Create.php b/src/Step/Pages/Create.php
index 89f79d9c8..25ddd21e6 100644
--- a/src/Step/Pages/Create.php
+++ b/src/Step/Pages/Create.php
@@ -53,7 +53,6 @@ public function process(): void
$total = \count($this->builder->getPagesFiles());
$count = 0;
-
foreach ($this->builder->getPagesFiles() as $file) {
$count++;
// create a page from its (Markdown) file
@@ -110,7 +109,7 @@ public function process(): void
$this->builder->getPages()->add($page);
}
- $message = sprintf('Page "%s" created', $page->getId());
+ $message = sprintf('Page "%s" (%s) created', $page->getId(), $page->getType());
$this->builder->getLogger()->info($message, ['progress' => [$count, $total]]);
}
}
diff --git a/src/Step/Pages/Load.php b/src/Step/Pages/Load.php
index c23c060b4..40299d3e5 100644
--- a/src/Step/Pages/Load.php
+++ b/src/Step/Pages/Load.php
@@ -58,6 +58,13 @@ public function process(): void
->files()
->in($this->config->getPagesPath())
->sort(function (SplFileInfo $a, SplFileInfo $b): int {
+ // root pages first
+ if (empty($a->getRelativePath()) && !empty($b->getRelativePath())) {
+ return -1;
+ }
+ if (empty($b->getRelativePath()) && !empty($a->getRelativePath())) {
+ return 1;
+ }
// section's index first
if ($a->getRelativePath() == $b->getRelativePath() && $a->getBasename('.' . $a->getExtension()) == 'index') {
return -1;
@@ -66,7 +73,7 @@ public function process(): void
return 1;
}
// sort by name
- return strnatcasecmp($a->getRealPath(), $b->getRealPath());
+ return strnatcasecmp($a->getRelativePath(), $b->getRelativePath());
});
// load only one page?
if ($this->page) {
diff --git a/src/Step/Pages/Render.php b/src/Step/Pages/Render.php
index 6d26758af..882ff29a5 100644
--- a/src/Step/Pages/Render.php
+++ b/src/Step/Pages/Render.php
@@ -14,8 +14,8 @@
namespace Cecil\Step\Pages;
use Cecil\Builder;
-use Cecil\Collection\Page\Collection;
use Cecil\Collection\Page\Page;
+use Cecil\Collection\Page\Type;
use Cecil\Exception\RuntimeException;
use Cecil\Renderer\Config;
use Cecil\Renderer\Layout;
@@ -63,28 +63,29 @@ public function process(): void
// adds global variables
$this->addGlobals();
- /** @var Collection $pages */
+ // prepares pages collections
$pages = $this->builder->getPages()
// published only
->filter(function (Page $page) {
return (bool) $page->getVariable('published');
})
- // enrichs some variables
+ // enriched variables
->map(function (Page $page) {
$formats = $this->getOutputFormats($page);
- // output formats
- $page->setVariable('output', $formats);
// alternates formats
+ $page->setVariable('output', $formats);
$page->setVariable('alternates', $this->getAlternates($formats));
// translations
$page->setVariable('translations', $this->getTranslations($page));
+ // parent
+ if (($parent = $this->findParent($page)) !== null) {
+ $page->setParent($parent);
+ }
return $page;
});
- $total = \count($pages);
- // renders each page
- $count = 0;
+ // loads post processors
$postprocessors = [];
foreach ($this->config->get('output.postprocessors') as $name => $postprocessor) {
if (!class_exists($postprocessor)) {
@@ -94,7 +95,10 @@ public function process(): void
$postprocessors[] = new $postprocessor($this->builder);
$this->builder->getLogger()->debug(sprintf('Output post processor "%s" loaded', $name));
}
- /** @var Page $page */
+
+ // renders each page
+ $total = \count($pages);
+ $count = 0;
foreach ($pages as $page) {
$count++;
$rendered = [];
@@ -107,7 +111,7 @@ public function process(): void
// global site variables
$this->builder->getRenderer()->addGlobal('site', new Site($this->builder, $language));
- // global config raw variables
+ // global config variables
$this->builder->getRenderer()->addGlobal('config', new Config($this->builder, $language));
// excluded format(s)?
@@ -301,4 +305,32 @@ protected function getTranslations(Page $refPage): \Cecil\Collection\Page\Collec
return $pages;
}
+
+ /**
+ * Find page parent or null.
+ */
+ protected function findParent(Page $page): ?Page
+ {
+ $parent = null;
+ $langPrefix = '';
+ if ($page->getVariable('language') !== null && $page->getVariable('language') != $this->config->getLanguageDefault()) {
+ $langPrefix = $page->getVariable('language') . "/";
+ }
+ // home page by default
+ if ($page->getType() !== Type::HOMEPAGE->value) {
+ $parent = $this->builder->getPages()->get($langPrefix . 'index');
+ }
+ // recursive folder search
+ $folderAsArray = explode('/', (string) $page->getFolder());
+ while (\count($folderAsArray) >= 1 && !empty($folderAsArray[0])) {
+ $parentFolder = implode('/', $folderAsArray);
+ if ($this->builder->getPages()->has($langPrefix . $parentFolder) && $this->builder->getPages()->get($langPrefix . $parentFolder)->getId() !== $page->getId()) {
+ $parent = $this->builder->getPages()->get($langPrefix . $parentFolder);
+ break;
+ }
+ array_pop($folderAsArray);
+ }
+
+ return $parent;
+ }
}
diff --git a/tests/fixtures/website/layouts/ancestors.html.twig b/tests/fixtures/website/layouts/ancestors.html.twig
new file mode 100644
index 000000000..39581cfdc
--- /dev/null
+++ b/tests/fixtures/website/layouts/ancestors.html.twig
@@ -0,0 +1,14 @@
+{% extends ['page.html.twig', '_default/page.html.twig'] %}
+
+{% block content %}
+
+Ancestors
+
+
+{% for item in page.ancestors|reverse %}
+ {% if loop.index != 1 %} > {% endif %}
+ {{ item.title }}
+{% endfor %}
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/tests/fixtures/website/layouts/intl.html.twig b/tests/fixtures/website/layouts/intl.html.twig
index d98fa081f..ef129db41 100644
--- a/tests/fixtures/website/layouts/intl.html.twig
+++ b/tests/fixtures/website/layouts/intl.html.twig
@@ -71,6 +71,8 @@
site.page('about', 'fr').title
+ {% if site.page('about', 'fr') is defined and site.page('about', 'fr') is not null %}
{{ site.page('about', 'fr').title }}
+ {% endif %}
{% endblock content %}
diff --git a/tests/fixtures/website/pages/Section/Sub section/Sub sub section/index.md b/tests/fixtures/website/pages/Section/Sub section/Sub sub section/index.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/fixtures/website/pages/Section/Sub section/index.md b/tests/fixtures/website/pages/Section/Sub section/index.md
new file mode 100644
index 000000000..fc7b05989
--- /dev/null
+++ b/tests/fixtures/website/pages/Section/Sub section/index.md
@@ -0,0 +1,3 @@
+---
+title: Sub section
+---
diff --git a/tests/fixtures/website/pages/docs/sub-dir/Page in sub-dir.md b/tests/fixtures/website/pages/docs/sub-dir/Page in sub-dir.md
new file mode 100644
index 000000000..ab893fef6
--- /dev/null
+++ b/tests/fixtures/website/pages/docs/sub-dir/Page in sub-dir.md
@@ -0,0 +1,3 @@
+---
+layout: nested-page
+---
diff --git a/tests/fixtures/website/pages/docs/sub-section/Page in sub-section.md b/tests/fixtures/website/pages/docs/sub-section/Page in sub-section.md
new file mode 100644
index 000000000..ab893fef6
--- /dev/null
+++ b/tests/fixtures/website/pages/docs/sub-section/Page in sub-section.md
@@ -0,0 +1,3 @@
+---
+layout: nested-page
+---
diff --git a/tests/fixtures/website/pages/docs/sub-section/index.md b/tests/fixtures/website/pages/docs/sub-section/index.md
new file mode 100644
index 000000000..ab893fef6
--- /dev/null
+++ b/tests/fixtures/website/pages/docs/sub-section/index.md
@@ -0,0 +1,3 @@
+---
+layout: nested-page
+---
diff --git a/tests/fixtures/website/pages/docs/sub-section/sub-sub-section/Page in sub-sub-section.md b/tests/fixtures/website/pages/docs/sub-section/sub-sub-section/Page in sub-sub-section.md
new file mode 100644
index 000000000..ab893fef6
--- /dev/null
+++ b/tests/fixtures/website/pages/docs/sub-section/sub-sub-section/Page in sub-sub-section.md
@@ -0,0 +1,3 @@
+---
+layout: nested-page
+---
diff --git a/tests/fixtures/website/pages/docs/sub-section/sub-sub-section/index.md b/tests/fixtures/website/pages/docs/sub-section/sub-sub-section/index.md
new file mode 100644
index 000000000..ab893fef6
--- /dev/null
+++ b/tests/fixtures/website/pages/docs/sub-section/sub-sub-section/index.md
@@ -0,0 +1,3 @@
+---
+layout: nested-page
+---
diff --git a/tests/fixtures/website/themes/a-theme/layouts/index.html.twig b/tests/fixtures/website/themes/a-theme/layouts/index.html.twig
index 9d03f0e81..df385860e 100644
--- a/tests/fixtures/website/themes/a-theme/layouts/index.html.twig
+++ b/tests/fixtures/website/themes/a-theme/layouts/index.html.twig
@@ -13,9 +13,11 @@
+{#
{% if site.foo is defined %}
Imported config (from theme(s)):
Variable `foo`=`{{ site.foo }}`
{% endif %}
+#}
{% endblock %}
diff --git a/tests/fixtures/website/themes/a-theme/layouts/partials/pages_as_table.html.twig b/tests/fixtures/website/themes/a-theme/layouts/partials/pages_as_table.html.twig
new file mode 100644
index 000000000..6e4984679
--- /dev/null
+++ b/tests/fixtures/website/themes/a-theme/layouts/partials/pages_as_table.html.twig
@@ -0,0 +1,41 @@
+
+ All pages
+
+ title |
+ filepath |
+ id |
+ type |
+ virtual |
+ section |
+ path |
+ folder |
+ slug |
+ menu |
+ lang |
+
+ {%- for item in site.pages ~%}
+
+
+ {{ item.title }}
+ {{ item.date|date("d/m/Y") }}
+ {{ item.updated|date("d/m/Y") }}
+ |
+ {{ item.filepath }} |
+ {{ item.id }} |
+ {{ item.type|upper }} |
+ {{ item.virtual ? 'virtual' : 'file' }} |
+ {{ item.section ?? 'root' }} |
+ {{ item.path }} |
+ {{ item.folder }} |
+ {{ item.slug }} |
+
+ {%- if item.menu is defined %}
+ {{- dump(item.menu) ~}}
+ {%- endif ~%}
+ |
+
+ {{- item.language|default ~}}
+ |
+
+ {%- endfor ~%}
+
\ No newline at end of file