code:
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: #75715e;
}

.token.punctuation {
	color: #f8f8f2;
}

.token.property,
.token.tag,
.token.boolean,
.token.number {
	color: #f92672;
}

.token.selector,
.token.attr-name,
.token.string {
	color: #a6e22e;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
	color: #a6e22e;
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #a6e22e;
}

.token.regex,
.token.important {
	color: #e90;
}

.token.important {
	font-weight: bold;
}

.token.eetag {
	color: #e6db74;
}

/*.token.variable {
	color: #e6db74;
}*/

.token.parameter {
	color: #e6db74;
}

.token.value {
	color: #a6e22e;
}

.token.conditional {
	color: #e6db74;
}

.token.conditional .keyword {
	color: #e6db74;
}

.token.conditional .subject {
	color: #f92672;
}

.token.variable {
	color: #e6db74;
}

/* Other */

.token.function {
	color: rgb(166, 226, 46);
}


code:
code {
	font-weight: normal;
	background: transparent;
}

/**
 * prism.js default theme for JavaScript, CSS and HTML
 * Based on dabblet (http://dabblet.com)
 * @author Lea Verou
 */

code[class*="language-"],
pre[class*="language-"] {
	color: black;
	text-shadow: 0 1px white;
	font-family: Consolas, Monaco, 'Andale Mono', monospace;
	direction: ltr;
	text-align: left;
	white-space: pre;
	word-spacing: normal;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #f5f2f0;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: slategray;
}

.token.punctuation {
	color: #999;
}

.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number {
	color: #905;
}

.token.selector,
.token.attr-name,
.token.string {
	color: #690;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
	color: #a67f59;
	background: hsla(0,0%,100%,.5);
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #07a;
}

.token.regex,
.token.important {
	color: #e90;
}

.token.important {
	font-weight: bold;
}

.token.eetag {
	color: rgb(1, 97, 122);
}

.token.entity {
	cursor: help;
}

/* -----------------------------------------
	Default EE styles
----------------------------------------- */

.token.parameter {
	color: #690;
}

.token.value {
	color: #07A;
}

.token.conditional {
	color: rgb(3, 161, 202);
}

.token.conditional .keyword {
	color: rgb(3, 161, 202);
}

.token.conditional .subject {
	color: rgb(39, 119, 101);
}

.token.variable {
	color: green;
}

repo_name: tkkonline/php

license: mit


code:
Prism.languages.ee = Prism.languages.extend('markup', {
	'eetag': {
		pattern: /(\{|{)\/?exp:\w*:\w*(\s|\n|\t|\w|=|\042|\047|\/|\{|\}|{|ӓ)*(\}|})/g,
		inside: {
			'tag_name': {
				pattern: /exp:\w*:\w*/,
				inside: {
					'exp': /exp/,
					'class': /:\w*(?=:)/,
					'method': /\w*(?=[^\w*]*$)/,
					'colon': /:/,
				}
			},
			// insert parameter
			// insert conditional
			// insert bracket
			'keyword': /(if|switch|else)/g
		}
	},
	'embed': {
		pattern: /({|{)embed=(\047|\042)[\w|\/]*(\047|\042)(}|})/,
		inside: {
			// insert bracket
			'keyword': /embed/g,
			'operator': /=/g,
			'punctuation': /(\047|\042)/g,
			'path': /.*/
		}
	},
	// insert conditional
	'variable': {
		pattern: /(\{|{)\/?\w.*(\}|})/,
		inside: {
			'bracket':  /\{|{|\}|}/g,
			'parameter': {
				pattern: /\w*=(\042|\047).*(\042|\047)/g,
				inside: {
					'name': /\w+(?=\=)/,
					'operator': /=/,
					'punctuation': /\042|\047/,
					'value': /.+/
				}
			}
		}
	},
	// insert bracket
	'keyword': /(if|switch|else)/g,
	'comment': /(<|\{)!--[\w\W]*?--(>|\})/g
});

Prism.languages.insertBefore('inside', 'keyword', {
	'bracket': Prism.languages.ee.variable.inside.bracket
}, Prism.languages.ee.eetag);

Prism.languages.insertBefore('inside', 'keyword', {
	'bracket': Prism.languages.ee.variable.inside.bracket
}, Prism.languages.ee.embed);

Prism.languages.insertBefore('inside', 'bracket', {
	'conditional': {
		pattern: /(\{|{)\/?if.*(\}|})/,
		inside: {
			'operator': /(>|<|\076|\075|\074)+/g,
			'bracket':  /\{|{|\}|}/g,
			'keyword': /(if|switch|else)/g,
			'subject': /(\042|\047|\w)+$/,
			'variable': /((\042|\047)\w+(\042|\047))|\w+(?=\}|}|((\076|\075|\074|\s)+))/
		}
	}
}, Prism.languages.ee.eetag);

Prism.languages.insertBefore('inside', 'conditional', {
	'parameter': Prism.languages.ee.variable.inside.parameter
}, Prism.languages.ee.eetag);

Prism.languages.insertBefore('ee', 'variable', {
	'conditional': Prism.languages.ee.eetag.inside.conditional
});

Prism.languages.insertBefore('ee', 'keyword', {
	'bracket': Prism.languages.ee.eetag.inside.bracket
});

// Prism.hooks.add('before-highlight', function(env) {
// 	if(env.language == 'ee') {
// 		var html = env.element.innerHTML;

// 		html = html.replace(/\074/g, '<');
// 		html = html.replace(/\076/g, '>');

// 		env.code = html;
// 	}
// });


code:
/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+twig+css+css-extras+clike+javascript+php+php-extras+sql+http+git+less+markdown */
/**
 * okaidia theme for JavaScript, CSS and HTML
 * Loosely based on Monokai textmate theme by http://www.monokai.nl/
 * @author ocodia
 */

code[class*="language-"],
pre[class*="language-"] {
	color: #f8f8f2;
	text-shadow: 0 1px rgba(0, 0, 0, 0.3);
	font-family: Consolas, Monaco, 'Andale Mono', monospace;
	direction: ltr;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	line-height: 1.5;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
	border-radius: 0.3em;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #272822;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: slategray;
}

.token.punctuation {
	color: #f8f8f2;
}

.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
	color: #f92672;
}

.token.boolean,
.token.number {
	color: #ae81ff;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
	color: #a6e22e;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
	color: #f8f8f2;
}

.token.atrule,
.token.attr-value {
	color: #e6db74;
}

.token.keyword {
	color: #66d9ef;
}

.token.regex,
.token.important {
	color: #fd971f;
}

.token.important,
.token.bold {
	font-weight: bold;
}
.token.italic {
	font-style: italic;
}

.token.entity {
	cursor: help;
}


code: <?php require __DIR__ . '/posts/atom.xml.php';


code:
<?php foreach ($this->config->blogroll as $title => $href): ?>

<?= $this->anchor($href, $title) ?>

<?php endforeach; ?>


code:
<?php foreach ($this->config->featured as $title => $href): ?>

<?= $this->anchor($href, $title) ?>

<?php endforeach; ?>


code: <?php require __DIR__ . '/posts/index.html.php';


code:
<body>
<?= $this->render('layout/body/header'); ?>
<?= $this->render('layout/body/main'); ?>
<?= $this->render('layout/body/footer'); ?>
</body>


code:
    <?= $this->penders('layout-footer-prepend'); ?>

Built with <?= $this->anchor(
        'https://github.com/getargo/app',
        'Argo',
        ['_target' => 'blank']
    ); ?>.

    <?= $this->penders('layout-footer-append'); ?>


code:
    <?= $this->penders('layout-header-prepend') ?>

<?= $this->anchorRaw('/', $this->config->general->title); ?>

<?= $this->config->general->tagline; ?>

    <?= $this->penders('layout-header-append') ?>

<?= $this->render('layout/body/nav'); ?>


code:
    <?= $this->render('layout/body/main/content'); ?>
    <?= $this->render('layout/body/main/sidebar'); ?>


code: <?= $this->getContent(); ?>


code:
<aside class="Sidebar col-md-3">
    <?php foreach ($this->config->theme->sidebar ?? [] as $widget): ?>
        <?= $this->render("widgets/{$widget}"); ?>
    <?php endforeach; ?>
</aside>


code:
<nav class="Menu navbar navbar-expand-md">
    <ul class="navbar-nav mx-auto">
        <script>Argo.shtml('/menu.shtml', document.currentScript)</script>
    </ul>
</nav>


code:
<head>
<?= $this->render('layout/head/meta'); ?>
<?= $this->render('layout/head/title'); ?>
<?= $this->render('layout/head/links'); ?>
<?= $this->render('layout/head/styles'); ?>
<?= $this->render('layout/head/scripts'); ?>
</head>


code:
<?php if ($this->hasAtom): ?>
    <link rel="alternate" type="application/atom+xml" href="atom.xml" />
<?php endif; ?>

<?php if ($this->hasJson): ?>
    <link rel="alternate" type="application/json" href="index.json" />
<?php endif; ?>


code:
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">


code:
<script src="/theme/<?= $this->config->general->theme; ?>/vendor/jquery/jquery.min.js"></script>
<script src="/theme/<?= $this->config->general->theme; ?>/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
<script src="/theme/<?= $this->config->general->theme; ?>/vendor/prism/prism.min.js" type="text/javascript"></script>
<script src="/theme/<?= $this->config->general->theme; ?>/argo.js" type="text/javascript"></script>


code:
<link rel="stylesheet" href="/theme/<?= $this->config->general->theme; ?>/vendor/bootstrap/css/bootstrap.min.css" type="text/css" media="screen" />
<link rel="stylesheet" href="/theme/<?= $this->config->general->theme; ?>/vendor/prism/prism.min.css" type="text/css" media="screen" />
<link rel="stylesheet" href="/theme/<?= $this->config->general->theme; ?>/style.css" type="text/css" media="screen" />
<link rel="stylesheet" href="/theme/<?= $this->config->general->theme; ?>-custom/style.css" type="text/css" media="screen" />


code:
<title><?php
    $title = $this->escape()->html($this->config->general->title);
    $title .= isset($this->item->title)
        ? ' | ' . $this->escape()->html($this->item->title)
        : '';
    echo $title;
?></title>


code:
<!DOCTYPE html>
<html lang="en">
<?= $this->render('layout/head'); ?>
<?= $this->render('layout/body'); ?>
</html>


code:
<?php foreach ($this->config->menu as $title => $href): ?>

<?= $this->anchorRaw(
        $href,
        $title,
        [
            'class' => 'nav-link'
        ]
    ); ?>

<?php endforeach; ?>


code:
<?= $this->anchor($this->page->href, $this->page->title); ?>

        <?= $this->body($this->page) ?>


