new: [tag] Started integration of tag plugin with custom helpers - WiP

pull/72/head
mokaddem 2021-08-24 10:48:53 +02:00
parent 9662e15afe
commit b1e5bbad1a
12 changed files with 243 additions and 9 deletions

View File

@ -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"
},

View File

@ -59,6 +59,7 @@ class Application extends BaseApplication implements AuthenticationServiceProvid
$this->addPlugin('DebugKit');
}
$this->addPlugin('Authentication');
$this->addPlugin('Tags');
// Load more plugins here
}

View File

@ -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',
[

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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';
}
}
}

View File

@ -28,6 +28,10 @@ echo $this->element(
'key' => __('Position'),
'path' => 'position'
],
[
'key' => __('Tags'),
'type' => 'tags',
],
[
'key' => __('Alignments'),
'type' => 'alignment',

View File

@ -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,
]);

View File

@ -86,6 +86,5 @@ $cakeDescription = 'Cerebrate';
</body>
<script>
const darkMode = (<?= empty($darkMode) ? 'false' : 'true' ?>)
$.fn.select2.defaults.set('theme', 'bootstrap4');
</script>
</html>

View File

@ -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;
}

View File

@ -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
}
}

View File

@ -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") {