Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81c239f043 | |||
| 0d91b61bd0 | |||
| fd9a71c4d6 | |||
| 71413283c8 | |||
| a32956b4a2 | |||
| 7bf3dc6610 | |||
| 7b064eb6da | |||
| 934e134941 | |||
| 6f410b5d0e | |||
| e5aadb3b82 | |||
| 08b8009dec | |||
| 208534b5fb | |||
| 466d34c39f | |||
| a14df54cd9 | |||
| bba62180fe | |||
| 4c598340a8 | |||
| 6d7a7a5e9d | |||
| 1cdf4f1fe8 | |||
| 12783d351c | |||
| 39a14a759b | |||
| 869c3a8d6a | |||
| a459b86169 | |||
| fb5407a60b | |||
| 344786ee95 |
23
README.md
23
README.md
@@ -24,6 +24,9 @@ docker run --rm --interactive --tty --volume ./novaconium/:/app composer:latest
|
|||||||
cp -R novaconium/vendor/4lt/novaconium/skeleton/. .;
|
cp -R novaconium/vendor/4lt/novaconium/skeleton/. .;
|
||||||
|
|
||||||
# Edit .env
|
# Edit .env
|
||||||
|
# pwgen -cnsB1v 12 root password
|
||||||
|
# pwgen -cnsB1v 12 mysql user password (need in both config and env)
|
||||||
|
# pwgen -cnsB1v 64 framework key (need in config)
|
||||||
# Edit novaconium/App/config.php
|
# Edit novaconium/App/config.php
|
||||||
|
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
@@ -33,3 +36,23 @@ docker compose up -d
|
|||||||
|
|
||||||
* [Novaconiumm Official Repo](https://git.4lt.ca/4lt/novaconium)
|
* [Novaconiumm Official Repo](https://git.4lt.ca/4lt/novaconium)
|
||||||
* [CORXN Apache and PHP Container for Novaconium](https://git.4lt.ca/4lt/CORXN)
|
* [CORXN Apache and PHP Container for Novaconium](https://git.4lt.ca/4lt/CORXN)
|
||||||
|
|
||||||
|
|
||||||
|
### How it works
|
||||||
|
|
||||||
|
#### htaccess
|
||||||
|
|
||||||
|
htaccess ensures that all requests are sent to index.php
|
||||||
|
|
||||||
|
#### index.php
|
||||||
|
|
||||||
|
index.php does two things:
|
||||||
|
1. Allows you to turn on error reporting (off by default)
|
||||||
|
2. Loads the novaconium bootstrap file novaconium.php
|
||||||
|
|
||||||
|
#### novaconium.php
|
||||||
|
|
||||||
|
What happens here:
|
||||||
|
|
||||||
|
1. Autoload composer
|
||||||
|
1. Loads configurations
|
||||||
@@ -6,16 +6,16 @@
|
|||||||
{
|
{
|
||||||
"name": "Nick Yeoman",
|
"name": "Nick Yeoman",
|
||||||
"email": "dev@4lt.ca",
|
"email": "dev@4lt.ca",
|
||||||
"homepage": "https://www.4lt.ca",
|
"homepage": "https://www.4lt.ca"
|
||||||
"role": "Consultant"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Novaconium\\\\": "src/"
|
"Novaconium\\": "src/"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
"php": "^8.1",
|
||||||
"twig/twig": "*",
|
"twig/twig": "*",
|
||||||
"nickyeoman/php-validation-class": "^5.0"
|
"nickyeoman/php-validation-class": "^5.0"
|
||||||
},
|
},
|
||||||
@@ -26,4 +26,3 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,14 @@ $framework_routes = [
|
|||||||
],
|
],
|
||||||
'/novaconium/login' => [
|
'/novaconium/login' => [
|
||||||
'post' => 'NOVACONIUM/authenticate',
|
'post' => 'NOVACONIUM/authenticate',
|
||||||
'get' => 'NOVACONIUM/login'
|
'get' => 'NOVACONIUM/auth/login'
|
||||||
],
|
],
|
||||||
'/novaconium/dashboard' => [
|
'/novaconium/dashboard' => [
|
||||||
'get' => 'NOVACONIUM/dashboard'
|
'get' => 'NOVACONIUM/dashboard'
|
||||||
],
|
],
|
||||||
|
'/novaconium/settings' => [
|
||||||
|
'get' => 'NOVACONIUM/settings'
|
||||||
|
],
|
||||||
'/novaconium/pages' => [
|
'/novaconium/pages' => [
|
||||||
'get' => 'NOVACONIUM/pages'
|
'get' => 'NOVACONIUM/pages'
|
||||||
],
|
],
|
||||||
@@ -38,7 +41,13 @@ $framework_routes = [
|
|||||||
'post' => 'NOVACONIUM/message_save'
|
'post' => 'NOVACONIUM/message_save'
|
||||||
],
|
],
|
||||||
'/novaconium/logout' => [
|
'/novaconium/logout' => [
|
||||||
'post' => 'NOVACONIUM/logout',
|
'post' => 'NOVACONIUM/auth/logout',
|
||||||
'get' => 'NOVACONIUM/logout'
|
'get' => 'NOVACONIUM/auth/logout'
|
||||||
]
|
],
|
||||||
|
'/novaconium/sitemap.xml' => [
|
||||||
|
'get' => 'NOVACONIUM/sitemap'
|
||||||
|
],
|
||||||
|
'/novaconium/sample/{slug}' => [
|
||||||
|
'get' => 'NOVACONIUM/samples'
|
||||||
|
],
|
||||||
];
|
];
|
||||||
4
controllers/404.php
Normal file
4
controllers/404.php
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
http_response_code('404');
|
||||||
|
header("Content-Type: text/html");
|
||||||
|
view('@novacore/404');
|
||||||
11
controllers/auth/login.php
Normal file
11
controllers/auth/login.php
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
$data = array_merge($data, [
|
||||||
|
'title' => 'Novaconium Login Page',
|
||||||
|
'pageclass' => 'novaconium'
|
||||||
|
]);
|
||||||
|
// Don't come here if logged in
|
||||||
|
if ($session->get('username')) {
|
||||||
|
$redirect->url('/novaconium/dashboard');
|
||||||
|
makeitso();
|
||||||
|
}
|
||||||
|
view('@novacore/auth/login');
|
||||||
9
controllers/coming-soon.php
Normal file
9
controllers/coming-soon.php
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
$data = array_merge($data, [
|
||||||
|
'title' => 'Coming Soon',
|
||||||
|
'heading' => 'Coming Soon',
|
||||||
|
'countdown' => true,
|
||||||
|
'launch_date' => '2026-01-01T00:00:00'
|
||||||
|
]);
|
||||||
|
|
||||||
|
view('@novacore/coming-soon', $data);
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
// Create an admin user (POST)
|
||||||
|
|
||||||
use Nickyeoman\Validation;
|
use Nickyeoman\Validation;
|
||||||
|
|
||||||
$validate = new Validation\Validate();
|
$validate = new Validation\Validate();
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'title' => 'Novaconium Dashboard Page',
|
'title' => 'Novaconium Dashboard Page',
|
||||||
'pageclass' => 'novaconium'
|
'pageclass' => 'novaconium',
|
||||||
|
'pageid' => 'controlPanel'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ( empty($session->get('username'))) {
|
if ( empty($session->get('username'))) {
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'title' => 'Novaconium Edit Page',
|
'title' => 'Novaconium Edit Page',
|
||||||
'pageclass' => 'novaconium'
|
'pageclass' => 'novaconium',
|
||||||
|
'pageid' => 'controlPanel',
|
||||||
|
'editor' => 'ace'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Check if logged in
|
// Check if logged in
|
||||||
@@ -18,25 +20,35 @@ $pageid = $router->parameters['id'] ?? null;
|
|||||||
if (!empty($pageid)) {
|
if (!empty($pageid)) {
|
||||||
// Existing page: fetch from database
|
// Existing page: fetch from database
|
||||||
$query = <<<EOSQL
|
$query = <<<EOSQL
|
||||||
SELECT
|
WITH all_tags AS (
|
||||||
id,
|
SELECT GROUP_CONCAT(DISTINCT name ORDER BY name SEPARATOR ',') AS tags_list
|
||||||
title,
|
FROM tags
|
||||||
heading,
|
)
|
||||||
description,
|
SELECT
|
||||||
keywords,
|
p.id,
|
||||||
author,
|
p.title,
|
||||||
slug,
|
p.heading,
|
||||||
path,
|
p.description,
|
||||||
intro,
|
p.keywords,
|
||||||
body,
|
p.author,
|
||||||
notes,
|
p.slug,
|
||||||
draft,
|
p.path,
|
||||||
changefreq,
|
p.intro,
|
||||||
priority,
|
p.body,
|
||||||
created,
|
p.notes,
|
||||||
updated
|
p.draft,
|
||||||
FROM pages
|
p.changefreq,
|
||||||
WHERE id = ?
|
p.priority,
|
||||||
|
p.created,
|
||||||
|
p.updated,
|
||||||
|
COALESCE(GROUP_CONCAT(DISTINCT t.name ORDER BY t.name SEPARATOR ','), '') AS page_tags,
|
||||||
|
at.tags_list AS existing_tags
|
||||||
|
FROM pages p
|
||||||
|
LEFT JOIN page_tags pt ON p.id = pt.page_id
|
||||||
|
LEFT JOIN tags t ON pt.tag_id = t.id
|
||||||
|
CROSS JOIN all_tags at -- Zero-cost join for scalar
|
||||||
|
WHERE p.id = ?
|
||||||
|
GROUP BY p.id;
|
||||||
EOSQL;
|
EOSQL;
|
||||||
|
|
||||||
$data['rows'] = $db->getRow($query, [$pageid]);
|
$data['rows'] = $db->getRow($query, [$pageid]);
|
||||||
@@ -50,7 +62,7 @@ EOSQL;
|
|||||||
if (empty($pageid)) {
|
if (empty($pageid)) {
|
||||||
// New page: set default values for all fields
|
// New page: set default values for all fields
|
||||||
$data['rows'] = [
|
$data['rows'] = [
|
||||||
'id' => '',
|
'id' => 'newpage',
|
||||||
'title' => '',
|
'title' => '',
|
||||||
'heading' => '',
|
'heading' => '',
|
||||||
'description' => '',
|
'description' => '',
|
||||||
@@ -70,4 +82,4 @@ if (empty($pageid)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the edit page view
|
// Render the edit page view
|
||||||
view('@novacore/editpage', $data);
|
view('@novacore/editpage/index', $data);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ $data = [
|
|||||||
'empty_users' => false,
|
'empty_users' => false,
|
||||||
'show_login' => false,
|
'show_login' => false,
|
||||||
'token' => $session->get('token'),
|
'token' => $session->get('token'),
|
||||||
|
'pageclass' => 'novaconium',
|
||||||
'title' => 'Novaconium Admin'
|
'title' => 'Novaconium Admin'
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -103,13 +104,39 @@ if ($result->num_rows === 0) {
|
|||||||
`updated` datetime DEFAULT NULL,
|
`updated` datetime DEFAULT NULL,
|
||||||
`draft` tinyint(1) NOT NULL DEFAULT 1,
|
`draft` tinyint(1) NOT NULL DEFAULT 1,
|
||||||
`changefreq` varchar(7) NOT NULL DEFAULT 'monthly',
|
`changefreq` varchar(7) NOT NULL DEFAULT 'monthly',
|
||||||
`priority` float(4,1) NOT NULL DEFAULT 0.0
|
`priority` float(4,1) NOT NULL DEFAULT 0.0,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
EOSQL;
|
EOSQL;
|
||||||
|
|
||||||
$db->query($query);
|
$db->query($query);
|
||||||
$log->info('Pages Table Created');
|
$log->info('Pages Table Created');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check ContactForm Table
|
||||||
|
$query = <<<EOSQL
|
||||||
|
SELECT TABLE_NAME
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND TABLE_NAME = 'contactForm';
|
||||||
|
EOSQL;
|
||||||
|
$result = $db->query($query);
|
||||||
|
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
$query = <<<EOSQL
|
||||||
|
CREATE TABLE `contactForm` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(255) NOT NULL,
|
||||||
|
`email` varchar(255) NOT NULL,
|
||||||
|
`message` text DEFAULT NULL,
|
||||||
|
`created` datetime NOT NULL DEFAULT current_timestamp(),
|
||||||
|
`unread` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Unread is true by default',
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
EOSQL;
|
||||||
|
|
||||||
|
$db->query($query);
|
||||||
|
$log->info('ContactForm Table Created');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a user exists
|
// Check if a user exists
|
||||||
@@ -125,4 +152,51 @@ if ($row['total'] < 1) {
|
|||||||
makeitso();
|
makeitso();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check Tags Table
|
||||||
|
$query = <<<EOSQL
|
||||||
|
SELECT TABLE_NAME
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND TABLE_NAME = 'tags';
|
||||||
|
EOSQL;
|
||||||
|
$result = $db->query($query);
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
$query = <<<EOSQL
|
||||||
|
CREATE TABLE IF NOT EXISTS `tags` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`name` varchar(100) NOT NULL UNIQUE,
|
||||||
|
`created` datetime NOT NULL,
|
||||||
|
`updated` datetime DEFAULT NULL,
|
||||||
|
PRIMARY KEY (`id`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
EOSQL;
|
||||||
|
$db->query($query);
|
||||||
|
$log->info('Tags Table Created');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Page Tags Junction Table (after tags table)
|
||||||
|
$query = <<<EOSQL
|
||||||
|
SELECT TABLE_NAME
|
||||||
|
FROM information_schema.tables
|
||||||
|
WHERE table_schema = DATABASE()
|
||||||
|
AND TABLE_NAME = 'page_tags';
|
||||||
|
EOSQL;
|
||||||
|
$result = $db->query($query);
|
||||||
|
if ($result->num_rows === 0) {
|
||||||
|
$query = <<<EOSQL
|
||||||
|
CREATE TABLE IF NOT EXISTS `page_tags` (
|
||||||
|
`id` int(11) NOT NULL AUTO_INCREMENT,
|
||||||
|
`page_id` int(11) NOT NULL,
|
||||||
|
`tag_id` int(11) NOT NULL,
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
KEY `page_id` (`page_id`),
|
||||||
|
KEY `tag_id` (`tag_id`),
|
||||||
|
FOREIGN KEY (`page_id`) REFERENCES `pages` (`id`) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (`tag_id`) REFERENCES `tags` (`id`) ON DELETE CASCADE
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
|
||||||
|
EOSQL;
|
||||||
|
$db->query($query);
|
||||||
|
$log->info('Page Tags Junction Table Created');
|
||||||
|
}
|
||||||
|
|
||||||
view('@novacore/init', $data);
|
view('@novacore/init', $data);
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
<?php
|
|
||||||
$data['title'] = 'Novaconium Login Page';
|
|
||||||
|
|
||||||
// Don't come here if logged in
|
|
||||||
if ($session->get('username')) {
|
|
||||||
$redirect->url('/novaconium/dashboard');
|
|
||||||
makeitso();
|
|
||||||
}
|
|
||||||
view('@novacore/login');
|
|
||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'title' => 'Novaconium Messages',
|
'title' => 'Novaconium Messages',
|
||||||
'pageclass' => 'novaconium'
|
'pageclass' => 'novaconium',
|
||||||
|
'pageid' => 'controlPanel'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ( empty($session->get('username'))) {
|
if ( empty($session->get('username'))) {
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
$data = array_merge($data, [
|
$data = array_merge($data, [
|
||||||
'title' => 'Novaconium Pages',
|
'title' => 'Novaconium Pages',
|
||||||
'pageclass' => 'novaconium'
|
'pageclass' => 'novaconium',
|
||||||
|
'pageid' => 'controlPanel'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ( empty($session->get('username'))) {
|
if ( empty($session->get('username'))) {
|
||||||
|
|||||||
34
controllers/samples.php
Normal file
34
controllers/samples.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Pure Twig, no db example
|
||||||
|
*
|
||||||
|
* Replicate Hugo but with html and twig (not markdown)
|
||||||
|
**/
|
||||||
|
|
||||||
|
// Variables
|
||||||
|
$pt = '@novacore/samples'; //Define the view directory
|
||||||
|
//$pt = 'samples'; //drop the core for your project
|
||||||
|
|
||||||
|
//Grab the slug
|
||||||
|
$slug = $router->parameters['slug'];
|
||||||
|
|
||||||
|
//build path
|
||||||
|
$tmpl = $pt . '/' . $slug;
|
||||||
|
|
||||||
|
//Check if file exits
|
||||||
|
$baseDir = (strpos($pt, 'novacore') !== false) ? FRAMEWORKPATH : BASEPATH;
|
||||||
|
if (strpos($pt, '@novacore') !== false) {
|
||||||
|
$baseDir = str_replace('@novacore', FRAMEWORKPATH . '/views', $pt);
|
||||||
|
} else {
|
||||||
|
$baseDir = str_replace('@novacore', BASEPATH . '/views', $pt);
|
||||||
|
}
|
||||||
|
|
||||||
|
$possibleFile = $baseDir . '/' . $slug . '.html.twig'; // add .twig extension if needed
|
||||||
|
|
||||||
|
if (is_file($possibleFile) && is_readable($possibleFile)) {
|
||||||
|
view($tmpl, $data);
|
||||||
|
} else {
|
||||||
|
http_response_code('404');
|
||||||
|
header("Content-Type: text/html");
|
||||||
|
view('@novacore/404');
|
||||||
|
}
|
||||||
@@ -1,25 +1,33 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Nickyeoman\Validation;
|
use Nickyeoman\Validation;
|
||||||
|
use Novaconium\Services\TagManager;
|
||||||
|
|
||||||
$v = new Nickyeoman\Validation\Validate();
|
$v = new Nickyeoman\Validation\Validate();
|
||||||
|
|
||||||
$url_error = '/novaconium/page/edit/' . $post->get('id'); // fallback for errors
|
$url_error = '/novaconium/page/edit/' . $post->get('id'); // fallback for errors
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
// Check login
|
// Check login
|
||||||
|
// -------------------------
|
||||||
if (empty($session->get('username'))) {
|
if (empty($session->get('username'))) {
|
||||||
$messages->error('You are not logged in');
|
$messages->error('You are not logged in');
|
||||||
$redirect->url('/novaconium/login');
|
$redirect->url('/novaconium/login');
|
||||||
makeitso();
|
makeitso();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check token
|
// -------------------------
|
||||||
|
// Check CSRF token
|
||||||
|
// -------------------------
|
||||||
if ($session->get('token') != $post->get('token')) {
|
if ($session->get('token') != $post->get('token')) {
|
||||||
$messages->error('Invalid Token');
|
$messages->error('Invalid Token');
|
||||||
$redirect->url('/novaconium/pages');
|
$redirect->url('/novaconium/pages');
|
||||||
makeitso();
|
makeitso();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
// Gather POST data
|
// Gather POST data
|
||||||
|
// -------------------------
|
||||||
$id = $post->get('id');
|
$id = $post->get('id');
|
||||||
$title = $_POST['title'] ?? '';
|
$title = $_POST['title'] ?? '';
|
||||||
$heading = $_POST['heading'] ?? '';
|
$heading = $_POST['heading'] ?? '';
|
||||||
@@ -34,8 +42,20 @@ $notes = $_POST['notes'] ?? '';
|
|||||||
$draft = !empty($post->get('draft')) ? 1 : 0;
|
$draft = !empty($post->get('draft')) ? 1 : 0;
|
||||||
$changefreq = $_POST['changefreq'] ?? 'monthly';
|
$changefreq = $_POST['changefreq'] ?? 'monthly';
|
||||||
$priority = $_POST['priority'] ?? 0.0;
|
$priority = $_POST['priority'] ?? 0.0;
|
||||||
|
$tags_json = $_POST['tags_json'] ?? '[]';
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// Decode & sanitize tags
|
||||||
|
// -------------------------
|
||||||
|
$tags = json_decode($tags_json, true);
|
||||||
|
if (!is_array($tags)) $tags = [];
|
||||||
|
$tags = array_map('trim', $tags);
|
||||||
|
$tags = array_filter($tags, fn($t) => $t !== '');
|
||||||
|
$tags = array_unique($tags);
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
|
// -------------------------
|
||||||
if (empty($title) || empty($slug) || empty($body)) {
|
if (empty($title) || empty($slug) || empty($body)) {
|
||||||
$messages->error('Title, Slug, and Body are required.');
|
$messages->error('Title, Slug, and Body are required.');
|
||||||
$redirect->url($url_error);
|
$redirect->url($url_error);
|
||||||
@@ -43,8 +63,30 @@ if (empty($title) || empty($slug) || empty($body)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!empty($id)) {
|
$tagManager = new TagManager();
|
||||||
|
|
||||||
|
if ($id == 'newpage') {
|
||||||
|
// -------------------------
|
||||||
|
// Create new page
|
||||||
|
// -------------------------
|
||||||
|
$query = "INSERT INTO `pages`
|
||||||
|
(`title`, `heading`, `description`, `keywords`, `author`,
|
||||||
|
`slug`, `path`, `intro`, `body`, `notes`,
|
||||||
|
`draft`, `changefreq`, `priority`, `created`)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
|
||||||
|
$params = [
|
||||||
|
$title, $heading, $description, $keywords, $author,
|
||||||
|
$slug, $path, $intro, $body, $notes,
|
||||||
|
$draft, $changefreq, $priority
|
||||||
|
];
|
||||||
|
$db->query($query, $params);
|
||||||
|
$id = $db->lastid;
|
||||||
|
$messages->notice('Page Created');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// -------------------------
|
||||||
// Update existing page
|
// Update existing page
|
||||||
|
// -------------------------
|
||||||
$query = "UPDATE `pages` SET
|
$query = "UPDATE `pages` SET
|
||||||
`title` = ?, `heading` = ?, `description` = ?, `keywords` = ?, `author` = ?,
|
`title` = ?, `heading` = ?, `description` = ?, `keywords` = ?, `author` = ?,
|
||||||
`slug` = ?, `path` = ?, `intro` = ?, `body` = ?, `notes` = ?,
|
`slug` = ?, `path` = ?, `intro` = ?, `body` = ?, `notes` = ?,
|
||||||
@@ -57,27 +99,18 @@ try {
|
|||||||
];
|
];
|
||||||
$db->query($query, $params);
|
$db->query($query, $params);
|
||||||
$messages->notice('Page Updated');
|
$messages->notice('Page Updated');
|
||||||
} else {
|
|
||||||
// Create new page
|
|
||||||
$query = "INSERT INTO `pages`
|
|
||||||
(`title`, `heading`, `description`, `keywords`, `author`,
|
|
||||||
`slug`, `path`, `intro`, `body`, `notes`,
|
|
||||||
`draft`, `changefreq`, `priority`, `created`)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())";
|
|
||||||
$params = [
|
|
||||||
$title, $heading, $description, $keywords, $author,
|
|
||||||
$slug, $path, $intro, $body, $notes,
|
|
||||||
$draft, $changefreq, $priority
|
|
||||||
];
|
|
||||||
$db->query($query, $params);
|
|
||||||
$id = $db->lastid; // Get new page ID
|
|
||||||
$messages->notice('Page Created');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------
|
||||||
|
// Save tags (for both new and existing pages)
|
||||||
|
// -------------------------
|
||||||
|
$tagManager->setTagsForPage($id, $tags);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$messages->error($e->getMessage());
|
$messages->error($e->getMessage());
|
||||||
$redirect->url($url_error);
|
$redirect->url($url_error);
|
||||||
makeitso();
|
makeitso();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to edit page
|
// Redirect back to edit page
|
||||||
$redirect->url('/novaconium/page/edit/' . $id);
|
$redirect->url('/novaconium/page/edit/' . $id);
|
||||||
|
|||||||
15
controllers/settings.php
Normal file
15
controllers/settings.php
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$data = array_merge($data, [
|
||||||
|
'title' => 'Novaconium Settings',
|
||||||
|
'pageclass' => 'novaconium',
|
||||||
|
'pageid' => 'controlPanel'
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ( empty($session->get('username'))) {
|
||||||
|
$redirect->url('/novaconium/login');
|
||||||
|
$messages->error('You are not loggedin');
|
||||||
|
makeitso();
|
||||||
|
}
|
||||||
|
|
||||||
|
view('@novacore/settings', $data);
|
||||||
42
controllers/sitemap.php
Normal file
42
controllers/sitemap.php
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
header('Content-Type: text/xml');
|
||||||
|
// https://www.sitemaps.org/protocol.html
|
||||||
|
// Check it here: https://www.mysitemapgenerator.com/service/check.html
|
||||||
|
|
||||||
|
$query=<<<EOSQL
|
||||||
|
SELECT draft, slug, updated, changefreq, priority, path
|
||||||
|
FROM pages
|
||||||
|
WHERE priority > 0
|
||||||
|
AND draft = 0
|
||||||
|
ORDER BY updated DESC;
|
||||||
|
EOSQL;
|
||||||
|
$thepages = $db->getRows($query);
|
||||||
|
|
||||||
|
// Start the view
|
||||||
|
echo '<?xml version="1.0" encoding="UTF-8"?>';
|
||||||
|
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
|
||||||
|
|
||||||
|
// Loop through the pages
|
||||||
|
if ( ! empty($thepages) ) {
|
||||||
|
foreach( $thepages as $v) {
|
||||||
|
|
||||||
|
$date = (new \DateTime($v['updated']))->format('Y-m-d');
|
||||||
|
|
||||||
|
echo "<url>";
|
||||||
|
|
||||||
|
if ( empty($v['path']) )
|
||||||
|
echo "<loc>" . $config['base_url'] . '/page/' . $v['slug'] . "</loc>";
|
||||||
|
else
|
||||||
|
echo "<loc>" . $config['base_url'] . $v['path'] . "</loc>";
|
||||||
|
|
||||||
|
echo "<lastmod>" . $date . "</lastmod>";
|
||||||
|
echo "<changefreq>" . $v['changefreq'] . "</changefreq>";
|
||||||
|
echo "<priority>" . sprintf("%.1f", $v['priority']) . "</priority>";
|
||||||
|
echo "</url>";
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
echo "no pages added yet";
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "</urlset>";
|
||||||
6
docs/404.md
Normal file
6
docs/404.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# 404 Page
|
||||||
|
|
||||||
|
404 page is created like any other page.
|
||||||
|
Create a 404.php in your controllers and a 404.html.twig in your views.
|
||||||
|
anytime a resource is not found by the router, it will default to this controller.
|
||||||
|
if you do not have this controller in your app, it will default to the novaconium 404 page.
|
||||||
52
docs/ConfigurationFile.md
Normal file
52
docs/ConfigurationFile.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Configuration File
|
||||||
|
|
||||||
|
## App/config.php
|
||||||
|
|
||||||
|
The configuration file holds a php multi dimentional array for configuration.
|
||||||
|
|
||||||
|
#### database
|
||||||
|
|
||||||
|
This is the connection setting for mariadb.
|
||||||
|
|
||||||
|
```
|
||||||
|
'database' => [
|
||||||
|
'host' => 'ny-db',
|
||||||
|
'name' => 'nydb',
|
||||||
|
'user' => 'nydbu',
|
||||||
|
'pass' => 'as7!d5fLKJ2DLKJS5',
|
||||||
|
'port' => 3306
|
||||||
|
],
|
||||||
|
```
|
||||||
|
|
||||||
|
#### base_url
|
||||||
|
|
||||||
|
Defines the url to use
|
||||||
|
```
|
||||||
|
'base_url' => 'https://www.nickyeoman.com',
|
||||||
|
```
|
||||||
|
|
||||||
|
#### secure_key
|
||||||
|
|
||||||
|
The security key is used to verify admin account and salt encrpytion functions.
|
||||||
|
You can generate a key with ```pwgen -cnsB1v 64```
|
||||||
|
but if you don't set one, novaconium will generate one for you to use (you have to explicily set it though).
|
||||||
|
|
||||||
|
```
|
||||||
|
'secure_key' => '',
|
||||||
|
```
|
||||||
|
|
||||||
|
#### logfile
|
||||||
|
|
||||||
|
sets the path of the log file.
|
||||||
|
|
||||||
|
```
|
||||||
|
'logfile' => '/logs/novaconium.log',
|
||||||
|
```
|
||||||
|
|
||||||
|
#### loglevel
|
||||||
|
|
||||||
|
Sets the logging level for the app.
|
||||||
|
|
||||||
|
```
|
||||||
|
'loglevel' => 'ERROR' // 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'NONE'
|
||||||
|
```
|
||||||
18
docs/Dev-Fake_autoload.md
Normal file
18
docs/Dev-Fake_autoload.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Fake autoload for dev
|
||||||
|
|
||||||
|
put this in index.php
|
||||||
|
|
||||||
|
```
|
||||||
|
// --- Dev-only autoloader for manually cloned vendor copy ---
|
||||||
|
spl_autoload_register(function ($class) {
|
||||||
|
if (str_starts_with($class, 'Novaconium\\')) {
|
||||||
|
$baseDir = BASEPATH . '/vendor/4lt/novaconium/src/';
|
||||||
|
$relativeClass = substr($class, strlen('Novaconium\\'));
|
||||||
|
$file = $baseDir . str_replace('\\', '/', $relativeClass) . '.php';
|
||||||
|
if (file_exists($file)) {
|
||||||
|
require_once $file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
```
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
You can use the logging class to output to a file.
|
You can use the logging class to output to a file.
|
||||||
|
|
||||||
|
|
||||||
use ```$log->info(The Message');```1
|
use ```$log->info(The Message');```
|
||||||
|
|
||||||
Logging levels are:
|
Logging levels are:
|
||||||
```
|
```
|
||||||
@@ -16,4 +16,4 @@ Logging levels are:
|
|||||||
|
|
||||||
It's recommended that production is set to ERROR.
|
It's recommended that production is set to ERROR.
|
||||||
You set the log level in /App/config.php under 'loglevel' => 'ERROR'
|
You set the log level in /App/config.php under 'loglevel' => 'ERROR'
|
||||||
If you are using CORXN a health check is run every 30 seconds which would fill the log file with info.
|
|
||||||
|
|||||||
33
docs/Sass.md
Normal file
33
docs/Sass.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Sass
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
There is a dockerfile in the sass directory you can build an image with.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd sass
|
||||||
|
docker build -t sass-container .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Sass
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo docker run --rm -v $(pwd):/usr/src/app -w /usr/src/app sass-container sass novaconium/sass/project.sass novaconium/public/css/novaconium.css
|
||||||
|
```
|
||||||
|
|
||||||
|
Compressed:
|
||||||
|
```bash
|
||||||
|
# Build Novaconium (compressed)
|
||||||
|
docker run --rm -v "$(pwd):/usr/src/app" -w /usr/src/app sass-container --style=compressed sass/novaconium.sass skeleton/novaconium/public/css/novaconium.css
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Dev:
|
||||||
|
```bash
|
||||||
|
docker run --rm \
|
||||||
|
-v "$(pwd)/sass:/usr/src/sass" \
|
||||||
|
-v "/home/nick/tmp/novaproject/novaconium/public/css:/usr/src/css" \
|
||||||
|
-w /usr/src \
|
||||||
|
sass-container \
|
||||||
|
sass sass/novaconium.sass css/novaconium.css --no-source-map --style=compressed
|
||||||
|
```
|
||||||
12
sass/Dockerfile
Normal file
12
sass/Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Use an official Node.js image as a base
|
||||||
|
FROM node:16-slim
|
||||||
|
|
||||||
|
# Install Sass globally
|
||||||
|
RUN npm install -g sass \
|
||||||
|
&& npm cache clean --force
|
||||||
|
|
||||||
|
# Set working directory inside container
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Set entrypoint to Sass CLI
|
||||||
|
ENTRYPOINT ["sass"]
|
||||||
80
sass/abstracts/_variables.sass
Normal file
80
sass/abstracts/_variables.sass
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
// abstracts/_variables.sass (updated)
|
||||||
|
|
||||||
|
@use 'sass:map'
|
||||||
|
|
||||||
|
// Color palette (Sass map: HSL for modularity; all lightnesses darkened ~10-20%)
|
||||||
|
$colors: (
|
||||||
|
'bg-dark': hsl(0, 0%, 2%),
|
||||||
|
'bg-darker': hsl(210, 7%, 5%),
|
||||||
|
'text-light': hsla(0, 0%, 100%, 1.00),
|
||||||
|
'text-lighter': hsl(120, 52%, 15%),
|
||||||
|
'text-muted': hsl(120, 40%, 45%),
|
||||||
|
'accent-light': hsl(120, 100%, 35%),
|
||||||
|
'accent-success': hsl(120, 80%, 45%),
|
||||||
|
'accent-warning': hsl(60, 100%, 35%),
|
||||||
|
'accent-error': hsl(0, 100%, 45%),
|
||||||
|
'border-light': hsla(120, 60%, 70%, 0.2)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper functions to pull from map
|
||||||
|
@function color($key)
|
||||||
|
@return map.get($colors, $key)
|
||||||
|
|
||||||
|
// Direct vars for common use
|
||||||
|
$bg-dark: color('bg-dark') !default
|
||||||
|
$bg-darker: color('bg-darker') !default
|
||||||
|
$text-light: color('text-light') !default
|
||||||
|
$text-lighter: color('text-lighter') !default
|
||||||
|
$text-muted: color('text-muted') !default
|
||||||
|
$accent-light: color('accent-light') !default
|
||||||
|
$accent-success: color('accent-success') !default
|
||||||
|
$accent-warning: color('accent-warning') !default
|
||||||
|
$accent-error: color('accent-error') !default
|
||||||
|
$border-light: color('border-light') !default
|
||||||
|
|
||||||
|
$font-stack: 'Courier New', 'SF Mono', monospace
|
||||||
|
$mono-font: 'Courier New', 'SF Mono', 'Fira Code', Consolas, monospace
|
||||||
|
|
||||||
|
$spacing: (
|
||||||
|
'xs': 0.25rem,
|
||||||
|
'sm': 0.5rem,
|
||||||
|
'md': 1rem,
|
||||||
|
'lg': 1.5rem,
|
||||||
|
'xl': 2.5rem,
|
||||||
|
'2xl': 4rem
|
||||||
|
)
|
||||||
|
|
||||||
|
@function space($key)
|
||||||
|
@return map.get($spacing, $key)
|
||||||
|
|
||||||
|
$breakpoints: (
|
||||||
|
'sm': 640px,
|
||||||
|
'md': 768px,
|
||||||
|
'lg': 1024px,
|
||||||
|
'xl': 1280px,
|
||||||
|
'2xl': 1536px
|
||||||
|
)
|
||||||
|
|
||||||
|
$z-index: (
|
||||||
|
'dropdown': 1000,
|
||||||
|
'sticky': 50,
|
||||||
|
'modal': 2000
|
||||||
|
)
|
||||||
|
|
||||||
|
@function z($key)
|
||||||
|
@return map.get($z-index, $key)
|
||||||
|
|
||||||
|
$dark-mode: true !default
|
||||||
|
|
||||||
|
:root
|
||||||
|
--bg-dark: #{color('bg-dark')}
|
||||||
|
--bg-darker: #{color('bg-darker')}
|
||||||
|
--text-light: #{color('text-light')}
|
||||||
|
--text-lighter: #{color('text-lighter')}
|
||||||
|
--text-muted: #{color('text-muted')}
|
||||||
|
--accent-light: #{color('accent-light')}
|
||||||
|
--accent-success: #{color('accent-success')}
|
||||||
|
--accent-warning: #{color('accent-warning')}
|
||||||
|
--accent-error: #{color('accent-error')}
|
||||||
|
--border-light: #{color('border-light')}
|
||||||
|
--font-stack: #{$font-stack}
|
||||||
1
sass/abstracts/index.sass
Normal file
1
sass/abstracts/index.sass
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@forward 'variables';
|
||||||
6
sass/base/_background.sass
Normal file
6
sass/base/_background.sass
Normal file
File diff suppressed because one or more lines are too long
178
sass/base/_reset.sass
Normal file
178
sass/base/_reset.sass
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// base/_reset.sass
|
||||||
|
|
||||||
|
@use '../abstracts' as *
|
||||||
|
|
||||||
|
// Global box-sizing and resets
|
||||||
|
*
|
||||||
|
box-sizing: border-box
|
||||||
|
|
||||||
|
html
|
||||||
|
font-size: 16px // Base font size
|
||||||
|
line-height: 1.5
|
||||||
|
scroll-behavior: smooth
|
||||||
|
background-color: $bg-dark // Dark bg
|
||||||
|
color: $text-light // Light text
|
||||||
|
|
||||||
|
body
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
font-family: $font-stack // e.g., -apple-system, sans-serif
|
||||||
|
background-color: $bg-dark
|
||||||
|
color: $text-light
|
||||||
|
min-height: 100vh
|
||||||
|
|
||||||
|
// Headings: Light, bold, with spacing
|
||||||
|
h1, h2, h3, h4, h5, h6
|
||||||
|
margin: 0 0 0.5em
|
||||||
|
font-weight: 600
|
||||||
|
line-height: 1.2
|
||||||
|
color: $accent-light
|
||||||
|
|
||||||
|
h1
|
||||||
|
font-size: 2.5rem
|
||||||
|
|
||||||
|
h2
|
||||||
|
font-size: 2rem
|
||||||
|
|
||||||
|
h3
|
||||||
|
font-size: 1.75rem
|
||||||
|
|
||||||
|
h4
|
||||||
|
font-size: 1.5rem
|
||||||
|
|
||||||
|
h5
|
||||||
|
font-size: 1.25rem
|
||||||
|
|
||||||
|
h6
|
||||||
|
font-size: 1rem
|
||||||
|
|
||||||
|
// Paragraphs and text
|
||||||
|
p
|
||||||
|
margin: 0 0 1em
|
||||||
|
|
||||||
|
small
|
||||||
|
font-size: 0.875rem
|
||||||
|
|
||||||
|
strong, b
|
||||||
|
font-weight: 700
|
||||||
|
|
||||||
|
em, i
|
||||||
|
font-style: italic
|
||||||
|
|
||||||
|
// Links: Light with hover underline
|
||||||
|
a
|
||||||
|
color: $accent-light
|
||||||
|
text-decoration: none
|
||||||
|
border-bottom: 1px solid transparent
|
||||||
|
transition: border-color 0.2s ease
|
||||||
|
|
||||||
|
&:hover, &:focus
|
||||||
|
border-bottom-color: $accent-light
|
||||||
|
outline: none
|
||||||
|
|
||||||
|
// Lists
|
||||||
|
ul, ol
|
||||||
|
margin: 0 0 1em
|
||||||
|
padding-left: 1.5em
|
||||||
|
|
||||||
|
li
|
||||||
|
margin-bottom: 0.25em
|
||||||
|
|
||||||
|
// Blockquote
|
||||||
|
blockquote
|
||||||
|
margin: 1em 0
|
||||||
|
padding: 0.5em 1em
|
||||||
|
border-left: 4px solid $accent-light
|
||||||
|
background-color: rgba($bg-dark, 0.5)
|
||||||
|
color: $text-light
|
||||||
|
|
||||||
|
// Code and pre
|
||||||
|
code, kbd, samp
|
||||||
|
font-family: $mono-font
|
||||||
|
font-size: 0.875em
|
||||||
|
background-color: rgba($text-light, 0.1)
|
||||||
|
padding: 0.125em 0.25em
|
||||||
|
border-radius: 3px
|
||||||
|
|
||||||
|
pre
|
||||||
|
margin: 1em 0
|
||||||
|
padding: 1em
|
||||||
|
overflow: auto
|
||||||
|
background-color: rgba($text-light, 0.1)
|
||||||
|
border-radius: 4px
|
||||||
|
code
|
||||||
|
background: none
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
// Tables
|
||||||
|
table
|
||||||
|
width: 100%
|
||||||
|
border-collapse: collapse
|
||||||
|
margin: 1em 0
|
||||||
|
|
||||||
|
th, td
|
||||||
|
padding: 0.75em
|
||||||
|
text-align: left
|
||||||
|
border-bottom: 1px solid rgba($text-light, 0.2)
|
||||||
|
|
||||||
|
th
|
||||||
|
font-weight: 600
|
||||||
|
|
||||||
|
// Forms
|
||||||
|
input, select, textarea, button
|
||||||
|
font-family: inherit
|
||||||
|
font-size: inherit
|
||||||
|
background-color: rgba($text-light, 0.1)
|
||||||
|
color: $text-light
|
||||||
|
border: 1px solid rgba($text-light, 0.3)
|
||||||
|
border-radius: 4px
|
||||||
|
padding: 0.5em
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
outline: none
|
||||||
|
border-color: $accent-light
|
||||||
|
box-shadow: 0 0 0 2px rgba($accent-light, 0.2)
|
||||||
|
|
||||||
|
button
|
||||||
|
cursor: pointer
|
||||||
|
transition: background-color 0.2s ease
|
||||||
|
|
||||||
|
&:disabled
|
||||||
|
opacity: 0.5
|
||||||
|
cursor: not-allowed
|
||||||
|
|
||||||
|
// Images and media
|
||||||
|
img
|
||||||
|
max-width: 100%
|
||||||
|
height: auto
|
||||||
|
border-radius: 4px // Optional subtle rounding
|
||||||
|
|
||||||
|
figure
|
||||||
|
margin: 1em 0
|
||||||
|
|
||||||
|
// HR
|
||||||
|
hr
|
||||||
|
border: none
|
||||||
|
height: 1px
|
||||||
|
background-color: rgba($text-light, 0.2)
|
||||||
|
margin: 2em 0
|
||||||
|
|
||||||
|
// Accessibility: Focus visible for all interactive elements
|
||||||
|
*:focus-visible
|
||||||
|
outline: 2px solid $accent-light
|
||||||
|
outline-offset: 2px
|
||||||
|
|
||||||
|
// Print styles (optional)
|
||||||
|
@media print
|
||||||
|
body
|
||||||
|
background: white !important
|
||||||
|
color: black !important
|
||||||
|
|
||||||
|
::selection
|
||||||
|
background: rgba($accent-light, 0.3) // Subtle accent highlight for that terminal select vibe
|
||||||
|
color: $text-light // Crisp text contrast
|
||||||
|
|
||||||
|
// Optional: Vendor prefixes for broader support (though modern browsers are solid)
|
||||||
|
*::-moz-selection
|
||||||
|
background: rgba($accent-light, 0.3)
|
||||||
|
color: $text-light
|
||||||
2
sass/base/index.sass
Normal file
2
sass/base/index.sass
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@forward 'reset';
|
||||||
|
@forward 'background';
|
||||||
94
sass/coming-soon/index.sass
Normal file
94
sass/coming-soon/index.sass
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
body#coming-soon
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
align-items: center
|
||||||
|
height: 100%
|
||||||
|
padding: 1rem
|
||||||
|
box-sizing: border-box
|
||||||
|
margin: 0
|
||||||
|
text-align: center
|
||||||
|
color: #eee
|
||||||
|
font-family: 'Segoe UI', Roboto, sans-serif
|
||||||
|
background-size: cover
|
||||||
|
background-repeat: no-repeat
|
||||||
|
background-position: center center
|
||||||
|
background-attachment: fixed
|
||||||
|
|
||||||
|
.container
|
||||||
|
max-width: 600px
|
||||||
|
background-color: rgba(30, 30, 30, 0.89)
|
||||||
|
padding: 2rem
|
||||||
|
border-radius: 8px
|
||||||
|
box-shadow: 0 0 20px rgba(0,0,0,0.5)
|
||||||
|
|
||||||
|
h1
|
||||||
|
font-size: 3rem
|
||||||
|
margin-bottom: 1rem
|
||||||
|
animation: pulse 1.5s infinite
|
||||||
|
|
||||||
|
p
|
||||||
|
font-size: 1.2rem
|
||||||
|
opacity: 0.85
|
||||||
|
margin-bottom: 1.5rem
|
||||||
|
|
||||||
|
.countdown
|
||||||
|
font-size: 2rem
|
||||||
|
font-weight: bold
|
||||||
|
margin: 1rem 0 2rem 0
|
||||||
|
|
||||||
|
form.newsletter
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
gap: 0.5rem
|
||||||
|
margin-top: 1rem
|
||||||
|
|
||||||
|
input[type="email"]
|
||||||
|
padding: 0.5rem
|
||||||
|
font-size: 1rem
|
||||||
|
border: 1px solid #666
|
||||||
|
border-radius: 4px
|
||||||
|
background: rgba(255,255,255,0.05)
|
||||||
|
color: #eee
|
||||||
|
|
||||||
|
button
|
||||||
|
padding: 0.5rem
|
||||||
|
font-size: 1rem
|
||||||
|
border: 1px solid #1e90ff
|
||||||
|
border-radius: 4px
|
||||||
|
background: #1e90ff
|
||||||
|
color: #fff
|
||||||
|
cursor: pointer
|
||||||
|
transition: background 0.2s
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: #1565c0
|
||||||
|
|
||||||
|
.social-icons
|
||||||
|
margin-top: 2rem
|
||||||
|
display: flex
|
||||||
|
justify-content: center
|
||||||
|
gap: 1rem
|
||||||
|
|
||||||
|
a
|
||||||
|
display: inline-flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
width: 40px
|
||||||
|
height: 40px
|
||||||
|
font-size: 1.5rem
|
||||||
|
color: #eee
|
||||||
|
text-decoration: none
|
||||||
|
transition: color 0.2s
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: #1e90ff
|
||||||
|
|
||||||
|
i
|
||||||
|
font-style: normal
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes pulse
|
||||||
|
0%, 100%
|
||||||
|
opacity: 0.8
|
||||||
|
50%
|
||||||
|
opacity: 1
|
||||||
4
sass/controlPanel/_footer.sass
Normal file
4
sass/controlPanel/_footer.sass
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
body#controlPanel
|
||||||
|
|
||||||
|
footer
|
||||||
|
padding: 1rem
|
||||||
8
sass/controlPanel/_header.sass
Normal file
8
sass/controlPanel/_header.sass
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
body#controlPanel
|
||||||
|
|
||||||
|
header
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
h1#biglogo
|
||||||
|
padding: 10px 0 0 0
|
||||||
|
margin: 0
|
||||||
11
sass/controlPanel/_main.sass
Normal file
11
sass/controlPanel/_main.sass
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
body#controlPanel
|
||||||
|
|
||||||
|
div#panel
|
||||||
|
|
||||||
|
main#content
|
||||||
|
flex: 1
|
||||||
|
padding: 20px 40px
|
||||||
|
min-width: 0
|
||||||
|
background-color: #0b0b0bd6
|
||||||
|
border-left: 2px solid #104910
|
||||||
|
|
||||||
22
sass/controlPanel/_menu.sass
Normal file
22
sass/controlPanel/_menu.sass
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
@use '../abstracts' as *
|
||||||
|
|
||||||
|
body#controlPanel
|
||||||
|
div#panel
|
||||||
|
div#cp-menu
|
||||||
|
background-color: #000
|
||||||
|
width: 360px
|
||||||
|
padding: 0
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
ul#cp-nav
|
||||||
|
list-style: none
|
||||||
|
margin: 0
|
||||||
|
padding: 20px 0 0 0
|
||||||
|
|
||||||
|
li a
|
||||||
|
display: block
|
||||||
|
border-bottom: 1px solid $border-light
|
||||||
|
padding: 4px 10px
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-color: #222
|
||||||
5
sass/controlPanel/_panel.sass
Normal file
5
sass/controlPanel/_panel.sass
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
body#controlPanel
|
||||||
|
|
||||||
|
div#panel
|
||||||
|
display: flex
|
||||||
|
flex-direction: row
|
||||||
10
sass/controlPanel/index.sass
Normal file
10
sass/controlPanel/index.sass
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@forward 'header';
|
||||||
|
@forward 'panel';
|
||||||
|
@forward 'menu';
|
||||||
|
@forward 'main';
|
||||||
|
@forward 'footer';
|
||||||
|
|
||||||
|
body#controlPanel
|
||||||
|
h1
|
||||||
|
font-family: "VT323", "Courier New", "SF Mono", "Fira Code", Consolas, monospace
|
||||||
|
font-size: 48px
|
||||||
29
sass/framework/_ace.sass
Normal file
29
sass/framework/_ace.sass
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
@use '../abstracts' as *
|
||||||
|
@use 'sass:color' // For color.adjust()—non-deprecated color tweaks
|
||||||
|
@use '../base' as *
|
||||||
|
|
||||||
|
// ACE Editor
|
||||||
|
.editor-container
|
||||||
|
width: 100%
|
||||||
|
min-height: 400px // ~10 rows
|
||||||
|
|
||||||
|
.ace-editor
|
||||||
|
height: 400px // Scrollable
|
||||||
|
border: 1px solid $accent-light
|
||||||
|
font-family: $mono-font // Your monospace
|
||||||
|
|
||||||
|
// Ace internals (dark theme tweaks)
|
||||||
|
.ace_gutter
|
||||||
|
background: $bg-darker !important
|
||||||
|
color: $text-muted !important
|
||||||
|
border-right: 1px solid $accent-light !important
|
||||||
|
|
||||||
|
.ace_scroller
|
||||||
|
background: $bg-dark !important
|
||||||
|
|
||||||
|
.ace_text-layer .ace_print-margin
|
||||||
|
background: transparent !important
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
.ace_marker-layer .ace_selection
|
||||||
|
background: color.adjust($accent-light, $alpha: -0.3) !important // Green tint
|
||||||
15
sass/framework/_edit_page.sass
Normal file
15
sass/framework/_edit_page.sass
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
@use '../abstracts' as *
|
||||||
|
@use '../base' as *
|
||||||
|
@use 'sass:color' // Already there for adjusts
|
||||||
|
|
||||||
|
#edit-page-title
|
||||||
|
margin-top: 2rem
|
||||||
|
|
||||||
|
#title
|
||||||
|
width: 100%
|
||||||
|
font-size: 1.9rem
|
||||||
|
padding: 20px
|
||||||
|
|
||||||
|
&>div
|
||||||
|
font-size: 0.8rem
|
||||||
|
margin: 8px 0 0 20px
|
||||||
62
sass/framework/_forms.sass
Normal file
62
sass/framework/_forms.sass
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// pages/_forms.sass
|
||||||
|
|
||||||
|
@use '../abstracts' as *
|
||||||
|
@use '../base' as *
|
||||||
|
|
||||||
|
body.novaconium
|
||||||
|
#edit-page-form-novaconium
|
||||||
|
// Form groups: Spacing
|
||||||
|
.form-group
|
||||||
|
margin-bottom: space('md') // 1rem
|
||||||
|
|
||||||
|
&.fullwidth textarea
|
||||||
|
width: 100%
|
||||||
|
resize: vertical
|
||||||
|
|
||||||
|
.checkbox-group
|
||||||
|
margin-top: space('lg') // 1.5rem
|
||||||
|
|
||||||
|
// Inputs: Dark bg, green focus
|
||||||
|
input[type="text"],
|
||||||
|
input[type="number"],
|
||||||
|
textarea,
|
||||||
|
select
|
||||||
|
background-color: color('bg-darker') // Deeper green-black
|
||||||
|
border: 1px solid color('border-light')
|
||||||
|
color: $text-light
|
||||||
|
padding: space('sm')
|
||||||
|
border-radius: 4px
|
||||||
|
font-family: 'VT323', $mono-font // Matrix monospace
|
||||||
|
|
||||||
|
&:focus
|
||||||
|
border-color: $accent-light // Lime focus
|
||||||
|
box-shadow: 0 0 0 space('xs') rgba($accent-light, 0.3) // Glow ring
|
||||||
|
outline: none
|
||||||
|
|
||||||
|
input[type="checkbox"]
|
||||||
|
accent-color: $accent-light // Green checkbox
|
||||||
|
|
||||||
|
// Submit button: Green primary
|
||||||
|
button[type="submit"]
|
||||||
|
background-color: $accent-light
|
||||||
|
color: color('bg-dark') // Dark text on green
|
||||||
|
border: none
|
||||||
|
padding: space('sm') space('md')
|
||||||
|
border-radius: 4px
|
||||||
|
cursor: pointer
|
||||||
|
font-family: 'VT323', $mono-font
|
||||||
|
|
||||||
|
&:hover, &:focus
|
||||||
|
background-color: hsl(120, 100%, 30%) // Darker green (35% L -5%)
|
||||||
|
box-shadow: 0 0 4px rgba($accent-light, 0.5)
|
||||||
|
|
||||||
|
// Text/links: Muted with hover
|
||||||
|
p
|
||||||
|
color: $text-muted
|
||||||
|
|
||||||
|
a
|
||||||
|
color: $accent-light
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
color: $text-lighter // Brighter green
|
||||||
|
text-decoration: underline
|
||||||
53
sass/framework/_login_form.sass
Normal file
53
sass/framework/_login_form.sass
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// framework/_login_form.sass
|
||||||
|
|
||||||
|
@use '../abstracts' as *
|
||||||
|
|
||||||
|
body.novaconium #login
|
||||||
|
// No background or border—use parent's ambient styling
|
||||||
|
padding: 0 // Or keep minimal if needed; adjust to 2rem if you want internal space
|
||||||
|
// No centering: left-aligned by default
|
||||||
|
|
||||||
|
// Wrapper for inputs/button to align widths
|
||||||
|
input[type="text"],
|
||||||
|
input[type="password"],
|
||||||
|
button[type="submit"]
|
||||||
|
width: 100% // Full width of form (no max cap for flow)
|
||||||
|
max-width: 300px // Keep cap for consistency across fields
|
||||||
|
padding: 1rem
|
||||||
|
margin-bottom: 1rem
|
||||||
|
box-sizing: border-box // Include padding in width
|
||||||
|
font-size: 1rem
|
||||||
|
border: 1px solid $border-light
|
||||||
|
border-radius: 4px
|
||||||
|
background: $bg-dark
|
||||||
|
color: $text-light
|
||||||
|
|
||||||
|
// Icon backgrounds: Cyber icons via data URI, now in white for visibility
|
||||||
|
input[type="text"] // Username input
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E")
|
||||||
|
background-repeat: no-repeat
|
||||||
|
background-position: 1rem center
|
||||||
|
background-size: 20px
|
||||||
|
padding-left: 3rem // Space for icon
|
||||||
|
|
||||||
|
input[type="password"] // Password input
|
||||||
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='11' width='18' height='11' rx='2' ry='2'/%3E%3Cpath d='M7 11V7a5 5 0 0 1 10 0v4'/%3E%3C/svg%3E")
|
||||||
|
background-repeat: no-repeat
|
||||||
|
background-position: 1rem center
|
||||||
|
background-size: 20px
|
||||||
|
padding-left: 3rem // Space for icon
|
||||||
|
|
||||||
|
button[type="submit"]
|
||||||
|
background: $accent-light // Green accent for submit
|
||||||
|
border-color: $accent-light
|
||||||
|
cursor: pointer
|
||||||
|
transition: background 0.2s
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background: $accent-light
|
||||||
|
text-shadow: 0 0 5px rgba($accent-light, 0.5) // Cyber glow
|
||||||
|
|
||||||
|
// No icon needed for button, but add if you want (e.g., arrow)
|
||||||
|
&::after
|
||||||
|
content: ' →' // Simple arrow, or swap for Material Icon
|
||||||
|
margin-left: 0.5rem
|
||||||
79
sass/framework/_logo.sass
Normal file
79
sass/framework/_logo.sass
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
body.novaconium
|
||||||
|
#biglogo
|
||||||
|
position: relative
|
||||||
|
display: inline-block
|
||||||
|
padding: 20px 0
|
||||||
|
border-radius: 0 // Keep it sharp, no curves
|
||||||
|
overflow: hidden
|
||||||
|
|
||||||
|
&::before
|
||||||
|
content: ''
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
bottom: 0
|
||||||
|
background-image: linear-gradient(rgba(0, 255, 0, 0.02) 1px, transparent 1px), linear-gradient(90deg, rgba(0, 255, 0, 0.02) 1px, transparent 1px)
|
||||||
|
background-size: 20px 20px
|
||||||
|
opacity: 0.1 // Subtle grid/matrix rain hint
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
|
// Scan lines for extra flicker
|
||||||
|
&::after
|
||||||
|
content: ''
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
height: 1px
|
||||||
|
background: linear-gradient(90deg, transparent, #00ff00, transparent)
|
||||||
|
opacity: 0.05
|
||||||
|
animation: scan 3s linear infinite
|
||||||
|
|
||||||
|
.main
|
||||||
|
display: block
|
||||||
|
font-family: 'Orbitron', sans-serif // Cyberpunk geometric punch
|
||||||
|
font-size: 4.5rem // Chunky figlet scale
|
||||||
|
font-weight: 900 // Black weight for max angular depth
|
||||||
|
color: #fff
|
||||||
|
letter-spacing: 0.1em // Slightly looser for Orbitron's geometry
|
||||||
|
line-height: 0.85 // Compact height like figlet blocks
|
||||||
|
text-transform: uppercase
|
||||||
|
|
||||||
|
// Multi-layer shadow to mimic ansishadow's depth: base shadow + edge glow
|
||||||
|
text-shadow: 4px 4px 0 #222, 5px 5px 0 #111, -1px -1px 0 #00ff00, 1px 1px 20px rgba(0, 255, 0, 0.3) // Main drop shadow, deeper offset, subtle green edge highlight for cyber pop, glow for security flair
|
||||||
|
filter: drop-shadow(2px 2px 4px rgba(0, 0, 0, 0.8)) // Browser shadow boost
|
||||||
|
|
||||||
|
.sub
|
||||||
|
display: block
|
||||||
|
font-family: 'Orbitron', sans-serif // Match for cohesion, or swap to monospace if you want contrast
|
||||||
|
font-size: 2.5rem
|
||||||
|
font-weight: 700 // Bold but not overpowering
|
||||||
|
color: #fff
|
||||||
|
letter-spacing: 0.15em // Wider for emphasis
|
||||||
|
margin-top: 0.5rem
|
||||||
|
text-shadow: 2px 2px 0 #222, -1px -1px 0 #00ff00, 1px 1px 10px rgba(0, 255, 0, 0.2) // Lighter shadow, subtle green edge, glow
|
||||||
|
filter: drop-shadow(1px 1px 2px rgba(0, 0, 0, 0.6))
|
||||||
|
|
||||||
|
// Optional: Glitch animation on hover for that hacked feel
|
||||||
|
&:hover
|
||||||
|
.main
|
||||||
|
animation: glitch 0.3s infinite
|
||||||
|
|
||||||
|
@keyframes scan
|
||||||
|
0%
|
||||||
|
top: -1px
|
||||||
|
100%
|
||||||
|
top: 100%
|
||||||
|
|
||||||
|
@keyframes glitch
|
||||||
|
0%, 100%
|
||||||
|
transform: translate(0)
|
||||||
|
20%
|
||||||
|
transform: translate(-2px, 2px)
|
||||||
|
40%
|
||||||
|
transform: translate(-2px, -2px)
|
||||||
|
60%
|
||||||
|
transform: translate(2px, 2px)
|
||||||
|
80%
|
||||||
|
transform: translate(2px, -2px)
|
||||||
62
sass/framework/_main.sass
Normal file
62
sass/framework/_main.sass
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// layout/_main.sass
|
||||||
|
|
||||||
|
@use '../abstracts' as *
|
||||||
|
@use '../base' as *
|
||||||
|
|
||||||
|
body.novaconium
|
||||||
|
// Main page wrapper: Centered flex
|
||||||
|
#page .container
|
||||||
|
display: flex
|
||||||
|
padding: 0
|
||||||
|
justify-content: center
|
||||||
|
align-items: flex-start
|
||||||
|
padding-top: space('lg')
|
||||||
|
|
||||||
|
// Article content: Fixed width, no shrink
|
||||||
|
article
|
||||||
|
width: 1140px
|
||||||
|
flex-shrink: 0
|
||||||
|
padding: 3rem
|
||||||
|
margin: 0
|
||||||
|
background-color: $bg-dark
|
||||||
|
border: 1px solid $bg-darker
|
||||||
|
|
||||||
|
ul#leftnav
|
||||||
|
width: 320px
|
||||||
|
flex-shrink: 0
|
||||||
|
list-style: none
|
||||||
|
padding: 0
|
||||||
|
margin: 0 space('xl') 0 0 // Right margin preserved (overrides the 0 for other sides)
|
||||||
|
background-color: $bg-dark
|
||||||
|
border: 1px solid $bg-darker
|
||||||
|
|
||||||
|
#leftnav li
|
||||||
|
border-bottom: 1px solid color('border-light')
|
||||||
|
|
||||||
|
&:last-child
|
||||||
|
border-bottom: none
|
||||||
|
|
||||||
|
#leftnav a
|
||||||
|
display: block
|
||||||
|
padding: space('sm') space('md') // Consistent spacing
|
||||||
|
text-decoration: none
|
||||||
|
color: $text-light // Theme text
|
||||||
|
|
||||||
|
&:hover, &:focus
|
||||||
|
background-color: rgba($accent-light, 0.1) // Green hover tint
|
||||||
|
color: $accent-light // Accent on hover
|
||||||
|
border-radius: 4px
|
||||||
|
|
||||||
|
// Responsive: Stack on mobile (add to themes/ for media queries)
|
||||||
|
@media (max-width: 1280px) // Your xl breakpoint
|
||||||
|
#page .container
|
||||||
|
flex-direction: column
|
||||||
|
align-items: center
|
||||||
|
|
||||||
|
article, #leftnav
|
||||||
|
width: 100%
|
||||||
|
max-width: 900px
|
||||||
|
margin: space('md') 0
|
||||||
|
|
||||||
|
#leftnav
|
||||||
|
margin-right: 0
|
||||||
49
sass/framework/_tabs.sass
Normal file
49
sass/framework/_tabs.sass
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
// framework/_tabs.sass
|
||||||
|
|
||||||
|
@use '../abstracts' as *
|
||||||
|
@use 'sass:color' // For color.adjust()—non-deprecated color tweaks
|
||||||
|
|
||||||
|
body#controlPanel
|
||||||
|
// Simplified tab styling: Square borders, no rounds
|
||||||
|
.tab-container
|
||||||
|
margin: space('lg') auto
|
||||||
|
background: $bg-dark
|
||||||
|
|
||||||
|
.tab-nav
|
||||||
|
display: flex
|
||||||
|
background-color: $bg-darker
|
||||||
|
border-bottom: 1px solid $border-light
|
||||||
|
|
||||||
|
.tab-button
|
||||||
|
padding: space('sm') space('md')
|
||||||
|
background: $bg-darker
|
||||||
|
border: 1px solid $border-light
|
||||||
|
border-bottom: none
|
||||||
|
cursor: pointer
|
||||||
|
font-size: 14px
|
||||||
|
min-width: 120px // Makes them more square/equal
|
||||||
|
text-align: center
|
||||||
|
color: $text-light
|
||||||
|
font-family: $mono-font // From vars
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
background-color: color.adjust($bg-darker, $lightness: 5%) // Modern, non-deprecated lighten equivalent
|
||||||
|
|
||||||
|
+ .tab-button
|
||||||
|
border-left: none // Shared borders
|
||||||
|
|
||||||
|
&.active
|
||||||
|
background-color: $bg-dark
|
||||||
|
border-bottom: 2px solid $accent-light
|
||||||
|
color: $accent-light
|
||||||
|
font-weight: bold
|
||||||
|
|
||||||
|
.tab-content
|
||||||
|
display: none
|
||||||
|
padding: space('lg')
|
||||||
|
background-color: $bg-dark
|
||||||
|
color: $text-light
|
||||||
|
border: none
|
||||||
|
|
||||||
|
&.active
|
||||||
|
display: block
|
||||||
109
sass/framework/_tags.sass
Normal file
109
sass/framework/_tags.sass
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
@use '../abstracts' as *
|
||||||
|
@use 'sass:color' // For color.adjust()—non-deprecated color tweaks
|
||||||
|
@use '../base' as *
|
||||||
|
|
||||||
|
body.novaconium
|
||||||
|
// Tags Dropdown
|
||||||
|
.tags-dropdown
|
||||||
|
position: absolute
|
||||||
|
top: 100%
|
||||||
|
left: 0
|
||||||
|
right: 0
|
||||||
|
max-height: 200px
|
||||||
|
overflow-y: auto
|
||||||
|
background: $bg-darker
|
||||||
|
border: 1px solid $border-light
|
||||||
|
border-top: none
|
||||||
|
border-radius: 0 0 4px 4px
|
||||||
|
list-style: none
|
||||||
|
margin: 0
|
||||||
|
padding: 0
|
||||||
|
z-index: 10
|
||||||
|
opacity: 0
|
||||||
|
visibility: hidden
|
||||||
|
transition: opacity 0.2s
|
||||||
|
|
||||||
|
&[aria-expanded="true"]
|
||||||
|
opacity: 1
|
||||||
|
visibility: visible
|
||||||
|
|
||||||
|
li
|
||||||
|
padding: space('xs') space('sm')
|
||||||
|
cursor: pointer
|
||||||
|
color: $text-light
|
||||||
|
font-size: 14px
|
||||||
|
border-bottom: 1px solid transparent
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&.selected
|
||||||
|
background: color.adjust($accent-light, $alpha: -0.1)
|
||||||
|
color: $accent-light
|
||||||
|
|
||||||
|
&:last-child
|
||||||
|
border-bottom: none
|
||||||
|
|
||||||
|
.tags-input
|
||||||
|
position: relative // For absolute dropdown
|
||||||
|
|
||||||
|
// Tags Input
|
||||||
|
.tags-input
|
||||||
|
display: flex
|
||||||
|
flex-wrap: wrap
|
||||||
|
gap: space('xs')
|
||||||
|
align-items: center
|
||||||
|
border: 1px solid $border-light
|
||||||
|
padding: space('xs')
|
||||||
|
background: $bg-darker
|
||||||
|
min-height: 40px
|
||||||
|
grid-column: 2 // Spans input area in .form-row grid
|
||||||
|
|
||||||
|
input
|
||||||
|
border: none
|
||||||
|
background: transparent
|
||||||
|
color: $text-light
|
||||||
|
font-family: $mono-font
|
||||||
|
font-size: 14px
|
||||||
|
flex: 1
|
||||||
|
min-width: 100px
|
||||||
|
outline: none
|
||||||
|
|
||||||
|
&::placeholder
|
||||||
|
color: $text-muted
|
||||||
|
|
||||||
|
.tag-chip
|
||||||
|
display: inline-flex
|
||||||
|
align-items: center
|
||||||
|
background: color.adjust($accent-light, $alpha: -0.2)
|
||||||
|
color: #fff
|
||||||
|
padding: space('xs') space('sm')
|
||||||
|
border-radius: 4px
|
||||||
|
font-size: 12px
|
||||||
|
max-width: 150px
|
||||||
|
overflow: hidden
|
||||||
|
text-overflow: ellipsis
|
||||||
|
white-space: nowrap
|
||||||
|
|
||||||
|
.tag-remove
|
||||||
|
background: none
|
||||||
|
border: none
|
||||||
|
color: inherit
|
||||||
|
font-size: 16px
|
||||||
|
margin-left: space('xs')
|
||||||
|
cursor: pointer
|
||||||
|
padding: 0
|
||||||
|
line-height: 1
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
opacity: 0.7
|
||||||
|
|
||||||
|
// Tags Autocomplete (native datalist styling)
|
||||||
|
.tags-input input[list]
|
||||||
|
// Existing input styles apply...
|
||||||
|
|
||||||
|
#tag-suggestions
|
||||||
|
// Native datalist not directly stylable, but options can be influenced via input focus
|
||||||
|
// For custom polyfill, add JS-generated <ul>, but native is fine for now
|
||||||
|
|
||||||
|
// Browser-specific tweaks (Chrome/Firefox)
|
||||||
|
.tags-input input[list]::-webkit-calendar-picker-indicator
|
||||||
|
display: none // Hide arrow if interfering
|
||||||
48
sass/framework/_tooltip.sass
Normal file
48
sass/framework/_tooltip.sass
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
@use '../abstracts' as *
|
||||||
|
@use '../base' as *
|
||||||
|
@use 'sass:color' // Already there for adjusts
|
||||||
|
|
||||||
|
body.novaconium
|
||||||
|
// Tooltip styling (mouse-follow version)
|
||||||
|
.tooltip
|
||||||
|
position: relative
|
||||||
|
display: inline-block
|
||||||
|
cursor: help
|
||||||
|
font-size: 16px
|
||||||
|
color: $accent-light // Green accent for ?
|
||||||
|
margin-left: space('xs') // 0.25rem
|
||||||
|
top: 2px
|
||||||
|
|
||||||
|
.tooltiptext
|
||||||
|
visibility: hidden
|
||||||
|
width: 200px
|
||||||
|
background-color: $bg-darker // Darker bg
|
||||||
|
color: $text-light // White text
|
||||||
|
text-align: left // Changed to left-align for better readability
|
||||||
|
border-radius: 0 // Square
|
||||||
|
padding: space('sm') // 0.5rem
|
||||||
|
position: fixed // Fixed to viewport, updated by JS
|
||||||
|
z-index: z('dropdown') // High z-index
|
||||||
|
opacity: 0
|
||||||
|
transition: opacity 0.3s
|
||||||
|
font-size: 14px
|
||||||
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) // Subtle shadow
|
||||||
|
pointer-events: none // Prevents mouse interference with tooltip
|
||||||
|
|
||||||
|
&::after
|
||||||
|
content: ""
|
||||||
|
position: absolute
|
||||||
|
top: -5px // Arrow above tooltip (points up to mouse)
|
||||||
|
left: 10px // Slight indent from left edge
|
||||||
|
border-width: 5px
|
||||||
|
border-style: solid
|
||||||
|
border-color: $bg-darker transparent transparent transparent // Upward arrow
|
||||||
|
|
||||||
|
&:hover .tooltiptext
|
||||||
|
visibility: visible
|
||||||
|
opacity: 1
|
||||||
|
|
||||||
|
// Accessibility: Hidden tooltip text for screen readers
|
||||||
|
.tooltip-desc
|
||||||
|
position: absolute
|
||||||
|
left: -9999px
|
||||||
59
sass/framework/_ui.sass
Normal file
59
sass/framework/_ui.sass
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// components/_ui.sass
|
||||||
|
|
||||||
|
@use '../abstracts' as *
|
||||||
|
@use '../base' as *
|
||||||
|
|
||||||
|
body.novaconium
|
||||||
|
// Code blocks: Monospace lock-in with darker green tint
|
||||||
|
code
|
||||||
|
font-family: 'VT323', $mono-font // Prioritize Matrix font
|
||||||
|
font-size: 0.875rem // Smaller for inline; reset has base
|
||||||
|
background-color: color('bg-darker') // Deeper green-black
|
||||||
|
color: $text-light // Lighter green text
|
||||||
|
padding: space('xs') space('sm')
|
||||||
|
border-radius: 4px
|
||||||
|
border: 1px solid color('border-light')
|
||||||
|
box-shadow: 0 0 2px rgba($accent-light, 0.1) // Subtle glow
|
||||||
|
|
||||||
|
// Utility: Small text (if still needed; consider rem-based)
|
||||||
|
.small
|
||||||
|
font-size: 0.625rem // 10px equiv; use sparingly
|
||||||
|
|
||||||
|
// Error/notice divs: Centered alerts with theme colors
|
||||||
|
div.error, div.notice, div#debug
|
||||||
|
margin: space('xl') auto // Top/bottom spacing from map
|
||||||
|
width: 900px // Fixed width; add media query for mobile later
|
||||||
|
padding: space('lg') // Generous padding
|
||||||
|
border-radius: 6px
|
||||||
|
border: 1px solid
|
||||||
|
|
||||||
|
div.error
|
||||||
|
background-color: rgba(color('accent-error'), 0.1) // Subtle red tint
|
||||||
|
color: color('accent-error') // Darker red text
|
||||||
|
border-color: color('accent-error')
|
||||||
|
|
||||||
|
div.notice
|
||||||
|
background-color: rgba(color('accent-success'), 0.1) // Green tint
|
||||||
|
color: color('accent-success') // Heavier green text
|
||||||
|
border-color: color('accent-success')
|
||||||
|
|
||||||
|
div#debug
|
||||||
|
background-color: color('bg-darker') // Deeper bg for debug
|
||||||
|
color: $text-muted // Muted green
|
||||||
|
border-color: color('border-light')
|
||||||
|
margin-top: space('2xl')
|
||||||
|
margin-bottom: space('2xl')
|
||||||
|
|
||||||
|
// Tables: Simplified; reset handles collapse/padding
|
||||||
|
.pages-table
|
||||||
|
width: 100%
|
||||||
|
border: 1px solid color('border-light') // Green border
|
||||||
|
|
||||||
|
th, td
|
||||||
|
border: 1px solid color('border-light')
|
||||||
|
text-align: left
|
||||||
|
|
||||||
|
th
|
||||||
|
background-color: rgba($accent-light, 0.05) // Subtle accent bg
|
||||||
|
color: $text-lighter // Header green
|
||||||
|
font-weight: 600 // Semi-bold; no bold in monospace
|
||||||
10
sass/framework/index.sass
Normal file
10
sass/framework/index.sass
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@forward 'main';
|
||||||
|
@forward 'ui';
|
||||||
|
@forward 'forms';
|
||||||
|
@forward 'login_form';
|
||||||
|
@forward 'logo';
|
||||||
|
@forward 'tabs';
|
||||||
|
@forward 'edit_page';
|
||||||
|
@forward 'tooltip';
|
||||||
|
@forward 'ace';
|
||||||
|
@forward 'tags';
|
||||||
6
sass/novaconium.sass
Normal file
6
sass/novaconium.sass
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// novaconium.sass
|
||||||
|
@use 'abstracts' as *
|
||||||
|
@use 'base' as *
|
||||||
|
@use 'framework' as *
|
||||||
|
@use 'controlPanel' as *
|
||||||
|
@use 'coming-soon' as *
|
||||||
@@ -5,8 +5,11 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "8000:80"
|
- "8000:80"
|
||||||
volumes:
|
volumes:
|
||||||
|
- "/etc/timezone:/etc/timezone:ro"
|
||||||
|
- "/etc/localtime:/etc/localtime:ro"
|
||||||
- ./novaconium:/data
|
- ./novaconium:/data
|
||||||
- ./data/logs:/var/log/apache2 # Optional Logs
|
- ./data/logs:/var/log/apache2 # Optional Logs
|
||||||
|
- "./logs:/data/logs"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
@@ -20,13 +23,14 @@ services:
|
|||||||
|
|
||||||
mariadb:
|
mariadb:
|
||||||
image: mariadb:latest
|
image: mariadb:latest
|
||||||
container_name: mariadb
|
|
||||||
environment:
|
environment:
|
||||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
|
||||||
MYSQL_DATABASE: novadb
|
MYSQL_DATABASE: novadb
|
||||||
MYSQL_USER: novaconium
|
MYSQL_USER: novaconium
|
||||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
|
- "/etc/timezone:/etc/timezone:ro"
|
||||||
|
- "/etc/localtime:/etc/localtime:ro"
|
||||||
- ./data/db:/var/lib/mysql
|
- ./data/db:/var/lib/mysql
|
||||||
networks:
|
networks:
|
||||||
- internal
|
- internal
|
||||||
|
|||||||
@@ -10,5 +10,7 @@ $config = [
|
|||||||
'base_url' => 'http://localhost:8000',
|
'base_url' => 'http://localhost:8000',
|
||||||
'secure_key' => '', //64 alphanumeric characters
|
'secure_key' => '', //64 alphanumeric characters
|
||||||
'logfile' => '/logs/novaconium.log',
|
'logfile' => '/logs/novaconium.log',
|
||||||
'loglevel' => 'ERROR' // 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'NONE'
|
'loglevel' => 'ERROR', // 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'NONE',
|
||||||
|
'matomo' => '1',
|
||||||
|
'fonts' => 'https://fonts.googleapis.com/css2?family=VT323:wght@400&family=Fira+Code:wght@400;500&display=swap&family=Material+Icons:wght@400;500&display=swap'
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
// Define our status code and message
|
|
||||||
$status_code = 404;
|
|
||||||
$status_message = 'The requested resource could not be found.';
|
|
||||||
|
|
||||||
// Set the HTTP response code and message
|
|
||||||
http_response_code($status_code);
|
|
||||||
header("Content-Type: text/html");
|
|
||||||
?>
|
|
||||||
|
|
||||||
<h1>Error 404 Resource Not found</h1>
|
|
||||||
<p><?php echo $status_message; ?></p>
|
|
||||||
<p style="font-size:10px; margin-top:60px">Novaconium Default 404 page.</p>
|
|
||||||
|
|
||||||
28
skeleton/novaconium/App/controllers/humans.php
Normal file
28
skeleton/novaconium/App/controllers/humans.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
header('Content-Type: text/plain; charset=UTF-8');
|
||||||
|
header('Cache-Control: public, max-age=604800');
|
||||||
|
|
||||||
|
echo <<<TXT
|
||||||
|
/*
|
||||||
|
* This humans.txt was generated by the framework.
|
||||||
|
* You may edit or remove it without affecting your site.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* FRAMEWORK */
|
||||||
|
Built With: Novaconium PHP
|
||||||
|
repo: https://git.4lt.ca/4lt/novaconium
|
||||||
|
Human: Nick Yeoman
|
||||||
|
url: https://www.nickyeoman.com/
|
||||||
|
Occupation: Linux Systems Administrator / Software Framework Author
|
||||||
|
Location: Canada
|
||||||
|
|
||||||
|
/* SITE */
|
||||||
|
Built with: Novaconium PHP
|
||||||
|
Runs on: CORXN Container
|
||||||
|
Optimized for: Humans
|
||||||
|
|
||||||
|
/* META */
|
||||||
|
There are four lights.
|
||||||
|
TXT;
|
||||||
@@ -1,2 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
$data = array_merge($data, [
|
||||||
|
'title' => 'Welcome to Novaconium Index Page',
|
||||||
|
'pageclass' => 'novaconium'
|
||||||
|
]);
|
||||||
|
|
||||||
view('index');
|
view('index');
|
||||||
|
|||||||
33
skeleton/novaconium/App/controllers/page.php
Normal file
33
skeleton/novaconium/App/controllers/page.php
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
$slug = $router->parameters['slug'];
|
||||||
|
$query=<<<EOSQL
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
heading,
|
||||||
|
description,
|
||||||
|
keywords,
|
||||||
|
author,
|
||||||
|
body,
|
||||||
|
created,
|
||||||
|
updated
|
||||||
|
FROM pages
|
||||||
|
WHERE slug = '$slug'
|
||||||
|
EOSQL;
|
||||||
|
|
||||||
|
//$db->debugGetRow($query);
|
||||||
|
$data = $db->getRow($query);
|
||||||
|
if(!$data) {
|
||||||
|
http_response_code('404');
|
||||||
|
header("Content-Type: text/html");
|
||||||
|
view('404');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = array_merge($data, [
|
||||||
|
'menuActive' => 'blog',
|
||||||
|
'pageid' => 'page',
|
||||||
|
'permissionsGroup' => $session->get('group')
|
||||||
|
]);
|
||||||
|
|
||||||
|
view('page', $data);
|
||||||
18
skeleton/novaconium/App/controllers/robots.php
Normal file
18
skeleton/novaconium/App/controllers/robots.php
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
// Send proper headers
|
||||||
|
header('Content-Type: text/plain; charset=UTF-8');
|
||||||
|
header('Cache-Control: public, max-age=604800');
|
||||||
|
|
||||||
|
// Use $config['base_url'] as-is
|
||||||
|
$baseUrl = $config['base_url'];
|
||||||
|
|
||||||
|
echo <<<TXT
|
||||||
|
# robots.txt for sites powered by Novaconium framework
|
||||||
|
|
||||||
|
User-agent: *
|
||||||
|
Disallow: /novaconium/
|
||||||
|
|
||||||
|
Sitemap: {$baseUrl}/sitemap.xml
|
||||||
|
TXT;
|
||||||
@@ -2,5 +2,14 @@
|
|||||||
$routes = [
|
$routes = [
|
||||||
'/' => [
|
'/' => [
|
||||||
'get' => 'index'
|
'get' => 'index'
|
||||||
|
],
|
||||||
|
'/robots.txt' => [
|
||||||
|
'get' => 'robots'
|
||||||
|
],
|
||||||
|
'/humans.txt' => [
|
||||||
|
'get' => 'humans'
|
||||||
|
],
|
||||||
|
'/page/{slug}' => [
|
||||||
|
'get' => 'page'
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{% extends '@novaconium/master.html.twig' %}
|
{% extends '@novaconium/master.html.twig' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<img src="https://git.4lt.ca/4lt/novaconium/media/branch/master/_assets/novaconium-logo.png" aalt="Novaconium framework logo" />
|
<h1 id="biglogo"><span class="main">Novaconium PHP</span></h1>
|
||||||
<h2>Minimalist PHP framework</h2>
|
<h2>Minimalist PHP framework</h2>
|
||||||
<p>
|
<p>
|
||||||
Edit <code>App/routes.php</code> and <code>App/controllers/index.php</code><br>
|
Edit <code>App/routes.php</code> and <code>App/controllers/index.php</code><br>
|
||||||
|
|||||||
11
skeleton/novaconium/App/views/page.html.twig
Normal file
11
skeleton/novaconium/App/views/page.html.twig
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{% extends '@novaconium/master.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<div class="page-meta">
|
||||||
|
{% if updated is not empty %}
|
||||||
|
<span class="page-updated">Last Updated: {{ updated | date("M\\. jS Y \\a\\t g:ia") }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{{ body | raw}}
|
||||||
|
{% endblock %}
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,19 @@
|
|||||||
<?php
|
<?php
|
||||||
|
// Enable error reporting for development environments
|
||||||
// error_reporting(E_ALL);
|
// error_reporting(E_ALL);
|
||||||
// ini_set('display_errors', 1);
|
// ini_set('display_errors', 1);
|
||||||
|
|
||||||
|
// Define the base path where the website is running from
|
||||||
define('BASEPATH', dirname(__DIR__, 1));
|
define('BASEPATH', dirname(__DIR__, 1));
|
||||||
require_once(BASEPATH . '/vendor/4lt/novaconium/src/novaconium.php');
|
|
||||||
?>
|
// Load Composer's autoload file to handle class autoloading
|
||||||
|
require BASEPATH . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
// Define the framework path
|
||||||
|
define('FRAMEWORKPATH', BASEPATH . '/vendor/4lt/novaconium');
|
||||||
|
|
||||||
|
// Bootstrap the Novaconium framework, which will create necessary objects like $session, $db, etc.
|
||||||
|
require FRAMEWORKPATH . '/src/novaconium.php';
|
||||||
|
|
||||||
|
// Run the application
|
||||||
|
makeitso();
|
||||||
|
|||||||
185
skeleton/novaconium/public/js/tabs.js
Normal file
185
skeleton/novaconium/public/js/tabs.js
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
// /js/tabs.js
|
||||||
|
|
||||||
|
// Tab switching
|
||||||
|
function switchTab(tabId, button) {
|
||||||
|
const contents = document.querySelectorAll('.tab-content');
|
||||||
|
contents.forEach(content => content.classList.remove('active'));
|
||||||
|
|
||||||
|
const buttons = document.querySelectorAll('.tab-button');
|
||||||
|
buttons.forEach(b => b.classList.remove('active'));
|
||||||
|
|
||||||
|
document.getElementById(tabId).classList.add('active');
|
||||||
|
button.classList.add('active');
|
||||||
|
|
||||||
|
if (tabId === 'content6') {
|
||||||
|
setTimeout(initTags, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tags Init (with custom dropdown autocomplete)
|
||||||
|
let tagsListeners = [];
|
||||||
|
|
||||||
|
function initTags() {
|
||||||
|
const tagsInput = document.getElementById('tags-input');
|
||||||
|
const tagsField = document.getElementById('tags');
|
||||||
|
const hiddenTags = document.getElementById('tags_json');
|
||||||
|
const dropdown = document.getElementById('tags-dropdown');
|
||||||
|
|
||||||
|
if (!tagsInput || !tagsField || !hiddenTags || !dropdown) {
|
||||||
|
console.warn('Tags elements missing');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old listeners
|
||||||
|
tagsListeners.forEach(ls => ls());
|
||||||
|
tagsListeners = [];
|
||||||
|
|
||||||
|
let tags = [];
|
||||||
|
let existingTags = [];
|
||||||
|
try {
|
||||||
|
tags = JSON.parse(tagsInput.dataset.tags || '[]');
|
||||||
|
existingTags = JSON.parse(tagsInput.dataset.existingTags || '[]');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('JSON error:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectedIndex = -1;
|
||||||
|
|
||||||
|
function renderTags() {
|
||||||
|
tagsInput.innerHTML = '';
|
||||||
|
tags.forEach((tag, index) => {
|
||||||
|
const chip = document.createElement('span');
|
||||||
|
chip.className = 'tag-chip';
|
||||||
|
chip.innerHTML = `${tag} <button type="button" class="tag-remove">×</button>`;
|
||||||
|
chip.querySelector('.tag-remove').onclick = () => {
|
||||||
|
tags.splice(index, 1);
|
||||||
|
renderTags();
|
||||||
|
hiddenTags.value = JSON.stringify(tags);
|
||||||
|
};
|
||||||
|
tagsInput.appendChild(chip);
|
||||||
|
});
|
||||||
|
tagsInput.appendChild(tagsField);
|
||||||
|
tagsInput.appendChild(dropdown);
|
||||||
|
hiddenTags.value = JSON.stringify(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDropdown() {
|
||||||
|
const value = tagsField.value.toLowerCase().trim();
|
||||||
|
dropdown.innerHTML = '';
|
||||||
|
dropdown.setAttribute('aria-expanded', value ? 'true' : 'false');
|
||||||
|
selectedIndex = -1;
|
||||||
|
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
|
const matches = existingTags
|
||||||
|
.filter(tag => tag.toLowerCase().startsWith(value) && !tags.includes(tag.toLowerCase()))
|
||||||
|
.slice(0, 10);
|
||||||
|
|
||||||
|
matches.forEach((tag, index) => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.textContent = tag;
|
||||||
|
li.setAttribute('role', 'option');
|
||||||
|
li.onclick = () => selectTag(tag);
|
||||||
|
li.onmouseover = () => { selectedIndex = index; updateHighlight(); };
|
||||||
|
dropdown.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (matches.length) dropdown.parentElement.classList.add('has-dropdown');
|
||||||
|
else dropdown.parentElement.classList.remove('has-dropdown');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHighlight() {
|
||||||
|
dropdown.querySelectorAll('li').forEach((li, index) => {
|
||||||
|
li.classList.toggle('selected', index === selectedIndex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectTag(tag) {
|
||||||
|
addTag(tag, true);
|
||||||
|
tagsField.value = '';
|
||||||
|
dropdown.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
function addTag(inputValue, refocus = false) {
|
||||||
|
const tag = inputValue.trim().toLowerCase().replace(/[^\w-]/g, '');
|
||||||
|
if (tag && tag.length > 0 && tag.length <= 50 && !tags.includes(tag)) {
|
||||||
|
tags.push(tag);
|
||||||
|
renderTags();
|
||||||
|
if (refocus) tagsField.focus();
|
||||||
|
}
|
||||||
|
tagsField.value = '';
|
||||||
|
updateDropdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
const keydownListener = (e) => {
|
||||||
|
if (dropdown.getAttribute('aria-expanded') === 'true') {
|
||||||
|
if (e.key === 'ArrowDown') {
|
||||||
|
e.preventDefault();
|
||||||
|
selectedIndex = Math.min(selectedIndex + 1, dropdown.querySelectorAll('li').length - 1);
|
||||||
|
updateHighlight();
|
||||||
|
dropdown.querySelector(`li:nth-child(${selectedIndex + 1})`)?.scrollIntoView({ block: 'nearest' });
|
||||||
|
} else if (e.key === 'ArrowUp') {
|
||||||
|
e.preventDefault();
|
||||||
|
selectedIndex = Math.max(selectedIndex - 1, 0);
|
||||||
|
updateHighlight();
|
||||||
|
dropdown.querySelector(`li:nth-child(${selectedIndex + 1})`)?.scrollIntoView({ block: 'nearest' });
|
||||||
|
} else if ((e.key === 'Enter' || e.key === 'Tab') && selectedIndex >= 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
const selected = dropdown.querySelector(`li:nth-child(${selectedIndex + 1})`)?.textContent;
|
||||||
|
if (selected) selectTag(selected);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
e.preventDefault();
|
||||||
|
tagsField.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dropdown.getAttribute('aria-expanded') === 'true' || selectedIndex < 0) {
|
||||||
|
if (e.key === 'Enter' || e.key === ',') {
|
||||||
|
e.preventDefault();
|
||||||
|
addTag(tagsField.value, true);
|
||||||
|
} else if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (tagsField.value.trim()) addTag(tagsField.value, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tagsField.addEventListener('keydown', keydownListener);
|
||||||
|
tagsListeners.push(() => tagsField.removeEventListener('keydown', keydownListener));
|
||||||
|
|
||||||
|
const inputListener = () => updateDropdown();
|
||||||
|
tagsField.addEventListener('input', inputListener);
|
||||||
|
tagsListeners.push(() => tagsField.removeEventListener('input', inputListener));
|
||||||
|
|
||||||
|
const blurListener = () => {
|
||||||
|
setTimeout(() => dropdown.setAttribute('aria-expanded', 'false'), 150);
|
||||||
|
if (tagsField.value.trim()) addTag(tagsField.value);
|
||||||
|
};
|
||||||
|
tagsField.addEventListener('blur', blurListener);
|
||||||
|
tagsListeners.push(() => tagsField.removeEventListener('blur', blurListener));
|
||||||
|
|
||||||
|
renderTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init on load and observe tab changes
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const tagsTab = document.getElementById('content6');
|
||||||
|
if (tagsTab?.classList.contains('active')) {
|
||||||
|
initTags();
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||||
|
const target = mutation.target;
|
||||||
|
if (target.id === 'content6' && target.classList.contains('active')) {
|
||||||
|
initTags();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
observer.observe(document.body, { subtree: true, attributes: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Export switchTab for external use if needed
|
||||||
|
window.switchTab = switchTab;
|
||||||
1
skeleton/novaconium/sass/project.sass
Normal file
1
skeleton/novaconium/sass/project.sass
Normal file
@@ -0,0 +1 @@
|
|||||||
|
@use '../vendor/4lt/novaconium/sass/novaconium'
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium;
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ class Database {
|
|||||||
public $lastid;
|
public $lastid;
|
||||||
|
|
||||||
public function __construct($dbinfo) {
|
public function __construct($dbinfo) {
|
||||||
$this->conn = new mysqli($dbinfo['host'], $dbinfo['user'], $dbinfo['pass'], $dbinfo['name']);
|
$this->conn = new \mysqli($dbinfo['host'], $dbinfo['user'], $dbinfo['pass'], $dbinfo['name']);
|
||||||
if ($this->conn->connect_error) {
|
if ($this->conn->connect_error) {
|
||||||
die("Connection failed: " . $this->conn->connect_error);
|
die("Connection failed: " . $this->conn->connect_error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium;
|
||||||
class Logger {
|
class Logger {
|
||||||
protected string $logFile;
|
protected string $logFile;
|
||||||
protected int $logLevelThreshold;
|
protected int $logLevelThreshold;
|
||||||
@@ -42,6 +43,11 @@ class Logger {
|
|||||||
$this->log('WARNING', $msg);
|
$this->log('WARNING', $msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Alias
|
||||||
|
public function warn(string $msg): void {
|
||||||
|
$this->log('WARNING', $msg);
|
||||||
|
}
|
||||||
|
|
||||||
public function error(string $msg): void {
|
public function error(string $msg): void {
|
||||||
$this->log('ERROR', $msg);
|
$this->log('ERROR', $msg);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium;
|
||||||
class MessageHandler {
|
class MessageHandler {
|
||||||
private $messages = [
|
private $messages = [
|
||||||
'error' => [],
|
'error' => [],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium;
|
||||||
class Post {
|
class Post {
|
||||||
private $data = [];
|
private $data = [];
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium;
|
||||||
/**
|
/**
|
||||||
* Use
|
* Use
|
||||||
* $redirect->url('/login');
|
* $redirect->url('/login');
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium;
|
||||||
class Router {
|
class Router {
|
||||||
public $routes = [];
|
public $routes = [];
|
||||||
public $query = [];
|
public $query = [];
|
||||||
@@ -19,10 +19,10 @@ class Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function loadRoutes() {
|
private function loadRoutes() {
|
||||||
require_once(FRAMEWORKPATH . '/config/routes.php');
|
require_once( \FRAMEWORKPATH . '/config/routes.php');
|
||||||
// Check if Path exists
|
// Check if Path exists
|
||||||
if (file_exists(BASEPATH . '/App/routes.php')) {
|
if (file_exists(\BASEPATH . '/App/routes.php')) {
|
||||||
require_once( BASEPATH . '/App/routes.php');
|
require_once( \BASEPATH . '/App/routes.php');
|
||||||
}
|
}
|
||||||
$routes = array_merge((array)$routes, (array)$framework_routes);
|
$routes = array_merge((array)$routes, (array)$framework_routes);
|
||||||
|
|
||||||
@@ -109,19 +109,19 @@ class Router {
|
|||||||
|
|
||||||
if (str_starts_with($this->controller, 'NOVACONIUM')) {
|
if (str_starts_with($this->controller, 'NOVACONIUM')) {
|
||||||
$trimmed = substr($this->controller, strlen('NOVACONIUM/'));
|
$trimmed = substr($this->controller, strlen('NOVACONIUM/'));
|
||||||
$cp = FRAMEWORKPATH . '/controllers/' . $trimmed . '.php';
|
$cp = \FRAMEWORKPATH . '/controllers/' . $trimmed . '.php';
|
||||||
} else {
|
} else {
|
||||||
$cp = BASEPATH . '/App/controllers/' . $this->controller . '.php';
|
$cp = \BASEPATH . '/App/controllers/' . $this->controller . '.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file_exists($cp)) {
|
if (file_exists($cp)) {
|
||||||
return $cp;
|
return $cp;
|
||||||
} else {
|
} else {
|
||||||
//Check if 404 exits
|
//Check if 404 exits
|
||||||
if (file_exists(BASEPATH . '/App/controllers/404.php')) {
|
if (file_exists( \BASEPATH . '/App/controllers/404.php')) {
|
||||||
return BASEPATH . '/App/controllers/404.php';
|
return \BASEPATH . '/App/controllers/404.php';
|
||||||
} else {
|
} else {
|
||||||
return FRAMEWORKPATH . '/defaults/App/controllers/404.php';
|
return \FRAMEWORKPATH . '/controllers/404.php';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium\Services;
|
||||||
class Auth
|
class Auth
|
||||||
{
|
{
|
||||||
|
|
||||||
53
src/Services/TagManager.php
Normal file
53
src/Services/TagManager.php
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Novaconium\Services;
|
||||||
|
|
||||||
|
class TagManager
|
||||||
|
{
|
||||||
|
protected $db;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
global $db;
|
||||||
|
$this->db = $db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign tags to a page.
|
||||||
|
*
|
||||||
|
* This will delete old links and insert new ones.
|
||||||
|
*
|
||||||
|
* @param int $pageId
|
||||||
|
* @param array $tags Array of tag names
|
||||||
|
*/
|
||||||
|
public function setTagsForPage(int $pageId, array $tags): void
|
||||||
|
{
|
||||||
|
// Remove existing links
|
||||||
|
$this->db->query("DELETE FROM page_tags WHERE page_id = ?", [$pageId]);
|
||||||
|
|
||||||
|
foreach ($tags as $tagName) {
|
||||||
|
$tagName = trim($tagName);
|
||||||
|
if ($tagName === '') continue;
|
||||||
|
|
||||||
|
// Check if tag exists
|
||||||
|
$stmt = $this->db->query("SELECT id FROM tags WHERE name = ?", [$tagName]);
|
||||||
|
$row = $stmt->fetch_assoc(); // mysqli_result -> assoc array
|
||||||
|
if ($row) {
|
||||||
|
$tagId = $row['id'];
|
||||||
|
} else {
|
||||||
|
// Insert new tag
|
||||||
|
$this->db->query(
|
||||||
|
"INSERT INTO tags (name, created) VALUES (?, NOW())",
|
||||||
|
[$tagName]
|
||||||
|
);
|
||||||
|
$tagId = $this->db->lastid;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link page to tag
|
||||||
|
$this->db->query(
|
||||||
|
"INSERT INTO page_tags (page_id, tag_id) VALUES (?, ?)",
|
||||||
|
[$pageId, $tagId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,24 +1,30 @@
|
|||||||
<?php
|
<?php
|
||||||
|
namespace Novaconium;
|
||||||
class Session {
|
class Session {
|
||||||
private $session;
|
private $session;
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
session_start();
|
session_start();
|
||||||
if (!isset($_SESSION['token'])) {
|
|
||||||
$this->setToken();
|
|
||||||
$this->session['messages'] = [];
|
|
||||||
} else {
|
|
||||||
$this->session = $_SESSION;
|
|
||||||
}
|
}
|
||||||
|
$this->session = &$_SESSION; // Reference $_SESSION to keep them in sync
|
||||||
|
if (!isset($this->session['token'])) {
|
||||||
|
$this->setToken();
|
||||||
|
}
|
||||||
|
if (!isset($this->session['messages'])) {
|
||||||
|
$this->session['messages'] = []; // Always ensure messages is an array
|
||||||
|
}
|
||||||
|
if (!isset($this->session['formData'])) {
|
||||||
|
$this->session['formData'] = []; // Initialize formData
|
||||||
|
}
|
||||||
|
if (!isset($this->session['errors'])) {
|
||||||
|
$this->session['errors'] = []; // Initialize errors
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setToken() {
|
public function setToken() {
|
||||||
if (!isset($this->session['token'])) {
|
|
||||||
$this->session['token'] = bin2hex(random_bytes(32));
|
$this->session['token'] = bin2hex(random_bytes(32));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public function set($key, $value) {
|
public function set($key, $value) {
|
||||||
$this->session[$key] = $value;
|
$this->session[$key] = $value;
|
||||||
@@ -45,13 +51,13 @@ class Session {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function write() {
|
public function write() {
|
||||||
$_SESSION = $this->session;
|
// No need to assign to $_SESSION since $this->session is a reference
|
||||||
session_write_close();
|
session_write_close();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function kill() {
|
public function kill() {
|
||||||
|
$this->session = [];
|
||||||
$_SESSION = [];
|
$_SESSION = [];
|
||||||
session_destroy();
|
session_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,60 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dump and Die
|
* Dump and Die (debug function)
|
||||||
|
*
|
||||||
|
* @param mixed ...$vars Any number of variables to dump and then exit the script.
|
||||||
*/
|
*/
|
||||||
function dd(...$vars) {
|
function dd(...$vars): void {
|
||||||
echo "<pre style='background:#222;color:#0f0;padding:10px;border-radius:5px;'>";
|
echo "<pre style='background:#1e1e1e;color:#00ff00;padding:12px;border-radius:6px;font-size:14px;'>";
|
||||||
foreach ($vars as $var) {
|
foreach ($vars as $var) {
|
||||||
var_dump($var);
|
var_dump($var);
|
||||||
echo "\n";
|
echo "\n";
|
||||||
}
|
}
|
||||||
echo "</pre>";
|
echo "</pre>";
|
||||||
die();
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeitso() {
|
/**
|
||||||
|
* Finalize the request lifecycle.
|
||||||
|
*
|
||||||
|
* This function safely writes session data, stores flash messages, closes the database
|
||||||
|
* connection if configured, and performs a redirect.
|
||||||
|
*/
|
||||||
|
function makeitso(): void {
|
||||||
global $session, $db, $redirect, $config, $messages, $log;
|
global $session, $db, $redirect, $config, $messages, $log;
|
||||||
|
|
||||||
if (!empty($config['database']['host'])) {
|
// ------------------------------
|
||||||
|
// Close database if configured
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// Check if database configuration exists and the $db object is set
|
||||||
|
if (!empty($config['database']['host']) && isset($db)) {
|
||||||
|
// If the $db object has a close method, call it to close the connection
|
||||||
|
if (method_exists($db, 'close')) {
|
||||||
$db->close();
|
$db->close();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Save flash messages to session
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// Set all messages in the session
|
||||||
$session->set('messages', $messages->getAllMessages());
|
$session->set('messages', $messages->getAllMessages());
|
||||||
|
|
||||||
|
// Write any buffered session data to persistent storage
|
||||||
$session->write();
|
$session->write();
|
||||||
|
|
||||||
|
// ------------------------------
|
||||||
|
// Perform redirect
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
// Execute the redirect operation
|
||||||
$redirect->execute();
|
$redirect->execute();
|
||||||
|
|
||||||
exit();
|
// Exit the script after processing is complete
|
||||||
|
exit;
|
||||||
}
|
}
|
||||||
@@ -1,63 +1,54 @@
|
|||||||
<?php
|
<?php
|
||||||
define('FRAMEWORKPATH', BASEPATH . '/vendor/4lt/novaconium');
|
|
||||||
|
|
||||||
require_once(BASEPATH . '/vendor/autoload.php');
|
// --- Load Config ---
|
||||||
|
if (file_exists(\BASEPATH . '/App/config.php')) {
|
||||||
//Check if config file exists
|
require_once \BASEPATH . '/App/config.php';
|
||||||
if (file_exists(BASEPATH . '/App/config.php')) {
|
|
||||||
require_once(BASEPATH . '/App/config.php');
|
|
||||||
} else {
|
} else {
|
||||||
require_once(FRAMEWORKPATH . '/defaults/App/config.php');
|
require_once \FRAMEWORKPATH . '/skeleton/novaconium/App/config.php';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logging
|
require_once \FRAMEWORKPATH . '/src/functions.php';
|
||||||
require_once(FRAMEWORKPATH . '/src/Logger.php');
|
require_once \FRAMEWORKPATH . '/src/twig.php';
|
||||||
$log = new Logger(BASEPATH . $config['logfile'], $config['loglevel']);
|
|
||||||
|
|
||||||
// Global Functions
|
// --- Logging ---
|
||||||
require_once(FRAMEWORKPATH . '/src/functions.php');
|
use Novaconium\Logger;
|
||||||
|
$log = new Logger(\BASEPATH . $config['logfile'], $config['loglevel']);
|
||||||
|
|
||||||
// Creates the view() function using twig
|
// --- Twig Data Array ---
|
||||||
$data = array();
|
$data = [];
|
||||||
require_once(FRAMEWORKPATH . '/src/twig.php');
|
$data['fonts'] = $config['fonts'] ?? [];
|
||||||
|
$data['matomo'] = $config['matomo'] ?? 0;
|
||||||
|
|
||||||
// Start a Session
|
// --- Session ---
|
||||||
require_once(FRAMEWORKPATH . '/src/Session.php');
|
use Novaconium\Session;
|
||||||
$session = new Session();
|
$session = new Session();
|
||||||
$data['token'] = $session->get('token');
|
$data['token'] = $session->get('token');
|
||||||
$data['username'] = $session->get('username');
|
$data['username'] = $session->get('username');
|
||||||
if ($config['loglevel'] == 'DEBUG') {
|
|
||||||
$data['debug'] = nl2br(print_r($session->debug(), true));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Messages
|
// --- Messages ---
|
||||||
require_once(FRAMEWORKPATH . '/src/MessageHandler.php');
|
use Novaconium\MessageHandler;
|
||||||
$messages = new MessageHandler($session->flash('messages'));
|
$messages = new MessageHandler($session->flash('messages'));
|
||||||
|
foreach (['error', 'notice'] as $key) {
|
||||||
foreach (['error','notice'] as $key){
|
|
||||||
$data[$key] = $messages->showMessages($key);
|
$data[$key] = $messages->showMessages($key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load Database Class
|
// --- Database ---
|
||||||
|
use Novaconium\Database;
|
||||||
if (!empty($config['database']['host'])) {
|
if (!empty($config['database']['host'])) {
|
||||||
require_once(FRAMEWORKPATH . '/src/Database.php');
|
|
||||||
$db = new Database($config['database']);
|
$db = new Database($config['database']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanatize POST Data
|
// --- POST Wrapper ---
|
||||||
|
use Novaconium\Post;
|
||||||
if (!empty($_POST)) {
|
if (!empty($_POST)) {
|
||||||
require_once(FRAMEWORKPATH . '/src/Post.php');
|
$post = new Post($_POST);
|
||||||
$post = new POST($_POST);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start a Redirect
|
// --- Redirect Handler ---
|
||||||
require_once(FRAMEWORKPATH . '/src/Redirect.php');
|
use Novaconium\Redirect;
|
||||||
$redirect = new Redirect();
|
$redirect = new Redirect();
|
||||||
|
|
||||||
// Load a controller
|
// --- Router ---
|
||||||
require_once(FRAMEWORKPATH . '/src/Router.php');
|
use Novaconium\Router;
|
||||||
$router = new Router();
|
$router = new Router();
|
||||||
//$router->debug();
|
require_once $router->controllerPath;
|
||||||
require_once($router->controllerPath);
|
|
||||||
|
|
||||||
makeitso();
|
|
||||||
|
|||||||
61
src/twig.php
61
src/twig.php
@@ -1,32 +1,69 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
//Twig
|
declare(strict_types=1);
|
||||||
function view($name = '', $moreData = []) {
|
|
||||||
global $config, $data; // Use the globally included $config
|
|
||||||
|
|
||||||
if (!empty($moreData)){
|
use Twig\Environment;
|
||||||
|
use Twig\Loader\FilesystemLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a Twig view.
|
||||||
|
*
|
||||||
|
* @param string $name Template name without extension (e.g. "index")
|
||||||
|
* @param array $moreData Additional variables to merge into template context
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function view(string $name = '', array $moreData = []): bool
|
||||||
|
{
|
||||||
|
global $config, $data;
|
||||||
|
|
||||||
|
if (!is_array($data)) {
|
||||||
|
$data = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($moreData)) {
|
||||||
$data = array_merge($data, $moreData);
|
$data = array_merge($data, $moreData);
|
||||||
}
|
}
|
||||||
|
|
||||||
$loader = new Twig\Loader\FilesystemLoader(BASEPATH . '/App/views/');
|
// ----------------------------------------
|
||||||
|
// Setup Twig
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
$loader = new FilesystemLoader(BASEPATH . '/App/views/');
|
||||||
|
|
||||||
|
// Add namespace paths
|
||||||
|
if (is_dir(FRAMEWORKPATH . '/twig')) {
|
||||||
$loader->addPath(FRAMEWORKPATH . '/twig', 'novaconium');
|
$loader->addPath(FRAMEWORKPATH . '/twig', 'novaconium');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dir(FRAMEWORKPATH . '/views')) {
|
||||||
$loader->addPath(FRAMEWORKPATH . '/views', 'novacore');
|
$loader->addPath(FRAMEWORKPATH . '/views', 'novacore');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dir(BASEPATH . '/App/templates')) {
|
||||||
$loader->addPath(BASEPATH . '/App/templates', 'override');
|
$loader->addPath(BASEPATH . '/App/templates', 'override');
|
||||||
|
}
|
||||||
|
|
||||||
$twig = new Twig\Environment($loader);
|
$twig = new Environment($loader);
|
||||||
|
|
||||||
// Add config to Twig globally
|
|
||||||
$twig->addGlobal('config', $config);
|
$twig->addGlobal('config', $config);
|
||||||
|
|
||||||
// Check if the template exists
|
// ----------------------------------------
|
||||||
if (file_exists(BASEPATH . '/App/views/' . $name . '.html.twig')) {
|
// Render template
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
$appTemplatePath = BASEPATH . '/App/views/' . $name . '.html.twig';
|
||||||
|
|
||||||
|
if (file_exists($appTemplatePath)) {
|
||||||
echo $twig->render($name . '.html.twig', $data);
|
echo $twig->render($name . '.html.twig', $data);
|
||||||
return true;
|
return true;
|
||||||
} elseif (str_starts_with($name, '@')) { // Check if using framework
|
}
|
||||||
|
|
||||||
|
if (str_starts_with($name, '@')) {
|
||||||
echo $twig->render($name . '.html.twig', $data);
|
echo $twig->render($name . '.html.twig', $data);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
|
|
||||||
echo "Error: Twig Template ($name) Not Found.";
|
echo "Error: Twig Template ($name) Not Found.";
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
71
twig/coming-soon/index.html.twig
Normal file
71
twig/coming-soon/index.html.twig
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{{ title | default('Coming Soon') }}</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link rel="stylesheet" href="/css/novaconium.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: url("{{ bg_image | default('https://w.wallhaven.cc/full/5y/wallhaven-5yymk9.jpg') }}") no-repeat center center fixed;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="coming-soon">
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ heading | default('Coming Soon') }}</h1>
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
{% if countdown %}
|
||||||
|
<div class="countdown" id="countdown"></div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# TODO: listmonk #}
|
||||||
|
<form class="newsletter" action="#" method="post">
|
||||||
|
<label for="email">Subscribe to our newsletter:</label>
|
||||||
|
<input type="email" id="email" name="email" placeholder="Your email address" required>
|
||||||
|
<button type="submit">Subscribe</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{# Social media icons using Font Awesome #}
|
||||||
|
<div class="social-icons">
|
||||||
|
{% block social %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if countdown %}
|
||||||
|
<script>
|
||||||
|
{% if countdown %}
|
||||||
|
{% set default_launch = "now"|date_modify("+30 days")|date("Y-m-d\TH:i:s") %}
|
||||||
|
const launchDate = new Date('{{ launch_date | default(default_launch) }}').getTime();
|
||||||
|
const countdownEl = document.getElementById('countdown');
|
||||||
|
|
||||||
|
function updateCountdown() {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
const distance = launchDate - now;
|
||||||
|
|
||||||
|
if (distance <= 0) {
|
||||||
|
countdownEl.innerText = 'Launched!';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const days = Math.floor(distance / (1000*60*60*24));
|
||||||
|
const hours = Math.floor((distance % (1000*60*60*24)) / (1000*60*60));
|
||||||
|
const mins = Math.floor((distance % (1000*60*60)) / (1000*60));
|
||||||
|
const secs = Math.floor((distance % (1000*60)) / 1000);
|
||||||
|
|
||||||
|
countdownEl.innerText = `${days}d ${hours}h ${mins}m ${secs}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCountdown();
|
||||||
|
setInterval(updateCountdown, 1000);
|
||||||
|
{% endif %}
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
42
twig/cp/control-panel.html.twig
Normal file
42
twig/cp/control-panel.html.twig
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html class="no-js" lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
{% include ['@novaconium/cp/head.html.twig'] %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body id="{{ pageid | default('pageid') }}" class="{{ pageclass | default('pageclass') }}" >
|
||||||
|
|
||||||
|
{# Page Header #}
|
||||||
|
<header>
|
||||||
|
<h1 id="biglogo"><span class="main">Novaconium</span></h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main Content Of The Page -->
|
||||||
|
<div id="panel" class="container">
|
||||||
|
|
||||||
|
{% include ['@novaconium/cp/menu.html.twig'] %}
|
||||||
|
|
||||||
|
<main id="content">
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Page Footer #}
|
||||||
|
<footer>
|
||||||
|
<div class="copyright">© {{ 'now' | date('Y') }} Novaconium</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
{% if editor == 'ace' %}
|
||||||
|
{% include '@novaconium/javascript/ace.html.twig' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if debug is not empty %}
|
||||||
|
<div id="debug">
|
||||||
|
<h2>Debugging Information</h2>
|
||||||
|
{{ debug|raw }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</body></html>
|
||||||
60
twig/cp/head.html.twig
Normal file
60
twig/cp/head.html.twig
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
{# =============================================================================
|
||||||
|
<HEAD>
|
||||||
|
=============================================================================
|
||||||
|
#}
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>{{ title | default('Welcome To Novaconium') }}</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
{# SEO & METADATA #}
|
||||||
|
<meta name="generator" content="Novaconium" />
|
||||||
|
<meta name="description" content="{{ description | default('No description given') }}">
|
||||||
|
<meta name="keywords" content="{{ keywords | default('website') }}">
|
||||||
|
<meta name="author" content="{{ author | default('anonymous') }}">
|
||||||
|
|
||||||
|
{# DARK MODE & THEME HINTS #}
|
||||||
|
<meta name="color-scheme" content="dark">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
|
||||||
|
{# OPEN GRAPH (OG) FOR SOCIAL SHARING #}
|
||||||
|
<meta property="og:title" content="{{ title | default('Welcome') }}">
|
||||||
|
<meta property="og:type" content="website">
|
||||||
|
<meta property="og:url" content="{{ app_url | default(request.scheme ~ '://' ~ request.host) }}">
|
||||||
|
<meta property="og:image" content="{{ og_image | default('/icon.png') }}">
|
||||||
|
|
||||||
|
{# PWA & FAVICONS #}
|
||||||
|
<link rel="manifest" href="site.webmanifest">
|
||||||
|
<link rel="apple-touch-icon" href="/icon.png">
|
||||||
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
|
||||||
|
{# GOOGLE FONTS (CDN VIA PRECONNECT) #}
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="{{ fonts | default('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=Roboto+Mono&family=Source+Code+Pro&family=Lato&family=Poppins&family=Material+Icons&family=Material+Icons+Outlined&family=VT323:wght@400&family=Fira+Code:wght@400;500&display=swap') }}"
|
||||||
|
rel="stylesheet"
|
||||||
|
>
|
||||||
|
|
||||||
|
{% if editor == 'ace' %}
|
||||||
|
<!-- ACE Editor -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.2/ace.min.css">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.2/ace.js"></script>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.2/mode-html.min.js"></script> {# HTML syntax #}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.2/theme-tomorrow_night.min.js"></script> {# Dark theme #}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.35.2/ext-language_tools.min.js"></script> {# Autocomplete #}
|
||||||
|
<!-- END ACE Editor -->
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# highlight.js #}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/ir-black.min.css">
|
||||||
|
|
||||||
|
<script src="/js/tabs.js"></script>
|
||||||
|
|
||||||
|
{# STYLESHEET #}
|
||||||
|
<link rel="stylesheet" href="/css/novaconium.css">
|
||||||
9
twig/cp/menu.html.twig
Normal file
9
twig/cp/menu.html.twig
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<div id="cp-menu">
|
||||||
|
<ul id="cp-nav">
|
||||||
|
<li><a href="/novaconium/dashboard">Dashboard</a></li>
|
||||||
|
<li><a href="/novaconium/pages">Pages</a></li>
|
||||||
|
<li><a href="/novaconium/messages">Messages</a></li>
|
||||||
|
<li><a href="/novaconium/settings">Settings</a></li>
|
||||||
|
<li><a href="/novaconium/logout">Logout</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
@@ -4,3 +4,25 @@
|
|||||||
like javascript
|
like javascript
|
||||||
or analytics
|
or analytics
|
||||||
-->
|
-->
|
||||||
|
{% include '@novaconium/javascript/page-edit.html.twig' %}
|
||||||
|
|
||||||
|
{% if editor == 'ace' %}
|
||||||
|
{% include '@novaconium/javascript/ace.html.twig' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if matomo > 0 %}
|
||||||
|
<!-- Matomo -->
|
||||||
|
<script>
|
||||||
|
var _paq = window._paq = window._paq || [];
|
||||||
|
_paq.push(['trackPageView']);
|
||||||
|
_paq.push(['enableLinkTracking']);
|
||||||
|
(function() {
|
||||||
|
var u = "//matomo.4lt.ca/";
|
||||||
|
_paq.push(['setTrackerUrl', u + 'matomo.php']);
|
||||||
|
_paq.push(['setSiteId', {{ matomo }}]);
|
||||||
|
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
|
||||||
|
g.async = true; g.src = u + 'matomo.js'; s.parentNode.insertBefore(g, s);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
<!-- End Matomo Code -->
|
||||||
|
{% endif %}
|
||||||
@@ -1,3 +1 @@
|
|||||||
<!--
|
<div class="copyright">© {{ 'now' | date('Y') }} Novaconium</div>
|
||||||
What goes in the footer html tag
|
|
||||||
-->
|
|
||||||
@@ -1,26 +1,48 @@
|
|||||||
<meta charset="utf-8">
|
{# =============================================================================
|
||||||
<title>{{ title | default('Welcome To Novaconium') }}</title>
|
<HEAD>
|
||||||
<meta name="generator" content="Novaconium" />
|
=============================================================================
|
||||||
|
#}
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
|
||||||
|
<title>{{ title | default('Welcome To Novaconium') }}</title>
|
||||||
|
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
{# SEO & METADATA #}
|
||||||
|
<meta name="generator" content="Novaconium" />
|
||||||
<meta name="description" content="{{ description | default('No description given') }}">
|
<meta name="description" content="{{ description | default('No description given') }}">
|
||||||
<meta name="keywords" content="{{ keywords | default('website') }}">
|
<meta name="keywords" content="{{ keywords | default('website') }}">
|
||||||
<meta name="author" content="{{ author | default('anonymous') }}">
|
<meta name="author" content="{{ author | default('anonymous') }}">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
|
|
||||||
|
{# DARK MODE & THEME HINTS #}
|
||||||
|
<meta name="color-scheme" content="dark">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
|
||||||
|
{# OPEN GRAPH (OG) FOR SOCIAL SHARING #}
|
||||||
<meta property="og:title" content="{{ title | default('Welcome') }}">
|
<meta property="og:title" content="{{ title | default('Welcome') }}">
|
||||||
<meta property="og:type" content="">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:url" content="">
|
<meta property="og:url" content="{{ app_url | default(request.scheme ~ '://' ~ request.host) }}">
|
||||||
<meta property="og:image" content="">
|
<meta property="og:image" content="{{ og_image | default('/icon.png') }}">
|
||||||
|
|
||||||
|
{# PWA & FAVICONS #}
|
||||||
<link rel="manifest" href="site.webmanifest">
|
<link rel="manifest" href="site.webmanifest">
|
||||||
<link rel="apple-touch-icon" href="/icon.png">
|
<link rel="apple-touch-icon" href="/icon.png">
|
||||||
|
|
||||||
<!-- Place favicon.ico in the root directory -->
|
|
||||||
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/favicon.ico">
|
||||||
|
|
||||||
{# https://developers.google.com/fonts/docs/getting_started #}
|
{# GOOGLE FONTS (CDN VIA PRECONNECT) #}
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Fira+Code&family=Source+Code+Pro&display=swap&family=Material+Icons&family=Material+Icons+Outlined" rel="stylesheet">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link
|
||||||
|
href="{{ fonts | default('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono&family=Roboto+Mono&family=Source+Code+Pro&family=Lato&family=Poppins&family=Material+Icons&family=Material+Icons+Outlined&family=VT323:wght@400&family=Fira+Code:wght@400;500&display=swap') }}"
|
||||||
|
rel="stylesheet"
|
||||||
|
>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/css/novaconium.css">
|
{# STYLESHEET #}
|
||||||
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
|
|
||||||
<meta name="theme-color" content="#000000">
|
{# highlight.js #}
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
|
<script>hljs.highlightAll();</script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/ir-black.min.css">
|
||||||
40
twig/javascript/ace.html.twig
Normal file
40
twig/javascript/ace.html.twig
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<!-- Ace Editor -->
|
||||||
|
<script>
|
||||||
|
// Ace Editor Init (cleaned: removed duplicate setUseWorker, added null checks)
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const bodyTextarea = document.getElementById('body');
|
||||||
|
const bodyEditorEl = document.getElementById('body-editor');
|
||||||
|
|
||||||
|
if (bodyTextarea && bodyEditorEl && typeof ace !== 'undefined') { // Check ace loaded
|
||||||
|
try {
|
||||||
|
const editor = ace.edit(bodyEditorEl);
|
||||||
|
editor.session.setValue(bodyTextarea.value || ''); // Load initial HTML
|
||||||
|
editor.session.setMode('ace/mode/html'); // HTML syntax highlighting
|
||||||
|
editor.setTheme('ace/theme/tomorrow_night'); // Dark theme (black bg, green accents)
|
||||||
|
editor.setOptions({
|
||||||
|
fontSize: 14,
|
||||||
|
showPrintMargin: false,
|
||||||
|
wrap: true, // Line wrapping
|
||||||
|
useWorker: false // Disable worker for linting if not needed (faster)
|
||||||
|
});
|
||||||
|
editor.session.setUseWorker(false); // No JS linting in HTML mode
|
||||||
|
|
||||||
|
// Enable basic autocomplete
|
||||||
|
editor.setOptions({ enableBasicAutocompletion: true });
|
||||||
|
|
||||||
|
// Sync back to textarea on change
|
||||||
|
editor.session.on('change', function() {
|
||||||
|
bodyTextarea.value = editor.getValue(); // Full HTML string
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Ace loaded! Initial value:', editor.getValue().substring(0, 50) + '...'); // Debug
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ace init failed:', error); // Graceful error
|
||||||
|
bodyEditorEl.style.display = 'none'; // Hide div, show plain textarea if needed
|
||||||
|
bodyTextarea.style.display = 'block';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('Ace elements or lib missing'); // Fallback to plain textarea
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
{% if username is not empty %}
|
|
||||||
<div class="left">
|
<div class="left">
|
||||||
|
{% if pageclass == "novaconium" %}
|
||||||
<ul id="leftnav">
|
<ul id="leftnav">
|
||||||
<li><a href="/">Home</a></li>
|
<li><a href="/">Home</a></li>
|
||||||
|
|
||||||
|
{% if username is not empty %}
|
||||||
<li><a href="/novaconium/dashboard">Dashboard</a></li>
|
<li><a href="/novaconium/dashboard">Dashboard</a></li>
|
||||||
<li><a href="/novaconium/pages">Pages</a></li>
|
<li><a href="/novaconium/pages">Pages</a></li>
|
||||||
<li><a href="/novaconium/messages">Messages</a></li>
|
<li><a href="/novaconium/messages">Messages</a></li>
|
||||||
<li><a href="/novaconium/logout">Logout</a></li>
|
<li><a href="/novaconium/logout">Logout</a></li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="/novaconium/login">Login</a></li>
|
||||||
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
|
||||||
7
views/404.html.twig
Normal file
7
views/404.html.twig
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{% extends '@novaconium/master.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>404 File Not Found</h1>
|
||||||
|
<p style="font-size:10px; margin-top:60px">Novaconium Default 404 page.</p>
|
||||||
|
<p><a href="/">Return Home</a></p>
|
||||||
|
{% endblock %}
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
|
|
||||||
|
|
||||||
<div id="login_form">
|
<div id="login">
|
||||||
|
|
||||||
<form method="post" action="/novaconium/login">
|
<form method="post" action="/novaconium/login">
|
||||||
<input type="hidden" name="token" value="{{ token }}" />
|
<input type="hidden" name="token" value="{{ token }}" />
|
||||||
32
views/coming-soon.html.twig
Normal file
32
views/coming-soon.html.twig
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{% extends '@novaconium/coming-soon/index.html.twig' %}
|
||||||
|
|
||||||
|
{% set bg_image = "https://i.4lt.ca/4lt/waterBubbles.webp" %}
|
||||||
|
{% set default_launch = "now"|date_modify("+30 days")|date("Y-m-d\TH:i:s") %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<p>Our website is under construction. We’re working hard to bring you a better experience.</p>
|
||||||
|
<p>Stay tuned for updates!</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block social %}
|
||||||
|
<a href="mailto:you@example.com" title="Email">
|
||||||
|
<i class="fa-solid fa-envelope"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="tel:+1234567890" title="Phone">
|
||||||
|
<i class="fa-solid fa-phone"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="#" title="Twitter">
|
||||||
|
<i class="fab fa-twitter"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="#" title="Facebook">
|
||||||
|
<i class="fab fa-facebook-f"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="#" title="Instagram">
|
||||||
|
<i class="fab fa-instagram"></i>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends '@novaconium/master.html.twig' %}
|
{% extends '@novaconium/cp/control-panel.html.twig' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
{% extends '@novaconium/master.html.twig' %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<h2>Edit Page - {{ title }}</h2>
|
|
||||||
|
|
||||||
<form method="post" action="/novaconium/savePage">
|
|
||||||
<input type="hidden" name="id" value="{{ rows.id }}">
|
|
||||||
<input type="hidden" name="token" value="{{ token }}">
|
|
||||||
|
|
||||||
<label for="title">Title:</label>
|
|
||||||
<input type="text" id="title" name="title" value="{{ rows.title }}" required>
|
|
||||||
|
|
||||||
<label for="heading">Heading:</label>
|
|
||||||
<input type="text" id="heading" name="heading" value="{{ rows.heading }}">
|
|
||||||
|
|
||||||
<label for="description">Description:</label>
|
|
||||||
<input type="text" id="description" name="description" value="{{ rows.description }}">
|
|
||||||
|
|
||||||
<label for="keywords">Keywords:</label>
|
|
||||||
<input type="text" id="keywords" name="keywords" value="{{ rows.keywords }}">
|
|
||||||
|
|
||||||
<label for="author">Author:</label>
|
|
||||||
<input type="text" id="author" name="author" value="{{ rows.author }}">
|
|
||||||
|
|
||||||
<label for="slug">Slug: (<a href="/page/{{ rows.slug }}" target="_new">/page/{{ rows.slug }}</a>)</label>
|
|
||||||
<input type="text" id="slug" name="slug" value="{{ rows.slug }}" required>
|
|
||||||
|
|
||||||
<label for="path">Path:</label>
|
|
||||||
<input type="text" id="path" name="path" value="{{ rows.path }}">
|
|
||||||
|
|
||||||
<label for="intro">Intro:</label>
|
|
||||||
<textarea id="intro" name="intro" rows="5">{{ rows.intro }}</textarea>
|
|
||||||
|
|
||||||
<label for="body">Body:</label>
|
|
||||||
<textarea id="body" name="body" rows="10">{{ rows.body }}</textarea>
|
|
||||||
|
|
||||||
<label for="notes">Notes:</label>
|
|
||||||
<textarea id="notes" name="notes" rows="5">{{ rows.notes }}</textarea>
|
|
||||||
|
|
||||||
<label for="draft">
|
|
||||||
<input type="checkbox" id="draft" name="draft" value="1" {% if rows.draft %}checked{% endif %}>
|
|
||||||
Save as Draft
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label for="changefreq">Change Frequency:</label>
|
|
||||||
<select id="changefreq" name="changefreq">
|
|
||||||
{% set freqs = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'] %}
|
|
||||||
{% for freq in freqs %}
|
|
||||||
<option value="{{ freq }}" {% if rows.changefreq == freq %}selected{% endif %}>{{ freq|capitalize }}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<label for="priority">Priority (0.0 - 1.0):</label>
|
|
||||||
<input type="number" id="priority" name="priority" value="{{ rows.priority }}" step="0.1" min="0" max="1">
|
|
||||||
|
|
||||||
<p><strong>Created:</strong> {{ rows.created|date("Y-m-d H:i:s") }}</p>
|
|
||||||
<p><strong>Last Updated:</strong> {{ rows.updated|date("Y-m-d H:i:s") }}</p>
|
|
||||||
|
|
||||||
<button type="submit">Save Changes</button>
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
|
||||||
62
views/editpage/index.html.twig
Normal file
62
views/editpage/index.html.twig
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
{% extends '@novaconium/cp/control-panel.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Edit Page - {{ title }}</h2>
|
||||||
|
|
||||||
|
<form method="post" action="/novaconium/savePage" id="edit-page-form-novaconium">
|
||||||
|
|
||||||
|
<input type="hidden" name="id" value="{{ rows.id }}">
|
||||||
|
<input type="hidden" name="token" value="{{ token }}">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit">Save Changes</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="edit-page-title" class="form-group">
|
||||||
|
<label for="title">
|
||||||
|
Title
|
||||||
|
<span class="tooltip">?<span class="tooltiptext">This is the title for the cms, it's the default in twig, heading and metadata can be overwritten.</span></span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<input type="text" id="title" name="title" value="{{ rows.title }}" required>
|
||||||
|
<div id="edit-page-dates">
|
||||||
|
<strong>Last Updated:</strong> {{ rows.updated|date("Y-m-d H:i:s") }},
|
||||||
|
<strong>Created:</strong> {{ rows.created|date("Y-m-d H:i:s") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-container">
|
||||||
|
|
||||||
|
<!-- Tab Navigation -->
|
||||||
|
<nav class="tab-nav">
|
||||||
|
<button type="button" class="tab-button active" onclick="switchTab('content1')">Basic Info</button>
|
||||||
|
<button type="button" class="tab-button" onclick="switchTab('content2')">SEO & Meta</button>
|
||||||
|
<button type="button" class="tab-button" onclick="switchTab('content3')">Sitemap</button>
|
||||||
|
<button type="button" class="tab-button" onclick="switchTab('content4')">Page Tweaks</button>
|
||||||
|
<button type="button" class="tab-button" onclick="switchTab('content5')">Page Notes</button>
|
||||||
|
<button type="button" class="tab-button" onclick="switchTab('content6', this)">Tags</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div id="content1" class="tab-content active">
|
||||||
|
{% include '@novacore/editpage/tab-main.html.twig' %}
|
||||||
|
</div>
|
||||||
|
<div id="content2" class="tab-content">
|
||||||
|
{% include '@novacore/editpage/tab-metadata.html.twig' %}
|
||||||
|
</div>
|
||||||
|
<div id="content3" class="tab-content">
|
||||||
|
{% include '@novacore/editpage/tab-other.html.twig' %}
|
||||||
|
</div>
|
||||||
|
<div id="content4" class="tab-content">
|
||||||
|
{% include '@novacore/editpage/tab-tweaks.html.twig' %}
|
||||||
|
</div>
|
||||||
|
<div id="content5" class="tab-content">
|
||||||
|
{% include '@novacore/editpage/tab-notes.html.twig' %}
|
||||||
|
</div>
|
||||||
|
<div id="content6" class="tab-content">
|
||||||
|
{% include '@novacore/editpage/tab-tags.html.twig' %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
16
views/editpage/tab-main.html.twig
Normal file
16
views/editpage/tab-main.html.twig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="form-group">
|
||||||
|
<label for="slug">
|
||||||
|
Slug: (<a href="/page/{{ rows.slug }}" target="_new">/page/{{ rows.slug }}</a>)
|
||||||
|
<span class="tooltip">?<span class="tooltiptext">Slug is a human readable but uri friendly name for the page.</span></span>
|
||||||
|
</label>
|
||||||
|
<input type="text" id="slug" name="slug" value="{{ rows.slug }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ace Editor -->
|
||||||
|
<div class="form-group fullwidth">
|
||||||
|
<label for="body">Body:</label>
|
||||||
|
<div class="editor-container">
|
||||||
|
<textarea id="body" name="body" rows="10" style="display: none;">{{ rows.body|default('')|e('html') }}</textarea>
|
||||||
|
<div id="body-editor" class="ace-editor"></div> {# Ace mounts here #}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
17
views/editpage/tab-metadata.html.twig
Normal file
17
views/editpage/tab-metadata.html.twig
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
<h2>Metadata</h2>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="description">Description:</label>
|
||||||
|
<input type="text" id="description" name="description" value="{{ rows.description }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="keywords">Keywords:</label>
|
||||||
|
<input type="text" id="keywords" name="keywords" value="{{ rows.keywords }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="author">Author:</label>
|
||||||
|
<input type="text" id="author" name="author" value="{{ rows.author }}">
|
||||||
|
</div>
|
||||||
4
views/editpage/tab-notes.html.twig
Normal file
4
views/editpage/tab-notes.html.twig
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="form-group fullwidth">
|
||||||
|
<label for="notes">Notes:</label>
|
||||||
|
<textarea id="notes" name="notes" rows="5">{{ rows.notes }}</textarea>
|
||||||
|
</div>
|
||||||
25
views/editpage/tab-other.html.twig
Normal file
25
views/editpage/tab-other.html.twig
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<h2>Sitemap</h2>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="path">Path:</label>
|
||||||
|
<input type="text" id="path" name="path" value="{{ rows.path }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="changefreq">Change Frequency:</label>
|
||||||
|
<select id="changefreq" name="changefreq">
|
||||||
|
{% set freqs = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'] %}
|
||||||
|
{% for freq in freqs %}
|
||||||
|
<option value="{{ freq }}" {% if rows.changefreq == freq %}selected{% endif %}>
|
||||||
|
{{ freq|capitalize }}
|
||||||
|
</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="priority">Priority (0.0 - 1.0):</label>
|
||||||
|
<input type="number" id="priority" name="priority" value="{{ rows.priority }}" step="0.1" min="0" max="1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
12
views/editpage/tab-tags.html.twig
Normal file
12
views/editpage/tab-tags.html.twig
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<h1>Tags</h1>
|
||||||
|
<label for="tags">
|
||||||
|
Tags:
|
||||||
|
<span class="tooltip">?<span class="tooltiptext">Comma-separated keywords for categorizing this page (e.g., blog, tutorial). Click chips to remove; type to see suggestions.</span><span class="tooltip-desc" id="desc-tags">Comma-separated keywords for categorizing this page (e.g., blog, tutorial). Click chips to remove; type to see suggestions.</span></span>
|
||||||
|
</label>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="tags-input" id="tags-input" data-tags="{{ rows.page_tags|default('')|split(',')|filter('trim')|default([])|json_encode|e('html_attr') }}" data-existing-tags="{{ rows.existing_tags|default('')|split(',')|filter('trim')|default([])|json_encode|e('html_attr') }}">
|
||||||
|
<input type="text" id="tags" name="tags" placeholder="e.g., seo, cms, php" aria-describedby="desc-tags">
|
||||||
|
<ul id="tags-dropdown" class="tags-dropdown" role="listbox" aria-expanded="false"></ul> {# Custom dropdown #}
|
||||||
|
</div>
|
||||||
|
<input type="hidden" id="tags_json" name="tags_json" value="{{ rows.page_tags|default('')|split(',')|filter('trim')|default([])|json_encode|e('html_attr') }}">
|
||||||
|
</div>
|
||||||
10
views/editpage/tab-tweaks.html.twig
Normal file
10
views/editpage/tab-tweaks.html.twig
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<h2>Page Data</h2>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="heading">Heading:</label>
|
||||||
|
<input type="text" id="heading" name="heading" value="{{ rows.heading }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group fullwidth">
|
||||||
|
<label for="intro">Intro:</label>
|
||||||
|
<textarea id="intro" name="intro" rows="5">{{ rows.intro }}</textarea>
|
||||||
|
</div>
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends '@novaconium/master.html.twig' %}
|
{% extends '@novaconium/cp/control-panel.html.twig' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{% extends '@novaconium/master.html.twig' %}
|
{% extends '@novaconium/cp/control-panel.html.twig' %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>{{title}}</h1>
|
<h1>{{title}}</h1>
|
||||||
|
|||||||
269
views/samples/basic.html.twig
Normal file
269
views/samples/basic.html.twig
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
{% extends '@novaconium/master.html.twig' %}
|
||||||
|
|
||||||
|
{% set title = "Basic HTML Elements" %}
|
||||||
|
{% set description = "This is the basic html page from hugo" %}
|
||||||
|
{% set keywords = "html, css, javascript, php, twig" %}
|
||||||
|
{% set author = "anonymous" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<article class="post">
|
||||||
|
<header class="post__header">
|
||||||
|
<h1 class="post__title">{{ title }}</h1>
|
||||||
|
<div class="post__meta meta">
|
||||||
|
<div class="meta__item-datetime meta__item">
|
||||||
|
<svg class="meta__icon icon icon-time" width="16" height="14" viewBox="0 0 30 28"><path d="M15 0a14 14 0 1 1 0 28 1 1 0 0 1 0-28m0 3a3 3 0 1 0 0 22 3 3 0 0 0 0-22m1 4h-2v8.4l6.8 4.4L22 18l-6-3.8z"></path></svg><time class="meta__text" datetime="2018-04-16T00:00:00Z">April 16, 2018</time></div><div class="meta__item-categories meta__item"><svg class="meta__icon icon icon-category" width="16" height="16" viewBox="0 0 16 16"><path d="m7 2 1 2h8v11H0V2z"></path></svg><span class="meta__text"><a class="meta__link" href="/categories/development/" rel="category">Development</a>
|
||||||
|
</span>
|
||||||
|
</div></div>
|
||||||
|
</header>
|
||||||
|
<div class="content post__content clearfix">
|
||||||
|
<p>The main purpose of this article is to make sure that all basic HTML Elements are decorated with CSS so as to not miss any possible elements when creating new themes for Hugo.</p>
|
||||||
|
|
||||||
|
<h2 id="headings">Headings</h2>
|
||||||
|
|
||||||
|
<p>Let’s start with all possible headings. The HTML <code><h1></code>—<code><h6></code> elements represent six levels of section headings. <code><h1></code> is the highest section level and <code><h6></code> is the lowest.</p>
|
||||||
|
|
||||||
|
<h1 id="heading-1">Heading 1</h1>
|
||||||
|
|
||||||
|
<h2 id="heading-2">Heading 2</h2>
|
||||||
|
|
||||||
|
<h3 id="heading-3">Heading 3</h3>
|
||||||
|
|
||||||
|
<h4 id="heading-4">Heading 4</h4>
|
||||||
|
|
||||||
|
<h5 id="heading-5">Heading 5</h5>
|
||||||
|
|
||||||
|
<h6 id="heading-6">Heading 6</h6>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<h2 id="paragraph">Paragraph</h2>
|
||||||
|
|
||||||
|
<p>According to the <a href="https://www.w3.org/TR/html5/dom.html#elements">HTML5 specification</a> by <a href="https://www.w3.org/">W3C</a>, <strong>HTML documents consist of a tree of elements and text</strong>. Each element is denoted in the source by a <a href="https://www.w3.org/TR/html5/syntax.html#syntax-start-tags">start tag</a>, such as <code><body></code>, and an <a href="https://www.w3.org/TR/html5/syntax.html#syntax-end-tags">end tag</a>, such as <code></body></code>. (<em>Certain start tags and end tags can in certain cases be omitted and are implied by other tags.</em>)</p>
|
||||||
|
|
||||||
|
<p>Elements can have attributes, which control how the elements work. For example, hyperlink are formed using the <code>a</code> element and its <code>href</code> attribute.</p>
|
||||||
|
|
||||||
|
<h2 id="list-types">List Types</h2>
|
||||||
|
|
||||||
|
<h3 id="ordered-list">Ordered List</h3>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>First item</li>
|
||||||
|
<li>Second item</li>
|
||||||
|
<li>Third item</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h3 id="unordered-list">Unordered List</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>List item</li>
|
||||||
|
<li>Another item</li>
|
||||||
|
<li>And another item</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="nested-list">Nested list</h3>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>First item</li>
|
||||||
|
<li>Second item
|
||||||
|
<ul>
|
||||||
|
<li>Second item First subitem</li>
|
||||||
|
<li>Second item second subitem
|
||||||
|
<ul>
|
||||||
|
<li>Second item Second subitem First sub-subitem</li>
|
||||||
|
<li>Second item Second subitem Second sub-subitem</li>
|
||||||
|
<li>Second item Second subitem Third sub-subitem</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Second item Third subitem
|
||||||
|
<ol>
|
||||||
|
<li>Second item Third subitem First sub-subitem</li>
|
||||||
|
<li>Second item Third subitem Second sub-subitem</li>
|
||||||
|
<li>Second item Third subitem Third sub-subitem</li>
|
||||||
|
</ol>
|
||||||
|
</li></ul>
|
||||||
|
</li>
|
||||||
|
<li>Third item</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3 id="definition-list">Definition List</h3>
|
||||||
|
|
||||||
|
<p>HTML also supports definition lists.</p>
|
||||||
|
|
||||||
|
<dl>
|
||||||
|
<dt>Blanco tequila</dt>
|
||||||
|
<dd>The purest form of the blue agave spirit...</dd>
|
||||||
|
<dt>Reposado tequila</dt>
|
||||||
|
<dd>Typically aged in wooden barrels for between two and eleven months...</dd>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h2 id="blockquotes">Blockquotes</h2>
|
||||||
|
|
||||||
|
<p>The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a <code>footer</code> or <code>cite</code> element, and optionally with in-line changes such as annotations and abbreviations.</p>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>Quoted text.
|
||||||
|
This line is part of the same quote.
|
||||||
|
Also you can <em>put</em> <strong>Markdown</strong> into a blockquote.</p>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<p>Blockquote with a citation.</p>
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
<p>My goal wasn't to make a ton of money. It was to build good computers. I only started the company when I realized I could be an engineer forever.</p>
|
||||||
|
<footer>— <cite>Steve Wozniak</cite></footer>
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
<p>According to Mozilla’s website, <q cite="https://www.mozilla.org/en-US/about/history/details/">Firefox 1.0 was released in 2004 and became a big success.</q></p>
|
||||||
|
|
||||||
|
<h2 id="tables">Tables</h2>
|
||||||
|
|
||||||
|
<p>Tables aren’t part of the core Markdown spec, but Hugo supports them.</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Make</th>
|
||||||
|
<th>Model</th>
|
||||||
|
<th>Year</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>1</td>
|
||||||
|
<td>Honda</td>
|
||||||
|
<td>Accord</td>
|
||||||
|
<td>2009</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>2</td>
|
||||||
|
<td>Toyota</td>
|
||||||
|
<td>Camry</td>
|
||||||
|
<td>2012</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>3</td>
|
||||||
|
<td>Hyundai</td>
|
||||||
|
<td>Elantra</td>
|
||||||
|
<td>2010</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>Colons can be used to align columns.</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th align="left">Tables</th>
|
||||||
|
<th align="center">Are</th>
|
||||||
|
<th align="right">Cool</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td align="left">align: left</td>
|
||||||
|
<td align="center">align: center</td>
|
||||||
|
<td align="right">align: right</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="left">align: left</td>
|
||||||
|
<td align="center">align: center</td>
|
||||||
|
<td align="right">align: right</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td align="left">align: left</td>
|
||||||
|
<td align="center">align: center</td>
|
||||||
|
<td align="right">align: right</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<p>You can also use inline Markdown.</p>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Inline</th>
|
||||||
|
<th>Markdown</th>
|
||||||
|
<th>In</th>
|
||||||
|
<th>Table</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><em>italics</em></td>
|
||||||
|
<td><strong>bold</strong></td>
|
||||||
|
<td><del>strikethrough</del></td>
|
||||||
|
<td><code>code</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h2 id="code">Code</h2>
|
||||||
|
|
||||||
|
<pre><code class="language-html"><!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Example HTML5 Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>Test</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
</code></pre>
|
||||||
|
|
||||||
|
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-html" data-lang="html"><span style="color:#75715e"><!DOCTYPE html></span>
|
||||||
|
<<span style="color:#f92672">html</span> <span style="color:#a6e22e">lang</span><span style="color:#f92672">=</span><span style="color:#e6db74">"en"</span>>
|
||||||
|
<<span style="color:#f92672">head</span>>
|
||||||
|
<<span style="color:#f92672">meta</span> <span style="color:#a6e22e">charset</span><span style="color:#f92672">=</span><span style="color:#e6db74">"UTF-8"</span>>
|
||||||
|
<<span style="color:#f92672">title</span>>Example HTML5 Document</<span style="color:#f92672">title</span>>
|
||||||
|
</<span style="color:#f92672">head</span>>
|
||||||
|
<<span style="color:#f92672">body</span>>
|
||||||
|
<<span style="color:#f92672">p</span>>Test</<span style="color:#f92672">p</span>>
|
||||||
|
</<span style="color:#f92672">body</span>>
|
||||||
|
</<span style="color:#f92672">html</span>></code></pre></div>
|
||||||
|
|
||||||
|
<h2 id="other-stuff-abbr-sub-sup-kbd-etc">Other stuff — abbr, sub, sup, kbd, etc.</h2>
|
||||||
|
|
||||||
|
<p><abbr title="Graphics Interchange Format">GIF</abbr> is a bitmap image format.</p>
|
||||||
|
|
||||||
|
<p>H<sub>2</sub>O</p>
|
||||||
|
|
||||||
|
<p>C<sub>6</sub>H<sub>12</sub>O<sub>6</sub></p>
|
||||||
|
|
||||||
|
<p>X<sup>n</sup> + Y<sup>n</sup> = Z<sup>n</sup></p>
|
||||||
|
|
||||||
|
<p>Press <kbd>X</kbd> to win. Or press <kbd><kbd>CTRL</kbd>+<kbd>ALT</kbd>+<kbd>F</kbd></kbd> to show FPS counter.</p>
|
||||||
|
|
||||||
|
<p><mark>As a unit of information in information theory, the bit has alternatively been called a shannon</mark>, named after Claude Shannon, the founder of field of information theory.</p>
|
||||||
|
</div>
|
||||||
|
<footer class="post__footer">
|
||||||
|
|
||||||
|
<div class="post__tags tags clearfix">
|
||||||
|
<svg class="tags__badge icon icon-tag" width="16" height="16" viewBox="0 0 32 32"><path d="M4 0h8s2 0 4 2l15 15s2 2 0 4L21 31s-2 2-4 0L2 16s-2-2-2-4V3s0-3 4-3m3 10a3 3 0 0 0 0-6 3 3 0 0 0 0 6"></path></svg>
|
||||||
|
<ul class="tags__list">
|
||||||
|
<li class="tags__item">
|
||||||
|
<a class="tags__link btn" href="/tags/html/" rel="tag">HTML</a>
|
||||||
|
</li>
|
||||||
|
<li class="tags__item">
|
||||||
|
<a class="tags__link btn" href="/tags/css/" rel="tag">CSS</a>
|
||||||
|
</li>
|
||||||
|
<li class="tags__item">
|
||||||
|
<a class="tags__link btn" href="/tags/basic-elements/" rel="tag">Basic Elements</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
6
views/settings.html.twig
Normal file
6
views/settings.html.twig
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{% extends '@novaconium/cp/control-panel.html.twig' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{title}}</h1>
|
||||||
|
<p>Settings will go here.</p>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user