init branch for Bludit v4

This commit is contained in:
dignajar 2020-11-01 11:55:34 +01:00
parent 8c4a6d8f9e
commit 226750af09
29 changed files with 2027 additions and 635 deletions

View file

@ -6,7 +6,7 @@ AddDefaultCharset UTF-8
RewriteEngine on
# Base directory
#RewriteBase /
RewriteBase /
# Deny direct access to the next directories
RewriteRule ^bl-content/(databases|workspaces|pages|tmp)/.*$ - [R=404,L]

View file

@ -19,8 +19,8 @@ checkRole(array('admin', 'editor', 'author'));
// ============================================================================
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
createPage($_POST);
Redirect::page('content');
$pageKey = createPage($_POST);
Redirect::page('edit-content/'.$pageKey);
}
// ============================================================================

View file

@ -1,9 +1,9 @@
a {
color: #0078D4;
color: #1A66A6;
}
a:hover {
color: #003f6f;
color: #1A66A6;
text-decoration: none;
}
@ -12,7 +12,7 @@ a:hover {
}
.text-primary {
color: #0078D4!important;
color: #06A8C5!important;
}
.text-danger {
@ -28,22 +28,61 @@ a.text-danger:hover {
border-radius: 2px;
}
.btn-sm {
padding: .25rem .75rem;
}
.btn-save {
color: #5b8e09;
}
.btn-save:hover {
color: #466d07;
}
.btn-cancel:hover {
color: #000;
}
.btn-primary {
background-color: #0078D4;
border-color: #0078D4;
background-color: #0378D3;
border-color: #0378D3;
}
.btn-primary:hover {
background-color: #4585CF;
border-color: #4a90e2;
background-color: #0270c4;
border-color: #0270c4;
}
.btn-light.focus, .btn-light:focus {
box-shadow: none;
.btn-primary-disabled {
background-color: #71b6ff !important;
border-color: #71b6ff !important;
}
.btn.focus, .btn:focus {
box-shadow: none;
.btn-secondary {
background-color: #6c757d;
border-color: #6c757d;
}
.btn-secondary:hover {
background-color: #5a6268;
border-color: #5a6268;
}
.btn-light {
color: #212529;
background-color: #f3f3f3;
border-color: #ced4d9;
}
.btn-light.focus,
.btn-light:focus {
box-shadow: none !important;
}
.btn.focus,
.btn:focus {
box-shadow: none !important;
}
/* Form */
@ -59,4 +98,14 @@ a.text-danger:hover {
.table thead th {
font-size: 0.8em;
text-transform: uppercase!important;
}
/* Right sidebar Options */
#sidebarOptions .card {
background: none;
border: none;
}
#sidebarOptions a {
color: #212529;
}

View file

@ -1,19 +1,13 @@
html {
height: 100%;
font-size: 0.9rem;
background: #fcfcfc;
background: #EFEFEF;
}
body {
background: #fcfcfc;
background: #EFEFEF;
color: #1b1b1b;
}
/* Prevent events in iframes */
/* iframe{
pointer-events: none;
} */
/*
ICONS
*/
@ -25,7 +19,6 @@ body {
/*
SIDEBAR
*/
div.sidebar .nav-item a {
padding-left:0;
padding-right:0;
@ -61,9 +54,9 @@ div.sidebar .nav-item h4 {
BOOTSTRAP Hacks
*/
@media (min-width: 1200px) {
@media (min-width: 1300px) {
.container {
max-width: 1250px;
max-width: 1350px;
}
}
@ -78,28 +71,6 @@ div.sidebar .nav-item h4 {
}
.btn-light {
color: #212529;
background-color: #f3f3f3;
border-color: #ced4d9;
}
.btn-form {
background-color: #F3F3F3;
border-color: #DDD;
color: #000;
}
.btn-form:hover {
background-color: rgb(228, 228, 228);
border-color: #DDD;
color: #000;
}
code {
padding: 3px 5px 2px;
margin: 0 1px;
@ -112,28 +83,6 @@ code {
cursor: pointer;
}
.modal-body {
padding: 2rem;
}
.modal-footer {
background-color: rgb(247, 247, 247);
}
.modal-dialog .btn-link {
color: #000;
}
/*
LOGIN
*/
body.login {
background: rgb(255,255,255);
background: linear-gradient(0deg, rgba(255,255,255,1) 0%, rgba(250,250,250,1) 53%);
height: 100%;
}
/*
DASHBOARD
@ -301,10 +250,10 @@ td.child {
max-width: 100%;
right: 0;
}
#jseditorToolbarRight button {
#editorToolbarRight button {
font-size: 0px !important;
}
#jseditorToolbarRight button span {
#editorToolbarRight button span {
font-size: 16px !important;
}
.contentTools .btn {
@ -359,6 +308,16 @@ img.profilePicture {
cursor: pointer;
}
.switch-icon-publish {
.switch-icon-published {
color: #1cb11c;
}
.switch-icon-draft {
color: #b11c1c;
}
.switch-icon-unlisted,
.switch-icon-static,
.switch-icon-sticky {
color: #1c81b1;
}

View file

@ -25,10 +25,10 @@
<?php $L->p('Content') ?></a>
</li>
<?php if (!checkRole(array('admin'),false)): ?>
<li class="nav-item">
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'edit-user/'.$login->username() ?>">
<?php $L->p('Profile') ?></a>
</li>
<?php $L->p('Profile') ?></a>
</li>
<?php endif; ?>
<?php if (checkRole(array('admin'),false)): ?>
<li class="nav-item">
@ -57,15 +57,15 @@
</li>
<?php endif; ?>
<?php if (checkRole(array('admin'),false)): ?>
<?php
if (!empty($plugins['adminSidebar'])) {
foreach ($plugins['adminSidebar'] as $pluginSidebar) {
echo '<li class="nav-item">';
echo $pluginSidebar->adminSidebar();
echo '</li>';
<?php
if (!empty($plugins['adminSidebar'])) {
foreach ($plugins['adminSidebar'] as $pluginSidebar) {
echo '<li class="nav-item">';
echo $pluginSidebar->adminSidebar();
echo '</li>';
}
}
}
?>
?>
<?php endif; ?>
<li class="nav-item">
<a class="nav-link" href="<?php echo HTML_PATH_ADMIN_ROOT.'logout' ?>">

View file

@ -1,6 +1,6 @@
<!DOCTYPE html>
<html>
<head>
<html class="h-100">
<hea>
<title><?php echo $layout['title'] ?></title>
<meta charset="<?php echo CHARSET ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@ -21,7 +21,8 @@
echo Theme::css(array(
'jquery.datetimepicker.min.css',
'select2.min.css',
'select2-bootstrap4.min.css'
'select2-bootstrap4.min.css',
'token-autocomplete.css'
), DOMAIN_CORE_CSS);
?>
@ -33,7 +34,9 @@
echo Theme::js(array(
'jquery.datetimepicker.full.min.js',
'select2.full.min.js',
'functions.js'
'token-autocomplete.js',
'functions.js',
'api.js'
), DOMAIN_CORE_JS, null);
?>

View file

@ -209,7 +209,7 @@ EOF;
$html = '<div class="form-group m-0">';
if (!empty($args['label'])) {
$html .= '<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100" for="'.$id.'">'.$args['label'].'</label>';
$html .= '<h6 class="mt-4 mb-2 pb-2 text-uppercase">'.$args['label'].'</h6>';
}
$html .= '<textarea class="'.$class.'" id="'.$id.'" name="'.$args['name'].'" rows="'.$args['rows'].'" placeholder="'.$args['placeholder'].'">'.$args['value'].'</textarea>';
@ -361,7 +361,7 @@ EOF;
$html = '<div class="form-group m-0">';
if (!empty($args['label'])) {
$html .= '<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100" for="'.$id.'">'.$args['label'].'</label>';
$html .= '<h6 class="mt-4 mb-2 pb-2 text-uppercase">'.$args['label'].'</h6>';
}
$html .= '<select id="'.$id.'" name="'.$args['name'].'" class="'.$class.'">';
@ -382,7 +382,7 @@ EOF;
public static function formInputHidden($args)
{
return '<input type="hidden" id="js'.$args['name'].'" name="'.$args['name'].'" value="'.$args['value'].'">';
return '<input type="hidden" id="'.$args['name'].'" name="'.$args['name'].'" value="'.$args['value'].'">';
}
public static function alert($args)

View file

@ -1,10 +1,10 @@
<!DOCTYPE html>
<html>
<html class="h-100">
<head>
<title>Bludit</title>
<meta charset="<?php echo CHARSET ?>">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="robots" content="noindex,nofollow">
<meta name="robots" content="noindex, nofollow">
<!-- Favicon -->
<link rel="shortcut icon" type="image/x-icon" href="<?php echo HTML_PATH_CORE_IMG.'favicon.png?version='.BLUDIT_VERSION ?>">
@ -27,7 +27,7 @@
<!-- Plugins -->
<?php Theme::plugins('loginHead') ?>
</head>
<body class="login">
<body class="h-100">
<!-- Plugins -->
<?php Theme::plugins('loginBodyBegin') ?>
@ -35,9 +35,9 @@
<!-- Alert -->
<?php include('html/alert.php'); ?>
<div class="container">
<div class="row justify-content-md-center pt-5">
<div class="col-md-4 pt-5">
<div class="container h-100">
<div class="row h-100 justify-content-center align-items-center">
<div class="col-8 col-md-6 col-lg-4">
<?php
if (Sanitize::pathFile(PATH_ADMIN_VIEWS, $layout['view'].'.php')) {
include(PATH_ADMIN_VIEWS.$layout['view'].'.php');
@ -51,4 +51,4 @@
<?php Theme::plugins('loginBodyEnd') ?>
</body>
</html>
</html>

@ -1 +1 @@
Subproject commit 09138280134ec8014982f260c1ebb6a5b0091fdc
Subproject commit c97e1681dda9576128298d299e42973646df2475

View file

@ -47,8 +47,8 @@ echo Bootstrap::formOpen(array(
?>
<!-- TOOLBAR -->
<div id="jseditorToolbar" class="mb-1">
<div id="jseditorToolbarRight" class="btn-group btn-group-sm float-right" role="group" aria-label="Toolbar right">
<div id="editorToolbar" class="mb-1">
<div id="editorToolbarRight" class="btn-group btn-group-sm float-right" role="group" aria-label="Toolbar right">
<button type="button" class="btn btn-light" id="jsmediaManagerOpenModal" data-toggle="modal" data-target="#jsmediaManagerModal"><span class="fa fa-image"></span> <?php $L->p('Images') ?></button>
<button type="button" class="btn btn-light" id="jsoptionsSidebar" style="z-index:30"><span class="fa fa-cog"></span> <?php $L->p('Options') ?></button>
</div>

View file

@ -1,6 +1,6 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
echo '<h1 class="text-center mb-5 mt-5 font-weight-normal" style="color: #555;">BLUDIT</h1>';
echo '<h1 class="text-center font-weight-normal mb-5" style="color: #555;">'.$site->title().'</h1>';
echo Bootstrap::formOpen(array());
@ -11,7 +11,7 @@ echo Bootstrap::formOpen(array());
echo '
<div class="form-group">
<input type="text" value="'.(isset($_POST['username'])?htmlspecialchars($_POST['username']):'').'" class="form-control form-control-lg" id="jsusername" name="username" placeholder="'.$L->g('Username').'" autofocus>
<input type="text" value="'.(isset($_POST['username'])?Sanitize::html($_POST['username']):'').'" class="form-control form-control-lg" id="jsusername" name="username" placeholder="'.$L->g('Username').'" autofocus>
</div>
';
@ -34,4 +34,6 @@ echo Bootstrap::formOpen(array());
echo '</form>';
echo '<p class="mt-5 text-right">Powered by <a href="https://www.bludit.com">Bludit</a></p>'
?>

View file

@ -1,454 +1,121 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<?php
// Start form
echo Bootstrap::formOpen(array(
'id'=>'jsform',
'class'=>'d-flex flex-column h-100'
));
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
// UUID
// The UUID is generated in the controller
echo Bootstrap::formInputHidden(array(
'name'=>'uuid',
'value'=>$uuid
));
// Type = published, draft, sticky, static
echo Bootstrap::formInputHidden(array(
'name'=>'type',
'value'=>'published'
));
// Cover image
echo Bootstrap::formInputHidden(array(
'name'=>'coverImage',
'value'=>''
));
// Content
echo Bootstrap::formInputHidden(array(
'name'=>'content',
'value'=>''
));
?>
<!-- TOOLBAR -->
<div id="jseditorToolbar" class="mb-1">
<div id="jseditorToolbarRight" class="btn-group btn-group-sm float-right" role="group" aria-label="Toolbar right">
<button type="button" class="btn btn-light" id="jsmediaManagerOpenModal" data-toggle="modal" data-target="#jsmediaManagerModal"><span class="fa fa-image"></span> <?php $L->p('Images') ?></button>
<button type="button" class="btn btn-light" id="jsoptionsSidebar" style="z-index:30"><span class="fa fa-cog"></span> <?php $L->p('Options') ?></button>
</div>
<div id="jseditorToolbarLeft">
<button id="jsbuttonSave" type="button" class="btn btn-sm btn-primary" ><?php $L->p('Save') ?></button>
<button id="jsbuttonPreview" type="button" class="btn btn-sm btn-secondary"><?php $L->p('Preview') ?></button>
<span id="jsbuttonSwitch" data-switch="publish" class="ml-2 text-secondary switch-button"><i class="fa fa-square switch-icon-publish"></i> <?php $L->p('Publish') ?></span>
</div>
</div>
<script>
$(document).ready(function() {
$("#jsoptionsSidebar").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
// ----------------------------------------------------------------------------
// Variables for the view
// ----------------------------------------------------------------------------
var _pageKey = null; // The page key is generated the first time the user click on the button "Save"
var _uuid = '<?php echo $uuid ?>'; // The UUID is generated at the begining if the user uploaded files to the page
// ----------------------------------------------------------------------------
// Functions for the view
// ----------------------------------------------------------------------------
// Default function for the editor area (textarea)
// This helps if the user doesn't activate any plugin as editor
if (typeof editorGetContent != 'function') {
window.editorGetContent = function(){
return $('#editor').val();
};
}
if (typeof editorInsertMedia != 'function') {
window.editorInsertMedia = function(filename){
$('#editor').val($('#editor').val()+'<img src="'+filename+'" alt="">');
};
}
// Creates or save the page
// This function set the global variable "_pageKey"
function save(args) {
args['uuid'] = _uuid;
// If the "page key" doesn't exists means the page not was created
// Create the page to generate a "page key"
if (_pageKey == null) {
logs('Creating page');
api.createPage(args).then(function(key) {
logs('Page created. Key: '+key);
// Set the global variable with the page key
_pageKey = key;
// Disable the button save and change text
//$("#btnSave").attr("disabled", true).html("Saved");
});
$("#jsshadow").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
} else {
logs('Saving page');
args['pageKey'] = _pageKey;
api.savePage(args).then(function(key) {
logs('Page saved. Old key: '+_pageKey+' / New key: '+key);
// Set the global variable with the page key
// The page key can change after save the page so you need to set again the variable
_pageKey = key;
// Disable the button save and change text
//$("#btnSave").attr("disabled", true).html("Saved");
});
});
</script>
<!-- SIDEBAR OPTIONS -->
<div id="jseditorSidebar">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-link active show" id="nav-general-tab" data-toggle="tab" href="#nav-general" role="tab" aria-controls="general"><?php $L->p('General') ?></a>
<a class="nav-link" id="nav-advanced-tab" data-toggle="tab" href="#nav-advanced" role="tab" aria-controls="advanced"><?php $L->p('Advanced') ?></a>
<?php if (!empty($site->customFields())): ?>
<a class="nav-link" id="nav-custom-tab" data-toggle="tab" href="#nav-custom" role="tab" aria-controls="custom"><?php $L->p('Custom') ?></a>
<?php endif ?>
<a class="nav-link" id="nav-seo-tab" data-toggle="tab" href="#nav-seo" role="tab" aria-controls="seo"><?php $L->p('SEO') ?></a>
</div>
</nav>
<div class="tab-content pr-3 pl-3 pb-3">
<div id="nav-general" class="tab-pane fade show active" role="tabpanel" aria-labelledby="general-tab">
<?php
// Category
echo Bootstrap::formSelectBlock(array(
'name'=>'category',
'label'=>$L->g('Category'),
'selected'=>'',
'class'=>'',
'emptyOption'=>'- '.$L->g('Uncategorized').' -',
'options'=>$categories->getKeyNameArray()
));
// Description
echo Bootstrap::formTextareaBlock(array(
'name'=>'description',
'label'=>$L->g('Description'),
'selected'=>'',
'class'=>'',
'value'=>'',
'rows'=>5,
'placeholder'=>$L->get('this-field-can-help-describe-the-content')
));
?>
<!-- Cover Image -->
<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100"><?php $L->p('Cover Image') ?></label>
<div>
<img id="jscoverImagePreview" class="mx-auto d-block w-100" alt="Cover image preview" src="<?php echo HTML_PATH_CORE_IMG ?>default.svg" />
</div>
<div class="mt-2 text-center">
<button type="button" id="jsbuttonSelectCoverImage" class="btn btn-primary btn-sm"><?php echo $L->g('Select cover image') ?></button>
<button type="button" id="jsbuttonRemoveCoverImage" class="btn btn-secondary btn-sm"><?php echo $L->g('Remove cover image') ?></button>
</div>
<script>
$(document).ready(function() {
$("#jscoverImagePreview").on("click", function() {
openMediaManager();
});
$("#jsbuttonSelectCoverImage").on("click", function() {
openMediaManager();
});
$("#jsbuttonRemoveCoverImage").on("click", function() {
$("#jscoverImage").val('');
$("#jscoverImagePreview").attr('src', HTML_PATH_CORE_IMG+'default.svg');
});
});
</script>
</div>
<div id="nav-advanced" class="tab-pane fade" role="tabpanel" aria-labelledby="advanced-tab">
<?php
// Date
echo Bootstrap::formInputTextBlock(array(
'name'=>'date',
'label'=>$L->g('Date'),
'placeholder'=>'',
'value'=>Date::current(DB_DATE_FORMAT),
'tip'=>$L->g('date-format-format')
));
// Type
echo Bootstrap::formSelectBlock(array(
'name'=>'typeSelector',
'label'=>$L->g('Type'),
'selected'=>'',
'options'=>array(
'published'=>'- '.$L->g('Default').' -',
'sticky'=>$L->g('Sticky'),
'static'=>$L->g('Static')
),
'tip'=>''
));
// Position
echo Bootstrap::formInputTextBlock(array(
'name'=>'position',
'label'=>$L->g('Position'),
'tip'=>$L->g('Field used when ordering content by position'),
'value'=>$pages->nextPositionNumber()
));
// Tags
echo Bootstrap::formInputTextBlock(array(
'name'=>'tags',
'label'=>$L->g('Tags'),
'placeholder'=>'',
'tip'=>$L->g('Write the tags separated by comma')
));
// Parent
echo Bootstrap::formSelectBlock(array(
'name'=>'parent',
'label'=>$L->g('Parent'),
'options'=>array(),
'selected'=>false,
'class'=>'',
'tip'=>$L->g('Start typing a page title to see a list of suggestions.'),
));
?>
<script>
$(document).ready(function() {
var parent = $("#jsparent").select2({
placeholder: "",
allowClear: true,
theme: "bootstrap4",
minimumInputLength: 2,
ajax: {
url: HTML_PATH_ADMIN_ROOT+"ajax/get-published",
data: function (params) {
var query = {
checkIsParent: true,
query: params.term
}
return query;
},
processResults: function (data) {
return data;
}
},
escapeMarkup: function(markup) {
return markup;
},
templateResult: function(data) {
var html = data.text;
if (data.type=="static") {
html += '<span class="badge badge-pill badge-light">'+data.type+'</span>';
}
return html;
}
});
});
</script>
<?php
// Template
echo Bootstrap::formInputTextBlock(array(
'name'=>'template',
'label'=>$L->g('Template'),
'placeholder'=>'',
'value'=>'',
'tip'=>$L->g('Write a template name to filter the page in the theme and change the style of the page.')
));
echo Bootstrap::formInputTextBlock(array(
'name'=>'externalCoverImage',
'label'=>$L->g('External cover image'),
'placeholder'=>"https://",
'value'=>'',
'tip'=>$L->g('Set a cover image from external URL, such as a CDN or some server dedicated for images.')
));
// Username
echo Bootstrap::formInputTextBlock(array(
'name'=>'',
'label'=>$L->g('Author'),
'placeholder'=>'',
'value'=>$login->username(),
'tip'=>'',
'disabled'=>true
));
?>
<script>
$(document).ready(function() {
// Changes in External cover image input
$("#jsexternalCoverImage").change(function() {
$("#jscoverImage").val( $(this).val() );
});
// Generate slug when the user type the title
$("#jstitle").keyup(function() {
var text = $(this).val();
var parent = $("#jsparent").val();
var currentKey = "";
var ajax = new bluditAjax();
var callBack = $("#jsslug");
ajax.generateSlug(text, parent, currentKey, callBack);
});
// Datepicker
$("#jsdate").datetimepicker({format:DB_DATE_FORMAT});
});
</script>
</div>
<?php if (!empty($site->customFields())): ?>
<div id="nav-custom" class="tab-pane fade" role="tabpanel" aria-labelledby="custom-tab">
<?php
$customFields = $site->customFields();
foreach ($customFields as $field=>$options) {
if ( !isset($options['position']) ) {
if ($options['type']=="string") {
echo Bootstrap::formInputTextBlock(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'value'=>(isset($options['default'])?$options['default']:''),
'tip'=>(isset($options['tip'])?$options['tip']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:'')
));
} elseif ($options['type']=="bool") {
echo Bootstrap::formCheckbox(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'checked'=>(isset($options['checked'])?true:false),
'labelForCheckbox'=>(isset($options['tip'])?$options['tip']:'')
));
}
}
}
?>
</div>
<?php endif ?>
<div id="nav-seo" class="tab-pane fade" role="tabpanel" aria-labelledby="seo-tab">
<?php
// Friendly URL
echo Bootstrap::formInputTextBlock(array(
'name'=>'slug',
'tip'=>$L->g('URL associated with the content'),
'label'=>$L->g('Friendly URL'),
'placeholder'=>$L->g('Leave empty for autocomplete by Bludit.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name'=>'noindex',
'label'=>'Robots',
'labelForCheckbox'=>$L->g('apply-code-noindex-code-to-this-page'),
'placeholder'=>'',
'checked'=>false,
'tip'=>$L->g('This tells search engines not to show this page in their search results.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name'=>'nofollow',
'label'=>'',
'labelForCheckbox'=>$L->g('apply-code-nofollow-code-to-this-page'),
'placeholder'=>'',
'checked'=>false,
'tip'=>$L->g('This tells search engines not to follow links on this page.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name'=>'noarchive',
'label'=>'',
'labelForCheckbox'=>$L->g('apply-code-noarchive-code-to-this-page'),
'placeholder'=>'',
'checked'=>false,
'tip'=>$L->g('This tells search engines not to save a cached copy of this page.')
));
?>
</div>
</div>
</div>
<!-- Custom fields: TOP -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field=>$options) {
if ( isset($options['position']) && ($options['position']=='top') ) {
if ($options['type']=="string") {
echo Bootstrap::formInputTextBlock(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'value'=>(isset($options['default'])?$options['default']:''),
'tip'=>(isset($options['tip'])?$options['tip']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'class'=>'mb-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
));
} elseif ($options['type']=="bool") {
echo Bootstrap::formCheckbox(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'checked'=>(isset($options['checked'])?true:false),
'labelForCheckbox'=>(isset($options['tip'])?$options['tip']:''),
'class'=>'mb-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
}
}
?>
// Close all modals
$('.modal').modal('hide');
return true;
}
<!-- Title -->
<div id="jseditorTitle" class="form-group mb-1">
<input id="jstitle" name="title" type="text" class="form-control form-control-lg rounded-0" value="" placeholder="<?php $L->p('Enter title') ?>">
</div>
// Open the modal and store the current value
// The current value is store to recover it if the user click in the button "Cancel"
function openModal(fieldName) {
var value = $('#'+fieldName).val();
localStorage.setItem(fieldName, value);
$('#modal-'+fieldName).modal('show');
}
<!-- Editor -->
<textarea id="jseditor" class="editable h-100 mb-1"></textarea>
// Close the modal when the user click in the button "Cancel"
// The function also recover the old value
function closeModal(fieldName) {
var value = localStorage.getItem(fieldName);
$('#'+fieldName).val(value);
$('#modal-'+fieldName).modal('hide');
}
<!-- Custom fields: BOTTOM -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field=>$options) {
if ( isset($options['position']) && ($options['position']=='bottom') ) {
if ($options['type']=="string") {
echo Bootstrap::formInputTextBlock(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'value'=>(isset($options['default'])?$options['default']:''),
'tip'=>(isset($options['tip'])?$options['tip']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'class'=>'mt-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
// This function is to catch all key press
// Provides Shortcuts
// The editor plugin need to call this function for the event "keydown"
function keypress(event) {
console.log(event);
));
} elseif ($options['type']=="bool") {
echo Bootstrap::formCheckbox(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'checked'=>(isset($options['checked'])?true:false),
'labelForCheckbox'=>(isset($options['tip'])?$options['tip']:''),
'class'=>'mt-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
// Shortcuts
// ------------------------------------------------------------------------
// Ctrl+S or Command+S
if ((event.ctrlKey || event.metaKey) && event.which == 83) {
var args = {
title: $('#title').val(),
content: editorGetContent()
}
save(args);
$('#btnSave').addClass('btn-primary-disabled').html('<?php $L->p('Saved') ?>');
event.preventDefault();
return false;
}
?>
</form>
$('#btnSave').removeClass('btn-primary-disabled').html('<?php $L->p('Save') ?>');
}
<!-- Modal for Media Manager -->
<?php include(PATH_ADMIN_THEMES.'booty/html/media.php'); ?>
<script>
// ----------------------------------------------------------------------------
// Events for the view
// ----------------------------------------------------------------------------
$(document).ready(function() {
// Define function if they doesn't exist
// This helps if the user doesn't activate any plugin as editor
if (typeof editorGetContent != "function") {
window.editorGetContent = function(){
return $("#jseditor").val();
};
}
if (typeof editorInsertMedia != "function") {
window.editorInsertMedia = function(filename){
$("#jseditor").val($('#jseditor').val()+'<img src="'+filename+'" alt="">');
};
}
// Button switch
$("#jsbuttonSwitch").on("click", function() {
if ($(this).data("switch")=="publish") {
$(this).html('<i class="fa fa-square switch-icon-draft"></i> <?php $L->p('Draft') ?>');
$(this).data("switch", "draft");
} else {
$(this).html('<i class="fa fa-square switch-icon-publish"></i> <?php $L->p('Publish') ?>');
$(this).data("switch", "publish");
}
// Main interface events
// ------------------------------------------------------------------------
$(this).keydown(function(event){
keypress(event);
});
// Button preview
$("#jsbuttonPreview").on("click", function() {
var uuid = $("#jsuuid").val();
$('#btnSave').on('click', function() {
var args = {
title: $('#title').val(),
content: editorGetContent()
}
save(args);
$(this).addClass('btn-primary-disabled').html('<?php $L->p('Saved') ?>');
});
$("#btnPreview").on("click", function() {
var title = $("#jstitle").val();
var content = editorGetContent();
bluditAjax.saveAsDraft(uuid, title, content).then(function(data) {
@ -457,23 +124,431 @@ $(document).ready(function() {
});
});
// Button Save
$("#jsbuttonSave").on("click", function() {
// If the switch is setted to "published", get the value from the selector
if ($("#jsbuttonSwitch").data("switch")=="publish") {
var value = $("#jstypeSelector option:selected").val();
$("#jstype").val(value);
} else {
$("#jstype").val("draft");
}
// Get the content
$("#jscontent").val( editorGetContent() );
// Submit the form
$("#jsform").submit();
$('#btnCurrenStatus').on('click', function() {
openModal('status');
});
$('#category').on("change", function() {
$('#btnSave').html('<?php $L->p('Save') ?>');
});
// Modal description events
// ------------------------------------------------------------------------
$('#btnSaveDescription').on('click', function() {
var args = {
description: $('#description').val()
};
save(args);
});
$('#btnCancelDescription').on('click', function() {
closeModal('description');
});
// Modal date events
// ------------------------------------------------------------------------
$('#btnSaveDate').on('click', function() {
var args = {
date: $('#date').val()
};
save(args);
});
$('#btnCancelDate').on('click', function() {
closeModal('date');
});
// Modal friendly-url events
// ------------------------------------------------------------------------
$('#btnSaveFriendlyURL').on('click', function() {
var args = {
slug: $('#friendlyURL').val()
};
save(args);
});
$('#btnCancelFriendlyURL').on('click', function() {
closeModal('FriendlyURL');
});
$('#btnGenURLFromTitle').on('click', function() {
var args = {
text: $('#title').val(),
parentKey: $('#parent').val(),
pageKey: _pageKey
}
api.friendlyURL(args).then(function(slug) {
$('#friendlyURL').val(slug);
});
});
// Modal status events
// ------------------------------------------------------------------------
$('#btnSaveStatus').on('click', function() {
var args = {
type: $('input[name="status"]:checked').val()
};
save(args);
if (args['type']=='draft') {
$('#btnCurrenStatus').html('<i class="fa fa-square switch-icon-draft"></i> <?php $L->p('Draft') ?>');
} else if (args['type']=='published') {
$('#btnCurrenStatus').html('<i class="fa fa-square switch-icon-published"></i> <?php $L->p('Published') ?>');
} else if (args['type']=='unlisted') {
$('#btnCurrenStatus').html('<i class="fa fa-square switch-icon-unlisted"></i> <?php $L->p('Unlisted') ?>');
} else if (args['type']=='sticky') {
$('#btnCurrenStatus').html('<i class="fa fa-square switch-icon-sticky"></i> <?php $L->p('Sticky') ?>');
} else if (args['type']=='static') {
$('#btnCurrenStatus').html('<i class="fa fa-square switch-icon-static"></i> <?php $L->p('Static') ?>');
}
});
$('#btnCancelStatus').on('click', function() {
closeModal('status');
});
// Modal parent events
// ------------------------------------------------------------------------
$('#btnSaveParent').on('click', function() {
var args = {
parent: $('#parent').val()
};
save(args);
});
$('#btnCancelParent').on('click', function() {
closeModal('parent');
});
});
// ----------------------------------------------------------------------------
// Initlization for the view
// ----------------------------------------------------------------------------
$(document).ready(function() {
// nothing here yet
// how do you hang your toilet paper ? over or under ?
});
</script>
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col-sm-9 h-100">
<!-- Toolbar > Save, Preview, Status and Options -->
<div id="editorToolbar" class="mb-2">
<div id="editorToolbarRight" class="btn-group btn-group-sm float-right" role="group" aria-label="Toolbar right">
<div class="dropdown">
<button type="button" class="btn switch-button btn-sm dropdown-toggle" type="button" id="dropdownMenuOptions" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"><span class="fa fa-cog"></span> <?php $L->p('Options') ?></button>
<div class="dropdown-menu" aria-labelledby="dropdownMenuOptions">
<a onclick="openModal('description')" class="dropdown-item" href="#"><i class="fa fa-comment"></i> Description</a>
<a onclick="openModal('date')" class="dropdown-item" href="#"><i class="fa fa-calendar"></i> Publish date</a>
<a onclick="openModal('friendlyURL')" class="dropdown-item" href="#"><i class="fa fa-link"></i> Change URL</a>
<a onclick="openModal('status')" class="dropdown-item" href="#"><i class="fa fa-eye"></i> Status</a>
<a onclick="openModal('seo')" class="dropdown-item" href="#"><i class="fa fa-compass"></i> SEO features</a>
<a onclick="openModal('parent')" class="dropdown-item" href="#"><i class="fa fa-sitemap"></i> Parent page</a>
</div>
</div>
</div>
<div id="editorToolbarLeft">
<button id="btnSave" type="button" class="btn btn-sm btn-primary" ><?php $L->p('Save') ?></button>
<button id="btnPreview" type="button" class="btn btn-sm btn-secondary"><?php $L->p('Preview') ?></button>
<span id="btnCurrenStatus" class="ml-2 switch-button"><i class="fa fa-square switch-icon-draft"></i> <?php $L->p('Draft') ?></span>
</div>
</div>
<!-- End Toolbar > Save, Preview, Status and Options -->
<!-- Modal Description -->
<div class="modal" id="modal-description" tabindex="-1" aria-labelledby="modal-description" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="form-group m-0">
<label for="parent" class="font-weight-bold">Page description</label>
<textarea id="description" name="description" class="form-control" rows="3"></textarea>
<small class="form-text text-muted"><?php echo $L->get('this-field-can-help-describe-the-content') ?></small>
</div>
</div>
<div class="modal-footer modal-footer pl-2 pr-2 pt-1 pb-1">
<button id="btnCancelDescription" type="button" class="btn btn-cancel font-weight-bold mr-auto"><i class="fa fa-times"></i> Cancel</button>
<button id="btnSaveDescription" type="button" class="btn btn-save font-weight-bold"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
<!-- End Modal Description -->
<!-- Modal Date -->
<div class="modal" id="modal-date" aria-labelledby="modal-date" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="form-group m-0">
<label for="date" class="font-weight-bold">Publish date</label>
<input id="date" name="date" type="text" class="form-control" value="<?php echo Date::current(DB_DATE_FORMAT) ?>">
<small class="form-text text-muted"><?php echo $L->g('date-format-format') ?></small>
</div>
</div>
<div class="modal-footer modal-footer pl-2 pr-2 pt-1 pb-1">
<button id="btnCancelDate" type="button" class="btn btn-cancel font-weight-bold mr-auto"><i class="fa fa-times"></i> Cancel</button>
<button id="btnSaveDate" type="button" class="btn btn-save font-weight-bold"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
$("#date").datetimepicker({format:DB_DATE_FORMAT});
});
</script>
<!-- End Modal Date -->
<!-- Modal friendly URL -->
<div class="modal" id="modal-friendlyURL" tabindex="-1" aria-labelledby="modal-friendlyURL" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="form-group m-0">
<div class="d-flex mb-2">
<label for="friendlyURL" class="p-0 m-0 mr-auto font-weight-bold">Page URL</label>
<button id="btnGenURLFromTitle" type="button" class="btn p-0 m-0"><i class="fa fa-magic"></i> Generate from page title</button>
</div>
<input id="friendlyURL" name="friendlyURL" type="text" class="form-control" value="">
<small class="form-text text-muted">https://www.varlogdiego.com/my-page-about-k8s</small>
</div>
</div>
<div class="modal-footer modal-footer pl-2 pr-2 pt-1 pb-1">
<button id="btnCancelfriendlyURL" type="button" class="btn btn-cancel font-weight-bold mr-auto"><i class="fa fa-times"></i> Cancel</button>
<button id="btnSavefriendlyURL" type="button" class="btn btn-save font-weight-bold"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
<!-- End Modal friendly URL -->
<!-- Modal Parent -->
<div class="modal" id="modal-parent" aria-labelledby="modal-parent" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="form-group m-0">
<label for="parent" class="font-weight-bold">Parent page</label>
<select id="parent" name="parent" class="custom-select"></select>
<small class="form-text text-muted"><?php echo $L->g('Start typing a page title to see a list of suggestions.') ?></small>
</div>
</div>
<div class="modal-footer modal-footer pl-2 pr-2 pt-1 pb-1">
<button id="btnCancelParent" type="button" class="btn btn-cancel font-weight-bold mr-auto"><i class="fa fa-times"></i> Cancel</button>
<button id="btnSaveParent" type="button" class="btn btn-save font-weight-bold"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function() {
var parent = $("#parent").select2({
placeholder: "",
allowClear: true,
theme: "bootstrap4",
minimumInputLength: 2,
ajax: {
url: HTML_PATH_ADMIN_ROOT+"ajax/get-published",
data: function (params) {
var query = {
checkIsParent: true,
query: params.term
}
return query;
},
processResults: function (data) {
return data;
}
},
escapeMarkup: function(markup) {
return markup;
},
templateResult: function(data) {
var html = data.text;
if (data.type=="static") {
html += '<span class="badge badge-pill badge-light">'+data.type+'</span>';
}
return html;
}
});
});
</script>
<!-- End Modal Parent -->
<!-- Modal Status -->
<div class="modal" id="modal-status" tabindex="-1" aria-labelledby="modal-status" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="form-group m-0">
<label class="font-weight-bold">Page status</label>
</div>
<div class="form-check mb-2">
<input id="statusDraft" name="status" class="form-check-input" type="radio" value="draft" checked>
<label class="form-check-label" for="statusDraft">Draft</label>
<small class="form-text text-muted">Page as draft, is not visible for visitors.</small>
</div>
<div class="form-check mb-2">
<input id="statusPublish" name="status" class="form-check-input" type="radio" value="published">
<label class="form-check-label" for="statusPublish">Publish</label>
<small class="form-text text-muted">Publish the page, everyone can see it.</small>
</div>
<hr>
<div class="form-check mb-2">
<input id="statusSticky" name="status" class="form-check-input" type="radio" value="sticky">
<label class="form-check-label" for="statusSticky">Publish as sticky</label>
<small class="form-text text-muted">The page can be seen by everyone in the top of the main page.</small>
</div>
<div class="form-check mb-2">
<input id="statusStatic" name="status" class="form-check-input" type="radio" value="static">
<label class="form-check-label" for="statusStatic">Publish as static</label>
<small class="form-text text-muted">The page can be seen by everyone as static page.</small>
</div>
<div class="form-check mb-2">
<input id="statusUnlisted" name="status" class="form-check-input" type="radio" value="unlisted">
<label class="form-check-label" for="statusUnlisted">Publish as unlisted</label>
<small class="form-text text-muted">The page can be seen and shared by anyone with the link.</small>
</div>
</div>
<div class="modal-footer modal-footer pl-2 pr-2 pt-1 pb-1">
<button id="btnCancelStatus" type="button" class="btn btn-cancel font-weight-bold mr-auto" data-dismiss="modal"><i class="fa fa-times"></i> Cancel</button>
<button id="btnSaveStatus" type="button" class="btn btn-save font-weight-bold"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
<!-- End Modal Status -->
<!-- Modal SEO -->
<div class="modal" id="modal-seo" tabindex="-1" aria-labelledby="modal-seo" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="form-group m-0">
<label class="font-weight-bold">SEO features</label>
</div>
<div class="form-check mb-2">
<input id="noindex" name="noindex" class="form-check-input" type="checkbox" value="noindex">
<label class="form-check-label" for="noindex"><?php echo $L->g('apply-code-noindex-code-to-this-page') ?></label>
<small class="form-text text-muted"><?php echo $L->g('This tells search engines not to show this page in their search results.') ?></small>
</div>
<div class="form-check mb-2">
<input id="nofollow" name="nofollow" class="form-check-input" type="checkbox" value="nofollow">
<label class="form-check-label" for="nofollow"><?php echo $L->g('apply-code-nofollow-code-to-this-page') ?></label>
<small class="form-text text-muted"><?php echo $L->g('This tells search engines not to follow links on this page.') ?></small>
</div>
<div class="form-check mb-2">
<input id="noarchive" name="noarchive" class="form-check-input" type="checkbox" value="noarchive">
<label class="form-check-label" for="noarchive"><?php echo $L->g('apply-code-noarchive-code-to-this-page') ?></label>
<small class="form-text text-muted"><?php echo $L->g('This tells search engines not to save a cached copy of this page.') ?></small>
</div>
</div>
<div class="modal-footer modal-footer pl-2 pr-2 pt-1 pb-1">
<button type="button" class="btn btn-cancel font-weight-bold mr-auto" data-dismiss="modal"><i class="fa fa-times"></i> Cancel</button>
<button type="button" class="btn btn-save font-weight-bold"><i class="fa fa-check"></i> Save</button>
</div>
</div>
</div>
</div>
<!-- End Modal SEO -->
<form class="d-flex flex-column h-100" id="jsform" method="post" action="" autocomplete="off">
<!-- Title -->
<div id="jseditorTitle" class="form-group mb-2">
<input id="title" name="title" type="text" class="form-control form-control-lg rounded-0" value="" placeholder="<?php $L->p('Enter title') ?>">
</div>
<!-- End Title -->
<!-- Editor -->
<textarea id="jseditor" class="editable h-100 mb-2"></textarea>
<!-- End Editor -->
</form>
</div> <!-- End <div class="col-sm-9 h-100"> -->
<div class="col-sm-3 h-100">
<!-- Cover Image -->
<h6 class="mt-1 mb-2 pb-2 text-uppercase"><?php $L->p('Cover Image') ?></h6>
<div>
<img id="jscoverImagePreview" class="mx-auto d-block w-100" alt="Cover image preview" src="<?php echo HTML_PATH_CORE_IMG ?>default.svg" />
</div>
<!-- End Cover Image -->
<!-- Images -->
<h6 class="mt-4 mb-2 pb-2 text-uppercase"><?php $L->p('Images') ?></h6>
<div class="media text-muted pt-3">
<svg class="align-self-center mr-3 rounded" width="32" height="32" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: 32x32"><title>Placeholder</title><rect width="100%" height="100%" fill="#007bff"></rect><text x="50%" y="50%" fill="#007bff" dy=".3em">32x32</text></svg>
<div class="media-body">
<div class="mt-0">
photo1.jpg
</div>
</div>
</div>
<div class="media text-muted pt-3">
<svg class="align-self-center mr-3 rounded" width="32" height="32" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: 32x32"><title>Placeholder</title><rect width="100%" height="100%" fill="#007bff"></rect><text x="50%" y="50%" fill="#007bff" dy=".3em">32x32</text></svg>
<div class="media-body">
<div class="mt-0">
photo2.jpg
</div>
</div>
</div>
<small class="d-block text-right mt-3">
<a href="#">All images</a>
</small>
<!-- End Images -->
<!-- Category -->
<div class="form-group m-0">
<h6 class="mt-4 mb-2 pb-2 text-uppercase">Category</h6>
<select id="category" name="category" class="custom-select">
<option value="">- Uncategorized -</option>
<?php foreach ($categories->db as $key=>$fields): ?>
<option value="<?php echo $key ?>"><?php echo $fields['name']?></option>
<?php endforeach; ?>
</select>
</div>
<!-- End Category -->
<!-- Tags -->
<h6 class="mt-4 mb-2 pb-2 text-uppercase">Tags</h6>
<div id="tags"></div>
<script>
$(document).ready(function() {
let tokenAutocomplete = new TokenAutocomplete({
name: 'tags',
selector: '#tags',
noMatchesText: 'No matching results...',
minCharactersForSuggestion: 2,
initialSuggestions: [
<?php
foreach ($tags->db as $key=>$fields) {
echo '{value: "'.$key.'", text: "'.$fields['name'].'"},';
}
?>
]
});
tokenAutocomplete.debug(true);
});
</script>
<!-- End Tags -->
</div> <!-- End <div class="col-sm-3 h-100"> -->
</div> <!-- End <div class="row h-100"> -->
</div> <!-- End <div class="container-fluid h-100"> -->
<script>
$(document).ready(function() {
// Autosave
var currentContent = editorGetContent();
setInterval(function() {

View file

@ -0,0 +1,644 @@
<?php defined('BLUDIT') or die('Bludit CMS.'); ?>
<div class="container-fluid h-100">
<div class="row h-100">
<div class="col-sm-9 h-100">
<?php
// Start form
echo Bootstrap::formOpen(array(
'id'=>'jsform',
'class'=>'d-flex flex-column h-100'
));
// Token CSRF
echo Bootstrap::formInputHidden(array(
'name'=>'tokenCSRF',
'value'=>$security->getTokenCSRF()
));
// UUID
// The UUID is generated in the controller
echo Bootstrap::formInputHidden(array(
'name'=>'uuid',
'value'=>$uuid
));
// Type = published, draft, sticky, static
echo Bootstrap::formInputHidden(array(
'name'=>'type',
'value'=>'published'
));
// Cover image
echo Bootstrap::formInputHidden(array(
'name'=>'coverImage',
'value'=>''
));
// Content
echo Bootstrap::formInputHidden(array(
'name'=>'content',
'value'=>''
));
?>
<!-- TOOLBAR -->
<div id="editorToolbar" class="mb-1">
<div id="editorToolbarRight" class="btn-group btn-group-sm float-right" role="group" aria-label="Toolbar right">
<button type="button" class="btn btn-light" id="jsmediaManagerOpenModal" data-toggle="modal" data-target="#jsmediaManagerModal"><span class="fa fa-image"></span> <?php $L->p('Images') ?></button>
<button type="button" class="btn btn-light" id="jsoptionsSidebar" style="z-index:30"><span class="fa fa-cog"></span> <?php $L->p('Options') ?></button>
</div>
<div id="jseditorToolbarLeft">
<button id="jsbuttonSave" type="button" class="btn btn-sm btn-primary" ><?php $L->p('Save') ?></button>
<button id="jsbuttonPreview" type="button" class="btn btn-sm btn-secondary"><?php $L->p('Preview') ?></button>
<span id="jsbuttonSwitch" data-switch="publish" class="ml-2 text-secondary switch-button"><i class="fa fa-square switch-icon-publish"></i> <?php $L->p('Publish') ?></span>
</div>
</div>
<script>
$(document).ready(function() {
$("#jsoptionsSidebar").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
});
$("#jsshadow").on("click", function() {
$("#jseditorSidebar").toggle();
$("#jsshadow").toggle();
});
});
</script>
<!-- SIDEBAR OPTIONS -->
<div id="jseditorSidebar">
<nav>
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<a class="nav-link active show" id="nav-general-tab" data-toggle="tab" href="#nav-general" role="tab" aria-controls="general"><?php $L->p('General') ?></a>
<a class="nav-link" id="nav-advanced-tab" data-toggle="tab" href="#nav-advanced" role="tab" aria-controls="advanced"><?php $L->p('Advanced') ?></a>
<?php if (!empty($site->customFields())): ?>
<a class="nav-link" id="nav-custom-tab" data-toggle="tab" href="#nav-custom" role="tab" aria-controls="custom"><?php $L->p('Custom') ?></a>
<?php endif ?>
<a class="nav-link" id="nav-seo-tab" data-toggle="tab" href="#nav-seo" role="tab" aria-controls="seo"><?php $L->p('SEO') ?></a>
</div>
</nav>
<div class="tab-content pr-3 pl-3 pb-3">
<div id="nav-general" class="tab-pane fade show active" role="tabpanel" aria-labelledby="general-tab">
<?php
// Category
echo Bootstrap::formSelectBlock(array(
'name'=>'category',
'label'=>$L->g('Category'),
'selected'=>'',
'class'=>'',
'emptyOption'=>'- '.$L->g('Uncategorized').' -',
'options'=>$categories->getKeyNameArray()
));
// Description
echo Bootstrap::formTextareaBlock(array(
'name'=>'description',
'label'=>$L->g('Description'),
'selected'=>'',
'class'=>'',
'value'=>'',
'rows'=>5,
'placeholder'=>$L->get('this-field-can-help-describe-the-content')
));
?>
<!-- Cover Image -->
<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100"><?php $L->p('Cover Image') ?></label>
<div>
<img id="jscoverImagePreview" class="mx-auto d-block w-100" alt="Cover image preview" src="<?php echo HTML_PATH_CORE_IMG ?>default.svg" />
</div>
<div class="mt-2 text-center">
<button type="button" id="jsbuttonSelectCoverImage" class="btn btn-primary btn-sm"><?php echo $L->g('Select cover image') ?></button>
<button type="button" id="jsbuttonRemoveCoverImage" class="btn btn-secondary btn-sm"><?php echo $L->g('Remove cover image') ?></button>
</div>
<script>
$(document).ready(function() {
$("#jscoverImagePreview").on("click", function() {
openMediaManager();
});
$("#jsbuttonSelectCoverImage").on("click", function() {
openMediaManager();
});
$("#jsbuttonRemoveCoverImage").on("click", function() {
$("#jscoverImage").val('');
$("#jscoverImagePreview").attr('src', HTML_PATH_CORE_IMG+'default.svg');
});
});
</script>
</div>
<div id="nav-advanced" class="tab-pane fade" role="tabpanel" aria-labelledby="advanced-tab">
<?php
// Date
echo Bootstrap::formInputTextBlock(array(
'name'=>'date',
'label'=>$L->g('Date'),
'placeholder'=>'',
'value'=>Date::current(DB_DATE_FORMAT),
'tip'=>$L->g('date-format-format')
));
// Type
echo Bootstrap::formSelectBlock(array(
'name'=>'typeSelector',
'label'=>$L->g('Type'),
'selected'=>'',
'options'=>array(
'published'=>'- '.$L->g('Default').' -',
'sticky'=>$L->g('Sticky'),
'static'=>$L->g('Static')
),
'tip'=>''
));
// Position
echo Bootstrap::formInputTextBlock(array(
'name'=>'position',
'label'=>$L->g('Position'),
'tip'=>$L->g('Field used when ordering content by position'),
'value'=>$pages->nextPositionNumber()
));
// Tags
echo Bootstrap::formInputTextBlock(array(
'name'=>'tags',
'label'=>$L->g('Tags'),
'placeholder'=>'',
'tip'=>$L->g('Write the tags separated by comma')
));
// Parent
echo Bootstrap::formSelectBlock(array(
'name'=>'parent',
'label'=>$L->g('Parent'),
'options'=>array(),
'selected'=>false,
'class'=>'',
'tip'=>$L->g('Start typing a page title to see a list of suggestions.'),
));
?>
<script>
$(document).ready(function() {
var parent = $("#jsparent").select2({
placeholder: "",
allowClear: true,
theme: "bootstrap4",
minimumInputLength: 2,
ajax: {
url: HTML_PATH_ADMIN_ROOT+"ajax/get-published",
data: function (params) {
var query = {
checkIsParent: true,
query: params.term
}
return query;
},
processResults: function (data) {
return data;
}
},
escapeMarkup: function(markup) {
return markup;
},
templateResult: function(data) {
var html = data.text;
if (data.type=="static") {
html += '<span class="badge badge-pill badge-light">'+data.type+'</span>';
}
return html;
}
});
});
</script>
<?php
// Template
echo Bootstrap::formInputTextBlock(array(
'name'=>'template',
'label'=>$L->g('Template'),
'placeholder'=>'',
'value'=>'',
'tip'=>$L->g('Write a template name to filter the page in the theme and change the style of the page.')
));
echo Bootstrap::formInputTextBlock(array(
'name'=>'externalCoverImage',
'label'=>$L->g('External cover image'),
'placeholder'=>"https://",
'value'=>'',
'tip'=>$L->g('Set a cover image from external URL, such as a CDN or some server dedicated for images.')
));
// Username
echo Bootstrap::formInputTextBlock(array(
'name'=>'',
'label'=>$L->g('Author'),
'placeholder'=>'',
'value'=>$login->username(),
'tip'=>'',
'disabled'=>true
));
?>
<script>
$(document).ready(function() {
// Changes in External cover image input
$("#jsexternalCoverImage").change(function() {
$("#jscoverImage").val( $(this).val() );
});
// Generate slug when the user type the title
$("#jstitle").keyup(function() {
var text = $(this).val();
var parent = $("#jsparent").val();
var currentKey = "";
var ajax = new bluditAjax();
var callBack = $("#jsslug");
ajax.generateSlug(text, parent, currentKey, callBack);
});
// Datepicker
$("#jsdate").datetimepicker({format:DB_DATE_FORMAT});
});
</script>
</div>
<?php if (!empty($site->customFields())): ?>
<div id="nav-custom" class="tab-pane fade" role="tabpanel" aria-labelledby="custom-tab">
<?php
$customFields = $site->customFields();
foreach ($customFields as $field=>$options) {
if ( !isset($options['position']) ) {
if ($options['type']=="string") {
echo Bootstrap::formInputTextBlock(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'value'=>(isset($options['default'])?$options['default']:''),
'tip'=>(isset($options['tip'])?$options['tip']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:'')
));
} elseif ($options['type']=="bool") {
echo Bootstrap::formCheckbox(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'checked'=>(isset($options['checked'])?true:false),
'labelForCheckbox'=>(isset($options['tip'])?$options['tip']:'')
));
}
}
}
?>
</div>
<?php endif ?>
<div id="nav-seo" class="tab-pane fade" role="tabpanel" aria-labelledby="seo-tab">
<?php
// Friendly URL
echo Bootstrap::formInputTextBlock(array(
'name'=>'slug',
'tip'=>$L->g('URL associated with the content'),
'label'=>$L->g('Friendly URL'),
'placeholder'=>$L->g('Leave empty for autocomplete by Bludit.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name'=>'noindex',
'label'=>'Robots',
'labelForCheckbox'=>$L->g('apply-code-noindex-code-to-this-page'),
'placeholder'=>'',
'checked'=>false,
'tip'=>$L->g('This tells search engines not to show this page in their search results.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name'=>'nofollow',
'label'=>'',
'labelForCheckbox'=>$L->g('apply-code-nofollow-code-to-this-page'),
'placeholder'=>'',
'checked'=>false,
'tip'=>$L->g('This tells search engines not to follow links on this page.')
));
// Robots
echo Bootstrap::formCheckbox(array(
'name'=>'noarchive',
'label'=>'',
'labelForCheckbox'=>$L->g('apply-code-noarchive-code-to-this-page'),
'placeholder'=>'',
'checked'=>false,
'tip'=>$L->g('This tells search engines not to save a cached copy of this page.')
));
?>
</div>
</div>
</div>
<!-- Custom fields: TOP -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field=>$options) {
if ( isset($options['position']) && ($options['position']=='top') ) {
if ($options['type']=="string") {
echo Bootstrap::formInputTextBlock(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'value'=>(isset($options['default'])?$options['default']:''),
'tip'=>(isset($options['tip'])?$options['tip']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'class'=>'mb-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
));
} elseif ($options['type']=="bool") {
echo Bootstrap::formCheckbox(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'checked'=>(isset($options['checked'])?true:false),
'labelForCheckbox'=>(isset($options['tip'])?$options['tip']:''),
'class'=>'mb-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
}
}
?>
<!-- Title -->
<div id="jseditorTitle" class="form-group mb-1">
<input id="jstitle" name="title" type="text" class="form-control form-control-lg rounded-0" value="" placeholder="<?php $L->p('Enter title') ?>">
</div>
<!-- Editor -->
<textarea id="jseditor" class="editable h-100 mb-1"></textarea>
<!-- Custom fields: BOTTOM -->
<?php
$customFields = $site->customFields();
foreach ($customFields as $field=>$options) {
if ( isset($options['position']) && ($options['position']=='bottom') ) {
if ($options['type']=="string") {
echo Bootstrap::formInputTextBlock(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'value'=>(isset($options['default'])?$options['default']:''),
'tip'=>(isset($options['tip'])?$options['tip']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'class'=>'mt-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
));
} elseif ($options['type']=="bool") {
echo Bootstrap::formCheckbox(array(
'name'=>'custom['.$field.']',
'label'=>(isset($options['label'])?$options['label']:''),
'placeholder'=>(isset($options['placeholder'])?$options['placeholder']:''),
'checked'=>(isset($options['checked'])?true:false),
'labelForCheckbox'=>(isset($options['tip'])?$options['tip']:''),
'class'=>'mt-2',
'labelClass'=>'mb-2 pb-2 border-bottom text-uppercase w-100'
));
}
}
}
?>
</form>
</div>
<div class="col-sm-3 h-100 p-0">
<!-- Options -->
<div class="accordion" id="sidebarOptions">
<!-- Options > General -->
<div class="general card">
<div class="card-header m-0 p-2" id="headingOne">
<a href="#optionsGeneral" class="w-100 text-left text-uppercase font-weight-bold" data-toggle="collapse" data-target="#optionsGeneral" aria-expanded="true" aria-controls="optionsGeneral">
General <span class="float-right fa fa-angle-down"></span>
</a>
</div>
<div id="optionsGeneral" class="collapse" aria-labelledby="headingOne" data-parent="#sidebarOptions">
<div class="card-body m-0 p-0">
<!-- Options > General > Cover Image -->
<label class="mt-4 mb-2 pb-2 text-uppercase w-100 font-weight-bold"><?php $L->p('Cover Image') ?></label>
<div>
<img id="jscoverImagePreview" class="mx-auto d-block w-100" alt="Cover image preview" src="<?php echo HTML_PATH_CORE_IMG ?>default.svg" />
</div>
<script>
$(document).ready(function() {
$("#jscoverImagePreview").on("click", function() {
openMediaManager();
});
$("#jsbuttonSelectCoverImage").on("click", function() {
openMediaManager();
});
$("#jsbuttonRemoveCoverImage").on("click", function() {
$("#jscoverImage").val('');
$("#jscoverImagePreview").attr('src', HTML_PATH_CORE_IMG+'default.svg');
});
});
</script>
<!-- End Options > General > Cover Image -->
<!-- Options > General > Category and Description -->
<?php
// Category
echo Bootstrap::formSelectBlock(array(
'name'=>'category',
'label'=>$L->g('Category'),
'selected'=>'',
'class'=>'',
'emptyOption'=>'- '.$L->g('Uncategorized').' -',
'options'=>$categories->getKeyNameArray()
));
// Description
echo Bootstrap::formTextareaBlock(array(
'name'=>'description',
'label'=>$L->g('Description'),
'selected'=>'',
'class'=>'',
'value'=>'',
'rows'=>5,
'placeholder'=>$L->get('this-field-can-help-describe-the-content')
));
?>
<!-- End Options > General > Category and Description -->
</div>
</div>
</div>
<!-- End Options > General -->
<!-- Options > Advanced -->
<div class="advanced card">
<div class="card-header m-0 p-2" id="headingOne">
<a href="#optionsAdvanced" class="w-100 text-left text-uppercase font-weight-bold" data-toggle="collapse" data-target="#optionsAdvanced" aria-expanded="true" aria-controls="optionsAdvanced">
Advanced <span class="float-right fa fa-angle-down"></span>
</a>
</div>
<div id="optionsAdvanced" class="collapse" aria-labelledby="headingOne" data-parent="#sidebarOptions">
<div class="card-body m-0 p-0">
<!-- Options > General > Cover Image -->
<label class="mt-4 mb-2 pb-2 border-bottom text-uppercase w-100"><?php $L->p('Cover Image') ?></label>
<div>
<img id="jscoverImagePreview" class="mx-auto d-block w-100" alt="Cover image preview" src="<?php echo HTML_PATH_CORE_IMG ?>default.svg" />
</div>
<script>
$(document).ready(function() {
$("#jscoverImagePreview").on("click", function() {
openMediaManager();
});
$("#jsbuttonSelectCoverImage").on("click", function() {
openMediaManager();
});
$("#jsbuttonRemoveCoverImage").on("click", function() {
$("#jscoverImage").val('');
$("#jscoverImagePreview").attr('src', HTML_PATH_CORE_IMG+'default.svg');
});
});
</script>
<!-- End Options > General > Cover Image -->
<!-- Options > General > Category and Description -->
<?php
// Category
echo Bootstrap::formSelectBlock(array(
'name'=>'category',
'label'=>$L->g('Category'),
'selected'=>'',
'class'=>'',
'emptyOption'=>'- '.$L->g('Uncategorized').' -',
'options'=>$categories->getKeyNameArray()
));
// Description
echo Bootstrap::formTextareaBlock(array(
'name'=>'description',
'label'=>$L->g('Description'),
'selected'=>'',
'class'=>'',
'value'=>'',
'rows'=>5,
'placeholder'=>$L->get('this-field-can-help-describe-the-content')
));
?>
<!-- End Options > General > Category and Description -->
</div>
</div>
</div>
<!-- End Options > General -->
</div>
<!-- End Options -->
</div>
</div>
<!-- Modal for Media Manager -->
<?php include(PATH_ADMIN_THEMES.'booty/html/media.php'); ?>
<script>
$(document).ready(function() {
// Define function if they doesn't exist
// This helps if the user doesn't activate any plugin as editor
if (typeof editorGetContent != "function") {
window.editorGetContent = function(){
return $("#jseditor").val();
};
}
if (typeof editorInsertMedia != "function") {
window.editorInsertMedia = function(filename){
$("#jseditor").val($('#jseditor').val()+'<img src="'+filename+'" alt="">');
};
}
// Button switch
$("#jsbuttonSwitch").on("click", function() {
if ($(this).data("switch")=="publish") {
$(this).html('<i class="fa fa-square switch-icon-draft"></i> <?php $L->p('Draft') ?>');
$(this).data("switch", "draft");
} else {
$(this).html('<i class="fa fa-square switch-icon-publish"></i> <?php $L->p('Publish') ?>');
$(this).data("switch", "publish");
}
});
// Button preview
$("#jsbuttonPreview").on("click", function() {
var uuid = $("#jsuuid").val();
var title = $("#jstitle").val();
var content = editorGetContent();
bluditAjax.saveAsDraft(uuid, title, content).then(function(data) {
var preview = window.open("<?php echo DOMAIN_PAGES.'autosave-'.$uuid.'?preview='.md5('autosave-'.$uuid) ?>", "bludit-preview");
preview.focus();
});
});
// Button Save
$("#jsbuttonSave").on("click", function() {
// If the switch is setted to "published", get the value from the selector
if ($("#jsbuttonSwitch").data("switch")=="publish") {
var value = $("#jstypeSelector option:selected").val();
$("#jstype").val(value);
} else {
$("#jstype").val("draft");
}
// Get the content
$("#jscontent").val( editorGetContent() );
// Submit the form
$("#jsform").submit();
});
// Autosave
var currentContent = editorGetContent();
setInterval(function() {
var uuid = $("#jsuuid").val();
var title = $("#jstitle").val() + "[<?php $L->p('Autosave') ?>]";
var content = editorGetContent();
// Autosave when content has at least 100 characters
if (content.length<100) {
return false;
}
// Autosave only when the user change the content
if (currentContent!=content) {
currentContent = content;
bluditAjax.saveAsDraft(uuid, title, content).then(function(data) {
if (data.status==0) {
showAlert("<?php $L->p('Autosave') ?>");
}
});
}
},1000*60*AUTOSAVE_INTERVAL);
});
</script>

View file

@ -2,12 +2,14 @@
header('Content-Type: application/json');
/*
| Returns a list of pages and the title contains the query string
| The returned list have published, sticky and statics pages
| Returns a list of pages that the title contains the query string.
| The returned list have published, sticky and statics pages.
| It's possible to filter the pages are parents by the flag "checkIsParent".
|
| @_POST['query'] string The string to search in the title of the pages
| @_POST['query'] string The string to search in the title of the pages.
| @_POST['checkIsParent'] boolean TRUE returns only parent pages, FALSE returns all pages.
|
| @return array
| @return json Ex. {"results":[{"disabled":false,"id":"follow-bludit","text":"Follow Bludit","type":"published"}]}
*/
// $_GET

View file

@ -1,14 +1,20 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// Start the session
// If the session is not possible to start the admin area is not available
// If the session is not started the admin area is not available
Session::start();
if (Session::started()===false) {
exit('Bludit CMS. Session initialization failed.');
}
// The login object contains the authentication system and/or the current user logged
$login = new Login();
// Initialize plugins
include(PATH_RULES.'60.plugins.php');
// Parameters for the controller and view
// For example "title" keeps the HTML tag <title>
$layout = array(
'controller'=>null,
'view'=>null,
@ -19,14 +25,14 @@ $layout = array(
'title'=>'Bludit'
);
// Get the Controller
// Get from the URL the controller and view
$explodeSlug = $url->explodeSlug();
$layout['controller'] = $layout['view'] = $layout['slug'] = empty($explodeSlug[0])?'dashboard':$explodeSlug[0];
unset($explodeSlug[0]);
// Get the Plugins
include(PATH_RULES.'60.plugins.php');
// Check if the user want to access to an admin controller or view from a plugin
// Check if the user want to get access to an admin controller or view from a plugin
// To get access to a plugin controller or view the URL should be: http://localhost/admin/plugin/<PLUGIN NAME>
// $explodeSlug = [0=>'<PLUGIN NAME>']
if ($layout['controller'] === 'plugin' && !empty($explodeSlug)) {
// Lowercase plugins class name to search by case-insensitive
$pluginsLowerCases = array_change_key_case($pluginsInstalled);
@ -53,52 +59,50 @@ if ($layout['slug']==='ajax') {
header('HTTP/1.1 401 User not logged.');
exit(0);
}
// --- ADMIN AREA ---
else
{
// Boot rules
include(PATH_RULES.'69.pages.php');
include(PATH_RULES.'99.header.php');
include(PATH_RULES.'99.paginator.php');
include(PATH_RULES.'99.themes.php');
include(PATH_RULES.'99.security.php');
// Page not found.
// User not logged.
// Slug is login.
if ($url->notFound() || !$login->isLogged() || ($url->slug()==='login') ) {
$layout['controller'] = 'login';
$layout['view'] = 'login';
$layout['template'] = 'login.php';
// Boot rules
include(PATH_RULES.'69.pages.php');
include(PATH_RULES.'99.header.php');
include(PATH_RULES.'99.paginator.php');
include(PATH_RULES.'99.themes.php');
include(PATH_RULES.'99.security.php');
// Generate the tokenCSRF for the user not logged, when the user log-in the token will be change.
$security->generateTokenCSRF();
}
// Define layout login-form for:
// - User not logged
// - Page not found
// - Slug is login. http://localhost/admin/login
if ($url->notFound() || !$login->isLogged() || ($url->slug()==='login') ) {
$layout['controller'] = 'login';
$layout['view'] = 'login';
$layout['template'] = 'login.php';
// Define variables
$ADMIN_CONTROLLER = $layout['controller'];
$ADMIN_VIEW = $layout['view'];
// Load plugins before the admin area will be load.
Theme::plugins('beforeAdminLoad');
// Load init.php if the theme has one.
if (Sanitize::pathFile(PATH_ADMIN_THEMES, $site->adminTheme().DS.'init.php')) {
include(PATH_ADMIN_THEMES.$site->adminTheme().DS.'init.php');
}
// Load controller.
if (Sanitize::pathFile(PATH_ADMIN_CONTROLLERS, $layout['controller'].'.php')) {
include(PATH_ADMIN_CONTROLLERS.$layout['controller'].'.php');
} elseif ($layout['plugin'] && method_exists($layout['plugin'], 'adminController')) {
$layout['plugin']->adminController();
}
// Load view and theme.
if (Sanitize::pathFile(PATH_ADMIN_THEMES, $site->adminTheme().DS.$layout['template'])) {
include(PATH_ADMIN_THEMES.$site->adminTheme().DS.$layout['template']);
}
// Load plugins after the admin area is loaded.
Theme::plugins('afterAdminLoad');
// Generate the tokenCSRF for the user not logged, when the user log-in the token will change
$security->generateTokenCSRF();
}
// Define global variables
$ADMIN_CONTROLLER = $layout['controller'];
$ADMIN_VIEW = $layout['view'];
// Execute plugins before load the admin area
execPluginsByHook('beforeAdminLoad');
// Load init.php if the theme has one
if (Sanitize::pathFile(PATH_ADMIN_THEMES, $site->adminTheme().DS.'init.php')) {
include(PATH_ADMIN_THEMES.$site->adminTheme().DS.'init.php');
}
// Load controller
if (Sanitize::pathFile(PATH_ADMIN_CONTROLLERS, $layout['controller'].'.php')) {
include(PATH_ADMIN_CONTROLLERS.$layout['controller'].'.php');
} elseif ($layout['plugin'] && method_exists($layout['plugin'], 'adminController')) {
$layout['plugin']->adminController();
}
// Load view and theme
if (Sanitize::pathFile(PATH_ADMIN_THEMES, $site->adminTheme().DS.$layout['template'])) {
include(PATH_ADMIN_THEMES.$site->adminTheme().DS.$layout['template']);
}
// Execute plugins after the admin area is loaded
execPluginsByHook('afterAdminLoad');

View file

@ -1,7 +1,7 @@
<?php defined('BLUDIT') or die('Bludit CMS.');
// ============================================================================
// Variables
// Global Variables
// ============================================================================
$plugins = array(
@ -37,12 +37,12 @@ $plugins = array(
'loginBodyBegin'=>array(),
'loginBodyEnd'=>array(),
'all'=>array()
'all'=>array() // $plugins['all'] keep installed and not installed plugins
);
$pluginsEvents = $plugins;
unset($pluginsEvents['all']);
// This array has only the installed plugins
// The array key is the "plugin class name" and the value is the object
// pluginsInstalled[pluginClass] = $Plugin
$pluginsInstalled = array();
// ============================================================================
@ -52,19 +52,20 @@ $pluginsInstalled = array();
function buildPlugins()
{
global $plugins;
global $pluginsEvents;
global $pluginsInstalled;
global $L;
global $site;
// This array is only to get the hooks names
$pluginsHooks = $plugins;
unset($pluginsHooks['all']); // remove "all" because is not a valid hook
// Get declared clasess BEFORE load plugins clasess
$currentDeclaredClasess = get_declared_classes();
// List plugins directories
// Load plugins clasess
$list = Filesystem::listDirectories(PATH_PLUGINS);
// Load each plugin clasess
foreach ($list as $pluginPath) {
// Check if the directory has the plugin.php
if (file_exists($pluginPath.DS.'plugin.php')) {
include_once($pluginPath.DS.'plugin.php');
}
@ -89,8 +90,7 @@ function buildPlugins()
$Plugin->setMetadata('name',$database['plugin-data']['name']);
$Plugin->setMetadata('description',$database['plugin-data']['description']);
// Remove name and description from the language file loaded and add new words if there are
// This function overwrite the key=>value
// Remove name and description from the language and includes new words to the global language dictionary
unset($database['plugin-data']);
if (!empty($database)) {
$L->add($database);
@ -99,22 +99,24 @@ function buildPlugins()
// $plugins['all'] Array with all plugins, installed and not installed
$plugins['all'][$pluginClass] = $Plugin;
// If the plugin is installed insert on the hooks
if ($Plugin->installed()) {
// Include custom hooks
// Include the plugin installed in the global array
$pluginsInstalled[$pluginClass] = $Plugin;
// Define new hooks from custom hooks
if (!empty($Plugin->customHooks)) {
foreach ($Plugin->customHooks as $customHook) {
if (!isset($plugins[$customHook])) {
$plugins[$customHook] = array();
$pluginsEvents[$customHook] = array();
foreach ($Plugin->customHooks as $hook) {
if (!isset($plugins[$hook])) {
$plugins[$hook] = array();
$pluginsHooks[$hook] = array();
}
}
}
$pluginsInstalled[$pluginClass] = $Plugin;
foreach ($pluginsEvents as $event=>$value) {
if (method_exists($Plugin, $event)) {
array_push($plugins[$event], $Plugin);
// Insert the plugin into the hooks
foreach ($pluginsHooks as $hook=>$value) {
if (method_exists($Plugin, $hook)) {
array_push($plugins[$hook], $Plugin);
}
}
}

View file

@ -0,0 +1,79 @@
.token-autocomplete-container {
display: block;
flex-wrap: wrap;
border: 1px solid #E6E6E6;
background-color: #FFFFFF;
}
.token-autocomplete-container, .token-autocomplete-container * {
box-sizing: border-box;
}
.token-autocomplete-container .token-autocomplete-input {
display: block;
line-height: 32px;
margin: 4px 2px;
padding: 0px 8px;
}
.token-autocomplete-container .token-autocomplete-input:empty::before {
content: attr(data-placeholder);
color: rgb(0,0,0,0.6);
}
.token-autocomplete-container .token-autocomplete-token {
width: 100%;
display: block;
padding: 2px 8px;
background: #ccc;
}
.token-autocomplete-container .token-autocomplete-token:hover {
background-color: #ddd;
}
.token-autocomplete-container .token-autocomplete-token .token-autocomplete-token-delete {
cursor: pointer;
font-size: 24px;
line-height: 16px;
margin-left: 4px;
pointer-events: auto;
border-radius: 50%;
height: 24px;
width: 24px;
display: inline-block;
text-align: center;
}
.token-autocomplete-container .token-autocomplete-token .token-autocomplete-token-delete:hover {
background-color: #e55858;
}
.token-autocomplete-container .token-autocomplete-suggestions {
display: none;
width: 100%;
list-style-type: none;
padding: 0px;
margin: 0px;
}
.token-autocomplete-container .token-autocomplete-suggestions li {
width: 100%;
padding: 8px;
cursor: pointer;
}
.token-autocomplete-container .token-autocomplete-suggestions li.token-autocomplete-suggestion-active {
color: #747474;
background-color: #fdfdfd;
}
.token-autocomplete-container .token-autocomplete-suggestions li.token-autocomplete-suggestion-highlighted {
background-color: #95caec;
}
.token-autocomplete-container .token-autocomplete-suggestions li .token-autocomplete-suggestion-description {
display:block;
font-size: 0.7em;
color: #808080;
}

View file

@ -194,9 +194,10 @@ function getPlugin($pluginClassName) {
return false;
}
// Returns TRUE if the plugin is activaed / installed, FALSE otherwise
// Check if the plugin is activated / installed
// Returns TRUE if the plugin is activated / installed, FALSE otherwise
function pluginActivated($pluginClassName) {
global $plugins;
global $plugins;
if (isset($plugins['all'][$pluginClassName])) {
return $plugins['all'][$pluginClassName]->installed();
@ -204,6 +205,8 @@ function pluginActivated($pluginClassName) {
return false;
}
// Activate / install the plugin
// Returns TRUE if the plugin is successfully activated, FALSE otherwise
function activatePlugin($pluginClassName) {
global $plugins;
global $syslog;
@ -227,6 +230,8 @@ function activatePlugin($pluginClassName) {
return false;
}
// Deactivate / uninstall the plugin
// Returns TRUE if the plugin is successfully deactivated, FALSE otherwise
function deactivatePlugin($pluginClassName) {
global $plugins;
global $syslog;
@ -291,6 +296,14 @@ function changePluginsPosition($pluginClassList) {
return true;
}
// Execute the plugins by hook
function execPluginsByHook($hook, $args = array()) {
global $plugins;
foreach ($plugins[$hook] as $plugin) {
echo call_user_func_array(array($plugin, $hook), $args);
}
}
/*
Create a new page

View file

@ -27,7 +27,7 @@ class Filesystem {
// $chunk = amount of chunks, FALSE if you don't want to chunk
public static function listFiles($path, $regex='*', $extension='*', $sortByDate=false, $chunk=false)
{
error_log($path.$regex.'.'.$extension);
Log::set('list files = '.$path.$regex.'.'.$extension, LOG_TYPE_INFO);
$files = glob($path.$regex.'.'.$extension);
if (empty($files)) {

View file

@ -1 +1,8 @@
<svg width="429" height="238" viewBox="0 0 429 238" xmlns="http://www.w3.org/2000/svg"><title>missing-image-4x3</title><g fill-rule="nonzero" fill="none"><path d="M0 0h429v238H0z" fill="#F0F1F2"/><path d="M160.875 79v77.7h107.25V79h-107.25zm101.888 4.9v39.8l-28.85-26.1-28.744 26.8-14.693-13.7-24.239 22.7V83.9h96.525zm-96.525 56.5l24.345-22.7 36.358 33.9h-60.704v-11.2zm68.21 11.3l-25.525-23.8 24.99-23.3 28.85 26.1v21h-28.314z" fill="#DCDDDF"/></g></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg width="429" height="238" viewBox="0 0 429 238" xmlns="http://www.w3.org/2000/svg">
<title>missing-image-4x3</title>
<g fill-rule="nonzero" fill="none">
<rect x="0.083" y="-0.184" width="428.965" height="237.991" style="fill: rgb(164, 164, 164);"/>
<path d="M160.875 79v77.7h107.25V79h-107.25zm101.888 4.9v39.8l-28.85-26.1-28.744 26.8-14.693-13.7-24.239 22.7V83.9h96.525zm-96.525 56.5l24.345-22.7 36.358 33.9h-60.704v-11.2zm68.21 11.3l-25.525-23.8 24.99-23.3 28.85 26.1v21h-28.314z" fill="#DCDDDF"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 456 B

After

Width:  |  Height:  |  Size: 568 B

89
bl-kernel/js/api.js Normal file
View file

@ -0,0 +1,89 @@
class API {
constructor(apiURL, apiToken, apiAuth) {
this.apiURL = "http://localhost:9000/api/";
this.body = {
token: '45643a4071fad6a12261bb0763550feb',
authentication: '18a8410f0043d004c2e87f404170e112'
}
}
async createPage(args) {
var url = this.apiURL+"pages";
var body = Object.assign({}, this.body, args);
try {
var response = await fetch(url, {
credentials: "same-origin",
method: "POST",
body: JSON.stringify(body),
headers: new Headers({
"Content-Type": "application/json"
})
});
var json = await response.json();
return json.data.key;
}
catch (err) {
console.log(err);
return true;
}
}
/*
Save page fields
$args['pageKey'] string Page key for the page to edit
$args array Arguments can be any of the fields from a page
returns string New page key
*/
async savePage(args) {
var url = this.apiURL+"pages/"+args['pageKey'];
var body = Object.assign({}, this.body, args);
try {
var response = await fetch(url, {
credentials: "same-origin",
method: "PUT",
body: JSON.stringify(body),
headers: new Headers({
"Content-Type": "application/json"
})
});
var json = await response.json();
return json.data.key;
}
catch (err) {
console.log(err);
return true;
}
}
/*
Generates unique slug text for the a page
$args['pageKey'] string Page key for the page to generate the slug url
$args['text'] string Text that you want to generate the slug url
$args['parentKey'] string Parent page key if the page has one, if not empty string
returns string Slug text
*/
async friendlyURL(args) {
var url = this.apiURL+"helper/friendly-url/";
var parameters = "?token="+this.body.token+"&authentication="+this.body.authentication;
parameters = parameters+"&pageKey="+args['pageKey'];
parameters = parameters+"&text="+args['text'];
parameters = parameters+"&parentKey="+args['parentKey'];
try {
const response = await fetch(url+parameters, {
method: "GET"
});
var json = await response.json();
return json.data;
}
catch (err) {
console.log(err);
return true;
}
}
}

View file

@ -1,5 +1,39 @@
class bluditAjax {
constructor(apiURL, apiToken, apiAuth, tokenCSRF) {
this.apiURL = "http://localhost:9000/api/";
this.apiToken = '45643a4071fad6a12261bb0763550feb';
this.apiAuth = '18a8410f0043d004c2e87f404170e112';
this.tokenCSRF = tokenCSRF;
}
static async savePage(uuid, title, content) {
let url = this.apiURL+"pages";
try {
const response = await fetch(url, {
credentials: "same-origin",
method: "POST",
body: JSON.stringify({
tokenCSRF: this.tokenCSRF,
token: this.apiToken,
authentication: this.apiAuth,
uuid: uuid,
title: title,
content: content
}),
headers: new Headers({
"Content-Type": "application/json"
}),
});
const json = await response.json();
return json.data.key;
}
catch (err) {
console.log(err);
return true;
}
}
static async saveAsDraft(uuid, title, content) {
let url = HTML_PATH_ADMIN_ROOT+"ajax/save-as-draft"
try {

View file

@ -34,3 +34,9 @@ function getCookie(name) {
function deleteCookie(name) {
document.cookie = name+'=; Max-Age=-999;';
}
function logs(message) {
if (DEBUG_MODE) {
console.log(message);
}
}

View file

@ -0,0 +1,372 @@
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var TokenAutocomplete = /** @class */ (function () {
function TokenAutocomplete(options) {
this.KEY_BACKSPACE = 8;
this.KEY_ENTER = 13;
this.KEY_UP = 38;
this.KEY_DOWN = 40;
this.defaults = {
name: '',
selector: '',
noMatchesText: null,
initialTokens: null,
initialSuggestions: null,
suggestionsUri: '',
suggestionRenderer: TokenAutocomplete.Autocomplete.defaultRenderer,
minCharactersForSuggestion: 1
};
this.options = __assign(__assign({}, this.defaults), options);
var passedContainer = document.querySelector(this.options.selector);
if (!passedContainer) {
throw new Error('passed selector does not point to a DOM element.');
}
this.container = passedContainer;
this.container.classList.add('token-autocomplete-container');
if (!Array.isArray(this.options.initialTokens) && !Array.isArray(this.options.initialSuggestions)) {
this.parseTokensAndSuggestions();
}
this.hiddenSelect = document.createElement('select');
this.hiddenSelect.id = this.container.id + '-select';
this.hiddenSelect.name = this.options.name;
this.hiddenSelect.setAttribute('multiple', 'true');
this.hiddenSelect.style.display = 'none';
this.textInput = document.createElement('span');
this.textInput.id = this.container.id + '-input';
this.textInput.classList.add('token-autocomplete-input');
this.textInput.setAttribute('data-placeholder', 'enter some text');
this.textInput.contentEditable = 'true';
this.container.appendChild(this.textInput);
this.container.appendChild(this.hiddenSelect);
this.select = new TokenAutocomplete.MultiSelect(this);
this.autocomplete = new TokenAutocomplete.Autocomplete(this);
this.debug(false);
var me = this;
if (Array.isArray(this.options.initialTokens)) {
this.options.initialTokens.forEach(function (token) {
if (typeof token === 'object') {
me.select.addToken(token.value, token.text);
}
});
}
this.textInput.addEventListener('keydown', function (event) {
if (event.which == me.KEY_ENTER || event.keyCode == me.KEY_ENTER) {
event.preventDefault();
var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
if (highlightedSuggestion !== null) {
if (highlightedSuggestion.classList.contains('token-autocomplete-suggestion-active')) {
me.select.removeTokenWithText(highlightedSuggestion.textContent);
}
else {
me.select.addToken(highlightedSuggestion.getAttribute('data-value'), highlightedSuggestion.textContent);
}
}
else {
me.select.addToken(me.textInput.textContent, me.textInput.textContent);
}
me.clearCurrentInput();
}
else if (me.textInput.textContent === '' && (event.which == me.KEY_BACKSPACE || event.keyCode == me.KEY_BACKSPACE)) {
event.preventDefault();
me.select.removeLastToken();
}
});
this.textInput.addEventListener('keyup', function (event) {
var _a, _b;
if ((event.which == me.KEY_UP || event.keyCode == me.KEY_UP) && me.autocomplete.suggestions.childNodes.length > 0) {
var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
var aboveSuggestion = (_a = highlightedSuggestion) === null || _a === void 0 ? void 0 : _a.previousSibling;
if (aboveSuggestion != null) {
me.autocomplete.highlightSuggestion(aboveSuggestion);
}
return;
}
if ((event.which == me.KEY_DOWN || event.keyCode == me.KEY_DOWN) && me.autocomplete.suggestions.childNodes.length > 0) {
var highlightedSuggestion = me.autocomplete.suggestions.querySelector('.token-autocomplete-suggestion-highlighted');
var belowSuggestion = (_b = highlightedSuggestion) === null || _b === void 0 ? void 0 : _b.nextSibling;
if (belowSuggestion != null) {
me.autocomplete.highlightSuggestion(belowSuggestion);
}
return;
}
me.autocomplete.hideSuggestions();
me.autocomplete.clearSuggestions();
var value = me.textInput.textContent || '';
if (value.length >= me.options.minCharactersForSuggestion) {
if (Array.isArray(me.options.initialSuggestions)) {
me.options.initialSuggestions.forEach(function (suggestion) {
if (typeof suggestion !== 'object') {
// the suggestion is of wrong type and therefore ignored
return;
}
if (value.localeCompare(suggestion.text.slice(0, value.length), undefined, { sensitivity: 'base' }) === 0) {
// The suggestion starts with the query text the user entered and will be displayed
me.autocomplete.addSuggestion(suggestion);
}
});
if (me.autocomplete.suggestions.childNodes.length > 0) {
me.autocomplete.highlightSuggestionAtPosition(0);
}
else if (me.options.noMatchesText) {
me.autocomplete.addSuggestion({ value: '_no_match_', text: me.options.noMatchesText, description: null });
}
}
else if (me.options.suggestionsUri.length > 0) {
me.autocomplete.requestSuggestions(value);
}
}
});
this.container.tokenAutocomplete = this;
}
/**
* Searches the element given as a container for option elements and creates active tokens (when the option is marked selected)
* and suggestions (all options found) from these. During this all found options are removed from the DOM.
*/
TokenAutocomplete.prototype.parseTokensAndSuggestions = function () {
var initialTokens = [];
var initialSuggestions = [];
var options = this.container.querySelectorAll('option');
var me = this;
options.forEach(function (option) {
if (option.text != null) {
if (option.hasAttribute('selected')) {
initialTokens.push({ value: option.value, text: option.text });
}
initialSuggestions.push({ value: option.value, text: option.text, description: null });
}
me.container.removeChild(option);
});
if (initialTokens.length > 0) {
this.options.initialTokens = initialTokens;
}
if (initialSuggestions.length > 0) {
this.options.initialSuggestions = initialSuggestions;
}
};
/**
* Clears the currently present tokens and creates new ones from the given input value.
*
* @param {(Array\|string)} value - either the name of a single token or a list of tokens to create
*/
TokenAutocomplete.prototype.val = function (value) {
this.select.clear();
if (Array.isArray(value)) {
var me_1 = this;
value.forEach(function (token) {
if (typeof token === 'object') {
me_1.select.addToken(token.value, token.text);
}
});
}
else {
this.select.addToken(value.value, value.text);
}
};
TokenAutocomplete.prototype.clearCurrentInput = function () {
this.textInput.textContent = '';
};
TokenAutocomplete.prototype.debug = function (state) {
if (state) {
this.log = console.log.bind(window.console);
}
else {
this.log = function () { };
}
};
var _a;
TokenAutocomplete.MultiSelect = /** @class */ (function () {
function class_1(parent) {
this.parent = parent;
this.container = parent.container;
this.options = parent.options;
}
/**
* Adds a token with the specified name to the list of currently prensent tokens displayed to the user and the hidden select.
*
* @param {string} tokenText - the name of the token to create
*/
class_1.prototype.addToken = function (tokenValue, tokenText) {
if (tokenValue === null || tokenText === null) {
return;
}
var option = document.createElement('option');
option.text = tokenText;
option.value = tokenValue;
option.setAttribute('selected', 'true');
option.setAttribute('data-text', tokenText);
option.setAttribute('data-value', tokenValue);
this.parent.hiddenSelect.add(option);
var token = document.createElement('span');
token.classList.add('token-autocomplete-token');
token.setAttribute('data-text', tokenText);
option.setAttribute('data-value', tokenValue);
token.textContent = tokenText;
var deleteToken = document.createElement('span');
deleteToken.classList.add('token-autocomplete-token-delete');
deleteToken.textContent = '\u00D7';
token.appendChild(deleteToken);
var me = this;
deleteToken.addEventListener('click', function (event) {
me.removeToken(token);
});
this.container.insertBefore(token, this.parent.textInput.nextSibling);
this.parent.log('added token', token);
};
/**
* Completely clears the currently present tokens from the field.
*/
class_1.prototype.clear = function () {
var tokens = this.container.querySelectorAll('.token-autocomplete-token');
var me = this;
tokens.forEach(function (token) { me.removeToken(token); });
};
/**
* Removes the last token in the list of currently present token. This is the last added token next to the input field.
*/
class_1.prototype.removeLastToken = function () {
var tokens = this.container.querySelectorAll('.token-autocomplete-token');
var token = tokens[tokens.length - 1];
this.removeToken(token);
};
/**
* Removes the specified token from the list of currently present tokens.
*
* @param {Element} token - the token to remove
*/
class_1.prototype.removeToken = function (token) {
var _a, _b;
this.container.removeChild(token);
var tokenText = token.getAttribute('data-text');
var hiddenOption = this.parent.hiddenSelect.querySelector('option[data-text="' + tokenText + '"]');
(_b = (_a = hiddenOption) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.removeChild(hiddenOption);
this.parent.log('removed token', token.textContent);
};
class_1.prototype.removeTokenWithText = function (tokenText) {
if (tokenText === null) {
return;
}
var token = this.container.querySelector('.token-autocomplete-token[data-text="' + tokenText + '"]');
if (token !== null) {
this.removeToken(token);
}
};
return class_1;
}());
TokenAutocomplete.Autocomplete = (_a = /** @class */ (function () {
function class_2(parent) {
this.parent = parent;
this.container = parent.container;
this.options = parent.options;
this.renderer = parent.options.suggestionRenderer;
this.suggestions = document.createElement('ul');
this.suggestions.id = this.container.id + '-suggestions';
this.suggestions.classList.add('token-autocomplete-suggestions');
this.container.appendChild(this.suggestions);
}
/**
* Hides the suggestions dropdown from the user.
*/
class_2.prototype.hideSuggestions = function () {
this.suggestions.style.display = '';
};
/**
* Shows the suggestions dropdown to the user.
*/
class_2.prototype.showSuggestions = function () {
this.suggestions.style.display = 'block';
};
class_2.prototype.highlightSuggestionAtPosition = function (index) {
var suggestions = this.suggestions.querySelectorAll('li');
suggestions.forEach(function (suggestion) {
suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
});
suggestions[index].classList.add('token-autocomplete-suggestion-highlighted');
};
class_2.prototype.highlightSuggestion = function (suggestion) {
this.suggestions.querySelectorAll('li').forEach(function (suggestion) {
suggestion.classList.remove('token-autocomplete-suggestion-highlighted');
});
suggestion.classList.add('token-autocomplete-suggestion-highlighted');
};
/**
* Removes all previous suggestions from the dropdown.
*/
class_2.prototype.clearSuggestions = function () {
this.suggestions.innerHTML = '';
};
/**
* Loads suggestions matching the given query from the rest service behind the URI given as an option while initializing the field.
*
* @param query the query to search suggestions for
*/
class_2.prototype.requestSuggestions = function (query) {
var me = this;
var request = new XMLHttpRequest();
request.onload = function () {
if (Array.isArray(request.response)) {
request.response.forEach(function (suggestion) {
me.addSuggestion(suggestion);
});
}
};
request.open('GET', me.options.suggestionsUri + '?query=' + query, true);
request.responseType = 'json';
request.setRequestHeader('Content-type', 'application/json');
request.send();
};
/**
* Adds a suggestion with the given text matching the users input to the dropdown.
*
* @param {string} suggestionText - the text that should be displayed for the added suggestion
*/
class_2.prototype.addSuggestion = function (suggestion) {
var element = this.renderer(suggestion);
element.setAttribute('data-value', suggestion.value);
var me = this;
element.addEventListener('click', function (_event) {
if (suggestion.text == me.options.noMatchesText) {
return true;
}
if (element.classList.contains('token-autocomplete-suggestion-active')) {
me.parent.select.removeTokenWithText(suggestion.text);
}
else {
me.parent.select.addToken(suggestion.value, suggestion.text);
}
me.clearSuggestions();
me.hideSuggestions();
me.parent.clearCurrentInput();
});
if (this.container.querySelector('.token-autocomplete-token[data-text="' + suggestion.text + '"]') !== null) {
element.classList.add('token-autocomplete-suggestion-active');
}
this.suggestions.appendChild(element);
this.showSuggestions();
me.parent.log('added suggestion', suggestion);
};
return class_2;
}()),
_a.defaultRenderer = function (suggestion) {
var option = document.createElement('li');
option.textContent = suggestion.text;
if (suggestion.description) {
var description = document.createElement('small');
description.textContent = suggestion.description;
description.classList.add('token-autocomplete-suggestion-description');
option.appendChild(description);
}
return option;
},
_a);
return TokenAutocomplete;
}());
//# sourceMappingURL=token-autocomplete.js.map

View file

@ -19,5 +19,7 @@ echo 'var AUTOSAVE_INTERVAL = "'.AUTOSAVE_INTERVAL.'";'.PHP_EOL;
echo 'var PAGE_BREAK = "'.PAGE_BREAK.'";'.PHP_EOL;
echo 'var tokenCSRF = "'.$security->getTokenCSRF().'";'.PHP_EOL;
echo 'var UPLOAD_MAX_FILESIZE = '.Text::toBytes( ini_get('upload_max_filesize') ).';'.PHP_EOL;
echo 'var DEBUG_MODE = '.(DEBUG_MODE?'true':'false').';'.PHP_EOL;
echo 'var api = new API("", "", "", tokenCSRF);'.PHP_EOL;
?>
?>

View file

@ -143,8 +143,8 @@ class pluginAPI extends Plugin {
}
// (PUT) /api/pages/<key>
elseif ( ($method==='PUT') && ($parameters[0]==='pages') && !empty($parameters[1]) && $writePermissions ) {
$pageKey = $parameters[1];
$data = $this->editPage($pageKey, $inputs);
$inputs['key'] = $parameters[1];
$data = $this->editPage($inputs);
}
// (DELETE) /api/pages/<key>
elseif ( ($method==='DELETE') && ($parameters[0]==='pages') && !empty($parameters[1]) && $writePermissions ) {
@ -204,6 +204,13 @@ class pluginAPI extends Plugin {
$pageKey = $parameters[1];
$data = $this->uploadFile($pageKey);
}
// (GET) /api/helper/<helper-name>
elseif ( ($method==='GET') && ($parameters[0]==='helper') && !empty($parameters[1]) ) {
$helperName = $parameters[1];
if ($helperName=='friendly-url') {
$data = $this->getFriendlyURL($inputs);
}
}
else {
$this->response(401, 'Unauthorized', array('message'=>'Access denied or invalid endpoint.'));
}
@ -429,14 +436,13 @@ class pluginAPI extends Plugin {
);
}
private function editPage($key, $args)
private function editPage($args)
{
// Unsanitize content because all values are sanitized
if (isset($args['content'])) {
$args['content'] = Sanitize::htmlDecode($args['content']);
}
$args['key'] = $key;
$newKey = editPage($args);
if ($newKey===false) {
@ -761,4 +767,25 @@ class pluginAPI extends Plugin {
'message'=>'Error moving the file to the final path.'
);
}
/*
Generates unique slug text for the a page
$args['text'] string
$args['parentKey'] string
$args['pageKey'] string
returns['data'] string
*/
private function getFriendlyURL($args)
{
global $pages;
$slug = $pages->generateKey($args['text'], $args['parentKey'], true, $args['pageKey']);
return array(
'status'=>'0',
'message'=>'Friendly URL generated.',
'data'=>$slug
);
}
}

View file

@ -91,6 +91,18 @@ return <<<EOF
return easymde.value();
}
// Insert HTML content at the cursor position
// Function required for Bludit
function editorInsertContent(html, type='') {
var text = easymde.value();
if (type == 'image') {
easymde.value(text + "![$langImage]("+filename+")" + "\\n");
} else {
easymde.value(html + "\\n");
}
easymde.codemirror.refresh();
}
easymde = new EasyMDE({
element: document.getElementById("jseditor"),
status: false,

View file

@ -92,6 +92,12 @@ $html = <<<EOF
return tinymce.get('jseditor').getContent();
}
// Insert HTML content at the cursor position
// Function required for Bludit
function editorInsertContent(html, type='') {
tinymce.activeEditor.insertContent(html);
}
tinymce.init({
selector: "#jseditor",
auto_focus: "jseditor",
@ -115,7 +121,12 @@ $html = <<<EOF
toolbar1: "$toolbar1",
toolbar2: "$toolbar2",
language: "$lang",
content_css: "$content_css"
content_css: "$content_css",
init_instance_callback: function(editor) {
editor.on("keydown", function(event) {
keypress(event);
});
}
});
</script>

BIN
bl-themes/tagg.zip Normal file

Binary file not shown.