Finished base for new composer, Messed up old content index, Improved lang include
Update composer.php, content.php, and 6 more files...
This commit is contained in:
656
commons/composer.php
Normal file
656
commons/composer.php
Normal file
@@ -0,0 +1,656 @@
|
||||
<?php
|
||||
// Making sure the file is included and not accessed directly.
|
||||
/*if(basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
header('HTTP/1.1 403 Forbidden');
|
||||
die();
|
||||
}*/
|
||||
|
||||
// Including required helpers.
|
||||
include_once 'config.php';
|
||||
include_once 'langs.php';
|
||||
|
||||
// Defining some options.
|
||||
$USE_CONFIG_URI_FOR_OPENGRAPH = true;
|
||||
$AUTO_DETECT_OPENGRAPH_MIME = true;
|
||||
$LANG_FALLBACK_KEY_PREFIX = "content.fallback";
|
||||
|
||||
// Defining the template types.
|
||||
abstract class ComposerTemplates {
|
||||
const RAW = "raw";
|
||||
const ARTICLE = "article";
|
||||
|
||||
/**
|
||||
* Returns all the constants present in the class.
|
||||
* @return array All the constants in as "[[k, v], [k, v], ...]".
|
||||
* @see https://www.php.net/manual/en/reflectionclass.getconstants.php
|
||||
*/
|
||||
static function getConstants(): array {
|
||||
$oClass = new ReflectionClass(__CLASS__);
|
||||
return $oClass->getConstants();
|
||||
}
|
||||
}
|
||||
|
||||
// Defining the different types of elements available.
|
||||
abstract class ComposerElementTypes {
|
||||
const UNSET = "unset";
|
||||
const H1 = "h1";
|
||||
const H2 = "h2";
|
||||
const H3 = "h3";
|
||||
const PARAGRAPH = "paragraph";
|
||||
const BUTTON = "button";
|
||||
const CODE = "code";
|
||||
const HR = "hr";
|
||||
const CONTAINER = "container";
|
||||
const COLLAPSE = "collapse";
|
||||
const SPACER = "spacer";
|
||||
const IMAGE = "image";
|
||||
const TABLE = "table";
|
||||
const GRID = "grid";
|
||||
const GLIDER = "glider";
|
||||
|
||||
/**
|
||||
* Returns all the constants present in the class.
|
||||
* @return array All the constants in as "[[k, v], [k, v], ...]".
|
||||
* @see https://www.php.net/manual/en/reflectionclass.getconstants.php
|
||||
*/
|
||||
static function getConstants(): array {
|
||||
$oClass = new ReflectionClass(__CLASS__);
|
||||
return $oClass->getConstants();
|
||||
}
|
||||
}
|
||||
|
||||
// Defining modifiers.
|
||||
abstract class ComposerElementModifiersTest {
|
||||
// Generic > Margin
|
||||
const GENERIC_MARGIN_NO_TOP = ["no-top-margin", "mt-0"];
|
||||
const GENERIC_MARGIN_NO_BOTTOM = ["no-bottom-margin", "mb-0"];
|
||||
const GENERIC_MARGIN_NO_LEFT = ["no-left-margin", "ml-0"];
|
||||
const GENERIC_MARGIN_NO_RIGHT = ["no-right-margin", "mr-0"];
|
||||
const GENERIC_MARGIN_NO_X = ["no-y-margin", "mx-0"];
|
||||
const GENERIC_MARGIN_NO_Y = ["no-x-margin", "my-0"];
|
||||
const GENERIC_MARGIN_NONE = ["no-margin", "m-0" ];
|
||||
|
||||
// Generic > Padding
|
||||
const GENERIC_PADDING_NO_TOP = ["no-top-padding", "pt-0"];
|
||||
const GENERIC_PADDING_NO_BOTTOM = ["no-bottom-padding", "pb-0"];
|
||||
const GENERIC_PADDING_NO_LEFT = ["no-left-padding", "pl-0"];
|
||||
const GENERIC_PADDING_NO_RIGHT = ["no-right-padding", "pr-0"];
|
||||
const GENERIC_PADDING_NO_X = ["no-y-padding", "px-0"];
|
||||
const GENERIC_PADDING_NO_Y = ["no-x-padding", "py-0"];
|
||||
const GENERIC_PADDING_NONE = ["no-padding", "p-0" ];
|
||||
|
||||
// Containers
|
||||
const CONTAINER_SCROLL_HORIZONTAL = ["h-scroll", "overflow-x-scroll hide-scrollbar"];
|
||||
const CONTAINER_CARD = ["card", "card"];
|
||||
|
||||
// Buttons
|
||||
const BUTTON_THIN = ["thin", "?thin"];
|
||||
const BUTTON_THICK = ["thick", "?thick"];
|
||||
|
||||
// Horizontal ruler
|
||||
const HR_SUBTLE = ["subtle", "subtle"];
|
||||
|
||||
// Other internal constants
|
||||
const _INDEX_KEY = 0;
|
||||
const _INDEX_CLASSES = 1;
|
||||
|
||||
/**
|
||||
* Returns all the constants present in the class.
|
||||
* @return array All the constants in as "[[k, v], [k, v], ...]".
|
||||
* @see https://www.php.net/manual/en/reflectionclass.getconstants.php
|
||||
*/
|
||||
static function getConstants(): array {
|
||||
$oClass = new ReflectionClass(__CLASS__);
|
||||
return $oClass->getConstants();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given modifier's constant's key
|
||||
* @param array $modifier_data A modifier constant defined in "ComposerElementModifiersTest".
|
||||
* @return string The modifier's key or an empty string if an error was encountered.
|
||||
*/
|
||||
static function get_modifier_key(array $modifier_data) : string {
|
||||
return sizeof($modifier_data) >= 1 ? $modifier_data[ComposerElementModifiersTest::_INDEX_KEY] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given modifier's constant's classes
|
||||
* @param array $modifier_data A modifier constant defined in "ComposerElementModifiersTest".
|
||||
* @return string The modifier's classes or an empty string if an error was encountered.
|
||||
*/
|
||||
static function get_modifier_classes(array $modifier_data) : string {
|
||||
return sizeof($modifier_data) >= 2 ? $modifier_data[ComposerElementModifiersTest::_INDEX_CLASSES] : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $modifier_key
|
||||
* @return string The resolved DOM classes, or an empty string if the given modifier is unknown.
|
||||
*/
|
||||
static function getClassesFromKey(string $modifier_key) : string {
|
||||
foreach(ComposerElementModifiersTest::getConstants() as $constant_values) {
|
||||
if(!is_array($constant_values)) {
|
||||
continue;
|
||||
}
|
||||
if($modifier_key == $constant_values[ComposerElementModifiersTest::_INDEX_KEY]) {
|
||||
return $constant_values[ComposerElementModifiersTest::_INDEX_CLASSES];
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
static function is_modifier_in_modifiers(array $modifier_data, array $modifiers) : bool {
|
||||
foreach($modifiers as $modifier) {
|
||||
if($modifier_data[ComposerElementModifiersTest::_INDEX_KEY] == $modifier) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Data classes
|
||||
class ComposerContent {
|
||||
public array $strings;
|
||||
public ComposerContentMetadata $metadata;
|
||||
public array $elements;
|
||||
|
||||
function __construct(array $strings, ComposerContentMetadata $metadata, array $elements) {
|
||||
$this->strings = $strings;
|
||||
$this->metadata = $metadata;
|
||||
$this->elements = $elements;
|
||||
}
|
||||
|
||||
static function from_json(array $json_data) : ComposerContent {
|
||||
global $default_language;
|
||||
return new ComposerContent(
|
||||
key_exists("strings", $json_data) ? $json_data["strings"] : array($default_language=>[]),
|
||||
ComposerContentMetadata::from_json(
|
||||
key_exists("metadata", $json_data) ? $json_data["metadata"] : array()
|
||||
),
|
||||
key_exists("elements", $json_data) ?
|
||||
ComposerElement::from_json_array($json_data["elements"]) : array()
|
||||
);
|
||||
}
|
||||
|
||||
public function get_html() : string {
|
||||
$htmlCode = "";
|
||||
|
||||
// FIXME: Check for the template after the loop
|
||||
|
||||
foreach($this->elements as $element) {
|
||||
/** @var ComposerElement $element */
|
||||
$htmlCode .= $element->get_html($this);
|
||||
}
|
||||
|
||||
return $this->metadata->apply_template($htmlCode);
|
||||
}
|
||||
}
|
||||
|
||||
class ComposerContentMetadata {
|
||||
public string $title;
|
||||
public string $description;
|
||||
public string $template;
|
||||
public ComposerContentMetadataOpengraph $opengraph;
|
||||
public ?ComposerContentMetadataArticle $article;
|
||||
|
||||
function __construct(string $title, string $description, string $template, ComposerContentMetadataOpengraph $opengraph,
|
||||
?ComposerContentMetadataArticle $article) {
|
||||
$this->title = $title;
|
||||
$this->description = $description;
|
||||
$this->template = $template;
|
||||
$this->opengraph = $opengraph;
|
||||
$this->article = $article;
|
||||
|
||||
// Safety checks.
|
||||
if($this->template == ComposerTemplates::ARTICLE && is_null($this->article)) {
|
||||
$this->article = ComposerContentMetadataArticle::from_json([]);
|
||||
}
|
||||
}
|
||||
|
||||
static function from_json(array $json_data) : ComposerContentMetadata {
|
||||
return new ComposerContentMetadata(
|
||||
key_exists("title", $json_data) ? $json_data["title"] : "",
|
||||
key_exists("description", $json_data) ? $json_data["description"] : "",
|
||||
key_exists("template", $json_data) ? $json_data["template"] : "",
|
||||
ComposerContentMetadataOpengraph::from_json(
|
||||
key_exists("opengraph", $json_data) ? $json_data["opengraph"] : array()
|
||||
),
|
||||
key_exists("article", $json_data) ?
|
||||
ComposerContentMetadataArticle::from_json($json_data["article"]) : null,
|
||||
);
|
||||
}
|
||||
|
||||
function apply_template(string $inner_html) : string {
|
||||
switch($this->template) {
|
||||
case ComposerTemplates::ARTICLE:
|
||||
$inner_html = '<div class="card p-0 mx-0"><div class="px-card py-10 border-bottom px-20">' .
|
||||
'<div class="container-fluid"><h2 class="card-title font-size-18 m-0">' .
|
||||
'<i class="' . $this->article->icon . '"></i> ' . localize($this->article->title) .
|
||||
'<span class="card-title font-size-18 m-0 text-super-muted float-right hidden-xs-and-down">' .
|
||||
'\$subTitle' . '</span></h2></div></div>' .
|
||||
'<article id="content-item-container" class="py-01 pb-0 bg-light-lm rounded-bottom px-0 bg-very-dark title-bkgd">' .
|
||||
$inner_html . '</article>' .
|
||||
'<div class="px-20 py-10 bg-light-lm bg-dark-dm rounded-bottom border-top">' .
|
||||
'<div class="content-tag-container"><i class="fad fa-tags"></i>';
|
||||
|
||||
if(sizeof($this->article->tags) > 0) {
|
||||
foreach($this->article->tags as $tag) {
|
||||
$inner_html .= '<a href="'.l10n_url_abs("/content/?tags=" . $tag .
|
||||
'" class="content-tag">#' . $tag . '</a>');
|
||||
}
|
||||
} else {
|
||||
$inner_html .= '<i>' . localize("error.content.data.no.tags") . '</i>';
|
||||
}
|
||||
|
||||
$inner_html .= '</div></div></div>';
|
||||
break;
|
||||
case ComposerTemplates::RAW:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return $inner_html;
|
||||
}
|
||||
|
||||
/*function start_content_card($iconClasses, $title, $subTitle) {
|
||||
echo('<div class="card p-0 mx-0"><div class="px-card py-10 border-bottom px-20"><div class="container-fluid">');
|
||||
echo('<h2 class="card-title font-size-18 m-0"><i class="'.$iconClasses.'"></i> '.localize($title));
|
||||
echo('<span class="card-title font-size-18 m-0 text-super-muted float-right hidden-xs-and-down">'.$subTitle.'</span></h2>');
|
||||
echo('</div></div>');
|
||||
}
|
||||
|
||||
function end_content_card() {
|
||||
echo('</div>');
|
||||
}*/
|
||||
}
|
||||
|
||||
class ComposerContentMetadataOpengraph {
|
||||
private ?string $title;
|
||||
private ?string $description;
|
||||
private ?string $type;
|
||||
private ?string $url;
|
||||
private ?string $image;
|
||||
private ?string $imageType;
|
||||
|
||||
function __construct(?string $title, ?string $description, ?string $type, ?string $url, ?string $image,
|
||||
?string $imageType) {
|
||||
$this->title = $title;
|
||||
$this->description = $description;
|
||||
$this->type = $type;
|
||||
$this->url = $url;
|
||||
$this->image = $image;
|
||||
$this->imageType = $imageType;
|
||||
}
|
||||
|
||||
static function from_json(array $json_data) : ComposerContentMetadataOpengraph {
|
||||
return new ComposerContentMetadataOpengraph(
|
||||
key_exists("title", $json_data) ? $json_data["title"] : null,
|
||||
key_exists("description", $json_data) ? $json_data["description"] : null,
|
||||
key_exists("type", $json_data) ? $json_data["type"] : null,
|
||||
key_exists("url", $json_data) ? $json_data["url"] : null,
|
||||
key_exists("image", $json_data) ? $json_data["image"] : null,
|
||||
key_exists("imageType", $json_data) ? $json_data["imageType"] : null,
|
||||
);
|
||||
}
|
||||
|
||||
public function __get($property) {
|
||||
return is_null($this->$property) ? "" : $this->$property;
|
||||
}
|
||||
}
|
||||
|
||||
class ComposerContentMetadataArticle {
|
||||
public string $icon;
|
||||
public string $title;
|
||||
public array $tags;
|
||||
|
||||
function __construct(string $icon, string $title, array $tags) {
|
||||
$this->icon = $icon;
|
||||
$this->title = $title;
|
||||
$this->tags = $tags;
|
||||
}
|
||||
|
||||
static function from_json(array $json_data) : ComposerContentMetadataArticle {
|
||||
return new ComposerContentMetadataArticle(
|
||||
key_exists("icon", $json_data) ? $json_data["icon"] : "fad fa-question",
|
||||
key_exists("title", $json_data) ?
|
||||
$json_data["title"] : '<i>'.localize("error.content.data.no.title").'</i>',
|
||||
key_exists("tags", $json_data) ? $json_data["tags"] : [],
|
||||
);
|
||||
}
|
||||
|
||||
public function __get($property) {
|
||||
return is_null($this->$property) ? "" : $this->$property;
|
||||
}
|
||||
}
|
||||
|
||||
class ComposerElement {
|
||||
// Global parameters
|
||||
private string $type;
|
||||
private ?array $modifiers;
|
||||
|
||||
// Any direct element-container
|
||||
private ?array $parts;
|
||||
|
||||
// Any direct text-container
|
||||
private ?string $content;
|
||||
private bool $localize;
|
||||
|
||||
// Generic modifier values
|
||||
private ?int $padding;
|
||||
private ?int $margin;
|
||||
|
||||
// Spacer's size
|
||||
private ?int $size;
|
||||
|
||||
// Table's parameters
|
||||
private ?array $head;
|
||||
private ?array $body;
|
||||
private ?int $colspan;
|
||||
private ?int $rowspan;
|
||||
|
||||
// Paragraph and code's parameters
|
||||
private ?int $indent;
|
||||
|
||||
// Code's parameters
|
||||
private ?array $code;
|
||||
|
||||
function __construct(string $type, ?array $modifiers, ?array $parts, ?string $content, bool $localize,
|
||||
?int $padding, ?int $margin, ?int $size, ?array $head, ?array $body, ?int $colspan,
|
||||
?int $rowspan, ?int $indent, ?array $code) {
|
||||
$this->type = $type;
|
||||
$this->modifiers = $modifiers;
|
||||
$this->parts = $parts;
|
||||
$this->content = $content;
|
||||
$this->localize = $localize;
|
||||
$this->padding = $padding;
|
||||
$this->margin = $margin;
|
||||
$this->size = $size;
|
||||
$this->head = $head;
|
||||
$this->body = $body;
|
||||
$this->colspan = $colspan;
|
||||
$this->rowspan = $rowspan;
|
||||
$this->indent = $indent;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
static function from_json_array(array $json_dataArray) : array {
|
||||
$parts = array();
|
||||
foreach($json_dataArray as $part) {
|
||||
$parts[] = ComposerElement::from_json($part);
|
||||
}
|
||||
return $parts;
|
||||
}
|
||||
|
||||
static function from_json(array $json_data) : ComposerElement {
|
||||
return new ComposerElement(
|
||||
key_exists("type", $json_data) ? $json_data["type"] : ComposerElementTypes::UNSET,
|
||||
key_exists("modifiers", $json_data) ? $json_data["modifiers"] : null,
|
||||
key_exists("parts", $json_data) ? ComposerElement::from_json_array($json_data["parts"]) : null,
|
||||
key_exists("content", $json_data) ? $json_data["content"] : null,
|
||||
key_exists("localize", $json_data) ? $json_data["localize"] : true,
|
||||
key_exists("padding", $json_data) ? $json_data["padding"] : null,
|
||||
key_exists("margin", $json_data) ? $json_data["margin"] : null,
|
||||
key_exists("size", $json_data) ? $json_data["size"] : null,
|
||||
key_exists("head", $json_data) ? $json_data["head"] : null,
|
||||
key_exists("body", $json_data) ? $json_data["body"] : null,
|
||||
key_exists("colspan", $json_data) ? $json_data["colspan"] : null,
|
||||
key_exists("rowspan", $json_data) ? $json_data["rowspan"] : null,
|
||||
key_exists("indent", $json_data) ? $json_data["indent"] : null,
|
||||
key_exists("code", $json_data) ? $json_data["code"] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the "content" and "parts" class' variables and returns their interpreted content as HTML.
|
||||
* @param ComposerContent $contentRoot The content in which this element is contained.
|
||||
* @param bool $doLocalization Whether the "content" variable should be processed to return localized text.
|
||||
* @param bool $doSubElements Whether the "parts" variable should be processed to return localized text.
|
||||
* @param bool $stopIfLocalized Whether the process should return if some text was in the "content" variable.
|
||||
* @return string The interpreted content as HTML.
|
||||
*/
|
||||
private function get_inner_html(ComposerContent $contentRoot, bool $doLocalization = true,
|
||||
bool $doSubElements = true, bool $stopIfLocalized = true) : string {
|
||||
global $LANG_FALLBACK_KEY_PREFIX;
|
||||
|
||||
$htmlCode = "";
|
||||
$wasTextLocalized = false;
|
||||
|
||||
if($doLocalization) {
|
||||
// Checking if "content" was declared.
|
||||
if(is_null($this->content) && !$doSubElements) {
|
||||
return "<p>error.no.inner.content</p>";
|
||||
}
|
||||
|
||||
// Checking if there is something to process.
|
||||
if(!empty($this->content)) {
|
||||
$wasTextLocalized = true;
|
||||
|
||||
if(!$this->localize) {
|
||||
$htmlCode .= $this->content;
|
||||
} else {
|
||||
// We can now localize the content key.
|
||||
$htmlCode .= localize_private($this->content, $contentRoot->strings, true,
|
||||
$LANG_FALLBACK_KEY_PREFIX);
|
||||
}
|
||||
}
|
||||
|
||||
// Checking for early stop.
|
||||
if($wasTextLocalized && $stopIfLocalized) {
|
||||
return $htmlCode;
|
||||
}
|
||||
}
|
||||
|
||||
if($doSubElements) {
|
||||
// Checking if "parts" was declared.
|
||||
if(is_null($this->parts)) {
|
||||
if(!$wasTextLocalized) {
|
||||
$htmlCode = "<p>error.no.inner.parts</p>";
|
||||
}
|
||||
return $htmlCode;
|
||||
}
|
||||
|
||||
// Appending each sub-element.
|
||||
foreach($this->parts as $subElement) {
|
||||
/** @var ComposerElement $subElement */
|
||||
$htmlCode .= $subElement->get_html($contentRoot);
|
||||
}
|
||||
}
|
||||
|
||||
return $htmlCode;
|
||||
}
|
||||
|
||||
private function get_inner_html_elements(ComposerContent $contentRoot) : string {
|
||||
return $this->get_inner_html($contentRoot, false, true, false);
|
||||
}
|
||||
|
||||
private function get_inner_html_text(ComposerContent $contentRoot) : string {
|
||||
return $this->get_inner_html($contentRoot, true, false, false);
|
||||
}
|
||||
|
||||
private function get_modifiers_classes() : string {
|
||||
if(!is_null($this->modifiers)) {
|
||||
$classes = "";
|
||||
|
||||
// Combining classes.
|
||||
foreach($this->modifiers as $modifier) {
|
||||
/** @var string $modifier */
|
||||
$classes .= ComposerElementModifiersTest::getClassesFromKey($modifier) . ' ';
|
||||
}
|
||||
|
||||
// Removing redundant and useless spaces.
|
||||
return preg_replace('/\s+/', ' ', trim($classes));
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes the element and returns its interpreted form as HTML.
|
||||
* @param ComposerContent $contentRoot The content in which this element is contained.
|
||||
* @return string The interpreted element as HTML.
|
||||
*/
|
||||
public function get_html(ComposerContent $contentRoot) : string {
|
||||
$htmlCode = "";
|
||||
|
||||
switch($this->type) {
|
||||
case ComposerElementTypes::UNSET:
|
||||
$htmlCode .= "<p>error.unset !</p>";
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::H1:
|
||||
case ComposerElementTypes::H2:
|
||||
case ComposerElementTypes::H3:
|
||||
// Defining the text's size.
|
||||
$_headingFontSize = ($this->type == ComposerElementTypes::H3 ? '18' : (
|
||||
$this->type == ComposerElementTypes::H2 ? '20' : '22'
|
||||
));
|
||||
|
||||
// Composing heading.
|
||||
$htmlCode .= '<' . strtolower($this->type) . ' class="font-weight-semi-bold font-size-' .
|
||||
$_headingFontSize . ' m-0">' . $this->get_inner_html($contentRoot) . '</' . strtolower($this->type) .
|
||||
'>';
|
||||
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::PARAGRAPH:
|
||||
// Defining the text's indent level.
|
||||
$_paragraph_ident_level = is_null($this->indent) ? 0 : $this->indent;
|
||||
|
||||
// Composing the paragraph
|
||||
$htmlCode .= '<p class="' .
|
||||
(ComposerElementModifiersTest::is_modifier_in_modifiers(
|
||||
ComposerElementModifiersTest::GENERIC_MARGIN_NO_TOP, $this->modifiers)
|
||||
? 'mt-0 mb-10' : 'my-10') . ' ml-md-' . ($_paragraph_ident_level * 5) . '">' .
|
||||
$this->get_inner_html($contentRoot) . '</p>';
|
||||
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::BUTTON:
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::CODE:
|
||||
// Defining the code's indent level.
|
||||
$_paragraph_ident_level = is_null($this->indent) ? 0 : $this->indent;
|
||||
|
||||
// Opening the code element.
|
||||
$htmlCode .= '<code class="code ' . $this->get_modifiers_classes() .
|
||||
' ml-md-' . ($_paragraph_ident_level * 5) . '">';
|
||||
|
||||
// Adding code lines.
|
||||
if(!is_null($this->code)) {
|
||||
foreach($this->code as $code_line) {
|
||||
$htmlCode .= htmlspecialchars($code_line) . '<br>';
|
||||
}
|
||||
}
|
||||
|
||||
// Closing code element.
|
||||
$htmlCode .= '</code>';
|
||||
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::HR:
|
||||
// Getting the modifiers' classes
|
||||
$_hr_classes = $this->get_modifiers_classes();
|
||||
|
||||
// Composing the element.
|
||||
if(empty($_hr_classes)) {
|
||||
$htmlCode .= '<div class="sidebar-divider"></div>';
|
||||
} else {
|
||||
$htmlCode .= '<hr class="' . $_hr_classes . '">';
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::CONTAINER:
|
||||
// Defining the padding's size.
|
||||
$_container_padding = is_null($this->padding) ? 10 : $this->padding;
|
||||
|
||||
// Getting the modifiers' classes
|
||||
$_container_classes = $this->get_modifiers_classes();
|
||||
|
||||
// Composing the container.
|
||||
$htmlCode .= '<div class="mt-10 p-' . $_container_padding .
|
||||
(empty($_container_classes) ? '' : ' ' . $_container_classes) . '">' .
|
||||
$this->get_inner_html($contentRoot) . '</div>';
|
||||
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::COLLAPSE:
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::SPACER:
|
||||
// Defining the spacer's size.
|
||||
$_spacer_size = is_null($this->size) ? 1 : $this->size;
|
||||
|
||||
// Composing spacer.
|
||||
$htmlCode .= '<div class="m-0 pt-'.($_spacer_size*5).' pb-md-'.($_spacer_size*5).'"></div>';
|
||||
|
||||
break;
|
||||
|
||||
case ComposerElementTypes::IMAGE:
|
||||
break;
|
||||
case ComposerElementTypes::TABLE:
|
||||
break;
|
||||
case ComposerElementTypes::GRID:
|
||||
break;
|
||||
case ComposerElementTypes::GLIDER:
|
||||
break;
|
||||
|
||||
default:
|
||||
$htmlCode .= "<p>error.unknown !</p>";
|
||||
break;
|
||||
}
|
||||
|
||||
return $htmlCode;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Generic functions
|
||||
function get_content_error(string $error_title_key, string $error_description_key) : ?ComposerContent {
|
||||
// FIXME: Make this non-nullable !!!
|
||||
return null;
|
||||
}
|
||||
|
||||
function get_content_file_path(string $content_id) : ?string {
|
||||
global $dir_content;
|
||||
|
||||
if(ctype_alnum(str_replace("-", "", $content_id))) {
|
||||
return realpath($dir_content . "/items/" . $content_id . ".json");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function load_content_by_file_path(string $file_path) : ?ComposerContent {
|
||||
// FIXME: Add handling for JSON errors !
|
||||
$content_json_data = json_decode(file_get_contents($file_path), true);
|
||||
return ComposerContent::from_json($content_json_data);
|
||||
}
|
||||
|
||||
function load_content_by_id(string $content_id) : ?ComposerContent {
|
||||
$content_file_path = get_content_file_path($content_id);
|
||||
|
||||
if(is_null($content_file_path)) {
|
||||
return null;
|
||||
} else {
|
||||
return load_content_by_file_path($content_file_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Test
|
||||
if(basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
$content = load_content_by_id("test2");
|
||||
|
||||
if(!is_null($content)) {
|
||||
echo "<pre>";
|
||||
print_r(htmlspecialchars($content->get_html()));
|
||||
echo "</pre>";
|
||||
|
||||
echo "<pre>";
|
||||
print_r($content);
|
||||
echo "</pre>";
|
||||
}
|
||||
|
||||
echo("<br>");
|
||||
}
|
||||
?>
|
@@ -5,12 +5,11 @@ if(basename(__FILE__) == basename($_SERVER["SCRIPT_FILENAME"])) {
|
||||
die();
|
||||
}
|
||||
|
||||
// Importing required scripts
|
||||
// Importing required scripts.
|
||||
include_once 'langs.php';
|
||||
include_once 'composer.php';
|
||||
|
||||
// This helper requires PHP 8 or newer !
|
||||
|
||||
$SHOW_CONTENT_DEBUG_CARD = false;
|
||||
// Defining some options.
|
||||
$PRINT_CONTENT_DEBUG_INFO_TEXT_ELEMENTS = true;
|
||||
$PRINT_CONTENT_DEBUG_ERROR_TEXT_ELEMENTS = true;
|
||||
|
||||
@@ -30,6 +29,7 @@ $requested_tags = array();
|
||||
$raw_additional_tags = "";
|
||||
$filtered_content_index_data = NULL;
|
||||
$requested_item_data = NULL;
|
||||
$content = null;
|
||||
|
||||
// Detecting content display type requested.
|
||||
$content_requested_url_part = explode("?", explode("#", preg_replace("^\/(content)^", "", l10n_url_switch(NULL)))[0])[0];
|
||||
@@ -93,7 +93,7 @@ if($requested_content_display_type == ContentDisplayType::SEARCH) {
|
||||
$content_error_message_key = "error.content.detect.empty";
|
||||
goto content_end;
|
||||
}
|
||||
} elseif($requested_content_display_type == ContentDisplayType::CONTENT) {
|
||||
} else if($requested_content_display_type == ContentDisplayType::CONTENT) {
|
||||
// Sanitizing the requested ID.
|
||||
if(!ctype_alnum(str_replace("-", "", $content_requested_url_part))) {
|
||||
$content_has_error = true;
|
||||
@@ -101,37 +101,46 @@ if($requested_content_display_type == ContentDisplayType::SEARCH) {
|
||||
goto content_end;
|
||||
}
|
||||
|
||||
// Loading the content's data.
|
||||
$content_file_path = realpath($dir_content . "/items/".$content_requested_url_part.".json");
|
||||
if($content_file_path) {
|
||||
// FIXME: Handle JSON errors cleanly
|
||||
$content_json = file_get_contents($content_file_path);
|
||||
$requested_item_data = json_decode($content_json, true);
|
||||
unset($content_json);
|
||||
} else {
|
||||
// Loading the content's data
|
||||
$content_file_path = get_content_file_path($content_requested_url_part);
|
||||
|
||||
if(is_null($content_file_path)) {
|
||||
// File doesn't exist !
|
||||
$content_has_error = true;
|
||||
$content_error_message_key = "error.content.data.not.exist";
|
||||
unset($content_file_path);
|
||||
goto content_end;
|
||||
} else {
|
||||
$content = load_content_by_file_path($content_file_path);
|
||||
|
||||
if(is_null($content)) {
|
||||
$content_has_error = true;
|
||||
$content_error_message_key = "error.content.cannot.load";
|
||||
unset($content_file_path);
|
||||
goto content_end;
|
||||
}
|
||||
}
|
||||
|
||||
unset($content_file_path);
|
||||
}
|
||||
|
||||
content_end:
|
||||
// TODO: Create error thingy
|
||||
$content_error_message = localize($content_error_message_key);
|
||||
|
||||
// These functions are placed here to prevent the main file from becoming impossible to read.
|
||||
function startMainCard($iconClasses, $title, $subTitle) {
|
||||
function start_content_card($iconClasses, $title, $subTitle) {
|
||||
echo('<div class="card p-0 mx-0"><div class="px-card py-10 border-bottom px-20"><div class="container-fluid">');
|
||||
echo('<h2 class="card-title font-size-18 m-0"><i class="'.$iconClasses.'"></i> '.localize($title));
|
||||
echo('<span class="card-title font-size-18 m-0 text-super-muted float-right hidden-xs-and-down">'.$subTitle.'</span></h2>');
|
||||
echo('</div></div>');
|
||||
}
|
||||
|
||||
function endMainCard() {
|
||||
function end_content_card() {
|
||||
echo('</div>');
|
||||
}
|
||||
|
||||
function getContentItemText(array $contentNode, bool $italicOnError = true, bool $returnMissingAsEmpty = false) : string {
|
||||
/*function getContentItemText(array $contentNode, bool $italicOnError = true, bool $returnMissingAsEmpty = false) : string {
|
||||
global $user_language, $default_language;
|
||||
if(array_key_exists("key", $contentNode)) {
|
||||
return localize($contentNode["key"]);
|
||||
@@ -607,6 +616,6 @@ function createElementNode(mixed $elementNode, string $prepend="", string $appen
|
||||
printErrorTextElement(sprintf(localize("error.content.data.part.unknown"), $elementNode["type"]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
?>
|
||||
|
@@ -33,26 +33,41 @@ if(str_starts_with($_SERVER['REQUEST_URI'], "/en/")) {
|
||||
$lang_json = file_get_contents(realpath($dir_commons . "/strings.json"));
|
||||
$lang_data = json_decode($lang_json, true);
|
||||
|
||||
// Localizer function
|
||||
function localize($stringKey) {
|
||||
// Localization functions
|
||||
function localize_private(string $string_key, array $private_lang_data, bool $fallback_to_common = false,
|
||||
string $fallback_prefix = "fallback.unknown") : string {
|
||||
global $user_language, $default_language, $lang_data;
|
||||
if(array_key_exists($stringKey, $lang_data[$user_language])) {
|
||||
return $lang_data[$user_language][$stringKey];
|
||||
} else {
|
||||
if(array_key_exists($stringKey, $lang_data[$default_language])) {
|
||||
return $lang_data[$default_language][$stringKey];
|
||||
} else {
|
||||
return $stringKey;
|
||||
|
||||
if(array_key_exists($user_language, $private_lang_data)) {
|
||||
if(array_key_exists($string_key, $private_lang_data[$user_language])) {
|
||||
// If found in direct array in user's language.
|
||||
return $private_lang_data[$user_language][$string_key];
|
||||
}
|
||||
} else if(array_key_exists($default_language, $private_lang_data)) {
|
||||
if(array_key_exists($string_key, $private_lang_data[$default_language])) {
|
||||
// If found in direct array in default language.
|
||||
return $private_lang_data[$default_language][$string_key];
|
||||
}
|
||||
} else if($fallback_to_common) {
|
||||
// If we can attempt to fallback on the common lang file.
|
||||
return localize_private($fallback_prefix . "." . $string_key, $lang_data, false);
|
||||
}
|
||||
|
||||
// If nothing could be done, we simply return the key.
|
||||
return $string_key;
|
||||
}
|
||||
|
||||
function l10n_url_abs($url) {
|
||||
function localize($string_key) : string {
|
||||
global $lang_data;
|
||||
return localize_private($string_key, $lang_data, false);
|
||||
}
|
||||
|
||||
function l10n_url_abs($url) : string {
|
||||
global $user_uri_language;
|
||||
return $user_uri_language . $url;
|
||||
}
|
||||
|
||||
function l10n_url_switch($lang) {
|
||||
function l10n_url_switch($lang) : string {
|
||||
if(is_null($lang)) {
|
||||
return preg_replace("^\/(lb|lu|fr|en)^", "", $_SERVER['REQUEST_URI']);
|
||||
} else {
|
||||
|
@@ -23,7 +23,7 @@
|
||||
"fr": "Conteneur et application Python hautement configurable qui permet d'automatiquement archiver et télécharger des livestreams et vidéos depuis YouTube."
|
||||
},
|
||||
"image": "/resources/Azias/imgs/yaa/icon-final.png",
|
||||
"tags": ["docker", "web", "python"]
|
||||
"tags": ["docker", "application", "web", "python"]
|
||||
},
|
||||
{
|
||||
"id": "excel-worksheet-password-remover",
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
// Includes
|
||||
// Importing required scripts.
|
||||
set_include_path('../commons/');
|
||||
include_once 'config.php';
|
||||
include_once 'langs.php';
|
||||
@@ -33,7 +33,7 @@ if($content_has_error) {
|
||||
} else {
|
||||
$_metaTitle = localize("error.content.data.no.title");
|
||||
$_metaDescription = localize("error.content.data.no.description");
|
||||
if($requested_content_display_type == ContentDisplayType::CONTENT) {
|
||||
/*if($requested_content_display_type == ContentDisplayType::CONTENT) {
|
||||
if(array_key_exists("meta", $requested_item_data)) {
|
||||
if(array_key_exists("title", $requested_item_data["meta"])) {
|
||||
$_metaTitle = getContentItemText($requested_item_data["meta"]["title"]);
|
||||
@@ -45,8 +45,9 @@ if($content_has_error) {
|
||||
} else {
|
||||
$_metaTitle = localize("content.title.search.card");
|
||||
$_metaDescription = "";
|
||||
}
|
||||
}/**/
|
||||
}
|
||||
//$content->metadata->title
|
||||
|
||||
// Printing the title, meta and opengraph tags.
|
||||
echo('<title>'.$_metaTitle.' - Nibble Poker</title>');
|
||||
@@ -71,21 +72,15 @@ if($content_has_error) {
|
||||
<i class="fad fa-briefcase"></i> <?php
|
||||
echo('<span class="hidden-xs-and-down">');
|
||||
if($content_has_error) {
|
||||
if(isset($_SERVER['HTTP_REFERER'])) {
|
||||
echo('<a href="'.$_SERVER['HTTP_REFERER'].'">'.localize("content.title.content").'</a>');
|
||||
} else {
|
||||
//echo('<a class="js-set-previous-url" href="#">'.localize("content.title.content").'</a>');
|
||||
echo(localize("content.title.content"));
|
||||
}
|
||||
echo('<span class="mx-10">❱</span>'.localize("content.title.error"));
|
||||
echo(localize("content.title.content").'<span class="mx-10">❱</span>'.localize("content.title.error"));
|
||||
} elseif($requested_content_display_type == ContentDisplayType::SEARCH) {
|
||||
echo(localize("content.title.content").'<span class="mx-10">❱</span>'.localize("content.title.search.header"));
|
||||
} elseif($requested_content_display_type == ContentDisplayType::CONTENT) {
|
||||
$_nav_title_text = '<i>' . localize("error.content.data.no.title") . '</i>';
|
||||
|
||||
if (array_key_exists("page", $requested_item_data["title"])) {
|
||||
$_nav_title_text = getContentItemText($requested_item_data["title"]["page"]);
|
||||
}
|
||||
//if (array_key_exists("page", $requested_item_data["title"])) {
|
||||
// $_nav_title_text = getContentItemText($requested_item_data["title"]["page"]);
|
||||
//}
|
||||
|
||||
echo(localize("content.title.content").'<span class="mx-10">❱</span></span>'.$_nav_title_text);
|
||||
}
|
||||
@@ -95,47 +90,12 @@ if($content_has_error) {
|
||||
</div>
|
||||
<div class="content mx-auto w-lg-p90">
|
||||
<?php
|
||||
if($SHOW_CONTENT_DEBUG_CARD) {
|
||||
// ################
|
||||
// Debugging card
|
||||
// ################
|
||||
echo('<div class="card p-0 mx-0">
|
||||
<div class="px-card py-10 border-bottom px-20">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<h2 class="card-title font-size-18 m-0"><i class="fad fa-debug"></i> Debug</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-card py-20 bg-light-lm rounded-bottom px-20 bg-very-dark title-bkgd">');
|
||||
echo('<p class="m-0 mb-5">REQUEST_URI: '.$_SERVER['REQUEST_URI'].'</p>');
|
||||
echo('<p class="m-0 mb-5">$requested_content_display_type: '.$requested_content_display_type.'</p>');
|
||||
echo('<p class="m-0 mb-5">$requested_tags: ['.implode(", ", $requested_tags).']</p>');
|
||||
echo('<p class="m-0 mb-5">count($requested_tags): '.count($requested_tags).'</p>');
|
||||
echo('<p class="m-0 mb-5">$content_has_error: '.$content_has_error.'</p>');
|
||||
echo('<p class="m-0 mb-5">$content_error_message_key: '.$content_error_message_key.'</p>');
|
||||
echo('<p class="m-0 mb-5">localize($content_error_message_key): '.localize($content_error_message_key).'</p>');
|
||||
echo('<p class="m-0 mb-5">$content_error_message: '.$content_error_message.'</p>');
|
||||
echo('<p class="m-0 mb-5">$raw_additional_tags: '.$raw_additional_tags.'</p>');
|
||||
echo('<p class="m-0 mb-5">$filtered_content_index_data: ');
|
||||
print_r($filtered_content_index_data);
|
||||
echo('</p>');
|
||||
echo('<p class="m-0 mb-5">$content_requested_url_part: '.$content_requested_url_part.'</p>');
|
||||
if($requested_content_display_type == ContentDisplayType::CONTENT) {
|
||||
echo('<hr class="subtle mb-10 mt-15">');
|
||||
echo('<p class="m-0 mb-5">$requested_item_data: ');
|
||||
print_r($requested_item_data);
|
||||
echo('</p>');
|
||||
}
|
||||
echo('</div></div>');
|
||||
}
|
||||
|
||||
// Checking if an error occurred.
|
||||
if($content_error_code != 200) {
|
||||
// #############
|
||||
// Error card
|
||||
// #############
|
||||
startMainCard("fad fa-exclamation-triangle", localize('error.content.title.generic'), "");
|
||||
start_content_card("fad fa-exclamation-triangle", localize('error.content.title.generic'), "");
|
||||
echo('<div class="py-20 bg-light-lm rounded-bottom px-0 bg-very-dark title-bkgd">');
|
||||
echo('<h3 class="m-0 font-size-20 text-center font-weight-semi-bold">'.$content_error_message.'</h3>');
|
||||
echo('</div>');
|
||||
@@ -143,7 +103,7 @@ if($content_has_error) {
|
||||
'<p class="font-size-12 m-0 text-super-muted">'.
|
||||
'Card footer here.'.
|
||||
'</p></div>');
|
||||
endMainCard();
|
||||
end_content_card();
|
||||
goto content_printing_end;
|
||||
}
|
||||
|
||||
@@ -152,9 +112,7 @@ if($content_has_error) {
|
||||
// #############
|
||||
// Search page
|
||||
// #############
|
||||
|
||||
// Creating the start of the card, only a "</div>" should be required afterward.
|
||||
startMainCard(
|
||||
start_content_card(
|
||||
"fad fa-file-search",
|
||||
localize("content.title.search.card"),
|
||||
strval(count($filtered_content_index_data)) . " " .
|
||||
@@ -198,70 +156,12 @@ if($content_has_error) {
|
||||
'<p class="font-size-12 m-0 text-super-muted">'.
|
||||
'Card footer here.'.
|
||||
'</p></div>');
|
||||
endMainCard();
|
||||
end_content_card();
|
||||
} elseif($requested_content_display_type == ContentDisplayType::CONTENT) {
|
||||
// ##############
|
||||
// Content page
|
||||
// ##############
|
||||
|
||||
// Preparing soma variables for the icon, title and subtitle.
|
||||
$_title_icon = "fad fa-question";
|
||||
$_title_text_main = '<i>'.localize("error.content.data.no.title").'</i>';
|
||||
$_title_text_sub = NULL;
|
||||
|
||||
// Attempting to read the card's icon, title and subtitle.
|
||||
if (array_key_exists("title", $requested_item_data)) {
|
||||
if (array_key_exists("icon", $requested_item_data["title"])) {
|
||||
$_title_icon = $requested_item_data["title"]["icon"];
|
||||
}
|
||||
if (array_key_exists("card", $requested_item_data["title"])) {
|
||||
if (array_key_exists("main", $requested_item_data["title"]["card"])) {
|
||||
$_title_text_main = getContentItemText($requested_item_data["title"]["card"]["main"], true);
|
||||
}
|
||||
if (array_key_exists("sub", $requested_item_data["title"]["card"])) {
|
||||
$_title_text_sub = getContentItemText($requested_item_data["title"]["card"]["sub"], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Opening the card.
|
||||
startMainCard($_title_icon, $_title_text_main, (is_null($_title_text_sub) ? "" : $_title_text_sub));
|
||||
|
||||
// Opening the content container.
|
||||
echo('<article id="content-item-container" class="py-01 pb-0 bg-light-lm rounded-bottom px-0 bg-very-dark title-bkgd">');
|
||||
|
||||
// Adding elements defined in the JSON file.
|
||||
if(array_key_exists("parts", $requested_item_data)) {
|
||||
for ($i = 0; $i < count($requested_item_data["parts"]); $i++) {
|
||||
createElementNode($requested_item_data["parts"][$i]);
|
||||
}
|
||||
} else {
|
||||
echo('<h3 class="m-0 font-size-20 text-center text-danger font-weight-semi-bold">');
|
||||
echo(localize("error.content.data.no.parts").'</h3>');
|
||||
}
|
||||
|
||||
// New elements test zone. - START
|
||||
|
||||
// New elements test zone. - END
|
||||
|
||||
// Closing the content container.
|
||||
echo('</article>');
|
||||
|
||||
// Printing the tags' section at the end of the card
|
||||
echo('<div class="px-20 py-10 bg-light-lm bg-dark-dm rounded-bottom border-top">');
|
||||
echo('<div class="content-tag-container"><i class="fad fa-tags"></i>');
|
||||
if(array_key_exists("tags", $requested_item_data)) {
|
||||
for($i = 0; $i < count($requested_item_data["tags"]); $i++) {
|
||||
echo('<a href="'.l10n_url_abs("/content/?tags=".$requested_item_data["tags"][$i]).'" class="content-tag">#');
|
||||
echo($requested_item_data["tags"][$i].'</a>');
|
||||
}
|
||||
} else {
|
||||
echo('<i>'.localize("error.content.data.no.tags").'</i>');
|
||||
}
|
||||
echo('</div></div>');
|
||||
|
||||
// Closing the card.
|
||||
endMainCard();
|
||||
echo($content->get_html());
|
||||
}
|
||||
|
||||
content_printing_end:
|
||||
|
@@ -4,7 +4,7 @@
|
||||
"page": {"en": "PB-ListComPort", "fr": "PB-ListComPort"},
|
||||
"card": {
|
||||
"main": {"en": "PB-ListComPort", "fr": "PB-ListComPort"},
|
||||
"sub": {"en": "CLI COM port enumerator", "fr": "Enumérateur de port COM pour invité de commande"}
|
||||
"sub": {"en": "<a href=\"https://github.com/aziascreations/PB-ListComPort\"><i class=\"fab fa-github\"></i></a>"}
|
||||
}
|
||||
},
|
||||
"meta": {
|
||||
@@ -123,7 +123,7 @@
|
||||
"type": "spacer", "size": 1
|
||||
},{
|
||||
"type": "container", "padding": 0,
|
||||
"modifiers": ["no-bottom-padding", "no-top-margin", "card"],
|
||||
"modifiers": ["no-bottom-padding", "no-top-margin", "card", "horizontal-scroll"],
|
||||
"content": {
|
||||
"parts": [{
|
||||
"type": "table",
|
||||
@@ -193,7 +193,7 @@
|
||||
"type": "spacer", "size": 1
|
||||
},{
|
||||
"type": "container", "padding": 0,
|
||||
"modifiers": ["no-bottom-padding", "no-top-margin", "card"],
|
||||
"modifiers": ["no-bottom-padding", "no-top-margin", "card", "horizontal-scroll"],
|
||||
"content": {
|
||||
"parts": [{
|
||||
"type": "table",
|
||||
@@ -307,7 +307,7 @@
|
||||
"type": "spacer", "size": 1
|
||||
},{
|
||||
"type": "container", "padding": 0,
|
||||
"modifiers": ["no-bottom-padding", "no-top-margin", "card"],
|
||||
"modifiers": ["no-bottom-padding", "no-top-margin", "card", "horizontal-scroll"],
|
||||
"content": {
|
||||
"parts": [{
|
||||
"type": "table", "modifiers": ["striped", "inner-bordered"],
|
||||
|
56
content/items/test2.json
Normal file
56
content/items/test2.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"strings": {
|
||||
"en": {
|
||||
"title.meta": "Test content",
|
||||
"title.og": "Test content",
|
||||
"title.article": "Test content",
|
||||
"heading.1": "t1",
|
||||
"text.1": "p1",
|
||||
"hello.world": "Hello World !"
|
||||
},
|
||||
"fr": {
|
||||
"title.meta": "Test content fr",
|
||||
"title.og": "Test content fr",
|
||||
"title.article": "Test content fr",
|
||||
"heading.1": "t1 fr",
|
||||
"text.1": "p1 fr",
|
||||
"hello.world": "Bonjour le monde !"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"title": "title.meta",
|
||||
"template": "article",
|
||||
"opengraph": {
|
||||
"title": "title.og",
|
||||
"description": "",
|
||||
"type": "",
|
||||
"url": null,
|
||||
"image": "",
|
||||
"imageType": ""
|
||||
},
|
||||
"article": {
|
||||
"icon": "fad fa-terminal",
|
||||
"title": "title.article",
|
||||
"tags": ["fuck", "you"]
|
||||
}
|
||||
},
|
||||
"elements": [
|
||||
{
|
||||
"type": "container",
|
||||
"padding": 10,
|
||||
"modifiers": ["no-bottom-padding", "no-top-margin"],
|
||||
"parts": [
|
||||
{
|
||||
"type": "paragraph",
|
||||
"modifiers": ["no-top-margin"],
|
||||
"content": "hello.world"
|
||||
},{
|
||||
"type": "paragraph",
|
||||
"modifiers": [],
|
||||
"content": "Joe mama !",
|
||||
"localize": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@@ -23,6 +23,6 @@
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"docker", "web", "python"
|
||||
"docker", "application", "web", "python"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user