code:
        <?= $this->penders('post-meta-prepend') ?>

        <time datetime="<?= $this->dateTime()->attr($this->post->created); ?>">
            <?= $this->dateTime()->html($this->post->created, 'l') ?>

            <?= $this->dateTime()->html($this->post->created, 'd') ?>

            <?= $this->dateTime()->html($this->post->created, 'M Y') ?>

        </time>

        <address rel="author"><?= $this->escape()->html($this->post->author) ?></address>

<?php foreach ($this->post->tags as $tag): ?>

<?= $this->anchor($tag->href, $tag->title); ?>

        <?php endforeach; ?>

        <?= $this->penders('post-meta-append') ?>

            <?= $this->penders('post-header-prepend') ?>

<?= $this->anchor(
                $this->post->href,
                $this->post->title
            ) ?>

            <?= $this->penders('post-header-append') ?>

            <?= $this->penders('post-body-prepend') ?>
            <?= $this->body($this->post); ?>
            <?= $this->penders('post-body-append') ?>

        <?= $this->render('prevnext') ?>


code:
<?= $this->post->toJson([
    'html' => $this->body($this->post)
]);


code:
<?='<?';?>xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

    <title><?= $this->config->general->title ?></title>
    <link href="<?= $this->config->general->url ?>" />
    <updated><?= $this->dateTime()->html('now', DATE_ATOM) ?></updated>

    <?php foreach ($this->postIndex->posts as $post): ?>
    <entry>
        <title><?= $post->title ?></title>
        <link href="<?= $this->config->general->url . $post->href ?>" />
        <updated><?= $this->dateTime()->html($post->lastUpdated, DATE_ATOM) ?></updated>
        <summary><?= $this->escape()->html(
            substr(
                trim(strip_tags($this->body($post))),
                0,
                250
            )
        ) ?>...</summary>
    </entry>
    <?php endforeach; ?>

</feed>


code:
<?php foreach ($this->postIndex->posts as $post): ?>

                        <time datetime="<?= $this->dateTime()->attr($post->created); ?>">
                            <?= $this->dateTime()->html($post->created, 'l') ?>

                            <?= $this->dateTime()->html($post->created, 'd') ?>

                            <?= $this->dateTime()->html($post->created, 'M Y') ?>

                        </time>

                        <address rel="author"><?= $this->escape()->html($post->author) ?></address>

<?php foreach ($post->tags as $tag): ?>

<?= $this->anchor($tag->href, $tag->title); ?>

                        <?php endforeach; ?>

<?= $this->anchor(
                                $post->href,
                                $post->title
                            ) ?>

                            <?= $this->bodyLess($post); ?>

                <?php endforeach; ?>

                        <?= $this->render('prevnext') ?>


code:
<?= $this->anchor(
                            $this->month->href,
                            $this->month->title
                        ) ?>

<?php foreach ($this->month->posts as $post): ?>

<?= $this->dateTime()->html($post->created, 'Y-m-d') ?>

<?= $this->anchor($post->href, $post->title) ?>

                        <?php endforeach; ?>

                <?= $this->render('prevnext') ?>


code:
<?= $this->month->toJson([
    'posts' => $this->month->posts
]);


code:
<?php
use Argo\Domain\Json;

echo Json::encode($this->months);


code:
<?php foreach ($this->months as $month): ?>

<?=
    $this->anchor($month->href, $month->title)
    . ' (' . count($month->posts) . ')'
; ?>

<?php endforeach; ?>


code:
<?php if ($this->item->prev !== null || $this->item->next !== null): ?>

<?php if ($this->item->prev !== null) {
            echo $this->anchorRaw($this->item->prev->href, "« {$this->item->prev->title}", ['class' => 'page-link']);
        } ?>

<?php if ($this->item->next !== null) {
            echo $this->anchorRaw($this->item->next->href, "{$this->item->next->title} »", ['class' => 'page-link']);
        } ?>

<?php endif; ?>


code:
<?='<?';?>xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

    <title><?= $this->config->general->title . ' | ' . $this->tag->title ?></title>
    <link href="<?= $this->config->general->url . $this->tag->href ?>" />
    <updated><?= $this->dateTime()->html('now', DATE_ATOM) ?></updated>
    <?php for (
        $i = 0;
        $i < $this->config->general->perPage && $i < count($this->tag->posts);
        $i ++
    ): $post = $this->tag->posts[$i]; ?>

    <entry>
        <title><?= $post->title ?></title>
        <link href="<?= $this->config->general->url . $post->href ?>" />
        <updated><?= $this->dateTime()->html($post->lastUpdated, DATE_ATOM) ?></updated>
        <summary><?= $this->escape()->html(
            substr(
                trim(strip_tags($this->body($post))),
                0,
                250
            )
        ) ?>...</summary>
    </entry>
    <?php endfor; ?>

</feed>


code:
<?= $this->anchor(
                            $this->tag->href,
                            $this->tag->title
                        ) ?>

                        <?= $this->body($this->tag) ?>

<?php foreach ($this->tag->posts as $post): ?>

<?= $this->dateTime()->html($post->created, 'Y-m-d') ?>

<?= $this->anchor($post->href, $post->title) ?>

                        <?php endforeach; ?>

                <?= $this->render('prevnext') ?>


code:
<?= $this->tag->toJson([
    'html' => $this->body($this->tag),
    'posts' => $this->tag->posts
]);


code:
<?php
use Argo\Domain\Json;

echo Json::encode($this->tags);


code:
<?php foreach ($this->tags as $tag): ?>

<?=
    $this->anchor($tag->href, $tag->title)
    . ' (' . count($tag->posts) . ')'
; ?>

<?php endforeach; ?>


code:
:root {<?php
    echo PHP_EOL;
    foreach ($this->config->theme->style ?? [] as $key => $val) {
        $val = trim($val);

        if ($val === '') {
            continue;
        }

        $key = str_replace('_', '-', $key);
        echo "    --{$key}: {$val};" . PHP_EOL;
    }
?>}

