added proper f3 tag extension for bulma form

This commit is contained in:
tp_dhu 2025-02-16 22:03:56 +00:00
parent 1f10cc7f89
commit 6c936208b7
12 changed files with 267 additions and 19 deletions

View File

@ -154,6 +154,7 @@ class KBController {
$f3->set('article_tag_ids', $article_tag_ids); $f3->set('article_tag_ids', $article_tag_ids);
// render // render
$f3->set('js', 'kb_edit.js');
$f3->set('content', '../ui/views/kb/edit.html'); $f3->set('content', '../ui/views/kb/edit.html');
echo \Template::instance()->render('../ui/templates/layout.html'); echo \Template::instance()->render('../ui/templates/layout.html');
$f3->clear('SESSION.error'); $f3->clear('SESSION.error');

View File

@ -0,0 +1,12 @@
<?php
class ParsedownPreview {
public function view($f3){
$preview_text = $f3->get('POST.content');
echo Parsedown::instance()->text($preview_text);
}
}

View File

@ -0,0 +1,108 @@
<?php
class BulmaFormHelper extends \Prefab {
const H_FIELD_INPUT = 1;
const H_FIELD_TEXTAREA = 2;
const H_FIELD_SELECT = 3;
static public function render($args) {
$type = strtoupper($args['@attrib']['type']);
// all *
$label = $args['@attrib']['label'];
$name = $args['@attrib']['name'];
$value = isset($args['@attrib']['value']) ? $args['@attrib']['value'] : '';
// select
$options = isset($args['@attrib']['options']) ? $args['@attrib']['options'] : '';
$selected = isset($args['@attrib']['selected']) ? $args['@attrib']['selected'] : '';
//
$label = \Template::instance()->build($label);
$name = \Template::instance()->build($name);
$value = \Template::instance()->build($value);
if(defined("BulmaFormHelper::$type")){
$type_const = constant("BulmaFormHelper::$type");
switch( $type_const ){
case BulmaFormHelper::H_FIELD_INPUT:
return BulmaFormHelper::build_h_field_input($label, $name, $value);
break;
case BulmaFormHelper::H_FIELD_TEXTAREA:
return BulmaFormHelper::build_h_field_textarea($label, $name, $value);
break;
case BulmaFormHelper::H_FIELD_SELECT:
return BulmaFormHelper::build_h_field_select($label, $name, $options, $selected);
break;
default:
return '<div class="notification is-danger">Error: Bulma CSS Form TYPE ('.$type.') not defined.</div>';
break;
}
} else {
return '<div class="notification is-danger">Error: Bulma CSS Form TYPE not defined.</div>';
}
}
static function build_h_field_input($label, $name, $value){
$string = '
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">'.$label.'</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<input class="input" type="text" id="'.$name.'" name="'.
$name.'" value="'.
$value.'">
</div>
</div>
</div>
</div>
';
return $string;
}
static function build_h_field_select($label, $name, $options, $selected){
$string =
'<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">'.$label.'</label>
</div>
<div class="field-body">
<div class="field">
<div class="select">
$options
</div>
</div>
</div>
</div>
';
return $string;
}
static function build_h_field_textarea($label, $name, $value){
$string = '
<div class="field is-horizontal">
<div class="field-label is-normal">
<label class="label">'.$label.'</label>
</div>
<div class="field-body">
<div class="field">
<div class="control">
<textarea class="textarea" id="'.$name.'" name="'.
$name.'">'.$value.'</textarea>
</div>
</div>
</div>
</div>
';
return $string;
}
}
\Template::instance()->extend('bulma', 'BulmaFormHelper::render');

View File

@ -0,0 +1,20 @@
<?php
class ParsedownHelper extends \Prefab {
static public function render($args) {
$content = $args[0];
$content_token = \Template::instance()->token($content);
return '
<parsedown_rendered class="content">
<?php echo \ParsedownHelper::instance()->build('.$content_token.'); ?>
</parsedown_rendered>';
}
function build($content){
return Parsedown::instance()->text($content);
}
}
\Template::instance()->extend('parsedown', 'ParsedownHelper::render');

View File

@ -8,7 +8,7 @@ class BulmaForm {
$string = ' $string = '
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label>%label%</label> <label class="label">%label%</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
@ -31,7 +31,7 @@ class BulmaForm {
$string = ' $string = '
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label>%label%</label> <label class="label">%label%</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">
@ -54,7 +54,7 @@ class BulmaForm {
$string = ' $string = '
<div class="field is-horizontal"> <div class="field is-horizontal">
<div class="field-label is-normal"> <div class="field-label is-normal">
<label>%label%</label> <label class="label">%label%</label>
</div> </div>
<div class="field-body"> <div class="field-body">
<div class="field"> <div class="field">

View File

@ -6,13 +6,16 @@ $f3 = \Base::instance();
/** /**
* Not required yet * Not required yet
*/ */
// $htmlpurifier = new \HTMLPurifier(); $htmlpurifier = \HTMLPurifier::instance();
// $htmlpurifier->purify($input); // $htmlpurifier->purify($input);
$md = \Parsedown::instance(); $md = \Parsedown::instance();
$md->setSafeMode(true);
$f3->set('DEBUG', 3); // development debug
$f3->config('../app/.env.cfg'); $f3->config('../app/.env.cfg');
$f3->set('DEBUG', 3); // development debug
$f3->set('CACHE', FALSE);
$f3->set('EXT', [new ParsedownHelper, new BulmaFormHelper]);
$f3->set('DB', new \DB\SQL( $f3->set('DB', new \DB\SQL(
'mysql:host=localhost;port=3306;dbname=' . $f3->get('database.db_name'), 'mysql:host=localhost;port=3306;dbname=' . $f3->get('database.db_name'),
@ -63,6 +66,8 @@ $f3->route('GET /tags', 'TagController->index');
$f3->route('GET /tag/create', 'TagController->createForm'); $f3->route('GET /tag/create', 'TagController->createForm');
$f3->route('POST /tag/create', 'TagController->create'); $f3->route('POST /tag/create', 'TagController->create');
// parsedown preview
$f3->route('POST /parsedown/preview', 'ParsedownPreview->view');
// dashboard // dashboard
$f3->route('GET /dashboard', 'DashboardController->index'); $f3->route('GET /dashboard', 'DashboardController->index');

75
public/js/kb_edit.js Normal file
View File

@ -0,0 +1,75 @@
// switch to target tab pane
function switchTab(targetId){
var panes = document.querySelectorAll('.tab-content .tab-pane');
for (var i=0; i< panes.length; i++){
panes[i].style.display = 'none';
}
var targetPane = document.getElementById(targetId);
if(targetPane){
targetPane.style.display = 'block';
}
}
// send ajax post request with content to specified url
function ajaxPost(content, url, callback){
var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function(){
if(xhr.readyState === XMLHttpRequest.DONE){
if(xhr.status === 200){
callback(xhr.responseText);
} else {
console.error("AJAX error: " + xhr.status);
}
}
};
var params = 'content=' + encodeURIComponent(content);
xhr.send(params);
}
// load preview via ajax into preview element
function loadPreview(previewElement){
var sourceId = previewElement.getAttribute('data-source');
var handlerUrl = previewElement.getAttribute('data-handler');
var method = previewElement.getAttribute('data-method');
var sourceElement = document.getElementById(sourceId);
if(sourceElement){
var content = sourceElement.value;
if(method && method.toLowerCase() == 'post'){
ajaxPost(content, handlerUrl, function (response){
previewElement.innerHTML = response;
});
}
}
}
// initialise tab links to handle tab switching
function initTabs(){
var tabLinks = document.querySelectorAll('.tabs a[data-target]');
for(var i=0; i<tabLinks.length; i++){
tabLinks[i].addEventListener('click', function(e){
e.preventDefault();
var target = this.getAttribute('data-target');
switchTab(target);
// if the new tab has a preview element, load the preview
var pane = document.getElementById(target);
if(pane){
var previewElement = pane.querySelector('.preview');
if(previewElement){
console.log('pane has preview el')
loadPreview(previewElement);
} else {
console.log('pane doesnt have preview el');
}
}
});
}
}
document.addEventListener('DOMContentLoaded', function(){
initTabs();
});

View File

@ -14,10 +14,13 @@
<link rel="stylesheet" href="/style.css"> <link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkradio@2.1/dist/css/bulma-checkradio.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma-checkradio@2.1/dist/css/bulma-checkradio.min.css">
<!-- font awesome --> <!-- font awesome -->
<link rel="stylesheet" <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css"
integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg=="
crossorigin="anonymous" referrerpolicy="no-referrer" /> crossorigin="anonymous" referrerpolicy="no-referrer" />
<!-- additional JS -->
<check if="{{ isset(@js) }}">
<script src="/js/{{ @js}}"></script>
</check>
</head> </head>
<body> <body>
@ -85,7 +88,9 @@
<!-- Footer --> <!-- Footer -->
<footer class="footer"> <footer class="footer">
<div class="content has-text-centered"> <div class="content has-text-centered">
<p>&copy; <?php echo date('Y'); ?> Terry Probert</p> <p>&copy;
<?php echo date('Y'); ?> Terry Probert
</p>
</div> </div>
</footer> </footer>

View File

@ -4,18 +4,23 @@
<div class="content"> <div class="content">
<form action="/kb/create" method="POST"> <form action="/kb/create" method="POST">
{{ BulmaForm::horizontal_field_input('Title:', 'title') }} <bulma type="H_FIELD_INPUT" label="Title:" name="title" value=""></bulma>
<div id="editor" class="block"> <div id="editor" class="block">
<div class="tabs"> <div class="tabs">
<ul> <ul>
<li class="is-active"><a>Write</a></li> <li class="is-active"><a>Write</a></li>
<li class=""><a>Preivew</a></li> <li class=""><a>Preview</a></li>
</ul> </ul>
</div> </div>
<div class="block"> <div class="block">
{{ BulmaForm::horizontal_field_textarea('Content:', 'content') }} <div class="tab-content">
<bulma type="H_FIELD_TEXTAREA" label="Content:" name="content" value=""></bulma>
</div>
<div class="tab-content">
</div>
</div> </div>
</div> </div>

View File

@ -3,18 +3,27 @@
<form action="/kb/{{@article.id}}/update" method="POST"> <form action="/kb/{{@article.id}}/update" method="POST">
{{ BulmaForm::horizontal_field_input('Title:', 'title', @article.title)}} <bulma type="H_FIELD_INPUT" label="Title:" name="title" value="{{@article.title}}"></bulma>
<div id="editor" class="block"> <div id="editor" class="block">
<div class="tabs"> <div class="tabs">
<ul> <ul>
<li class="is-active"><a>Write</a></li> <li class="is-active"><a data-target="pane-editor">Write</a></li>
<li class=""><a>Preivew</a></li> <li class=""><a data-target="pane-preview">Preview</a></li>
</ul> </ul>
</div> </div>
<div class="block"> <div class="tab-content">
{{ BulmaForm::horizontal_field_textarea('Content:', 'content', @article.content) }} <div class="tab-pane" id="pane-editor">
<div class="block">
<bulma type="H_FIELD_TEXTAREA" label="Content:" name="content" value="{{@article.content}}"></bulma>
</div>
</div>
<div class="tab-pane" id="pane-preview">
<div class="block content">
<div class="preview" data-source="content" data-handler="/parsedown/preview" data-method="post"></div>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -13,7 +13,15 @@
<form method="GET" action="/kb"> <form method="GET" action="/kb">
<div class="level"> <div class="level">
<div class="level-item"> <div class="level-item">
<input class="input" type="text" name="search" placeholder="Search by title..."> <check if="{{ isset(@GET.search)}}">
<true>
<input class="input" type="text" name="search" placeholder="Search by title..."
value="{{ HTMLPurifier::instance()->purify( @GET.search) }}">
</true>
<false>
<input class="input" type="text" name="search" placeholder="Search by title...">
</false>
</check>
</div> </div>
<div class="level-right"> <div class="level-right">
<div class="select"> <div class="select">

View File

@ -1,7 +1,7 @@
<h1 class="title">{{@article.title}}</h1> <h1 class="title">{{@article.title}}</h1>
<div class="content"> <div class="content">
{{ Parsedown::instance()->text(@article.content) }} <parsedown>{{ @article.content | raw }}</parsedown>
</div> </div>