@@ -122,7 +122,8 @@ | |||
"public/css/form.css", | |||
"public/css/mobile.css", | |||
"public/css/kanban.css", | |||
"public/css/controls.css" | |||
"public/css/controls.css", | |||
"public/css/tags.css" | |||
], | |||
"css/frappe-rtl.css": [ | |||
"public/css/bootstrap-rtl.css", | |||
@@ -132,7 +133,6 @@ | |||
"js/libs.min.js": [ | |||
"public/js/lib/awesomplete/awesomplete.min.js", | |||
"public/js/lib/Sortable.min.js", | |||
"public/js/lib/taggle/taggle.min.js", | |||
"public/js/lib/jquery/jquery.hotkeys.js", | |||
"public/js/lib/summernote/summernote.js", | |||
"public/js/lib/bootstrap.min.js", | |||
@@ -292,7 +292,6 @@ | |||
"public/js/frappe/form/quick_entry.js" | |||
], | |||
"css/list.min.css": [ | |||
"public/js/lib/taggle/taggle.min.css", | |||
"public/css/list.css", | |||
"public/css/calendar.css", | |||
"public/css/role_editor.css", | |||
@@ -307,6 +306,7 @@ | |||
"public/js/frappe/ui/filters/filters.js", | |||
"public/js/frappe/ui/filters/edit_filter.html", | |||
"public/js/frappe/ui/tags.js", | |||
"public/js/frappe/ui/tag_editor.js", | |||
"public/js/frappe/ui/like.js", | |||
"public/js/frappe/ui/liked_by.html", | |||
"public/html/print_template.html", | |||
@@ -0,0 +1,19 @@ | |||
.tags-list { | |||
float: left; | |||
width: 100%; | |||
padding-left: 3px; | |||
} | |||
.tags-input { | |||
width: 100px; | |||
font-size: 11px; | |||
border: none; | |||
outline: none; | |||
} | |||
.tags-list-item { | |||
display: inline-block; | |||
margin: 0px 3px; | |||
} | |||
.tags-placeholder { | |||
display: inline-block; | |||
font-size: 11px; | |||
} |
@@ -0,0 +1,129 @@ | |||
frappe.ui.TagEditor = Class.extend({ | |||
init: function(opts) { | |||
/* docs: | |||
Arguments | |||
- parent | |||
- user_tags | |||
- doctype | |||
- docname | |||
*/ | |||
$.extend(this, opts); | |||
this.setup_tags(); | |||
if (!this.user_tags) { | |||
this.user_tags = ""; | |||
} | |||
this.initialized = true; | |||
this.refresh(this.user_tags); | |||
}, | |||
setup_tags: function() { | |||
var me = this; | |||
// hidden form, does not have parent | |||
if (!this.parent) { | |||
return; | |||
} | |||
this.wrapper = $('<div class="tag-line" style="position: relative">').appendTo(this.parent); | |||
if(!this.wrapper.length) return; | |||
this.tags = new frappe.ui.Tags({ | |||
parent: this.wrapper, | |||
placeholder: "Add a tag ...", | |||
onTagAdd: (tag) => { | |||
if(me.initialized && !me.refreshing) { | |||
tag = toTitle(tag); | |||
return frappe.call({ | |||
method: 'frappe.desk.tags.add_tag', | |||
args: me.get_args(tag), | |||
callback: function(r) { | |||
var user_tags = me.user_tags ? me.user_tags.split(",") : []; | |||
user_tags.push(tag) | |||
me.user_tags = user_tags.join(","); | |||
me.on_change && me.on_change(me.user_tags); | |||
} | |||
}); | |||
} | |||
}, | |||
onTagRemove: (tag) => { | |||
if(!me.refreshing) { | |||
return frappe.call({ | |||
method: 'frappe.desk.tags.remove_tag', | |||
args: me.get_args(tag), | |||
callback: function(r) { | |||
var user_tags = me.user_tags.split(","); | |||
user_tags.splice(user_tags.indexOf(tag), 1); | |||
me.user_tags = user_tags.join(","); | |||
me.on_change && me.on_change(me.user_tags); | |||
} | |||
}); | |||
} | |||
} | |||
}); | |||
this.setup_awesomplete(); | |||
this.setup_complete = true; | |||
}, | |||
setup_awesomplete: function() { | |||
var me = this; | |||
var $input = this.wrapper.find("input.tags-input"); | |||
var input = $input.get(0); | |||
this.awesomplete = new Awesomplete(input, { | |||
minChars: 0, | |||
maxItems: 99, | |||
list: [] | |||
}); | |||
$input.on("awesomplete-open", function(e){ | |||
$input.attr('state', 'open'); | |||
}); | |||
$input.on("awesomplete-close", function(e){ | |||
$input.attr('state', 'closed'); | |||
}); | |||
$input.on("input", function(e) { | |||
var value = e.target.value; | |||
frappe.call({ | |||
method:"frappe.desk.tags.get_tags", | |||
args:{ | |||
doctype: me.frm.doctype, | |||
txt: value.toLowerCase(), | |||
cat_tags: me.list_sidebar ? | |||
JSON.stringify(me.list_sidebar.get_cat_tags()) : '[]' | |||
}, | |||
callback: function(r) { | |||
me.awesomplete.list = r.message; | |||
} | |||
}); | |||
}); | |||
$input.on("focus", function(e) { | |||
if($input.attr('state') != 'open') { | |||
$input.trigger("input"); | |||
} | |||
}); | |||
}, | |||
get_args: function(tag) { | |||
return { | |||
tag: tag, | |||
dt: this.frm.doctype, | |||
dn: this.frm.docname, | |||
} | |||
}, | |||
refresh: function(user_tags) { | |||
var me = this; | |||
if (!this.initialized || !this.setup_complete || this.refreshing) return; | |||
me.refreshing = true; | |||
try { | |||
me.tags.clearTags(); | |||
if(user_tags) { | |||
me.tags.addTags(user_tags.split(',')); | |||
} | |||
} catch(e) { | |||
me.refreshing = false; | |||
// wtf bug | |||
setTimeout( function() { me.refresh(); }, 100); | |||
} | |||
me.refreshing = false; | |||
} | |||
}) |
@@ -1,131 +1,135 @@ | |||
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors | |||
// MIT License. See license.txt | |||
frappe.ui.TagEditor = Class.extend({ | |||
init: function(opts) { | |||
/* docs: | |||
Arguments | |||
- parent | |||
- user_tags | |||
- doctype | |||
- docname | |||
*/ | |||
$.extend(this, opts); | |||
this.setup_taggle(); | |||
if (!this.user_tags) { | |||
this.user_tags = ""; | |||
} | |||
this.initialized = true; | |||
this.refresh(this.user_tags); | |||
}, | |||
setup_taggle: function() { | |||
var me = this; | |||
// hidden form, does not have parent | |||
if (!this.parent) { | |||
return; | |||
} | |||
frappe.ui.Tags = class { | |||
constructor({ | |||
parent, placeholder, tagsList, | |||
onTagAdd, | |||
onTagRemove, | |||
onTagClick, | |||
onChange | |||
}) { | |||
this.tagsList = tagsList || []; | |||
this.onTagAdd = onTagAdd; | |||
this.onTagRemove = onTagRemove; | |||
this.onTagClick = onTagClick; | |||
this.onChange = onChange; | |||
this.setup(parent, placeholder); | |||
} | |||
setup(parent, placeholder) { | |||
this.$wrapper = $(`<div class="tags-wrapper"></div>`).appendTo(parent); | |||
this.$ul = $(`<ul class="tags-list"></ul>`).appendTo(this.$wrapper); | |||
this.$input = $(`<input class="tags-input"></input>`); | |||
this.$inputWrapper = this.getListElement(this.$input); | |||
this.$placeholder = this.getListElement($(`<span class="tags-placeholder text-muted">${placeholder}</span>`)); | |||
this.$inputWrapper.appendTo(this.$ul); | |||
this.$placeholder.appendTo(this.$ul); | |||
this.wrapper = $('<div class="tag-line" style="position: relative">').appendTo(this.parent); | |||
if(!this.wrapper.length) return; | |||
var id = frappe.dom.set_unique_id(this.wrapper); | |||
this.taggle = new Taggle(id, { | |||
placeholder: __('Add a tag') + "...", | |||
onTagAdd: function(e, tag) { | |||
if(me.initialized && !me.refreshing) { | |||
tag = toTitle(tag); | |||
return frappe.call({ | |||
method: 'frappe.desk.tags.add_tag', | |||
args: me.get_args(tag), | |||
callback: function(r) { | |||
var user_tags = me.user_tags ? me.user_tags.split(",") : []; | |||
user_tags.push(tag) | |||
me.user_tags = user_tags.join(","); | |||
me.on_change && me.on_change(me.user_tags); | |||
} | |||
}); | |||
} | |||
}, | |||
onTagRemove: function(e, tag) { | |||
if(!me.refreshing) { | |||
return frappe.call({ | |||
method: 'frappe.desk.tags.remove_tag', | |||
args: me.get_args(tag), | |||
callback: function(r) { | |||
var user_tags = me.user_tags.split(","); | |||
user_tags.splice(user_tags.indexOf(tag), 1); | |||
me.user_tags = user_tags.join(","); | |||
me.on_change && me.on_change(me.user_tags); | |||
} | |||
}); | |||
} | |||
this.deactivate(); | |||
this.bind(); | |||
this.boot(); | |||
} | |||
bind() { | |||
this.$input.keypress((e) => { | |||
if(e.which == 13 || e.keyCode == 13) { | |||
this.addTag(this.$input.val()); | |||
this.$input.val(''); | |||
} | |||
}); | |||
this.setup_awesomplete(); | |||
this.setup_complete = true; | |||
}, | |||
setup_awesomplete: function() { | |||
var me = this; | |||
var $input = this.wrapper.find("input.taggle_input"); | |||
var input = $input.get(0); | |||
this.awesomplete = new Awesomplete(input, { | |||
minChars: 0, | |||
maxItems: 99, | |||
list: [] | |||
this.$input.on('blur', () => { | |||
this.deactivate(); | |||
}); | |||
$input.on("awesomplete-open", function(e){ | |||
$input.attr('state', 'open'); | |||
this.$placeholder.on('click', () => { | |||
this.activate(); | |||
}); | |||
$input.on("awesomplete-close", function(e){ | |||
$input.attr('state', 'closed'); | |||
} | |||
boot() { | |||
this.addTags(this.tagsList); | |||
} | |||
activate() { | |||
this.$placeholder.hide(); | |||
this.$inputWrapper.show(); | |||
this.$input.focus(); | |||
} | |||
deactivate() { | |||
this.$inputWrapper.hide(); | |||
this.$placeholder.show(); | |||
} | |||
refresh() { | |||
this.deactivate(); | |||
this.activate(); | |||
} | |||
addTag(label) { | |||
if(label && !this.tagsList.includes(label)) { | |||
let $tag = this.getTag(label); | |||
this.getListElement($tag).insertBefore(this.$inputWrapper); | |||
this.tagsList.push(label); | |||
this.onTagAdd && this.onTagAdd(label); | |||
this.refresh(); | |||
} | |||
} | |||
removeTag(label) { | |||
if(this.tagsList.includes(label)) { | |||
let $tag = this.$ul.find(`.frappe-tag[data-tag-label="${label}"]`); | |||
$tag.remove(); | |||
this.tagsList = this.tagsList.filter(d => d !== label); | |||
this.onTagRemove && this.onTagRemove(label); | |||
} | |||
} | |||
addTags(labels) { | |||
labels.map(this.addTag.bind(this)); | |||
} | |||
clearTags() { | |||
this.$ul.find('.frappe-tag').remove(); | |||
this.tagsList = []; | |||
} | |||
getListElement($element, className) { | |||
let $li = $(`<li class="tags-list-item ${className}"></li>`); | |||
$element.appendTo($li); | |||
return $li; | |||
} | |||
getTag(label) { | |||
let $tag = $(`<div class="frappe-tag btn-group" data-tag-label=${label}> | |||
<button class="btn btn-default btn-xs toggle-tag" | |||
title="${ __("toggle Tag") }" | |||
data-tag-label=${label}>${label} | |||
</button> | |||
<button class="btn btn-default btn-xs remove-tag" | |||
title="${ __("Remove Tag") }" | |||
data-tag-label=${label}> | |||
<i class="fa fa-remove text-muted"></i> | |||
</button></div>`); | |||
let $removeTag = $tag.find(".remove-tag"); | |||
$removeTag.on("click", () => { | |||
this.removeTag($removeTag.attr('data-tag-label')); | |||
}); | |||
$input.on("input", function(e) { | |||
var value = e.target.value; | |||
frappe.call({ | |||
method:"frappe.desk.tags.get_tags", | |||
args:{ | |||
doctype: me.frm.doctype, | |||
txt: value.toLowerCase(), | |||
cat_tags: me.list_sidebar ? | |||
JSON.stringify(me.list_sidebar.get_cat_tags()) : '[]' | |||
}, | |||
callback: function(r) { | |||
me.awesomplete.list = r.message; | |||
} | |||
if(this.onTagClick) { | |||
let $toggle_tag = $tag.find(".toggle-tag"); | |||
$toggle_tag.on("click", () => { | |||
this.onTagClick($toggle_tag.attr('data-tag-label')); | |||
}); | |||
}); | |||
$input.on("focus", function(e) { | |||
if($input.attr('state') != 'open') { | |||
$input.trigger("input"); | |||
} | |||
}); | |||
}, | |||
get_args: function(tag) { | |||
return { | |||
tag: tag, | |||
dt: this.frm.doctype, | |||
dn: this.frm.docname, | |||
} | |||
}, | |||
refresh: function(user_tags) { | |||
var me = this; | |||
if (!this.initialized || !this.setup_complete || this.refreshing) return; | |||
me.refreshing = true; | |||
try { | |||
me.taggle.removeAll(); | |||
if(user_tags) { | |||
me.taggle.add(user_tags.split(',')); | |||
} | |||
} catch(e) { | |||
me.refreshing = false; | |||
// wtf bug | |||
setTimeout( function() { me.refresh(); }, 100); | |||
} | |||
me.refreshing = false; | |||
return $tag; | |||
} | |||
}) | |||
} |
@@ -1,103 +0,0 @@ | |||
.taggle_list { | |||
float: left; | |||
width: 100%; | |||
} | |||
.taggle_input { | |||
border: none; | |||
outline: none; | |||
font-size: 16px; | |||
font-weight: 300; | |||
} | |||
.taggle_list li { | |||
float: left; | |||
display: inline-block; | |||
white-space: nowrap; | |||
font-weight: 500; | |||
margin-bottom: 5px; | |||
} | |||
.taggle_list .taggle { | |||
margin-right: 8px; | |||
background: #E2E1DF; | |||
padding: 5px 10px; | |||
border-radius: 3px; | |||
position: relative; | |||
cursor: pointer; | |||
transition: all .3s; | |||
animation-duration: 1s; | |||
animation-fill-mode: both; | |||
} | |||
.taggle_list .taggle_hot { | |||
background: #cac8c4; | |||
} | |||
.taggle_list .taggle .close { | |||
font-size: 1.1rem; | |||
position: absolute; | |||
top: 5px; | |||
right: 3px; | |||
text-decoration: none; | |||
padding-left: 2px; | |||
padding-top: 3px; | |||
line-height: 0.5; | |||
color: #ccc; | |||
color: rgba(0, 0, 0, 0.2); | |||
padding-bottom: 4px; | |||
display: none; | |||
border: 0; | |||
background: none; | |||
cursor: pointer; | |||
} | |||
.taggle_list .taggle:hover { | |||
padding: 5px; | |||
padding-right: 15px; | |||
background: #ccc; | |||
transition: all .3s; | |||
} | |||
.taggle_list .taggle:hover > .close { | |||
display: block; | |||
} | |||
.taggle_list .taggle .close:hover { | |||
color: #333; | |||
} | |||
.taggle_placeholder { | |||
position: absolute; | |||
color: #CCC; | |||
top: 12px; | |||
left: 8px; | |||
transition: opacity, .25s; | |||
user-select: none; | |||
} | |||
.taggle_input { | |||
padding: 8px; | |||
padding-left: 0; | |||
float: left; | |||
margin-top: -5px; | |||
background: none; | |||
width: 100%; | |||
max-width: 100%; | |||
} | |||
.taggle_sizer { | |||
padding: 0; | |||
margin: 0; | |||
position: absolute; | |||
top: -500px; | |||
z-index: -1; | |||
visibility: hidden; | |||
} | |||
.clearfix:before, .clearfix:after { | |||
display: block; | |||
content: ""; | |||
line-height: 0; | |||
clear: both; | |||
} |
@@ -0,0 +1,22 @@ | |||
.tags-list { | |||
float: left; | |||
width: 100%; | |||
padding-left: 3px; | |||
} | |||
.tags-input { | |||
width: 100px; | |||
font-size: 11px; | |||
border: none; | |||
outline: none; | |||
} | |||
.tags-list-item { | |||
display: inline-block; | |||
margin: 0px 3px; | |||
} | |||
.tags-placeholder { | |||
display: inline-block; | |||
font-size: 11px; | |||
} |