a,
a.page-link {
    color: var(--link-color, #438287);
}

a:hover,
a.page-link:hover {
    color: var(--link-color-hover, #205d5f);
}

blockquote {
    background-color: #ededed;
    border-left: 1rem solid #dcdcdc;
    padding-bottom: 1rem;
    padding-left: 2rem;
    padding-right: 2rem;
    padding-top: 1rem;
}

body {
    font-family: var(--sans-serif-fonts, sans-serif);
}

code {
    color: black;
    font-family: var(--monospace-fonts, monospace);
}

h1,
h2,
h3,
h4,
h5,
h6 {
    font-family: var(--sans-serif-fonts, sans-serif);
}

pre {
    font-fmaily: var(--monospace-fonts, monospace);
}

.Header {
    background-color: var(--header-background-color, #f7f7f7);
    background-image: var(--header-background-image, none);
    background-position: center top;
    background-repeat: no-repeat;
    background-size: cover;
    color: var(--header-font-color, black);
    margin: 0;
    padding: 1.5rem;
    text-align: center;
}

.Header p {
    font-style: italic;
}

.Header a,
.Header a:hover {
    color: var(--header-font-color, black);
}

.Menu {
    background-color: var(--header-background-color, #f7f7f7);
    margin: 0;
    padding: 0;
}

.MenuItem {
    font-size: 90%;
    font-weight: 550;
    white-space: nowrap;
    padding-left: 0.5rem;
    padding-right: 0.5rem;
}

.MenuItem a:hover {
    background-color: #fff;
}

.ArticleMeta address::before {
    content: "by ";
}

.ArticleHeader {
    margin-bottom: 2rem;
}

.ArticleBody {
    font-family: var(--serif-fonts, serif);
}

.Footer {
    color: var(--footer-font-color, black);
    background-color: var(--footer-background-color, #f7f7f7);
}


code:
                    <form action="https://duckduckgo.com/" method="get">

                            <input type="hidden" name="sites" value="<?=
                                $this->escape()->attr(
                                    parse_url($this->config->general->url, PHP_URL_HOST)
                                );
                            ?>">
                            <input type="search" name="q" class="form-control" placeholder="Search …">

                                <input type="submit" class="btn btn-secondary" value="Go!"></input>

                    </form>


argo/original

description: Original theme for Argo.

type: argo-theme


sidebar: widgets/search widgets/blogroll widgets/months widgets/tags


names:
Loader
PrismCodeHighlighting
[object Object]
loadCss
loadJs
then
window
Prism
highlightAll


code:
class Loader{static loadJs(e){return new Promise((t,r)=>{if(document.querySelector(`script[src="${e}"]`))return void t();const d=document.createElement("script");d.type="text/javascript",d.async=!0,d.src=e,d.addEventListener("load",t),d.addEventListener("error",r),d.addEventListener("abort",r),document.head.appendChild(d)})}static loadCss(e){return new Promise((t,r)=>{if(document.querySelector(`link[href="${e}"]`))return void t();const d=document.createElement("link");d.rel="stylesheet",d.href=e,d.addEventListener("load",t),d.addEventListener("error",r),d.addEventListener("abort",r),document.head.appendChild(d)})}}export default Loader;
//# sourceMappingURL=Loader.js.map


names:
Loader
[object Object]
src
Promise
resolve
reject
document
querySelector
el
createElement
type
async
addEventListener
head
appendChild
rel
href


names: Argo shtml href id fetch then response text html document getElementById innerHTML catch error console warn


code:
import PrismCodeHighlighting from"./Components/PrismCodeHighlighting.js";(document.querySelector("code")||document.querySelector("pre"))&&new PrismCodeHighlighting;
//# sourceMappingURL=main.js.map


code:
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: #75715e;
}

.token.punctuation {
	color: #f8f8f2;
}

.token.property,
.token.tag,
.token.boolean,
.token.number {
	color: #f92672;
}

.token.selector,
.token.attr-name,
.token.string {
	color: #a6e22e;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
	color: #a6e22e;
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #a6e22e;
}

.token.regex,
.token.important {
	color: #e90;
}

.token.important {
	font-weight: bold;
}

.token.eetag {
	color: #e6db74;
}

/*.token.variable {
	color: #e6db74;
}*/

.token.parameter {
	color: #e6db74;
}

.token.value {
	color: #a6e22e;
}

.token.conditional {
	color: #e6db74;
}

.token.conditional .keyword {
	color: #e6db74;
}

.token.conditional .subject {
	color: #f92672;
}

.token.variable {
	color: #e6db74;
}

/* Other */

.token.function {
	color: rgb(166, 226, 46);
}


code:
code {
	font-weight: normal;
	background: transparent;
}

/**
 * prism.js default theme for JavaScript, CSS and HTML
 * Based on dabblet (http://dabblet.com)
 * @author Lea Verou
 */

code[class*="language-"],
pre[class*="language-"] {
	color: black;
	text-shadow: 0 1px white;
	font-family: Consolas, Monaco, 'Andale Mono', monospace;
	direction: ltr;
	text-align: left;
	white-space: pre;
	word-spacing: normal;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #f5f2f0;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: slategray;
}

.token.punctuation {
	color: #999;
}

.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number {
	color: #905;
}

.token.selector,
.token.attr-name,
.token.string {
	color: #690;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
	color: #a67f59;
	background: hsla(0,0%,100%,.5);
}

.token.atrule,
.token.attr-value,
.token.keyword {
	color: #07a;
}

.token.regex,
.token.important {
	color: #e90;
}

.token.important {
	font-weight: bold;
}

.token.eetag {
	color: rgb(1, 97, 122);
}

.token.entity {
	cursor: help;
}

/* -----------------------------------------
	Default EE styles
----------------------------------------- */

.token.parameter {
	color: #690;
}

.token.value {
	color: #07A;
}

.token.conditional {
	color: rgb(3, 161, 202);
}

.token.conditional .keyword {
	color: rgb(3, 161, 202);
}

.token.conditional .subject {
	color: rgb(39, 119, 101);
}

.token.variable {
	color: green;
}


code:
Prism.languages.ee = Prism.languages.extend('markup', {
	'eetag': {
		pattern: /(\{|{)\/?exp:\w*:\w*(\s|\n|\t|\w|=|\042|\047|\/|\{|\}|{|ӓ)*(\}|})/g,
		inside: {
			'tag_name': {
				pattern: /exp:\w*:\w*/,
				inside: {
					'exp': /exp/,
					'class': /:\w*(?=:)/,
					'method': /\w*(?=[^\w*]*$)/,
					'colon': /:/,
				}
			},
			// insert parameter
			// insert conditional
			// insert bracket
			'keyword': /(if|switch|else)/g
		}
	},
	'embed': {
		pattern: /({|{)embed=(\047|\042)[\w|\/]*(\047|\042)(}|})/,
		inside: {
			// insert bracket
			'keyword': /embed/g,
			'operator': /=/g,
			'punctuation': /(\047|\042)/g,
			'path': /.*/
		}
	},
	// insert conditional
	'variable': {
		pattern: /(\{|{)\/?\w.*(\}|})/,
		inside: {
			'bracket':  /\{|{|\}|}/g,
			'parameter': {
				pattern: /\w*=(\042|\047).*(\042|\047)/g,
				inside: {
					'name': /\w+(?=\=)/,
					'operator': /=/,
					'punctuation': /\042|\047/,
					'value': /.+/
				}
			}
		}
	},
	// insert bracket
	'keyword': /(if|switch|else)/g,
	'comment': /(<|\{)!--[\w\W]*?--(>|\})/g
});

Prism.languages.insertBefore('inside', 'keyword', {
	'bracket': Prism.languages.ee.variable.inside.bracket
}, Prism.languages.ee.eetag);

Prism.languages.insertBefore('inside', 'keyword', {
	'bracket': Prism.languages.ee.variable.inside.bracket
}, Prism.languages.ee.embed);

Prism.languages.insertBefore('inside', 'bracket', {
	'conditional': {
		pattern: /(\{|{)\/?if.*(\}|})/,
		inside: {
			'operator': /(>|<|\076|\075|\074)+/g,
			'bracket':  /\{|{|\}|}/g,
			'keyword': /(if|switch|else)/g,
			'subject': /(\042|\047|\w)+$/,
			'variable': /((\042|\047)\w+(\042|\047))|\w+(?=\}|}|((\076|\075|\074|\s)+))/
		}
	}
}, Prism.languages.ee.eetag);

Prism.languages.insertBefore('inside', 'conditional', {
	'parameter': Prism.languages.ee.variable.inside.parameter
}, Prism.languages.ee.eetag);

Prism.languages.insertBefore('ee', 'variable', {
	'conditional': Prism.languages.ee.eetag.inside.conditional
});

Prism.languages.insertBefore('ee', 'keyword', {
	'bracket': Prism.languages.ee.eetag.inside.bracket
});

// Prism.hooks.add('before-highlight', function(env) {
// 	if(env.language == 'ee') {
// 		var html = env.element.innerHTML;

// 		html = html.replace(/\074/g, '<');
// 		html = html.replace(/\076/g, '>');

// 		env.code = html;
// 	}
// });


code:
/* http://prismjs.com/download.html?themes=prism-okaidia&languages=markup+twig+css+css-extras+clike+javascript+php+php-extras+sql+http+git+less+markdown */
/**
 * okaidia theme for JavaScript, CSS and HTML
 * Loosely based on Monokai textmate theme by http://www.monokai.nl/
 * @author ocodia
 */

code[class*="language-"],
pre[class*="language-"] {
	color: #f8f8f2;
	text-shadow: 0 1px rgba(0, 0, 0, 0.3);
	font-family: Consolas, Monaco, 'Andale Mono', monospace;
	direction: ltr;
	text-align: left;
	white-space: pre;
	word-spacing: normal;
	word-break: normal;
	line-height: 1.5;

	-moz-tab-size: 4;
	-o-tab-size: 4;
	tab-size: 4;

	-webkit-hyphens: none;
	-moz-hyphens: none;
	-ms-hyphens: none;
	hyphens: none;
}

/* Code blocks */
pre[class*="language-"] {
	padding: 1em;
	margin: .5em 0;
	overflow: auto;
	border-radius: 0.3em;
}

:not(pre) > code[class*="language-"],
pre[class*="language-"] {
	background: #272822;
}

/* Inline code */
:not(pre) > code[class*="language-"] {
	padding: .1em;
	border-radius: .3em;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
	color: slategray;
}

.token.punctuation {
	color: #f8f8f2;
}

.namespace {
	opacity: .7;
}

.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
	color: #f92672;
}

.token.boolean,
.token.number {
	color: #ae81ff;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
	color: #a6e22e;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
	color: #f8f8f2;
}

.token.atrule,
.token.attr-value {
	color: #e6db74;
}

.token.keyword {
	color: #66d9ef;
}

.token.regex,
.token.important {
	color: #fd971f;
}

.token.important,
.token.bold {
	font-weight: bold;
}
.token.italic {
	font-style: italic;
}

.token.entity {
	cursor: help;
}


code: <?php require __DIR__ . '/posts/atom.xml.php';


code:
<?php foreach ($this->config->blogroll as $title => $href): ?>

<?= $this->anchor(
                        $href,
                        $title,
                        [
                            'class' => 'LinkList__ItemLink'
                        ]
                    ) ?>

                    <?php endforeach; ?>


code:
<?php foreach ($this->config->featured as $title => $href): ?>

<?= $this->anchor($href, $title) ?>

<?php endforeach; ?>


code: <?php require __DIR__ . '/posts/index.html.php';


code:
        <?= $this->render('templates', $this->config->theme->layout_footer_prepend ?? []) ?>

Built with <?= $this->anchor('https://github.com/getargo/app', 'Argo'); ?>

        <?= $this->render('templates', $this->config->theme->layout_footer_append ?? []) ?>


code:
<?php if ($this->hasAtom): ?>
    <link rel="alternate" type="application/atom+xml" href="atom.xml" />
<?php endif; ?>

<?php if ($this->hasJson): ?>
    <link rel="alternate" type="application/json" href="index.json" />
<?php endif; ?>


code:
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="x-ua-compatible" content="ie=edge">


code:
    <script src="/theme/<?= $this->config->general->theme; ?>/js/main.js" type="module"></script>
    <script src="/theme/<?= $this->config->general->theme; ?>/js/argo.js" type="text/javascript"></script>


code:
    <link rel="stylesheet" href="/theme/<?= $this->config->general->theme; ?>/css/style.min.css" type="text/css" media="screen" />
    <link rel="stylesheet" href="/theme/custom/style.css" type="text/css" media="screen" />


code:
    <title><?php
        $title = $this->escape()->html($this->config->general->title);
        $title .= isset($this->item->title)
            ? ' | ' . $this->escape()->html($this->item->title)
            : '';
        echo $title;
    ?></title>


code:
        <?= $this->render('templates', $this->config->theme->layout_header_prepend ?? []) ?>

            <picture>
                <img
                    src="<?= $this->config->theme->layout_header_img_src ?>"
                    alt="<?= $this->config->theme->layout_header_img_alt ?>"
                    class="SiteHeader__BackgroundImageTag"
                >
            </picture>

<?= $this->config->general->title ?>

<?= $this->config->general->tagline ?>

        <?= $this->render('templates', $this->config->theme->layout_header_append ?? []) ?>


code:
<!DOCTYPE html>

<?php
    echo $this->render('layout/head/meta');
    echo $this->render('layout/head/title');
    echo $this->render('layout/head/links');
    echo $this->render('layout/head/styles');
    echo $this->render('layout/head/scripts');
?>

    <?= $this->render('layout/header') ?>

<?= $this->getContent() ?>

        <?= $this->render('layout/sidebar') ?>

    <?= $this->render('layout/footer') ?>


code:
        <aside class="SidebarLayout__Sidebar">

            <?php foreach ($this->config->theme->sidebar as $widget) {
                echo $this->render($widget);
            } ?>

        </aside>


code:
            <?php foreach ($this->config->menu as $title => $href): ?>

                <?= $this->anchor(
                    $href,
                    $title,
                    [
                        'class' => 'SiteNav__Item'
                    ]
                ); ?>

            <?php endforeach; ?>


code:
<?= $this->anchor(
                                $this->page->href,
                                $this->page->title,
                                [
                                    'class' => 'Article__HeadingLink'
                                ]
                            ) ?>

                            <?= $this->body($this->page) ?>


code:
                        <?= $this->render('templates', $this->config->theme->post_meta_prepend ?? []) ?>

<?= $this->dateTime()->html($this->post->created, 'l') ?>

<?= $this->dateTime()->html($this->post->created, 'd') ?>

<?= $this->dateTime()->html($this->post->created, 'F Y') ?>

<?= $this->escape()->html($this->post->author) ?>

                            <?php foreach ($this->post->tags as $k => $tag): ?>
                                <?php if (isset($tag->href) && isset($tag->title)): ?>
                                    <?= $this->anchor($tag->href, $tag->title) . ($k + 1 < count($this->post->tags) ? ', ' : '') ?>
                                <?php endif; ?>
                            <?php endforeach; ?>

                        <?= $this->render('templates', $this->config->theme->post_meta_append ?? []) ?>

                            <?= $this->render('templates', $this->config->theme->post_header_prepend ?? []) ?>

<?= $this->anchor(
                                $this->post->href,
                                $this->post->title,
                                [
                                    'class' => 'Article__HeadingLink'
                                ]
                            ) ?>

                            <?= $this->render('templates', $this->config->theme->post_header_append ?? []) ?>

                            <?= $this->render('templates', $this->config->theme->post_body_prepend ?? []) ?>
                            <?= $this->body($this->post) ?>
                            <?= $this->render('templates', $this->config->theme->post_body_append ?? []) ?>

                            <?= $this->render('templates', $this->config->theme->post_footer_prepend ?? []) ?>
                            <?= $this->render('templates', $this->config->theme->post_footer_append ?? []) ?>

                        <?= $this->render('prevnext') ?>


code:
<?= $this->post->toJson([
    'html' => $this->body($this->post)
]);


code:
<?='<?';?>xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

    <title><?= $this->config->general->title ?></title>
    <link href="<?= $this->config->general->url ?>" />
    <updated><?= $this->dateTime()->html('now', DATE_ATOM) ?></updated>

    <?php foreach ($this->postIndex->posts as $post): ?>
    <entry>
        <title><?= $post->title ?></title>
        <link href="<?= $this->config->general->url . $post->href ?>" />
        <updated><?= $this->dateTime()->html($post->lastUpdated, DATE_ATOM) ?></updated>
        <summary><?= $this->escape()->html(
            substr(
                trim(strip_tags($this->body($post))),
                0,
                250
            )
        ) ?>...</summary>
    </entry>
    <?php endforeach; ?>

</feed>


code:
<?php foreach ($this->postIndex->posts as $post): ?>

<?= $this->dateTime()->html($post->created, 'l') ?>

<?= $this->dateTime()->html($post->created, 'd') ?>

<?= $this->dateTime()->html($post->created, 'F Y') ?>

<?= $this->escape()->html($post->author) ?>

<?php foreach ($post->tags as $k => $tag): ?>
                            <?= $this->anchor($tag->href, $tag->title) . ($k + 1 < count($post->tags) ? ', ' : '') ?>
                        <?php endforeach; ?>

<?= $this->anchor(
                                $post->href,
                                $post->title,
                                [
                                    'class' => 'Article__HeadingLink'
                                ]
                            ) ?>

                            <?= $this->bodyLess($post) ?>

                <?php endforeach; ?>

                <?= $this->render('prevnext') ?>


code:
<?= $this->anchor(
                                $this->month->href,
                                $this->month->title,
                                [
                                    'class' => 'Article__HeadingLink'
                                ]
                            ) ?>

<?php foreach ($this->month->posts as $post): ?>

<?= $this->dateTime()->html($post->created, 'Y-m-d') ?>

<?= $this->anchor($post->href, $post->title) ?>

                            <?php endforeach; ?>

                    <?= $this->render('prevnext') ?>


code:
<?= $this->month->toJson([
    'posts' => $this->month->posts
]);


code:
<?php
use Argo\Domain\Json;

echo Json::encode($this->months);


code:
<?php foreach ($this->months as $month): ?>

<?=
                        $this->anchor(
                            $month->href,
                            $month->title . ' (' . count($month->posts) . ')',
                            [
                                'class' => 'LinkList__ItemLink'
                            ]
                        )
                    ?>

                    <?php endforeach; ?>


code:
<?php if ($this->item->prev !== null || $this->item->next !== null): ?>

                    <?php if ($this->item->prev !== null): ?>

                            <a href="<?=$this->item->prev->href?>" class="Pagination__Link">
                                « <?=$this->item->prev->title?>

                    <?php endif; ?>
                    <?php if ($this->item->next !== null): ?>

                            <a href="<?=$this->item->next->href?>" class="Pagination__Link">
                                <?=$this->item->next->title?> »

                    <?php endif; ?>

            <?php endif; ?>


code:
<?='<?';?>xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

    <title><?= $this->config->general->title . ' | ' . $this->tag->title ?></title>
    <link href="<?= $this->config->general->url . $this->tag->href ?>" />
    <updated><?= $this->dateTime()->html('now', DATE_ATOM) ?></updated>
    <?php for (
        $i = 0;
        $i < $this->config->general->perPage && $i < count($this->tag->posts);
        $i ++
    ): $post = $this->tag->posts[$i]; ?>

    <entry>
        <title><?= $post->title ?></title>
        <link href="<?= $this->config->general->url . $post->href ?>" />
        <updated><?= $this->dateTime()->html($post->lastUpdated, DATE_ATOM) ?></updated>
        <summary><?= $this->escape()->html(
            substr(
                trim(strip_tags($this->body($post))),
                0,
                250
            )
        ) ?>...</summary>
    </entry>
    <?php endfor; ?>

</feed>


code:
<?= $this->anchor(
                                $this->tag->href,
                                $this->tag->title,
                                [
                                    'class' => 'Article__HeadingLink'
                                ]
                            ) ?>

                            <?= $this->body($this->tag) ?>

<?php foreach ($this->tag->posts as $post): ?>

<?= $this->dateTime()->html($post->created, 'Y-m-d') ?>

<?= $this->anchor($post->href, $post->title) ?>

                            <?php endforeach; ?>

                    <?= $this->render('prevnext') ?>


code:
<?= $this->tag->toJson([
    'html' => $this->body($this->tag),
    'posts' => $this->tag->posts
]);


code:
<?php
use Argo\Domain\Json;

echo Json::encode($this->tags);


code:
<?php foreach ($this->tags as $tag): ?>

<?=
                        $this->anchor(
                            $tag->href,
                            $tag->title . ' (' . count($tag->posts) . ')',
                            [
                                'class' => 'LinkList__ItemLink'
                            ]
                        )
                    ?>

                    <?php endforeach; ?>


code:
<?php
foreach ($__VARS__ as $name) {
    echo $this->render($name);
}


code: /* intentionally empty */


code:
                <form
                    action="https://duckduckgo.com/"
                    method="get"
                    class="Search"
                    onsubmit="
                        document.getElementById('search-q').value
                            = 'site:<?= $this->escape()->js(parse_url($this->config->general->url, PHP_URL_HOST)) ?> '
                            +  document.getElementById('search-input').value;
                    "
                >

                        <input type="hidden" id="search-q" name="q" value="" />
                        <input type="search" id="search-input" placeholder="Search …" class="input Search__Input">

                        <input type="submit" value="Search" class="Search__SubmitButton">

                            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0.2 3.5 20.42 20.75">
                                <g transform="translate(-560.008 -1318.754) rotate(45)">
                                    <g fill="none" stroke="currentColor" stroke-width="4px" transform="translate(1337 528)">
                                        <circle class="cls-2" stroke="none" cx="8.515" cy="8.515" r="8.515"></circle>
                                        <circle class="cls-3" fill="none" cx="8.515" cy="8.515" r="6.515"></circle>
                                    </g>
                                    <line stroke="currentColor" stroke-width="4px" fill="none" y2="7.618" transform="translate(1345.739 543.909)"></line>
                                </g>
                            </svg>

                </form>


code:
<?php
declare(strict_types=1);

namespace Argo\App;

use Argo\Infra\InfraContainerFactory;
use Capsule\Di\Definitions;

class AppContainerFactory extends InfraContainerFactory
{
    static protected function define() : Definitions
    {
        $def = parent::define();
        return $def;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Config;

use Argo\Domain\Config\Config;
use Argo\Domain\Json;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchConfig extends UseCase
{
    protected $config;

    public function __construct(Config $config)
    {
        $this->config = $config;
    }

    protected function exec(string $name) : Payload
    {
        $text = isset($this->config->$name)
            ? $this->config->$name->getText()
            : null;

        if ($text === null) {
            return Payload::notFound();
        }

        return Payload::found([
            'name' => $name,
            'text' => $text . PHP_EOL,
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Config;

use Argo\Domain\Config\Config;
use Argo\Domain\Config\ConfigGateway;
use Argo\Domain\Json;
use Argo\Infra\BuildFactory;
use Argo\App\Payload;
use Argo\App\UseCase;

class SaveConfig extends UseCase
{
    protected $config;

    protected $configGateway;

    protected $buildFactory;

    public function __construct(
        Config $config,
        ConfigGateway $configGateway,
        BuildFactory $buildFactory
    ) {
        $this->config = $config;
        $this->configGateway = $configGateway;
        $this->buildFactory = $buildFactory;
    }

    protected function exec(string $name, string $text) : Payload
    {
        if (! isset($this->config->$name)) {
            return Payload::notFound();
        }

        $text = trim($text);

        if ($text === '') {
            return Payload::invalid(['invalid' => 'The config cannot be blank.']);
        }

        $data = Json::decode($text);
        $this->config->$name->setData($data);
        $this->configGateway->saveValues($this->config->$name);

        switch ($name) {
            case 'menu':
                $this->buildFactory->new()->menuShtml();
                break;
            case 'blogroll':
                $this->buildFactory->new()->blogrollShtml();
                break;
            case 'theme':
                $this->buildFactory->new()->theme();
                break;
            default:
                // @todo: $this->config->admin->needsBuild = true;
                break;
        }

        return Payload::updated([
            'item' => (object) [
                'type' => 'config',
                'relId' => $name,
            ]
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Draft;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\ContentLocator;
use Argo\Domain\Content\Draft\Draft;
use Argo\Domain\DateTime;
use Argo\Domain\Json;
use Argo\App\Payload;
use Argo\App\UseCase;

class AddDraft extends UseCase
{
    protected $dateTime;

    protected $config;

    protected $content;

    public function __construct(
        DateTime $dateTime,
        Config $config,
        ContentLocator $content
    ) {
        $this->dateTime = $dateTime;
        $this->config = $config;
        $this->content = $content;
    }

    protected function exec(string $title) : Payload
    {
        $title = trim($title);
        if ($title === '') {
            $title = 'Untitled';
        }

        $utcTime = $this->dateTime->utc();

        $data = [
            'title' => $title,
            'author' => $this->config->general->author,
            'tags' => [
                'general',
            ],
        ];

        $relId = $this->dateTime->local($utcTime, 'Ymd\THis');

        $draft = new Draft(
            Draft::absId($relId),
            $data
        );

        $this->content->drafts->save($draft, '');

        return Payload::created(['item' => $draft]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Draft;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchDraft extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(string $relId) : Payload
    {
        $draft = $this->content->drafts->getItem($relId);

        if ($draft === null) {
            return Payload::notFound();
        }

        return Payload::found([
            'draft' => $draft,
            'body' => $this->content->drafts->getBody($draft),
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Draft;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\ContentLocator;
use Argo\Domain\Content\Draft\Draft;
use Argo\Domain\Content\Post\Post;
use Argo\Domain\DateTime;
use Argo\Domain\Exception;
use Argo\Domain\Storage;
use Argo\Infra\BuildFactory;
use Argo\App\Payload;
use Argo\App\UseCase;

class PublishDraft extends UseCase
{
    protected $dateTime;

    protected $storage;

    protected $config;

    protected $content;

    protected $buildFactory;

    public function __construct(
        DateTime $dateTime,
        Storage $storage,
        Config $config,
        ContentLocator $content,
        BuildFactory $buildFactory
    ) {
        $this->dateTime = $dateTime;
        $this->storage = $storage;
        $this->config = $config;
        $this->content = $content;
        $this->buildFactory = $buildFactory;
    }

    // this is really more like save-and-publish draft
    protected function exec(string $relId, array $data, string $body) : Payload
    {
        $draft = $this->content->drafts->getItem($relId);

        if ($draft === null) {
            return Payload::notFound();
        }

        $draft->fill($data);
        $this->content->drafts->save($draft, $body);

        $name = Draft::normalize($draft->title ?? '');

        if ($name === '') {
            return Payload::invalid([
                'invalid' => "Cannot create post URL from draft title '{$draft->title}'"
            ]);
        }

        $date = $this->dateTime->ymd();
        $id = Post::absId("{$date}/{$name}");
        $post = new Post($id, (array) $draft->data);
        $post->data->created = null;
        $post->data->updated = [];

        if ($this->storage->exists($id)) {
            return Payload::invalid([
                'invalid' => "A post at '{$post->relId}' already exists."
            ]);
        }

        $this->storage->move($draft->id, $post->id);
        $this->content->posts->save($post, null);

        $this->buildFactory->new()->onePost($post, true);

        return Payload::created(['item' => $post]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Draft;

use Argo\Domain\Content\ContentLocator;
use Argo\Domain\DateTime;
use Argo\App\Payload;
use Argo\App\UseCase;

class SaveDraft extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(
        string $relId,
        array $data,
        string $body
    ) : Payload
    {
        $draft = $this->content->drafts->getItem($relId);

        if ($draft === null) {
            return Payload::notFound();
        }

        $draft->fill($data);
        $this->content->drafts->save($draft, $body);

        return Payload::updated(['item' => $draft]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Draft;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class TrashDraft extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(string $relId) : Payload
    {
        $draft = $this->content->drafts->getItem($relId);

        if ($draft === null) {
            return Payload::notFound();
        }

        $this->content->drafts->trash($draft);

        return Payload::deleted(['item' => $draft]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Page;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\ContentLocator;
use Argo\Domain\Content\Page\Page;
use Argo\Domain\DateTime;
use Argo\Domain\Json;
use Argo\Domain\Storage;
use Argo\App\Payload;
use Argo\App\UseCase;

class AddPage extends UseCase
{
    protected $storage;

    protected $config;

    protected $content;

    public function __construct(
        Storage $storage,
        Config $config,
        ContentLocator $content
    ) {
        $this->storage = $storage;
        $this->config = $config;
        $this->content = $content;
    }

    protected function exec(string $id) : Payload
    {
        $id = trim($id, '/ ');
        Page::assertId($id);

        if ($this->storage->exists($id)) {
            return Payload::invalid([
                'invalid' => "Page {$id} already exists.",
            ]);
        }

        $parent = '';
        $parts = explode('/', $id);
        array_pop($parts);

        while (! empty($parts)) {
            $parent = implode('/', $parts);
            $file = $parent . '/argo.json';

            if (! $this->storage->exists($file)) {
                return Payload::invalid([
                    'invalid' => "Cannot create page '{$id}' without parent page '{$parent}' first.",
                ]);
            }

            array_pop($parts);
        }

        $data = [
            'title' => Page::titleize($id),
            'author' => $this->config->general->author,
            'tags' => [],
        ];

        $page = new Page($id, $data);
        $this->content->pages->save($page, '');

        // @todo build the page

        return Payload::created(['item' => $page]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Page;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchPage extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(string $id) : Payload
    {
        $page = $this->content->pages->getItem($id);

        if ($page === null) {
            return Payload::notFound();
        }

        return Payload::found([
            'page' => $page,
            'body' => $this->content->pages->getBody($page),
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Page;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchPages extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec() : Payload
    {
        $pages = $this->content->pages->getItems();
        return Payload::found([
            'pages' => $pages,
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Page;

use Argo\Domain\Config\Config;
use Argo\Domain\DateTime;
use Argo\Domain\Content\ContentLocator;
use Argo\Infra\BuildFactory;
use Argo\App\Payload;
use Argo\App\UseCase;

class SavePage extends UseCase
{
    protected $config;

    protected $content;

    protected $buildFactory;

    public function __construct(
        Config $config,
        ContentLocator $content,
        BuildFactory $buildFactory
    ) {
        $this->config = $config;
        $this->content = $content;
        $this->buildFactory = $buildFactory;
    }

    protected function exec(string $id, array $data, string $body) : Payload
    {
        $page = $this->content->pages->getItem($id);

        if ($page === null) {
            return Payload::notFound();
        }

        $page->fill($data);
        $this->content->pages->save($page, $body);

        $this->buildFactory->new()->onePage($page);

        return Payload::updated(['item' => $page]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Page;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class TrashPage extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(string $id) : Payload
    {
        $page = $this->content->pages->getItem($id);

        if ($page === null) {
            return Payload::notFound();
        }

        $this->content->pages->trash($page);

        return Payload::deleted(['item' => $page]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Post;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchPost extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(string $relId) : Payload
    {
        $post = $this->content->posts->getItem($relId);

        if ($post === null) {
            return Payload::notFound();
        }

        return Payload::found([
            'post' => $post,
            'body' => $this->content->posts->getBody($post),
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Post;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchPosts extends UseCase
{
    protected $config;

    protected $content;

    public function __construct(Config $config, ContentLocator $content)
    {
        $this->config = $config;
        $this->content = $content;
    }

    protected function exec(int $pageNum) : Payload
    {
        $perPage = $this->config->general->perPage;
        $posts = $this->content->posts->getItems($pageNum, $perPage);

        if (empty($posts)) {
            return Payload::notFound();
        }

        list($itemCount, $pageCount) = $this->content->posts->listCounts($perPage);
        $output = [
            'pageNum' => $pageNum,
            'pageCount' => $pageCount,
            'posts' => $posts,
        ];

        return Payload::found($output);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Post;

use Argo\Domain\Config\Config;
use Argo\Domain\DateTime;
use Argo\Domain\Content\ContentLocator;
use Argo\Infra\BuildFactory;
use Argo\App\Payload;
use Argo\App\UseCase;

class SavePost extends UseCase
{
    protected $config;

    protected $content;

    protected $buildFactory;

    public function __construct(
        Config $config,
        ContentLocator $content,
        BuildFactory $buildFactory
    ) {
        $this->config = $config;
        $this->content = $content;
        $this->buildFactory = $buildFactory;
    }

    protected function exec(string $relId, array $data, string $body) : Payload
    {
        $post = $this->content->posts->getItem($relId);

        if ($post === null) {
            return Payload::notFound();
        }

        $post->fill($data);
        $this->content->posts->save($post, $body);
        $this->buildFactory->new()->onePost($post);

        return Payload::updated(['item' => $post]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Post;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;
use Argo\Infra\BuildFactory;

class TrashPost extends UseCase
{
    protected $content;

    public function __construct(
        ContentLocator $content,
        BuildFactory $buildFactory
    ) {
        $this->content = $content;
        $this->buildFactory = $buildFactory;
    }

    protected function exec(string $relId) : Payload
    {
        $post = $this->content->posts->getItem($relId);

        if ($post === null) {
            return Payload::notFound();
        }

        $this->content->posts->trash($post);
        $this->buildFactory->new()->trashedPost($post);

        return Payload::deleted(['item' => $post]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Tag;

use Argo\Domain\Content\ContentLocator;
use Argo\Domain\Content\Tag\Tag;
use Argo\App\Payload;
use Argo\App\UseCase;

class AddTag extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(string $relId) : Payload
    {
        $relId = trim($relId);

        if ($relId === '') {
            return Payload::invalid([
                'invalid' => "Tag name cannot be blank.",
            ]);
        }

        Tag::assertId($relId);
        $tag = $this->content->tags->getItem($relId);

        if ($tag !== null) {
            return Payload::invalid([
                'invalid' => "Tag '$relId' already exists.",
            ]);
        }

        $tag = new Tag(Tag::absId($relId));
        $this->content->tags->save($tag, '');

        // @todo: build the tag html, shtml, etc.

        return Payload::created(['item' => $tag]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Tag;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchTag extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec(string $relId) : Payload
    {
        $tag = $this->content->tags->getItem($relId);

        if ($tag === null) {
            return Payload::notFound();
        }

        return Payload::found([
            'tag' => $tag,
            'body' => $this->content->tags->getBody($tag),
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Tag;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchTags extends UseCase
{
    protected $content;

    public function __construct(ContentLocator $content)
    {
        $this->content = $content;
    }

    protected function exec() : Payload
    {
        return Payload::found([
            'tags' => $this->content->tags->getItems()
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Tag;

use Argo\Domain\Content\ContentLocator;
use Argo\Infra\BuildFactory;
use Argo\App\Payload;
use Argo\App\UseCase;

class SaveTag extends UseCase
{
    protected $content;

    public function __construct(
        ContentLocator $content,
        BuildFactory $buildFactory
    ) {
        $this->content = $content;
        $this->buildFactory = $buildFactory;
    }

    protected function exec(string $relId, array $data, string $body) : Payload
    {
        $tag = $this->content->tags->getItem($relId);

        if ($tag === null) {
            return Payload::notFound();
        }

        $tag->fill($data);
        $this->content->tags->save($tag, $body);

        $this->buildFactory->new()->tags();

        // @todo if the title changed, rebuild all content with that tag

        return Payload::updated(['item' => $tag]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Content\Tag;

use Argo\Domain\Content\ContentLocator;
use Argo\App\Payload;
use Argo\App\UseCase;
use Argo\Infra\BuildFactory;

class TrashTag extends UseCase
{
    protected $content;

    public function __construct(
        ContentLocator $content,
        BuildFactory $buildFactory
    ) {
        $this->content = $content;
        $this->buildFactory = $buildFactory;
    }

    protected function exec(string $relId) : Payload
    {
        $tag = $this->content->tags->getItem($relId);

        if ($tag === null) {
            return Payload::notFound();
        }

        $posts = $this->content->posts->getItems();

        foreach ($posts as $i => $post) {
            if (! in_array($tag->relId, $post->data->tags)) {
                unset($posts[$i]);
                continue;
            }

            $key = array_search($tag->relId, $post->data->tags);
            unset($post->data->tags[$key]);

            if (empty($post->data->tags)) {
                $post->data->tags[] = 'general';
            }

            $this->content->posts->save($post);
        }

        $this->content->tags->trash($tag);
        $build = $this->buildFactory->new()->trashedTag($tag, $posts);

        return Payload::deleted(['item' => $tag]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\ContentLocator;
use Argo\Domain\Storage;
use Argo\App\Payload;
use Argo\App\UseCase;

class Dashboard extends UseCase
{
    protected $system;

    protected $config;

    protected $content;

    public function __construct(
        Storage $storage,
        Config $config,
        ContentLocator $content
    ) {
        $this->storage = $storage;
        $this->config = $config;
        $this->content = $content;
    }

    protected function exec() : Payload
    {
        return Payload::found([
            'drafts' => $this->content->drafts->getItems(),
            'posts' => $this->content->posts->getItems(1, $this->config->general->perPage),
            'remote' => trim((string) $this->config->general->url),
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Import;

use Argo\Infra\BuildFactory;
use Argo\Infra\Import\ImportWordpress;
use Argo\App\Payload;
use Argo\App\UseCase;
use SapiUpload;

class Import extends UseCase
{
    protected $errors = [
        UPLOAD_ERR_INI_SIZE => "The uploaded file exceeds the upload_max_filesize directive in php.ini.",
        UPLOAD_ERR_FORM_SIZE => "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.",
        UPLOAD_ERR_PARTIAL => "The uploaded file was only partially uploaded.",
        UPLOAD_ERR_NO_FILE => "No file was uploaded.",
        UPLOAD_ERR_NO_TMP_DIR => "Missing a temporary folder.",
        UPLOAD_ERR_CANT_WRITE => "Failed to write file to disk.",
        UPLOAD_ERR_EXTENSION => "A PHP extension stopped the file upload.",
    ];

    public function __construct(ImportWordpress $import, BuildFactory $buildFactory)
    {
        $this->import = $import;
        $this->buildFactory = $buildFactory;
    }

    protected function exec(SapiUpload $upload) : Payload
    {
        if ($upload->error !== 0) {
            return Payload::error([
                'error' => $this->errors[$upload->error] ?? 'Unknown upload error.',
            ]);
        }

        return Payload::processing([
            'callable' => function () use ($upload) {
                $old = ini_get('max_execution_time');
                ini_set('max_execution_time', '0');

                ($this->import)($upload->tmpName);
                $this->buildFactory->new('echo')->all();

                ini_set('max_execution_time', $old);
            },
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App;

use Argo\Domain\Storage;
use Argo\Infra\System;
use Argo\App\Payload;
use Argo\App\UseCase;

class OpenFolder extends UseCase
{
    protected $storage;

    protected $system;

    public function __construct(System $system, Storage $storage)
    {
        $this->system = $system;
        $this->storage = $storage;
    }

    protected function exec(string $id) : Payload
    {
        $path = $this->storage->path($id);
        $folder = realpath($path);

        if ($folder === false || ! is_dir($folder)) {
            return Payload::error([
                'error' => "Folder '$path' not found.",
            ]);
        }

        $this->system->open($folder);

        return Payload::accepted();
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App;

use PayloadInterop\DomainPayload;

class Payload implements DomainPayload
{
    protected $status;

    protected $result;

    public function __construct(string $status, array $result)
    {
        $this->status = $status;
        $this->result = $result;
    }

    public function getStatus() : string
    {
        return $this->status;
    }

    public function getResult() : array
    {
        return $this->result;
    }

    public static function created(array $result = []) : Payload
    {
        return new Payload(Status::CREATED, $result);
    }

    public static function deleted(array $result = []) : Payload
    {
        return new Payload(Status::DELETED, $result);
    }

    public static function notFound(array $result = []) : Payload
    {
        return new Payload(Status::NOT_FOUND, $result);
    }

    public static function found(array $result = []) : Payload
    {
        return new Payload(Status::FOUND, $result);
    }

    public static function invalid(array $result = []) : Payload
    {
        return new Payload(Status::INVALID, $result);
    }

    public static function success(array $result = []) : Payload
    {
        return new Payload(Status::SUCCESS, $result);
    }

    public static function updated(array $result = []) : Payload
    {
        return new Payload(Status::UPDATED, $result);
    }

    public static function error(array $result = []) : Payload
    {
        return new Payload(Status::ERROR, $result);
    }

    public static function processing(array $result = []) : Payload
    {
        return new Payload(Status::PROCESSING, $result);
    }

    public static function accepted(array $result = []) : Payload
    {
        return new Payload(Status::ACCEPTED, $result);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Site;

use Argo\Domain\Content\Post\Post;
use Argo\Domain\DateTime;
use Argo\Domain\Json;
use Argo\Infra\Server;
use Argo\Infra\System;
use Argo\App\Payload;
use Argo\App\UseCase;

class AddSite extends UseCase
{
    protected $system;

    protected $dateTime;

    protected $server;

    protected $general = [];

    protected $admin = [
        'initialize' => true,
        'version' => '1.2.0',
    ];

    protected $docroot;

    public function __construct(
        System $system,
        DateTime $dateTime,
        Server $server
    ) {
        $this->system = $system;
        $this->server = $server;
        $this->dateTime = $dateTime;
    }

    protected function exec(array $input = null) : Payload
    {
        return $this->default($input)
            ?? $this->validate($input)
            ?? $this->create();
    }

    protected function default(?array $input) : ?Payload
    {
        if ($input === null) {
            return Payload::found([
                'name' => '',
                'title' => '',
                'tagline' => '',
                'author' => $this->system->whoami(),
                'url' => '',
            ]);
        }

        return null;
    }

    protected function validate(array $input) : ?Payload
    {
        $name = strtolower(trim($input['name'] ?? ''));

        if ($name === '') {
            return Payload::invalid([
                'invalid' => 'The folder name may not be blank.'
            ]);
        }

        if (! preg_match('/^[a-z0-9-]+$/', $name)) {
            return Payload::invalid([
                'invalid' => "The folder name may use only a-z, 0-9, and dashes.",
            ]);
        }

        $docroot = $this->system->sitesDir() . "/{$name}";

        if (is_dir($docroot)) {
            return Payload::invalid([
                'invalid' => "A folder with the name '$name' already exists."
            ]);
        }

        $title = trim($input['title'] ?? '');

        if ($title === '') {
            return Payload::invalid([
                'invalid' => 'The blog title may not be blank.'
            ]);
        }

        $tagline = trim($input['tagline'] ?? '');
        $author = trim($input['author'] ?? '');

        if ($author === '') {
            return Payload::invalid([
                'invalid' => 'The author username may not be blank.',
            ]);
        }

        if (! preg_match('/^[a-z0-9-]+$/', $author)) {
            return Payload::invalid([
                'invalid' => 'The author username may use only a-z, 0-9, and dashes.',
            ]);
        }

        $url = trim($input['url'] ?? '');

        if ($url === '') {
            return Payload::invalid([
                'invalid' => 'The blog URL may not be blank.',
            ]);
        }

        $parts = parse_url($url);

        if ($parts === false || empty($parts['host'])) {
            return Payload::invalid([
                'invalid' => 'The blog URL does not look right.',
            ]);
        }

        $this->docroot = $docroot;
        $this->general = [
            'title' => $title,
            'tagline' => $tagline,
            'author' => $author,
            'url' => $url,
            'theme' => 'argo/bootstrap4',
            'perPage' => 10,
        ];

        return null;
    }

    protected function create() : Payload
    {
        mkdir("{$this->docroot}/_argo", 0755, true);

        file_put_contents(
            "{$this->docroot}/_argo/general.json",
            Json::encode($this->general)
        );

        file_put_contents(
            "{$this->docroot}/_argo/admin.json",
            Json::encode($this->admin)
        );

        $this->server->stop($this->docroot);

        return Payload::success();
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Site;

use Argo\Infra\BuildFactory;
use Argo\App\Payload;
use Argo\App\UseCase;
use Throwable;

class BuildSite extends UseCase
{
    protected $buildFactory;

    public function __construct(BuildFactory $buildFactory)
    {
        $this->buildFactory = $buildFactory;
    }

    protected function exec() : Payload
    {
        return Payload::processing([
            'callable' => function () {
                $this->buildFactory->new('echo')->all();
            },
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Site;

use Argo\Domain\Storage;
use Argo\Infra\System;
use Argo\App\Payload;
use Argo\App\UseCase;

class FetchSites extends UseCase
{
    protected $system;

    public function __construct(System $system)
    {
        $this->system = $system;
    }

    protected function exec() : Payload
    {
        return Payload::found([
            'sites' => $this->system->sites(),
            'author' => $this->system->whoami(),
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Site;

use Argo\Infra\System;
use Argo\Infra\Server;
use Argo\App\Payload;
use Argo\App\UseCase;

class SwapSite extends UseCase
{
    protected $system;

    protected $server;

    public function __construct(System $system, Server $server)
    {
        $this->system = $system;
        $this->server = $server;
    }

    protected function exec(string $name) : Payload
    {
        $docroot = $this->system->sitesDir() . "/{$name}";

        if (! is_dir($docroot)) {
            return Payload::notFound();
        }

        $this->server->stop($docroot);
        return Payload::success();
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App\Site;

use Argo\Infra\Sync;
use Argo\App\Payload;
use Argo\App\UseCase;

class SyncSite extends UseCase
{
    protected $sync;

    public function __construct(Sync $sync)
    {
        $this->sync = $sync;
    }

    protected function exec() : Payload
    {
        return Payload::processing([
            'callable' => $this->sync,
        ]);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\App;

use PayloadInterop\DomainStatus;

interface Status extends DomainStatus
{
}


code:
<?php
declare(strict_types=1);

namespace Argo\App;

use Argo\Domain\Exception\Invalid;
use Throwable;

abstract class UseCase
{
    public function __invoke(/* mixed */ ...$args) : Payload
    {
        try {
            return $this->exec(...$args);
        } catch (Invalid $e) {
            $args['invalid'] = $e->getMessage();
            return Payload::invalid($args);
        } catch (Throwable $e) {
            $args['error'] = (string) $e;
            return Payload::error($args);
        }
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Config;

use Argo\Domain\Exception;

class Config
{
    protected $instances = [];

    public function __construct(array $instances)
    {
        foreach ($instances as $key => $val) {
            $this->$key = $val;
        }
    }

    public function __get($key)
    {
        return $this->instances[$key];
    }

    public function __set($key, $val)
    {
        $this->assertValues($val);
        $this->instances[$key] = $val;
    }

    public function __isset($key)
    {
        return isset($this->instances[$key]);
    }

    public function __unset($key)
    {
        unset($this->instances[$key]);
    }

    protected function assertValues($val) : void
    {
        if (! $val instanceof Values) {
            throw new Exception('Not an instance of ' . Values::CLASS);
        }
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Config;

use Argo\Domain\Json;
use Argo\Domain\Storage;

class ConfigGateway
{
    protected $storage;

    public function __construct(Storage $storage)
    {
        $this->storage = $storage;
    }

    public function getConfig() : Config
    {
        $instances = [];
        $files = $this->storage->glob('_argo/*.json');

        foreach ($files as $file) {
            $file = basename($file);
            $name = substr($file, 0, strpos(basename($file), '.'));
            $id = "_argo/{$name}";
            $text = $this->storage->read("{$id}.json");
            $data = Json::decode($text);
            $instances[$name] = $this->newValues($id, $data);
        }

        if (isset($instances['general'])) {
            $theme = $instances['general']->theme ?? 'default';
            $id = "_argo/theme/{$theme}";
            $text = $this->storage->read("{$id}.json") ?? '{}';
            $data = Json::decode($text);
            $instances['theme'] = $this->newValues($id, $data);
        }

        return $this->newConfig($instances);
    }

    protected function newConfig(array $instances) : Config
    {
        return new Config($instances);
    }

    public function newValues(string $id, object $data) : Values
    {
        return new Values($id, $data);
    }

    public function saveValues(Values $values) : void
    {
        $id = $values->getId();
        $data = $values->getData();
        $text = Json::encode($data);
        $this->storage->write("{$id}.json", $text);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Config;

use Argo\Domain\Json;
use ArrayIterator;
use IteratorAggregate;

class Values implements IteratorAggregate
{
    protected $id;

    protected $data;

    public function __construct(string $id, object $data = null)
    {
        $this->id = $id;
        $this->data = (object) [];

        if ($data !== null) {
            $this->setData($data);
        }
    }

    public function __get($key)
    {
        return $this->data->$key;
    }

    public function __set($key, $val)
    {
        $this->data->$key = $val;
    }

    public function __isset($key)
    {
        return isset($this->data->$key);
    }

    public function __unset($key)
    {
        unset($this->data->$key);
    }

    public function getText() : string
    {
        return Json::encode($this->data);
    }

    public function setData(object $data) : void
    {
        $this->data = $data;
    }

    public function getData() : object
    {
        return $this->data;
    }

    public function getIterator()
    {
        return new ArrayIterator($this->data);
    }

    public function getId() : string
    {
        return $this->id;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\Draft\DraftRepository;
use Argo\Domain\Content\Page\PageRepository;
use Argo\Domain\Content\Post\PostRepository;
use Argo\Domain\Content\Tag\TagRepository;
use Argo\Domain\DateTime;
use Argo\Domain\Exception;
use Argo\Domain\Storage;

class ContentLocator
{
    protected $config;

    protected $dateTime;

    protected $storage;

    protected $instances = [
        'drafts' => DraftRepository::CLASS,
        'tags' => TagRepository::CLASS,
        'pages' => PageRepository::CLASS,
        'posts' => PostRepository::CLASS,
    ];

    public function __construct(
        DateTime $dateTime,
        Storage $storage,
        Config $config
    ) {
        $this->dateTime = $dateTime;
        $this->storage = $storage;
        $this->config = $config;
    }

    public function __get($key)
    {
        if (! isset($this->instances[$key])) {
            throw new Exception("No such repository: $key");
        }

        if (is_string($this->instances[$key])) {
            $this->instances[$key] = $this->new($this->instances[$key]);
        }

        return $this->instances[$key];
    }

    protected function new(string $class)
    {
        return new $class($this->dateTime, $this->storage, $this->config, $this);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Draft;

use Argo\Domain\Content\Item;
use Argo\Domain\Exception;

class Draft extends Item
{
    public static function assertId(string $id) : void
    {
        if (! preg_match('/^_draft\/\d{8}T\d{6}$/', $id)) {
            throw new Exception\InvalidData("Draft ID is not valid: {$id}");
        }
    }

    public static function absId(string $relId) : string
    {
        return "_draft/{$relId}";
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Draft;

use Argo\Domain\Content\ItemRepository;

class DraftRepository extends ItemRepository
{
    protected function getGlob() : string
    {
        return '_draft/*';
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\ContentLocator;
use Argo\Domain\DateTime;
use Argo\Domain\Storage;

class Folio
{
    static public function new(Storage $storage, Config $config, ContentLocator $content, DateTime $dateTime) : Folio
    {
        $start = microtime(true);
        $folio = new Folio();
        $folio->config = $config;
        $folio->drafts = $content->drafts->getItems();
        $folio->posts = $content->posts->getItems();
        $folio->postIndexes = PostIndex::getAllFromPosts($folio->posts, $folio->config->general->perPage);
        $folio->tags = $content->tags->getAllFromPosts($folio->posts);
        $folio->months = Month::getAllFromPosts($folio->posts, $dateTime);
        $folio->pages = $content->pages->getItems();
        $folio->utc = $dateTime->utc();
        $folio->time = number_format(microtime(true) - $start, 2);

        $penders = [];
        $theme = $folio->config->general->theme;
        $dir = "_theme/{$theme}-custom/templates/penders";
        foreach ($storage->glob("$dir/*/*.php") as $path) {
            $parts = explode('/', $path);
            $name = array_pop($parts);
            $type = array_pop($parts);
            $penders[$type][] = substr($name, 0, -4);
        }
        $folio->penders = $penders;

        return $folio;
    }

    protected $config;

    protected $drafts;

    protected $posts;

    protected $postIndexes;

    protected $tags;

    protected $months;

    protected $pages;

    protected $utc;

    protected $time;

    protected $penders;

    final private function __construct()
    {
    }

    public function __get(string $key)
    {
        return $this->$key;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content;

use Argo\Domain\Exception;
use Argo\Domain\Json;
use Argo\Domain\Storage;
use JsonSerializable;

abstract class Item implements JsonSerializable
{
    abstract public static function absId(string $relId) : string;

    public static function assertId(string $id) : void
    {
        $type = trim(strrchr(static::CLASS, '\\'), '\\');

        if (trim($id) === '') {
            throw new Exception\InvalidData("{$type} ID cannot be blank");
        }

        if (! preg_match('/^[\/a-z0-9-]+$/', $id)) {
            throw new Exception\InvalidData("{$type} ID has invalid characters; use only a-z, 0-9, and dashes.");
        }

        if (trim($id, '/') !== $id) {
            throw new Exception\InvalidData("{$type} ID cannot begin or end with slashes: {$id}");
        }

        if (strpos($id, '//') !== false) {
            throw new Exception\InvalidData("{$type} ID cannot have contiguous slashes: {$id}");
        }
    }

    public static function normalize(string $string) : string
    {
        return preg_replace(
            '/[^a-z0-9-]/',
            '',
            strtolower(preg_replace(
                '/[\s-]+/',
                '-',
                $string
            ))
        );
    }

    public static function titleize(string $str) : string
    {
        return ucwords(str_replace(['-', '/'], ' ', $str));
    }

    protected $id;

    protected $relId;

    protected $type;

    protected $href;

    protected $data;

    protected $prev;

    protected $next;

    public function __construct(string $id, array $data = [])
    {
        static::assertId($id);
        $this->id = $id;
        $this->relId = $this->getRelId();
        $this->type = strtolower(substr(strrchr(static::CLASS, '\\'), 1));
        $this->href = "/{$id}/";
        $this->data = (object) $this->fixData($data);
        $this->fill($data);
    }

    protected function fixData(array $data) : array
    {
        return $data += [
            'title' => null,
            'author' => null,
            'tags' => [],
            'created' => null,
            'updated' => [],
            'markup' => 'markdown',
        ];
    }

    public function __get(string $key)
    {
        if (property_exists($this, $key)) {
            return $this->$key;
        }

        if (property_exists($this->data, $key)) {
            return $this->data->$key;
        }

        throw new Exception("Cannot get \$$key on content object.");
    }

    public function __isset(string $key)
    {
        return isset($this->$key) || isset($this->data->$key);
    }

    public function __set(string $key, /* mixed */ $val)
    {
        throw new Exception("Cannot set \$$key on content object.");
    }

    public function __unset(string $key)
    {
        throw new Exception("Cannot unset \$$key on content object.");
    }

    public function fill(array $data) : void
    {
        if (isset($data['title'])) {
            $this->data->title = $data['title'];
        }

        if (isset($data['author'])) {
            if (! preg_match('/^[a-z0-9-]+$/', $data['author'])) {
                throw new Exception\InvalidData("Author has invalid characters; use only a-z, 0-9, and dashes.");
            }
            $this->data->author = $data['author'];
        }

        if (isset($data['markup'])) {
            $data['markup'] = strtolower($data['markup']);

            if (! preg_match('/^[a-z]+$/', $data['markup'])) {
                throw new Exception\InvalidData("Markup type has invalid characters; use only a-z.");
            }

            if ($data['markup'] === 'json') {
                throw new Exception\InvalidData("Markup type may not be JSON.");
            }

            $this->data->markup = $data['markup'];
        }

        if (isset($data['tags'])) {
            if (! is_array($data['tags'])) {
                $tags = explode(',', $data['tags']);
            } else {
                $tags = $data['tags'];
            }

            foreach ($tags as $key => $tag) {
                $tag = trim($tag);

                if ($tag === '') {
                    unset($tags[$key]);
                    continue;
                }

                if (! preg_match('/^[a-z0-9-]+$/', $tag)) {
                    throw new Exception\InvalidData("Tag '{$tag}' has invalid characters; use only a-z, 0-9, and dashes.");
                }

                $tags[$key] = $tag;
            }

            $this->data->tags = $tags;
        }
    }

    public function getText() : string
    {
        return Json::encode($this->data);
    }

    public function getBodyFile() : string
    {
        return "{$this->id}/argo.{$this->data->markup}";
    }

    public function jsonSerialize() /* : mixed */
    {
        $base = ['href' => $this->href, 'relId' => $this->relId];
        return $base + (array) $this->data;
    }

    public function getArrayCopy() : array
    {
        return get_object_vars($this);
    }

    public function toJson(array $extra = []) : string
    {
        $encoded = Json::encode($this);
        $decoded = Json::decode($encoded, true);
        return Json::encode($decoded + $extra);
    }

    public function setPrev(?Item $prev) : void
    {
        $this->prev = $prev;
    }

    public function setNext(?Item $next) : void
    {
        $this->next = $next;
    }

    protected function getRelId() : string
    {
        // type/foo/bar/baz => foo/bar/baz
        return substr(strchr($this->id, '/'), 1);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content;

use Argo\Domain\Config\Config;
use Argo\Domain\Content\ContentLocator;
use Argo\Domain\DateTime;
use Argo\Domain\Exception;
use Argo\Domain\Json;
use Argo\Domain\Repository;
use Argo\Domain\Storage;

abstract class ItemRepository
{
    protected $config;

    protected $content;

    protected $dateTime;

    protected $storage;

    protected $type;

    public function __construct(
        DateTime $dateTime,
        Storage $storage,
        Config $config,
        ContentLocator $content
    ) {
        $this->dateTime = $dateTime;
        $this->storage = $storage;
        $this->config = $config;
        $this->content = $content;
        $this->type = substr(static::CLASS, 0, -10);
    }

    abstract protected function getGlob() : string;

    public function save(Item $item, ?string $body) : void
    {
        $this->assertType($item);
        $utcTime = $this->dateTime->utc();

        if ($item->data->created === null) {
            $item->data->created = $utcTime;
        }

        $item->data->updated[] = $utcTime;
        $this->storage->write("{$item->id}/argo.json", $item->getText());

        if ($body !== null) {
            $this->storage->write($item->getBodyFile(), $body);
        }
    }

    public function getItem(string $relId) : ?Item
    {
        $id = ($this->type)::absId($relId);
        $text = $this->read($id, 'argo.json');
        if ($text === null) {
            return null;
        }

        $data = Json::decode($text, true);
        $class = $this->type;
        return new $class($id, $data);
    }

    public function getBody(Item $item) : ?string
    {
        $file = "argo.{$item->markup}";
        return $this->read($item->id, $file);
    }

    public function listCounts() : array
    {
        $perPage = $this->config->general->perPage;
        $items = $this->storage->glob($this->getGlob());
        $itemCount = count($items);
        $pageCount = (int) ceil($itemCount / $perPage);
        return [$itemCount, $pageCount];
    }

    public function getItems(?int $pageNum = null) : array
    {
        $perPage = $this->config->general->perPage;

        if ($pageNum === null) {
            $first = null;
            $last = null;
        } else {
            $first = ($pageNum - 1) * $perPage;
            $last = ($first + $perPage) - 1;
        }

        $class = $this->type;
        $items = [];

        $prev = null;
        foreach ($this->glob() as $i => $file) {
            if ($first !== null && $i < $first) {
                continue;
            }

            if ($last !== null && $i > $last) {
                break;
            }

            $id = $this->getIdFromFile($file);
            $text = $this->read($id, 'argo.json');
            $data = Json::decode($text, true);
            $item = new $class($id, $data);
            $item->setPrev($prev);

            if ($prev !== null) {
                $prev->setNext($item);
            }

            $items[$id] = $item;
            $prev = $item;
        }

        return $items;
    }

    public function trash(Item $item) : void
    {
        $this->assertType($item);
        $this->storage->trash($item->id);
    }

    protected function assertType(Item $item) : void
    {
        if (! $item instanceof $this->type) {
            throw new Exception("Wrong type: " . get_class($item));
        }
    }

    protected function getIdFromFile(string $file) : string
    {
        $root = $this->storage->path('/');
        return substr($file, strlen($root));
    }

    protected function glob() : array
    {
        return $this->storage->glob($this->getGlob());
    }

    protected function read(string $id, string $file) : ?string
    {
        try {
            ($this->type)::assertId($id);
        } catch (Exception\InvalidData $e) {
            return null;
        }

        return $this->storage->read("{$id}/{$file}");
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content;

use Argo\Domain\Content\Post\Post;
use Argo\Domain\DateTime;
use Argo\Domain\Json;
use JsonSerializable;

class Month implements JsonSerializable
{
    public static function getAllFromPosts(array $posts, DateTime $dateTime) : array
    {
        $months = [];

        foreach ($posts as $post) {
            $ym = str_replace('/', '-', substr($post->relId, 0, 7));
            if (! isset($months[$ym])) {
                $months[$ym] = new Month($ym, $dateTime->month($ym));
            }
            $months[$ym]->attachPost($post);
        }

        $list = array_values($months);
        foreach ($list as $i => $month) {
            $month->setPrev($list[$i - 1] ?? null);
            $month->setNext($list[$i + 1] ?? null);
        }

        return $months;
    }

    protected $href;

    protected $title;

    protected $posts = [];

    protected $prev;

    protected $next;

    public function __construct(string $ym, string $title)
    {
        $this->href = '/posts/month/' . str_replace('-', '/', $ym) . '/';
        $this->title = $title;
    }

    public function __get(string $key)
    {
        return $this->$key;
    }

    public function attachPost(Post $post) : void
    {
        $this->posts[] = $post;
    }

    public function setPrev(?Month $prev) : void
    {
        $this->prev = $prev;
    }

    public function setNext(?Month $next) : void
    {
        $this->next = $next;
    }

    public function jsonSerialize() /* : mixed */
    {
        return [
            'href' => $this->href,
            'title' => $this->title,
            'count' => count($this->posts),
        ];
    }

    public function toJson(array $extra = []) : string
    {
        $encoded = Json::encode($this);
        $decoded = Json::decode($encoded, true);
        return Json::encode($decoded + $extra);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Page;

use Argo\Domain\Content\Item;
use Argo\Domain\Exception;

class Page extends Item
{
    public static function assertId(string $id) : void
    {
        parent::assertId($id);

        $invalid = ['author', 'authors', 'category', 'categories', 'post', 'posts', 'tag', 'tags', 'theme'];
        if (in_array($id, $invalid)) {
            throw new Exception\InvalidData("Page ID disallowed: {$id}");
        }
    }

    public static function absId(string $relId) : string
    {
        return $relId;
    }

    protected function getRelId() : string
    {
        return $this->id;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Page;

use Argo\Domain\Content\ItemRepository;

class PageRepository extends ItemRepository
{
    protected function getGlob() : string
    {
        return '*/argo.json';
    }

    protected function glob() : array
    {
        return $this->recursiveGlob();
    }

    protected function recursiveGlob(string $id = '', array $list = []) : array
    {
        $pattern = trim("{$id}/" . $this->getGlob(), '/');
        $files = $this->storage->glob($pattern);
        foreach ($files as $file) {
            $file = dirname($file);
            $list[] = $file;
            $subId = $this->getIdFromFile($file);
            $list = $this->recursiveGlob($subId, $list);
        }
        return $list;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Post;

use Argo\Domain\Content\Item;
use Argo\Domain\DateTime;
use Argo\Domain\Content\Draft\Draft;
use Argo\Domain\Exception;

class Post extends Item
{
    public static function assertId(string $id) : void
    {
        parent::assertId($id);
        $regex = '/^post\/\d{4}\/\d{2}\/\d{2}\/[a-z0-9-]+$/';
        if (! preg_match($regex, $id)) {
            throw new Exception\InvalidData("Post ID is not valid: {$id}");
        }
    }

    public static function absId(string $relId) : string
    {
        return "post/{$relId}";
    }

    protected $postIndexKey;

    protected $lastUpdated;

    public function __construct(string $id, array $data = [])
    {
        parent::__construct($id, $data);
        $this->lastUpdated = end($this->data->updated);
    }

    public function setPostIndexKey(int $postIndexKey) : void
    {
        $this->postIndexKey = $postIndexKey;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Post;

use Argo\Date;
use Argo\Domain\Content\Item;
use Argo\Domain\Content\ItemRepository;

class PostRepository extends ItemRepository
{
    protected function getGlob() : string
    {
        return 'post/[0-9][0-9][0-9][0-9]/[0-9][0-9]/[0-9][0-9]/*';
    }

    protected function glob() : array
    {
        $glob = parent::glob();
        rsort($glob);
        return $glob;
    }

    public function getItems(?int $pageNum = null) : array
    {
        $items = parent::getItems($pageNum);

        uasort($items, function (Post $a, Post $b) {
            return $b->created <=> $a->created;
        });

        if ($pageNum === null) {
            foreach ($items as $item) {
                $item->setPostIndexKey($pageNum - 1);
            }
        } else {
            $i = 0;
            $perPage = $this->config->general->perPage;

            foreach ($items as $item) {
                $item->setPostIndexKey((int) ($i / $perPage));
                $i += 1;
            }
        }

        return $items;
    }

    public function save(Item $post, string $body = null) : void
    {
        parent::save($post, $body);
        $this->content->tags->adhocs($post->tags);
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content;

class PostIndex
{
    static public function getAllFromPosts(array $posts, int $perPage) : array
    {
        $indexes = [];
        $prev = null;
        $chunks = array_chunk($posts, $perPage);

        foreach ($chunks as $i => $chunk) {
            $pageNum = $i + 1;

            $href = ($pageNum === 1)
                ? "/posts/"
                : "/posts/{$pageNum}/";

            $curr = new PostIndex($chunk, $href);

            if ($prev !== null) {
                $curr->setPrev($prev);
                $prev->setNext($curr);
            }

            $indexes[] = $curr;
            $prev = $curr;
        }

        return $indexes;
    }

    protected $posts;

    protected $href;

    protected $prev;

    protected $next;

    protected $title;

    public function __construct(array $posts, string $href)
    {
        $this->posts = $posts;
        $this->href = $href;
    }

    public function __get(string $key)
    {
        return $this->$key;
    }

    public function setTitle(string $title) : void
    {
        $this->title = $title;
    }

    public function setPrev(?PostIndex $prev) : void
    {
        $this->prev = $prev;
    }

    public function setNext(?PostIndex $next) : void
    {
        $this->next = $next;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Tag;

use Argo\Domain\Content\Item;
use Argo\Domain\Content\Post\Post;
use Argo\Domain\Exception\InvalidData;

class Tag extends Item
{
    public static function absId(string $relId) : string
    {
        return "tag/{$relId}";
    }

    protected $posts = [];

    protected function fixData(array $data) : array
    {
        $data = parent::fixData($data);
        unset($data['tags']);
        return $data;
    }

    public function fill(array $data) : void
    {
        parent::fill($data);
        $this->data->title = $this->data->title ?? self::titleize($this->relId);
    }

    public function attachPost(Post $post)
    {
        $this->posts[] = $post;
    }
}


code:
<?php
declare(strict_types=1);

namespace Argo\Domain\Content\Tag;

use Argo\Domain\Content\ItemRepository;
use Argo\Exception;

class TagRepository extends ItemRepository
{
    protected function getGlob() : string
    {
        return 'tag/*';
    }

    /**
     * @todo blow up on bad relId
     */
    public function adhocs(array $relIds) : void
    {
        foreach ($relIds as $relId) {
            if ($this->getItem($relId) === null) {
                $id = Tag::absId($relId);
                $tag = new Tag($id);
                $this->save($tag, '');
            }
        }
    }

    /**
     * Note that this also converts $post->data->tags from an array of relIds
     * to an array of Tag objects.
     */
    public function getAllFromPosts(array $posts) : array
    {
        $tags = $this->getItems();
        ksort($tags);

        foreach ($posts as $post) {
            foreach ($post->tags as $i => $relId) {
                $id = Tag::absId($relId);
                $tags[$id]->attachPost($post);
                $post->data->tags[$i] = $tags[$id];
            }
        }

        return $tags;
    }
}