diff --git a/plugins/Tags/webroot/css/tagging.css b/plugins/Tags/webroot/css/tagging.css new file mode 100644 index 0000000..260b4ce --- /dev/null +++ b/plugins/Tags/webroot/css/tagging.css @@ -0,0 +1,3 @@ +.tag { + filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.5)); +} diff --git a/plugins/Tags/webroot/js/tagging.js b/plugins/Tags/webroot/js/tagging.js new file mode 100644 index 0000000..355f6a9 --- /dev/null +++ b/plugins/Tags/webroot/js/tagging.js @@ -0,0 +1,142 @@ +function createTagPicker(clicked) { + + function closePicker($select, $container) { + $select.appendTo($container) + $container.parent().find('.picker-container').remove() + } + + function getEditableButtons($select, $container) { + const $saveButton = $('').addClass(['btn btn-primary btn-sm', 'align-self-start']).attr('type', 'button') + .append($('').text('Save').addClass('text-nowrap').prepend($('').addClass('fa fa-save mr-1'))) + .click(function() { + const tags = $select.select2('data').map(tag => tag.text) + addTags($select.data('url'), tags, $(this)) + }) + const $cancelButton = $('').addClass(['btn btn-secondary btn-sm', 'align-self-start']).attr('type', 'button') + .append($('').text('Cancel').addClass('text-nowrap').prepend($('').addClass('fa fa-times mr-1'))) + .click(function() { + closePicker($select, $container) + }) + const $buttons = $('').addClass(['picker-action', 'btn-group']).append($saveButton, $cancelButton) + return $buttons + } + + const $clicked = $(clicked) + const $container = $clicked.closest('.tag-container') + const $select = $container.parent().find('select.tag-input').removeClass('d-none') + closePicker($select, $container) + const $pickerContainer = $('
').addClass(['picker-container', 'd-flex']) + + $select.prependTo($pickerContainer) + $pickerContainer.append(getEditableButtons($select, $container)) + $container.parent().append($pickerContainer) + initSelect2Picker($select) +} + +function deleteTag(url, tags, clicked) { + if (!Array.isArray(tags)) { + tags = [tags]; + } + const data = { + tag_list: JSON.stringify(tags) + } + const $statusNode = $(clicked).closest('.tag') + const APIOptions = { + statusNode: $statusNode, + skipFeedback: true, + } + return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((apiResult) => { + let $container = $statusNode.closest('.tag-container-wrapper') + refreshTagList(apiResult, $container).then(($tagContainer) => { + $container = $tagContainer // old container might not exist anymore since it was replaced after the refresh + }) + const theToast = UI.toast({ + variant: 'success', + title: apiResult.message, + bodyHtml: $('').append( + $('').text('Cancel untag operation.'), + $('').addClass(['btn', 'btn-primary', 'btn-sm', 'ml-3']).text('Restore tag').click(function() { + const split = url.split('/') + const controllerName = split[1] + const id = split[3] + const urlRetag = `/${controllerName}/tag/${id}` + addTags(urlRetag, tags, $container.find('.tag-container')).then(() => { + theToast.removeToast() + }) + }), + ), + }) + }).catch((e) => {}) +} + +function addTags(url, tags, $statusNode) { + const data = { + tag_list: JSON.stringify(tags) + } + const APIOptions = { + statusNode: $statusNode + } + return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((apiResult) => { + const $container = $statusNode.closest('.tag-container-wrapper') + refreshTagList(apiResult, $container) + }).catch((e) => {}) +} + +function refreshTagList(apiResult, $container) { + const controllerName = apiResult.url.split('/')[1] + const entityId = apiResult.data.id + const url = `/${controllerName}/viewTags/${entityId}` + return UI.reload(url, $container) +} + +function initSelect2Pickers() { + $('select.tag-input').each(function() { + if (!$(this).hasClass("select2-hidden-accessible")) { + initSelect2Picker($(this)) + } + }) +} + +function initSelect2Picker($select) { + + function templateTag(state, $select) { + if (!state.id) { + return state.label; + } + if (state.colour === undefined) { + state.colour = $(state.element).data('colour') + } + if ($select !== undefined && state.text[0] === '!') { + // fetch corresponding tag and set colors? + // const baseTag = state.text.slice(1) + // const existingBaseTag = $select.find('option').filter(function() { + // return $(this).val() === baseTag + // }) + // if (existingBaseTag.length > 0) { + // state.colour = existingBaseTag.data('colour') + // state.text = baseTag + // } + } + return buildTag(state) + } + + $select.select2({ + placeholder: 'Pick a tag', + tags: true, + width: '100%', + templateResult: (state) => templateTag(state), + templateSelection: (state) => templateTag(state, $select), + }) +} + +function buildTag(options={}) { + if (!options.colour) { + options.colour = '#924da6' + } + const $tag = $('') + .addClass(['tag', 'badge', 'align-text-top']) + .css({color: getTextColour(options.colour), 'background-color': options.colour}) + .text(options.text) + + return $tag +} \ No newline at end of file diff --git a/templates/layout/default.php b/templates/layout/default.php index d97b800..93bd9eb 100644 --- a/templates/layout/default.php +++ b/templates/layout/default.php @@ -60,6 +60,10 @@ $cakeDescription = 'Cerebrate'; = $this->fetch('css') ?> = $this->fetch('script') ?> = $this->Html->css('bootstrap-additional.css') ?> + + = $this->Html->script('Tags.tagging') ?> + = $this->Html->css('Tags.tagging') ?> + = $this->Html->meta('favicon.ico', '/img/favicon.ico', ['type' => 'icon']); ?> diff --git a/webroot/css/main.css b/webroot/css/main.css index f1f2d47..9466306 100644 --- a/webroot/css/main.css +++ b/webroot/css/main.css @@ -164,7 +164,3 @@ input[type="checkbox"]:disabled.change-cursor { border-top-left-radius: 0 !important; border-bottom-left-radius: 0 !important; } - -.tag { - filter: drop-shadow(2px 2px 2px rgba(0, 0, 0, 0.5)); -} \ No newline at end of file diff --git a/webroot/js/bootstrap-helper.js b/webroot/js/bootstrap-helper.js index 39cc6ce..ce1591f 100644 --- a/webroot/js/bootstrap-helper.js +++ b/webroot/js/bootstrap-helper.js @@ -1120,15 +1120,4 @@ class HtmlHelper { return $table } - static tag(options={}) { - if (!options.colour) { - options.colour = '#924da6' - } - const $tag = $('') - .addClass(['tag', 'badge', 'align-text-top']) - .css({color: getTextColour(options.colour), 'background-color': options.colour}) - .text(options.text) - - return $tag - } } diff --git a/webroot/js/main.js b/webroot/js/main.js index 3acc35f..9097365 100644 --- a/webroot/js/main.js +++ b/webroot/js/main.js @@ -115,138 +115,6 @@ function getTextColour(hex) { } } -function createTagPicker(clicked) { - - function closePicker($select, $container) { - $select.appendTo($container) - $container.parent().find('.picker-container').remove() - } - - function getEditableButtons($select, $container) { - const $saveButton = $('').addClass(['btn btn-primary btn-sm', 'align-self-start']).attr('type', 'button') - .append($('').text('Save').addClass('text-nowrap').prepend($('').addClass('fa fa-save mr-1'))) - .click(function() { - const tags = $select.select2('data').map(tag => tag.text) - addTags($select.data('url'), tags, $(this)) - }) - const $cancelButton = $('').addClass(['btn btn-secondary btn-sm', 'align-self-start']).attr('type', 'button') - .append($('').text('Cancel').addClass('text-nowrap').prepend($('').addClass('fa fa-times mr-1'))) - .click(function() { - closePicker($select, $container) - }) - const $buttons = $('').addClass(['picker-action', 'btn-group']).append($saveButton, $cancelButton) - return $buttons - } - - const $clicked = $(clicked) - const $container = $clicked.closest('.tag-container') - const $select = $container.parent().find('select.tag-input').removeClass('d-none')//.addClass('flex-grow-1') - closePicker($select, $container) - const $pickerContainer = $('').addClass(['picker-container', 'd-flex']) - - $select.prependTo($pickerContainer) - $pickerContainer.append(getEditableButtons($select, $container)) - $container.parent().append($pickerContainer) - initSelect2Picker($select) -} - -function deleteTag(url, tags, clicked) { - if (!Array.isArray(tags)) { - tags = [tags]; - } - const data = { - tag_list: JSON.stringify(tags) - } - const $statusNode = $(clicked).closest('.tag') - const APIOptions = { - statusNode: $statusNode, - skipFeedback: true, - } - return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((apiResult) => { - let $container = $statusNode.closest('.tag-container-wrapper') - refreshTagList(apiResult, $container).then(($tagContainer) => { - $container = $tagContainer // old container might not exist anymore since it was replaced after the refresh - }) - const theToast = UI.toast({ - variant: 'success', - title: apiResult.message, - bodyHtml: $('').append( - $('').text('Cancel untag operation.'), - $('').addClass(['btn', 'btn-primary', 'btn-sm', 'ml-3']).text('Restore tag').click(function() { - const split = url.split('/') - const controllerName = split[1] - const id = split[3] - const urlRetag = `/${controllerName}/tag/${id}` - addTags(urlRetag, tags, $container.find('.tag-container')).then(() => { - theToast.removeToast() - }) - }), - ), - }) - }).catch((e) => {}) -} - -function addTags(url, tags, $statusNode) { - const data = { - tag_list: JSON.stringify(tags) - } - const APIOptions = { - statusNode: $statusNode - } - return AJAXApi.quickFetchAndPostForm(url, data, APIOptions).then((apiResult) => { - const $container = $statusNode.closest('.tag-container-wrapper') - refreshTagList(apiResult, $container) - }).catch((e) => {}) -} - -function refreshTagList(apiResult, $container) { - const controllerName = apiResult.url.split('/')[1] - const entityId = apiResult.data.id - const url = `/${controllerName}/viewTags/${entityId}` - return UI.reload(url, $container) -} - -function initSelect2Pickers() { - $('select.tag-input').each(function() { - if (!$(this).hasClass("select2-hidden-accessible")) { - initSelect2Picker($(this)) - } - }) -} - -function initSelect2Picker($select) { - - function templateTag(state, $select) { - if (!state.id) { - return state.label; - } - if (state.colour === undefined) { - state.colour = $(state.element).data('colour') - } - if ($select !== undefined && state.text[0] === '!') { - // fetch corresponding tag and set colors? - // const baseTag = state.text.slice(1) - // const existingBaseTag = $select.find('option').filter(function() { - // return $(this).val() === baseTag - // }) - // if (existingBaseTag.length > 0) { - // state.colour = existingBaseTag.data('colour') - // state.text = baseTag - // } - } - return HtmlHelper.tag(state) - } - - $select.select2({ - placeholder: 'Pick a tag', - tags: true, - width: '100%', - templateResult: (state) => templateTag(state), - templateSelection: (state) => templateTag(state, $select), - }) -} - - var UI $(document).ready(() => { if (typeof UIFactory !== "undefined") {