diff --git a/public/css/tags.less b/public/css/tags.less new file mode 100644 index 0000000..94effe3 --- /dev/null +++ b/public/css/tags.less @@ -0,0 +1,58 @@ +@light-gray: #e5e5e5; +@gray: #666666; +@black: #141414; +@blue: rgb(30, 135, 240); +@white1: rgba(255, 255, 255, .1); +@white2: rgba(255, 255, 255, .2); +@white7: rgba(255, 255, 255, .7); + +.select2-container--default { + .select2-selection--multiple { + border: 1px solid @light-gray; + .select2-selection__choice, + .select2-selection__choice__remove, + .select2-selection__choice__remove:hover + { + background-color: @blue; + color: white; + border: none; + border-radius: 2px; + } + } + .select2-dropdown { + .select2-results__option--highlighted.select2-results__option--selectable { + background-color: @blue; + } + .select2-results__option--selected:not(.select2-results__option--highlighted) { + background-color: @light-gray + } + } +} + +.uk-light { + .select2-container--default { + .select2-selection { + background-color: @white1; + } + .select2-selection--multiple { + border: 1px solid @white2; + .select2-selection__choice, + .select2-selection__choice__remove, + .select2-selection__choice__remove:hover + { + background-color: white; + color: @gray; + border: none; + } + .select2-search__field { + color: @white7; + } + } + } + .select2-dropdown { + background-color: @black; + .select2-results__option--selected:not(.select2-results__option--highlighted) { + background-color: @white2; + } + } +} diff --git a/public/js/title.js b/public/js/title.js index 7ddd093..0ec33ff 100644 --- a/public/js/title.js +++ b/public/js/title.js @@ -255,48 +255,64 @@ const bulkProgress = (action, el) => { const tagsComponent = () => { return { - loading: true, isAdmin: false, tags: [], - newTag: '', - inputShown: false, tid: $('.upload-field').attr('data-title-id'), + loading: true, + load(admin) { this.isAdmin = admin; - const url = `${base_url}api/tags/${this.tid}`; - this.request(url, 'GET', (data) => { - this.tags = data.tags; - this.loading = false; + + $('.tag-select').select2({ + tags: true, + placeholder: 'Tag the title', + templateSelection(state) { + const a = document.createElement('a'); + a.setAttribute('href', `${base_url}tags/${encodeURIComponent(state.text)}`); + a.setAttribute('class', 'uk-link-reset'); + a.onclick = event => { + event.stopPropagation(); + }; + a.innerText = state.text; + return a; + } }); - }, - add() { - const tag = this.newTag.trim(); - const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`; - this.request(url, 'PUT', () => { - this.tags.push(tag); - this.newTag = ''; - }); - }, - keydown(event) { - if (event.key === 'Enter') - this.add() - }, - rm(event) { - const tag = event.currentTarget.id.split('-')[0]; - const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`; - this.request(url, 'DELETE', () => { - const idx = this.tags.indexOf(tag); - if (idx < 0) return; - this.tags.splice(idx, 1); - }); - }, - toggleInput(nextTick) { - this.inputShown = !this.inputShown; - if (this.inputShown) { - nextTick(() => { - $('#tag-input').get(0).focus(); + + this.request(`${base_url}api/tags`, 'GET', (data) => { + const allTags = data.tags; + const url = `${base_url}api/tags/${this.tid}`; + this.request(url, 'GET', data => { + this.tags = data.tags; + allTags.forEach(t => { + const op = new Option(t, t, false, this.tags.indexOf(t) >= 0); + $('.tag-select').append(op); + }); + $('.tag-select').on('select2:select', e => { + this.onAdd(e); + }); + $('.tag-select').on('select2:unselect', e => { + this.onDelete(e); + }); + $('.tag-select').on('change', () => { + this.onChange(); + }); + $('.tag-select').trigger('change'); + this.loading = false; }); - } + }); + }, + onChange() { + this.tags = $('.tag-select').select2('data').map(o => o.text); + }, + onAdd(event) { + const tag = event.params.data.text; + const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`; + this.request(url, 'PUT'); + }, + onDelete(event) { + const tag = event.params.data.text; + const url = `${base_url}api/admin/tags/${this.tid}/${encodeURIComponent(tag)}`; + this.request(url, 'DELETE'); }, request(url, method, cb) { $.ajax({ @@ -305,9 +321,9 @@ const tagsComponent = () => { dataType: 'json' }) .done(data => { - if (data.success) - cb(data); - else { + if (data.success) { + if (cb) cb(data); + } else { alert('danger', data.error); } }) diff --git a/src/routes/api.cr b/src/routes/api.cr index d4f1aa4..7d60710 100644 --- a/src/routes/api.cr +++ b/src/routes/api.cr @@ -713,6 +713,24 @@ struct APIRouter end end + Koa.describe "Returns all tags" + Koa.response 200, ref: "$tagsResult" + get "/api/tags" do |env| + begin + tags = Storage.default.list_tags + send_json env, { + "success" => true, + "tags" => tags, + }.to_json + rescue e + Logger.error e + send_json env, { + "success" => false, + "error" => e.message, + }.to_json + end + end + Koa.describe "Adds a new tag to a title" Koa.path "tid", desc: "A title ID" Koa.response 200, ref: "$result" diff --git a/src/views/components/tags.html.ecr b/src/views/components/tags.html.ecr deleted file mode 100644 index 67588a5..0000000 --- a/src/views/components/tags.html.ecr +++ /dev/null @@ -1,12 +0,0 @@ -
-

- Tags: - - -

- -
diff --git a/src/views/title.html.ecr b/src/views/title.html.ecr index 5f5c232..30b240e 100644 --- a/src/views/title.html.ecr +++ b/src/views/title.html.ecr @@ -34,7 +34,10 @@

<%= title.content_label %> found

-<%= render_component "tags" %> +
+ +
@@ -121,6 +124,9 @@ <% content_for "script" do %> <%= render_component "dots-scripts" %> + + +