new: [tag] Started integration of tag plugin with custom helpers - WiP
parent
9662e15afe
commit
b1e5bbad1a
|
@ -11,6 +11,7 @@
|
|||
"cakephp/cakephp": "^4.0",
|
||||
"cakephp/migrations": "^3.0",
|
||||
"cakephp/plugin-installer": "^1.2",
|
||||
"dereuromark/cakephp-tags": "^1.2",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"mobiledetect/mobiledetectlib": "^2.8"
|
||||
},
|
||||
|
|
|
@ -59,6 +59,7 @@ class Application extends BaseApplication implements AuthenticationServiceProvid
|
|||
$this->addPlugin('DebugKit');
|
||||
}
|
||||
$this->addPlugin('Authentication');
|
||||
$this->addPlugin('Tags');
|
||||
// Load more plugins here
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ class IndividualsTable extends AppTable
|
|||
{
|
||||
parent::initialize($config);
|
||||
$this->addBehavior('UUID');
|
||||
$this->addBehavior('Tags.Tag', [
|
||||
'taggedCounter' => false,
|
||||
'strategy' => 'array',
|
||||
]);
|
||||
$this->hasMany(
|
||||
'Alignments',
|
||||
[
|
||||
|
|
|
@ -670,6 +670,7 @@ class BoostrapButton extends BootstrapGeneric {
|
|||
'class' => [],
|
||||
'type' => 'button',
|
||||
'nodeType' => 'button',
|
||||
'title' => '',
|
||||
'params' => [],
|
||||
'badge' => false
|
||||
];
|
||||
|
@ -678,7 +679,7 @@ class BoostrapButton extends BootstrapGeneric {
|
|||
|
||||
function __construct($options) {
|
||||
$this->allowedOptionValues = [
|
||||
'variant' => BootstrapGeneric::$variants,
|
||||
'variant' => array_merge(BootstrapGeneric::$variants, ['link', 'text']),
|
||||
'size' => ['', 'sm', 'lg'],
|
||||
'type' => ['button', 'submit', 'reset']
|
||||
];
|
||||
|
@ -701,11 +702,15 @@ class BoostrapButton extends BootstrapGeneric {
|
|||
$this->bsClasses[] = "btn-{$this->options['variant']}";
|
||||
}
|
||||
if (!empty($this->options['size'])) {
|
||||
$this->bsClasses[] = "btn-$this->options['size']";
|
||||
$this->bsClasses[] = "btn-{$this->options['size']}";
|
||||
}
|
||||
if ($this->options['block']) {
|
||||
$this->bsClasses[] = 'btn-block';
|
||||
}
|
||||
if ($this->options['variant'] == 'text') {
|
||||
$this->bsClasses[] = 'p-0';
|
||||
$this->bsClasses[] = 'lh-1';
|
||||
}
|
||||
}
|
||||
|
||||
public function button()
|
||||
|
@ -718,7 +723,8 @@ class BoostrapButton extends BootstrapGeneric {
|
|||
$html = $this->openNode($this->options['nodeType'], array_merge($this->options['params'], [
|
||||
'class' => array_merge($this->options['class'], $this->bsClasses),
|
||||
'role' => "alert",
|
||||
'type' => $this->options['type']
|
||||
'type' => $this->options['type'],
|
||||
'title' => h($this->options['title']),
|
||||
]));
|
||||
|
||||
$html .= $this->genIcon();
|
||||
|
@ -734,7 +740,8 @@ class BoostrapButton extends BootstrapGeneric {
|
|||
private function genIcon()
|
||||
{
|
||||
return $this->genNode('span', [
|
||||
'class' => ['mr-1', "fa fa-{$this->options['icon']}"],
|
||||
// 'class' => ['mr-1', "fa fa-{$this->options['icon']}"],
|
||||
'class' => ['', "fa fa-{$this->options['icon']}"],
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -749,7 +756,8 @@ class BoostrapBadge extends BootstrapGeneric {
|
|||
'text' => '',
|
||||
'variant' => 'primary',
|
||||
'pill' => false,
|
||||
'title' => ''
|
||||
'title' => '',
|
||||
'class' => [],
|
||||
];
|
||||
|
||||
function __construct($options) {
|
||||
|
@ -773,11 +781,11 @@ class BoostrapBadge extends BootstrapGeneric {
|
|||
private function genBadge()
|
||||
{
|
||||
$html = $this->genNode('span', [
|
||||
'class' => [
|
||||
'class' => array_merge($this->options['class'], [
|
||||
'badge',
|
||||
"badge-{$this->options['variant']}",
|
||||
$this->options['pill'] ? 'badge-pill' : '',
|
||||
],
|
||||
]),
|
||||
'title' => $this->options['title']
|
||||
], h($this->options['text']));
|
||||
return $html;
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace App\View\Helper;
|
||||
|
||||
use Cake\View\Helper;
|
||||
use Cake\Utility\Hash;
|
||||
|
||||
class TagHelper extends Helper
|
||||
{
|
||||
public $helpers = [
|
||||
'Bootstrap',
|
||||
'TextColour',
|
||||
'FontAwesome',
|
||||
'Tags.Tag',
|
||||
];
|
||||
|
||||
protected $_defaultConfig = [
|
||||
'default_colour' => '#983965',
|
||||
];
|
||||
|
||||
public function control(array $options = [])
|
||||
{
|
||||
return $this->Tag->control($options);
|
||||
}
|
||||
|
||||
public function picker(array $options = [])
|
||||
{
|
||||
$optionsHtml = '';
|
||||
foreach ($options['allTags'] as $i => $tag) {
|
||||
$optionsHtml .= $this->Bootstrap->genNode('option', [
|
||||
'value' => h($tag['text']),
|
||||
'data-colour' => h($tag['colour']),
|
||||
], h($tag['text']));
|
||||
}
|
||||
$html = $this->Bootstrap->genNode('select', [
|
||||
'class' => ['tag-input', 'd-none'],
|
||||
'multiple' => '',
|
||||
], $optionsHtml);
|
||||
$html .= $this->Bootstrap->button([
|
||||
'size' => 'sm',
|
||||
'icon' => 'plus',
|
||||
'variant' => 'secondary',
|
||||
'class' => ['badge'],
|
||||
'params' => [
|
||||
'onclick' => 'createTagPicker(this)',
|
||||
]
|
||||
]);
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function tags(array $options = [])
|
||||
{
|
||||
$tags = !empty($options['tags']) ? $options['tags'] : [];
|
||||
$html = '<div class="tag-container my-1">';
|
||||
$html .= '<div class="tag-list d-inline-block">';
|
||||
foreach ($tags as $tag) {
|
||||
if (is_array($tag)) {
|
||||
$html .= $this->tag($tag);
|
||||
} else {
|
||||
$html .= $this->tag([
|
||||
'name' => $tag
|
||||
]);
|
||||
}
|
||||
}
|
||||
$html .= '</div>';
|
||||
|
||||
if (!empty($options['picker'])) {
|
||||
$html .= $this->picker($options);
|
||||
}
|
||||
$html .= '</div>';
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function tag(array $tag)
|
||||
{
|
||||
$tag['colour'] = !empty($tag['colour']) ? $tag['colour'] : $this->getConfig()['default_colour'];
|
||||
$textColour = $this->TextColour->getTextColour(h($tag['colour']));
|
||||
$deleteButton = $this->Bootstrap->button([
|
||||
'size' => 'sm',
|
||||
'icon' => 'times',
|
||||
'class' => ['ml-1', 'border-0', "text-${textColour}"],
|
||||
'variant' => 'text',
|
||||
'title' => __('Delete tag'),
|
||||
]);
|
||||
|
||||
$html = $this->Bootstrap->genNode('span', [
|
||||
'class' => [
|
||||
'tag',
|
||||
'badge',
|
||||
'mx-1',
|
||||
'align-middle',
|
||||
],
|
||||
'title' => h($tag['name']),
|
||||
'style' => sprintf('color:%s; background-color:%s', $textColour, h($tag['colour'])),
|
||||
], h($tag['name']) . $deleteButton);
|
||||
return $html;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
namespace App\View\Helper;
|
||||
|
||||
use Cake\View\Helper;
|
||||
|
||||
// This helper helps determining the brightness of a colour (initially only used for the tagging) in order to decide
|
||||
// what text colour to use against the background (black or white)
|
||||
class TextColourHelper extends Helper {
|
||||
|
||||
public function getTextColour($RGB) {
|
||||
$r = hexdec(substr($RGB, 1, 2));
|
||||
$g = hexdec(substr($RGB, 3, 2));
|
||||
$b = hexdec(substr($RGB, 5, 2));
|
||||
$average = ((2 * $r) + $b + (3 * $g))/6;
|
||||
if ($average < 127) {
|
||||
return 'white';
|
||||
} else {
|
||||
return 'black';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,6 +28,10 @@ echo $this->element(
|
|||
'key' => __('Position'),
|
||||
'path' => 'position'
|
||||
],
|
||||
[
|
||||
'key' => __('Tags'),
|
||||
'type' => 'tags',
|
||||
],
|
||||
[
|
||||
'key' => __('Alignments'),
|
||||
'type' => 'alignment',
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
// $tags = Cake\Utility\Hash::extract($data, $field['path']);
|
||||
$tagList = Cake\Utility\Hash::get($data, 'tag_list');
|
||||
$tagList = ['tlp:red'];
|
||||
$allTags = [
|
||||
['id' => 'tlp:red', 'text' => 'tlp:red', 'colour' => 'red'],
|
||||
['id' => 'tlp:green', 'text' => 'tlp:green', 'colour' => 'green'],
|
||||
['id' => 'tlp:amber', 'text' => 'tlp:amber', 'colour' => '#983965'],
|
||||
['id' => 'tlp:white', 'text' => 'tlp:white', 'colour' => 'white'],
|
||||
];
|
||||
$this->loadHelper('Tag');
|
||||
echo $this->Tag->tags([
|
||||
'allTags' => $allTags,
|
||||
'tags' => $tagList,
|
||||
'picker' => true,
|
||||
]);
|
|
@ -86,6 +86,5 @@ $cakeDescription = 'Cerebrate';
|
|||
</body>
|
||||
<script>
|
||||
const darkMode = (<?= empty($darkMode) ? 'false' : 'true' ?>)
|
||||
$.fn.select2.defaults.set('theme', 'bootstrap4');
|
||||
</script>
|
||||
</html>
|
||||
|
|
|
@ -115,6 +115,13 @@
|
|||
.text-black {color:black;}
|
||||
.text-white {color:white;}
|
||||
|
||||
.lh-1 {
|
||||
line-height: 1;
|
||||
}
|
||||
.lh-2 {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.link-unstyled, .link-unstyled:link, .link-unstyled:hover {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
|
@ -145,3 +152,15 @@ input[type="checkbox"]:disabled.change-cursor {
|
|||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.select2-selection__choice { /* Our app do not have the same font size */
|
||||
padding-left: 1.5em !important;
|
||||
}
|
||||
.picker-container .select2-selection {
|
||||
border-top-right-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
.picker-container .picker-action .btn:first-child {
|
||||
border-top-left-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
|
@ -1108,4 +1108,13 @@ class HtmlHelper {
|
|||
}
|
||||
return $table
|
||||
}
|
||||
}
|
||||
|
||||
static tag(options={}) {
|
||||
const $tag = $('<span/>')
|
||||
.addClass(['tag', 'badge', 'border'])
|
||||
.css({color: getTextColour(options.colour), 'background-color': options.colour})
|
||||
.text(options.text)
|
||||
|
||||
return $tag
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,6 +99,60 @@ function syntaxHighlightJson(json, indent) {
|
|||
});
|
||||
}
|
||||
|
||||
function getTextColour(hex) {
|
||||
if (hex === undefined || hex.length == 0) {
|
||||
return 'black'
|
||||
}
|
||||
hex = hex.slice(1)
|
||||
var r = parseInt(hex.substring(0,2), 16)
|
||||
var g = parseInt(hex.substring(2,4), 16)
|
||||
var b = parseInt(hex.substring(4,6), 16)
|
||||
var avg = ((2 * r) + b + (3 * g))/6
|
||||
if (avg < 128) {
|
||||
return 'white'
|
||||
} else {
|
||||
return 'black'
|
||||
}
|
||||
}
|
||||
|
||||
function createTagPicker(clicked) {
|
||||
|
||||
function templateTag(state) {
|
||||
if (!state.id) {
|
||||
return state.text;
|
||||
}
|
||||
if (state.colour === undefined) {
|
||||
state.colour = $(state.element).data('colour')
|
||||
}
|
||||
return HtmlHelper.tag(state)
|
||||
}
|
||||
|
||||
const $clicked = $(clicked)
|
||||
const $container = $clicked.closest('.tag-container')
|
||||
$('.picker-container').remove()
|
||||
const $pickerContainer = $('<div></div>').addClass(['picker-container', 'd-flex'])
|
||||
const $select = $container.find('select.tag-input').removeClass('d-none').addClass('flex-grow-1')
|
||||
const $saveButton = $('<button></button>').addClass(['btn btn-primary btn-sm', 'align-self-start'])
|
||||
.append($('<span></span>').text('Save').prepend($('<i></i>').addClass('fa fa-save mr-1')))
|
||||
const $cancelButton = $('<button></button>').addClass(['btn btn-secondary btn-sm', 'align-self-start'])
|
||||
.append($('<span></span>').text('Cancel').prepend($('<i></i>').addClass('fa fa-times mr-1')))
|
||||
.click(function() {
|
||||
$select.appendTo($container)
|
||||
$pickerContainer.remove()
|
||||
})
|
||||
const $buttons = $('<span></span>').addClass(['picker-action', 'btn-group']).append($saveButton, $cancelButton)
|
||||
$select.prependTo($pickerContainer)
|
||||
$pickerContainer.append($buttons)
|
||||
$container.parent().append($pickerContainer)
|
||||
$select.select2({
|
||||
placeholder: 'Pick a tag',
|
||||
tags: true,
|
||||
templateResult: templateTag,
|
||||
templateSelection: templateTag,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
var UI
|
||||
$(document).ready(() => {
|
||||
if (typeof UIFactory !== "undefined") {
|
||||
|
|
Loading…
Reference in New Issue