Elastic: Kolab Notes support, tags functionality refactoring

This commit is contained in:
Aleksander Machniak 2018-01-12 11:50:28 +01:00
parent d406ee4ec0
commit 623cf117ad
15 changed files with 523 additions and 400 deletions

View file

@ -1118,7 +1118,7 @@ class kolab_notes extends rcube_plugin
$path_imap = explode($delim, $newfolder);
$list['name'] = kolab_storage::object_name($newfolder);
$list['editname'] = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP');
$list['listname'] = (!empty($path_imap) ? str_repeat('   ', count($path_imap)) . '» ' : '') . $list['editname'];
$list['listname'] = $list['editname'];
}
break;

View file

@ -40,7 +40,6 @@ class kolab_notes_ui
*/
public function init_templates()
{
$this->plugin->register_handler('plugin.tagslist', array($this, 'tagslist'));
$this->plugin->register_handler('plugin.notebooks', array($this, 'folders'));
#$this->plugin->register_handler('plugin.folders_select', array($this, 'folders_select'));
$this->plugin->register_handler('plugin.searchform', array($this->rc->output, 'search_form'));
@ -55,8 +54,6 @@ class kolab_notes_ui
$this->rc->output->include_script('treelist.js');
$this->plugin->include_script('notes.js');
jqueryui::tagedit();
// include kolab folderlist widget if available
if (in_array('libkolab', $this->plugin->api->loaded_plugins())) {
$this->plugin->api->include_script('libkolab/js/folderlist.js');
@ -216,13 +213,6 @@ class kolab_notes_ui
return html::tag('table', $attrib, '<tbody></tbody>', html::$common_attrib);
}
public function tagslist($attrib)
{
$attrib += array('id' => 'rcmkolabnotestagslist');
$this->rc->output->add_gui_object('notestagslist', $attrib['id']);
return html::tag('ul', $attrib, '', html::$common_attrib);
}
public function editform($attrib)
{
$attrib += array('action' => '#', 'id' => 'rcmkolabnoteseditform');
@ -267,9 +257,9 @@ class kolab_notes_ui
)
. html::div('form-group row',
html::label(array('class' => 'col-sm-2 col-form-label'), $this->plugin->gettext('kolab_notes.tags'))
. html::div(array('class' => 'tagline tagedit col-sm-10', 'style' => 'display:none'), '&nbsp;')
. html::div(array('class' => 'tagline tagedit col-sm-10'), '&nbsp;')
)
. html::div(array('class' => 'dates', 'style' => 'display:none'),
. html::div(array('class' => 'dates text-only', 'style' => 'display:none'),
html::div('form-group row',
html::label(array('class' => 'col-sm-2 col-form-label'), $this->plugin->gettext('created'))
. html::span('col-sm-10', html::span('notecreated form-control-plaintext', ''))

View file

@ -36,11 +36,9 @@ function rcube_kolab_notes_ui(settings)
var notebookslist;
var noteslist;
var notesdata = {};
var tagsfilter = [];
var tags = [];
var taglist;
var search_request;
var search_query;
var tag_draghelper;
var render_no_focus;
var me = this;
@ -273,62 +271,38 @@ function rcube_kolab_notes_ui(settings)
$('#notessortmenu a.by-' + settings.sort_col).addClass('selected');
}
// click-handler on tags list
$(rcmail.gui_objects.notestagslist).on('click', 'li', function(e){
var item = e.target.nodeName == 'LI' ? $(e.target) : $(e.target).closest('li'),
tag = item.data('value');
if (!tag)
return false;
// reset selection on regular clicks
var index = $.inArray(tag, tagsfilter);
var shift = e.shiftKey || e.ctrlKey || e.metaKey;
if (!shift) {
if (tagsfilter.length > 1)
index = -1;
$('li', rcmail.gui_objects.notestagslist).removeClass('selected').attr('aria-checked', 'false');
tagsfilter = [];
}
// add tag to filter
if (index < 0) {
item.addClass('selected').attr('aria-checked', 'true');
tagsfilter.push(tag);
}
else if (shift) {
item.removeClass('selected').attr('aria-checked', 'false');
var a = tagsfilter.slice(0,index);
tagsfilter = a.concat(tagsfilter.slice(index+1));
}
filter_notes();
// clear text selection in IE after shift+click
if (shift && document.selection)
document.selection.empty();
e.preventDefault();
return false;
})
.on('keypress', 'li', function(e) {
if (e.keyCode == 13) {
$(this).trigger('click', { pointerType:'keyboard' });
}
})
.mousedown(function(e){
// disable content selection with the mouse
e.preventDefault();
return false;
});
init_editor();
if (settings.selected_list) {
notebookslist.select(settings.selected_list)
}
rcmail.addEventListener('kolab-tags-search', filter_notes)
.addEventListener('kolab-tags-drop-data', function(e) { return notesdata[e.id]; })
.addEventListener('kolab-tags-drop', function(e) {
if ($(e.list).is('#kolabnoteslist')) {
return;
}
var rec = notesdata[e.id];
if (rec && rec.id && e.tag) {
savedata = me.selected_note && rec.uid == me.selected_note.uid ? get_save_data() : $.extend({}, rec);
if (savedata.id) delete savedata.id;
if (savedata.html) delete savedata.html;
if (!savedata.tags)
savedata.tags = [];
savedata.tags.push(e.tag);
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: savedata, _do: 'edit' }, true);
}
});
rcmail.triggerEvent('kolab-notes-init');
}
this.init = init;
@ -387,7 +361,7 @@ function rcube_kolab_notes_ui(settings)
this.init_dialog = init_dialog;
/**
* initialize tinyMCE editor
* Initialize TinyMCE editor
*/
function init_editor(callback)
{
@ -682,24 +656,25 @@ function rcube_kolab_notes_ui(settings)
reset_view();
noteslist.clear(true);
notesdata = {};
tagsfilter = [];
update_state();
}
function filter_notes()
/**
* Filter notes by tag
*/
function filter_notes(tags)
{
// tagsfilter
var id, note, tr, match;
for (id in noteslist.rows) {
tr = noteslist.rows[id].obj;
note = notesdata[id];
match = note.tags && note.tags.length;
for (var i=0; match && note && i < tagsfilter.length; i++) {
if ($.inArray(tagsfilter[i], note.tags) < 0)
for (var i=0; match && note && i < tags.length; i++) {
if ($.inArray(tags[i], note.tags) < 0)
match = false;
}
if (match || !tagsfilter.length) {
if (match || !tags.length) {
$(tr).show();
}
else {
@ -711,9 +686,7 @@ function rcube_kolab_notes_ui(settings)
me.selected_note = null;
noteslist.clear_selection();
}, function(){
tagsfilter = [];
filter_notes();
update_tagcloud();
filter_notes(tags);
});
}
}
@ -748,7 +721,7 @@ function rcube_kolab_notes_ui(settings)
notesdata[rec.id] = rec;
}
render_tagslist(data.tags || [], !data.search)
update_taglist();
rcmail.set_busy(false, 'loading', ui_loading);
rcmail.triggerEvent('listupdate', {list: noteslist, rowcount:noteslist.rowcount});
@ -796,6 +769,12 @@ function rcube_kolab_notes_ui(settings)
titlecontainer = rcmail.gui_objects.noteviewtitle || container,
is_html = false;
// tag-edit line
if (window.kolab_tags_input) {
$('.tagline', titlecontainer).parent('.form-group').show();
taglist = kolab_tags_input($('.tagline', titlecontainer), data.tags, readonly);
}
$('.notetitle', titlecontainer).val(data.title).prop('disabled', readonly).show();
$('.dates .notecreated', titlecontainer).text(data.created || '');
$('.dates .notechanged', titlecontainer).text(data.changed || '');
@ -803,34 +782,6 @@ function rcube_kolab_notes_ui(settings)
$('.dates', titlecontainer).show();
}
// tag-edit line
var tagline = $('.tagline', titlecontainer).empty()[readonly?'addClass':'removeClass']('disabled').show();
$.each(typeof data.tags == 'object' && data.tags.length ? data.tags : [''], function(i,val) {
$('<input>')
.attr('name', 'tags[]')
.attr('tabindex', '0')
.addClass('tag')
.val(val)
.appendTo(tagline);
});
if (!data.tags || !data.tags.length) {
$('<span>').addClass('placeholder')
.html(rcmail.gettext('notags', 'kolab_notes'))
.appendTo(tagline)
.click(function(e) { $(this).parent().find('.tagedit-list').trigger('click'); });
}
$('.tagline input.tag', titlecontainer).tagedit({
animSpeed: 100,
allowEdit: false,
allowAdd: !readonly,
allowDelete: !readonly,
checkNewEntriesCaseSensitive: false,
autocompleteOptions: { source: tags, minLength: 0, noCheck: true },
texts: { removeLinkTitle: rcmail.gettext('removetag', 'kolab_notes') }
});
if (data.links) {
$.each(data.links, function(i, link) {
var li = $('<li>').addClass('link')
@ -858,11 +809,6 @@ function rcube_kolab_notes_ui(settings)
});
}
if (!readonly) {
$('.tagedit-list', titlecontainer)
.on('click', function(){ $('.tagline .placeholder').hide(); });
}
if (!data.list)
data.list = list.id;
@ -891,9 +837,6 @@ function rcube_kolab_notes_ui(settings)
// read possibly re-formatted content back from editor for later comparison
me.selected_note.description = rcmail.editor.get_content().replace(/^\s*(<p><\/p>\n*)?/, '');
is_html = true;
if (!me.selected_note.uid)
$('.notetitle', titlecontainer).focus().select();
}
else {
gui_object('noteseditform', container).hide();
@ -911,6 +854,9 @@ function rcube_kolab_notes_ui(settings)
// Elastic
$(container).parents('.watermark').addClass('formcontainer');
$('#notedetailsbox').parent().trigger('loaded');
if (!readonly)
$('.notetitle', titlecontainer).focus().select();
}
/**
@ -1161,88 +1107,26 @@ function rcube_kolab_notes_ui(settings)
}
/**
*
* Display the given counts to each tag
*/
function render_tagslist(newtags, replace)
function update_taglist(tags)
{
if (replace) {
tags = newtags;
}
else {
var i, append = [];
for (i=0; i < newtags.length; i++) {
if ($.inArray(newtags[i], tags) < 0)
append.push(newtags[i]);
// compute counts first by iterating over all notes
var counts = {};
$.each(notesdata, function(id, rec) {
for (var t, j=0; rec && rec.tags && j < rec.tags.length; j++) {
t = rec.tags[j];
if (typeof counts[t] == 'undefined')
counts[t] = 0;
counts[t]++;
}
if (!append.length) {
update_tagcloud();
return; // nothing to be added
}
tags = tags.concat(append);
}
// sort tags first
tags.sort(function(a,b){
return a.toLowerCase() > b.toLowerCase() ? 1 : -1;
})
var widget = $(rcmail.gui_objects.notestagslist).html('');
// append tags to tag cloud
$.each(tags, function(i, tag){
li = $('<li role="checkbox" aria-checked="false" tabindex="0"></li>')
.attr('rel', tag)
.data('value', tag)
.html(Q(tag) + '<span class="count"></span>')
.appendTo(widget)
.draggable({
addClasses: false,
revert: 'invalid',
revertDuration: 300,
helper: tag_draggable_helper,
start: tag_draggable_start,
appendTo: 'body',
cursor: 'pointer'
});
});
update_tagcloud();
}
rcmail.triggerEvent('kolab-tags-counts', {counter: counts});
/**
* Display the given counts to each tag and set those inactive which don't
* have any matching records in the current view.
*/
function update_tagcloud(counts)
{
// compute counts first by iterating over all visible task items
if (typeof counts == 'undefined') {
counts = {};
$.each(notesdata, function(id, rec){
for (var t, j=0; rec && rec.tags && j < rec.tags.length; j++) {
t = rec.tags[j];
if (typeof counts[t] == 'undefined')
counts[t] = 0;
counts[t]++;
}
});
if (tags && tags.length) {
rcmail.triggerEvent('kolab-tags-refresh', {tags: tags});
}
$(rcmail.gui_objects.notestagslist).children('li').each(function(i,li){
var elem = $(li), tag = elem.attr('rel'),
count = counts[tag] || 0;
elem.children('.count').html(count+'');
if (count == 0) elem.addClass('inactive');
else elem.removeClass('inactive');
if (tagsfilter && tagsfilter.length && $.inArray(tag, tagsfilter)) {
elem.addClass('selected').attr('aria-checked', 'true');
}
else {
elem.removeClass('selected').attr('aria-checked', 'false');
}
});
}
/**
@ -1257,10 +1141,10 @@ function rcube_kolab_notes_ui(settings)
if (is_new || me.selected_note && data.id == me.selected_note.id) {
render_note(data);
render_tagslist(data.tags || []);
update_taglist(data.tags || []);
}
else if (data.tags) {
render_tagslist(data.tags);
update_taglist(data.tags);
}
// add list item on top
@ -1291,7 +1175,8 @@ function rcube_kolab_notes_ui(settings)
close_history_dialog();
me.selected_note = null;
$('.notetitle', rcmail.gui_objects.noteviewtitle).val('').hide();
$('.tagline, .dates', rcmail.gui_objects.noteviewtitle).hide();
$('.dates', rcmail.gui_objects.noteviewtitle).hide();
$('.tagline', rcmail.gui_objects.noteviewtitle).parent('.form-group').hide();
$(rcmail.gui_objects.noteseditform).hide().parents('.watermark').removeClass('formcontainer');
$(rcmail.gui_objects.notesdetailview).hide();
$(rcmail.gui_objects.notesattachmentslist).html('');
@ -1350,7 +1235,7 @@ function rcube_kolab_notes_ui(settings)
description: rcmail.editor.get_content().replace(/^\s*(<p><\/p>\n*)?/, ''),
list: listselect.length ? listselect.val() : me.selected_note.list || me.selected_list,
uid: me.selected_note.uid,
tags: []
tags: taglist ? kolab_tags_input_value(taglist) : []
};
// copy links
@ -1358,17 +1243,6 @@ function rcube_kolab_notes_ui(settings)
savedata.links = me.selected_note.links;
}
// collect tags
$('.tagedit-list input[type="hidden"]', rcmail.gui_objects.noteviewtitle).each(function(i, elem){
if (elem.value)
savedata.tags.push(elem.value);
});
// including the "pending" one in the text box
var newtag = $('#tagedit-input').val();
if (newtag != '') {
savedata.tags.push(newtag);
}
return savedata;
}
@ -1491,7 +1365,7 @@ function rcube_kolab_notes_ui(settings)
rcmail.http_post('action', { _data: { uid: uids.join(','), list: me.selected_list }, _do: 'delete' }, true);
reset_view();
update_tagcloud();
update_taglist();
noteslist.clear_selection();
});
}
@ -1533,81 +1407,6 @@ function rcube_kolab_notes_ui(settings)
window.history.replaceState({}, document.title, rcmail.url('', query));
}
}
/* Helper functions for drag & drop functionality of tags */
function tag_draggable_helper()
{
if (!tag_draghelper)
tag_draghelper = $('<div class="tag-draghelper"></div>');
else
tag_draghelper.html('');
$(this).clone().addClass('tag').appendTo(tag_draghelper);
return tag_draghelper;
}
function tag_draggable_start(event, ui)
{
// register notes list to receive drop events
$('li', rcmail.gui_objects.noteslist).droppable({
hoverClass: 'droptarget',
accept: tag_droppable_accept,
drop: tag_draggable_dropped,
addClasses: false
});
// allow to drop tags onto edit form title
$(rcmail.gui_objects.noteviewtitle).droppable({
drop: function(event, ui){
$('#tagedit-input').val(ui.draggable.data('value')).trigger('transformToTag');
$('.tagline .placeholder', rcmail.gui_objects.noteviewtitle).hide();
},
addClasses: false
})
}
function tag_droppable_accept(draggable)
{
if (rcmail.busy)
return false;
var tag = draggable.data('value'),
drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
drop_rec = notesdata[drop_id];
// target already has this tag assigned
if (!drop_rec || (drop_rec.tags && $.inArray(tag, drop_rec.tags) >= 0)) {
return false;
}
return true;
}
function tag_draggable_dropped(event, ui)
{
var drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
tag = ui.draggable.data('value'),
rec = notesdata[drop_id],
savedata;
if (rec && rec.id) {
savedata = me.selected_note && rec.uid == me.selected_note.uid ? get_save_data() : $.extend({}, rec);
if (savedata.id) delete savedata.id;
if (savedata.html) delete savedata.html;
if (!savedata.tags)
savedata.tags = [];
savedata.tags.push(tag);
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: savedata, _do: 'edit' }, true);
}
}
}

View file

@ -6,7 +6,7 @@
<!-- notebooks list -->
<div class="sidebar listbox" role="navigation" aria-labelledby="arial-label-notebooks">
<div class="header">
<a class="button icon menu-button" href="#menu"><span class="inner"><roundcube:label name="menu" /></span></a>
<a class="button icon back-list-button" href="#back"><span class="inner"><roundcube:label name="back" /></span></a>
<span id="aria-label-notebooks" class="header-title"><roundcube:label name="kolab_notes.lists" /></span>
<div id="notebook-search" class="searchbar toolbar" role="search" aria-labelledby="aria-label-notebooksearchform">
<h2 id="aria-label-label-notebooksearchform" class="voice"><roundcube:label name="kolab_notes.arialabelfoldersearchform" /></h2>
@ -21,16 +21,8 @@
</a>
</div>
</div>
<div class="scroller">
<div id="notebooks-content" class="scroller">
<roundcube:object name="plugin.notebooks" id="notebooks" class="listing treelist iconized" />
<!--
<div id="tagsbox" class="uibox listbox">
<h2 class="boxtitle" id="aria-label-tagsbox"><roundcube:label name="kolab_notes.tags" id="taglist" /></h2>
<div class="scroller">
<roundcube:object name="plugin.tagslist" id="tagslist" class="tagcloud" role="region" aria-labelledby="aria-label-tagsbox" aria-controls="kolabnoteslist" />
</div>
</div>
-->
</div>
<div class="footer toolbar" role="toolbar">
<roundcube:button command="list-create" type="link" title="kolab_notes.newnotebook"
@ -45,13 +37,12 @@
<div class="list listbox selected" aria-labelledby="aria-label-noteslist">
<div class="header">
<a class="button icon menu-button" href="#menu"><span class="inner"><roundcube:label name="menu" /></span></a>
<a class="button icon back-sidebar-button" href="#sidebar"><span class="inner"><roundcube:label name="kolab_notes.notebooks" /></span></a>
<a class="button icon back-sidebar-button folders" href="#sidebar"><span class="inner"><roundcube:label name="kolab_notes.notebooks" /></span></a>
<span id="aria-label-noteslist" class="header-title"><roundcube:label name="kolab_notes.notes" /></span>
<roundcube:object name="plugin.searchform" id="searchform" wrapper="searchbar toolbar"
label="notesearchform" label-domain="kolab_notes" buttontitle="kolab_notes.findnotes" ariatag="h2" />
<a class="button icon toolbar-menu-button" href="#list-menu"><span class="inner"><roundcube:label name="menu" /></span></a>
</div>
<div class="pagenav toolbar" role="toolbar"></div>
<div class="scroller">
<h2 id="aria-label-noteslist" class="voice"><roundcube:label name="kolab_notes.notes" /></h2>
<roundcube:object name="plugin.listing" id="kolabnoteslist" class="listing" summary="kolab_notes.arialabelnoteslist"
@ -65,7 +56,7 @@
</div>
</div>
<!-- contact details frame -->
<!-- note details frame -->
<div class="content" role="main">
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
<div class="header" role="toolbar" aria-labelledby="aria-label-toolbar">

View file

@ -68,17 +68,9 @@
left: 252px;
}
.notesview #tagsbox {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 240px;
}
.notesview #notebooksbox {
position: absolute;
top: 252px;
top: 0;
left: 0;
width: 100%;
bottom: 0px;
@ -272,11 +264,6 @@
border-bottom: 0;
}
.notesview .ui-dialog-content .tagline {
display: none !important;
}
.notesview .notetitle .disabled .tagedit-list,
.notesview .notetitle input.inline-edit:disabled {
outline: none;
padding-left: 0;
@ -306,7 +293,6 @@
}
.notesview .notetitle .dates,
.notesview .notetitle .tagline,
.notesdialog .notebookselect label {
color: #999;
font-weight: normal;
@ -318,38 +304,6 @@
margin-top: 4px;
}
.notesview .notetitle .tagline {
position: relative;
cursor: text;
margin: 4px 0 0 0;
}
.notesview .notetitle .tagline.disabled {
margin-top: 0;
}
.notesview .notetitle .tagline .placeholder {
position: absolute;
top: 6px;
left: 6px;
z-index: 2;
}
.notesview .notetitle .tagline.disabled .placeholder {
left: 0;
}
.notesview .notetitle .tagedit-list {
position: relative;
z-index: 1;
min-height: 32px;
/* padding: 2px; */
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.notetitle .col-form-label {
display: none;
}
@ -366,23 +320,6 @@
clear: both;
}
/* Firefox 3.6 */
_:not(), _:-moz-handler-blocked, .notesview .notetitle .tagedit-list {
min-height: 26px;
}
.notesview .notetitle .disabled .tagedit-list {
min-height: 26px;
}
.notesview .notetitle #tagedit-input {
background: none;
}
.notesview .tag-draghelper {
z-index: 1000;
}
.notesview .notetitle .notecreated,
.notesview .notetitle .notechanged {
display: inline-block;

View file

@ -0,0 +1,18 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="iframe fullheight">
<h1 class="voice"><roundcube:label name="kolab_notes.notes" /> : <roundcube:label name="kolab_notes.arialabelnotebookform" /></h1>
<div class="boxcontent">
<roundcube:object name="notebookform" class="tabbed propform" />
</div>
<roundcube:include file="/includes/footer.html" />
</body>
</html>

View file

@ -30,13 +30,6 @@
<div id="mainscreencontent">
<div id="sidebar">
<div id="tagsbox" class="uibox listbox">
<h2 class="boxtitle" id="aria-label-tagsbox"><roundcube:label name="kolab_notes.tags" id="taglist" /></h2>
<div class="scroller">
<roundcube:object name="plugin.tagslist" id="tagslist" class="tagcloud" role="region" aria-labelledby="aria-label-tagsbox" aria-controls="kolabnoteslist" />
</div>
</div>
<div id="notebooksbox" class="uibox listbox" role="navigation" aria-labelledby="aria-label-notebooks">
<h2 class="boxtitle" id="aria-label-notebooks"><roundcube:label name="kolab_notes.lists" />
<a href="#notebooks" class="iconbutton search" title="<roundcube:label name='kolab_notes.findnotebooks' />" tabindex="0"><roundcube:label name="kolab_notes.findnotebooks" /></a>
@ -160,8 +153,6 @@ $(document).ready(function(e){
orientation:'v', relative:true, start:240, min:180, size:12, render:layout_view }).init();
new rcube_splitter({ id:'noteslistsplitter2', p1:'#noteslistbox', p2:'#notedetailsbox',
orientation:'v', relative:true, start:242, min:180, size:12, render:layout_view }).init();
new rcube_splitter({ id:'notesviewsplitterv', p1:'#tagsbox', p2:'#notebooksbox',
orientation:'h', relative:true, start:242, min:120, size:12, offset:4 }).init();
function layout_view()
{
@ -217,7 +208,6 @@ $(document).ready(function(e){
return false;
});
});
</script>

View file

@ -26,7 +26,7 @@
*/
window.rcmail && rcmail.addEventListener('init', function() {
if (rcmail.task == 'mail') {
if (rcmail.task == 'mail' || rcmail.task == 'notes') {
var msg_view = rcmail.env.action == 'show' || rcmail.env.action == 'preview';
if (!msg_view && rcmail.env.action) {
@ -35,7 +35,13 @@ window.rcmail && rcmail.addEventListener('init', function() {
// load tags cloud
if (rcmail.gui_objects.taglist) {
load_tags();
// Tags for kolab_notes plugin have to be initialized via an event
if (rcmail.task == 'notes' && !window.kolabnotes) {
rcmail.addEventListener('kolab-notes-init', load_tags);
}
else {
load_tags();
}
}
// display tags in message subject (message window)
@ -79,7 +85,7 @@ window.rcmail && rcmail.addEventListener('init', function() {
return true;
}
if (p.command == 'reset-tags') {
return !!(tagsfilter.length && rcmail.message_list);
return !!(tagsfilter.length && main_list_widget());
}
});
}
@ -99,16 +105,31 @@ window.rcmail && rcmail.addEventListener('init', function() {
$('a.menuselector span', $(this).parent()).text(title);
});
}
// Allow other plugins to update tags list
rcmail.addEventListener('kolab-tags-counts', update_counts)
.addEventListener('kolab-tags-refresh', refresh_tags);
}
});
var tagsfilter = [], tag_selector_element, tag_form_data, tag_form_save_func,
var tag_selector_element, tag_form_data, tag_form_save_func,
tagsfilter = [],
tagscounts = [],
reset_css = {color: '', backgroundColor: ''};
function main_list_widget()
{
if (rcmail.task == 'mail' && rcmail.message_list)
return rcmail.message_list;
if (rcmail.task == 'notes' && rcmail.noteslist)
return rcmail.noteslist;
}
// fills tag cloud with tags list
function load_tags()
{
var ul = $('#taglist'), clickable = rcmail.message_list;
var ul = $('#taglist'), clickable = !!main_list_widget();
$.each(rcmail.env.tags, function(i, tag) {
var li = add_tag_element(ul, tag, clickable);
@ -130,9 +151,10 @@ function load_tags()
function add_tag_element(list, tag, clickable)
{
// @todo: .append('<span class="count"></span>')
var element = $('<li>').attr('class', tag_class_name(tag))
.text(tag.name).data('tag', tag.uid).appendTo(list);
.text(tag.name).data('tag', tag.uid)
.append('<span class="count">')
.appendTo(list);
if (clickable) {
element.click(function(e) {
@ -173,12 +195,38 @@ function add_tag_element(list, tag, clickable)
e.preventDefault();
return false;
})
});
if (!$('html.touch').length) {
element.draggable({
addClasses: false,
cursor: 'default',
cursorAt: {left: -10},
revert: 'invalid',
revertDuration: 300,
helper: tag_draggable_helper,
start: tag_draggable_start,
appendTo: 'body'
});
}
}
return element;
}
function update_counts(p)
{
$('#taglist > li').each(function(i,li) {
var elem = $(li), tagid = elem.data('tag'),
tag = tag_find(tagid);
count = tag && p.counter ? p.counter[tag.name] : '';
elem.children('.count').text(count ? count : '');
});
tagscounts = p.counter;
}
function manage_tags()
{
// display it as popup
@ -388,6 +436,29 @@ function update_tags(response)
// reset tag selector popup
tag_selector_reset();
if (response.refresh) {
var list = $('#taglist');
tagsfilter = $.map(list.children('.selected'), function(li) {
return $(li).data('tag');
});
list.html('');
load_tags();
update_counts({counter: tagscounts});
if (tagsfilter.length) {
list.children('li').each(function() {
if ($.inArray($(this).data('tag'), tagsfilter) > -1) {
$(this).addClass('selected');
}
});
apply_tags_filter();
}
return;
}
// remove deleted tags
remove_tags(response['delete'], response.mark);
@ -405,9 +476,9 @@ function update_tags(response)
var i, old, tag = this, id = tag.uid,
filter = function() { return $(this).data('tag') == id; },
tagbox = $('#taglist li').filter(filter),
elements = $('span.tagbox').filter(filter),
elements = $('.tagbox').filter(filter),
win = rcmail.get_frame_window(rcmail.env.contentframe),
framed = win && win.jQuery ? win.jQuery('span.tagbox').filter(function() { return win.jQuery(this).data('tag') == id; }) : [];
framed = win && win.jQuery ? win.jQuery('.tagbox').filter(function() { return win.jQuery(this).data('tag') == id; }) : [];
selected = $.inArray(String(id), tagsfilter);
for (i in rcmail.env.tags)
@ -454,7 +525,8 @@ function remove_tags(tags, selection)
}
var taglist = $('#taglist li'),
tagboxes = $((selection && rcmail.message_list ? 'tr.selected ' : '') + 'span.tagbox'),
list = main_list_widget(),
tagboxes = $((selection && list ? 'tr.selected ' : '') + 'span.tagbox'),
win = rcmail.get_frame_window(rcmail.env.contentframe),
frame_tagboxes = win && win.jQuery ? win.jQuery('span.tagbox') : [],
update_filter = false;
@ -492,11 +564,28 @@ function remove_tags(tags, selection)
}
});
if (update_filter && rcmail.message_list) {
if (update_filter && list) {
apply_tags_filter();
}
}
// kolab-tags-refresh event handler, allowing plugins to refresh
// the tags list e.g. when a new tag is created
function refresh_tags(e)
{
// find a new tag
$.each(e.tags || [], function() {
for (var i in rcmail.env.tags) {
if (rcmail.env.tags[i].name == this) {
return;
}
}
rcmail.http_post('plugin.kolab_tags', {_act: 'refresh'}, rcmail.display_message(rcmail.get_label('loading'), 'loading'));
return false;
});
}
// unselect all selected tags in the tag cloud
function reset_tags()
{
@ -602,8 +691,21 @@ function tag_remove(props, obj, event)
// executes messages search according to selected messages
function apply_tags_filter()
{
rcmail.enable_command('reset-tags', tagsfilter.length && rcmail.message_list);
rcmail.qsearch();
rcmail.enable_command('reset-tags', tagsfilter.length && main_list_widget());
if (rcmail.task == 'mail')
rcmail.qsearch();
else {
// Convert tag id to tag label
var tags = [];
$.each(rcmail.env.tags, function() {
if ($.inArray(this.uid, tagsfilter) > -1) {
tags.push(this.name);
}
});
rcmail.triggerEvent('kolab-tags-search', tags);
}
}
// adds _tags argument to http search request
@ -896,3 +998,162 @@ function tag_class_name(tag)
{
return 'kolab-tag-' + tag.uid.replace(/[^a-z0-9]/ig, '');
}
function kolab_tags_input(element, tags, readonly)
{
var list,
tagline = $(element)[readonly ? 'addClass' : 'removeClass']('disabled').empty().show(),
source_callback = function(request, response) {
request = request.term.toUpperCase();
response($.map(rcmail.env.tags || [], function(v) {
if (request.length && v.name.toUpperCase().indexOf(request) > -1)
return v.name;
}));
};
$.each(tags && tags.length ? tags : [''], function(i,val) {
$('<input>').attr({name: 'tags[]', tabindex: '0', 'class': 'tag'})
.val(val)
.appendTo(tagline);
});
if (!tags || !tags.length) {
$('<span>').addClass('placeholder')
.text(rcmail.gettext('kolab_tags.notags'))
.appendTo(tagline)
.click(function(e) { list.trigger('click'); });
}
$('input.tag', element).tagedit({
animSpeed: 100,
allowEdit: false,
allowAdd: !readonly,
allowDelete: !readonly,
checkNewEntriesCaseSensitive: false,
autocompleteOptions: { source: source_callback, minLength: 0, noCheck: true },
texts: { removeLinkTitle: rcmail.gettext('kolab_tags.untag') }
});
list = element.find('.tagedit-list');
list.addClass('form-control'); // Elastic
if (!readonly) {
list.on('click', function() { $('.placeholder', element).hide(); });
}
// Track changes on the list to style tag elements
if (window.MutationObserver) {
var observer = new MutationObserver(kolab_tags_input_update);
observer.observe(list[0], {childList: true});
}
else {
list.on('click keyup', kolab_tags_input_update);
}
kolab_tags_input_update({target: list});
return list;
}
function kolab_tags_input_update(e)
{
var tags = {},
target = e.length && e[0].target ? e[0].target : e.target;
// first generate tags list indexed by name
$.each(rcmail.env.tags, function(i, tag) {
tags[tag.name] = tag;
});
$(target).find('li.tagedit-listelement-old:not([class*="tagbox"])').each(function() {
var text = $('span', this).text();
$(this).addClass('tagbox');
if (tags[text]) {
$(this).data('tag', tags[text].uid);
tag_set_color(this, tags[text]);
}
});
}
function kolab_tags_input_value(element)
{
var tags = [];
$(element || '.tagedit-list').find('input').each(function(i, elem) {
if (elem.value)
tags.push(elem.value);
});
return tags;
}
function tag_draggable_helper()
{
var draghelper = $('.tag-draghelper'),
tagid = $(this).data('tag'),
node = $(this).clone(),
tagbox = $('<span class="tagbox">');
if (!draghelper.length) {
draghelper = $('<div class="tag-draghelper"></div>');
}
$('span.count', node).remove();
tagbox.text(node.text()).appendTo(draghelper.html(''));
tag_set_color(tagbox, tag_find(tagid));
return draghelper[0];
}
function tag_draggable_start(event, ui)
{
// register notes list to receive drop events
if (rcmail.gui_objects.noteslist) {
$('tr', rcmail.gui_objects.noteslist).droppable({
addClasses: false,
hoverClass: 'droptarget',
accept: tag_droppable_accept,
drop: tag_draggable_dropped
});
}
// allow to drop tags onto edit form title
$('body.task-notes .content.formcontainer,#notedetailstitle.boxtitle').droppable({
addClasses: false,
accept: function() { return $(this).is('.formcontainer,.boxtitle'); },
drop: function(event, ui) {
var tag = tag_find(ui.draggable.data('tag'));
$('#tagedit-input').val(tag.name).trigger('transformToTag');
$('.tagline .placeholder', rcmail.gui_objects.noteviewtitle).hide();
}
}).addClass('tag-droppable');
}
function tag_draggable_dropped(event, ui)
{
var drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
tag = tag_find(ui.draggable.data('tag'));
rcmail.triggerEvent('kolab-tags-drop', {id: drop_id, tag: tag.name, list: $(this).parent()});
}
function tag_droppable_accept(draggable)
{
if (rcmail.busy) {
return false;
}
var tag = tag_find(draggable.data('tag')),
drop_id = $(this).attr('id').replace(/^rcmrow/, ''),
data = rcmail.triggerEvent('kolab-tags-drop-data', {id: drop_id});
// target already has this tag assigned
if (!data || (data.tags && $.inArray(tag.name, data.tags) >= 0)) {
return false;
}
return true;
}

View file

@ -23,7 +23,7 @@
class kolab_tags extends rcube_plugin
{
public $task = 'mail';
public $task = 'mail|notes';
public $rc;
public $home;

View file

@ -46,12 +46,7 @@ class kolab_tags_engine
*/
public function ui()
{
// set templates of Files UI and widgets
if ($this->rc->task != 'mail') {
return;
}
if ($this->rc->action && !in_array($this->rc->action, array('show', 'preview'))) {
if ($this->rc->action && !in_array($this->rc->action, array('show', 'preview', 'dialog-ui'))) {
return;
}
@ -62,7 +57,7 @@ class kolab_tags_engine
$this->rc->output->add_label('cancel', 'save');
$this->plugin->add_label('tags', 'add', 'edit', 'delete', 'saving',
'nameempty', 'nameexists', 'colorinvalid', 'untag', 'tagname',
'tagcolor', 'tagsearchnew', 'newtag');
'tagcolor', 'tagsearchnew', 'newtag', 'notags');
$this->rc->output->add_handlers(array(
'plugin.taglist' => array($this, 'taglist'),
@ -71,11 +66,12 @@ class kolab_tags_engine
$ui = $this->rc->output->parse('kolab_tags.ui', false, false);
$this->rc->output->add_footer($ui);
// load miniColors
// load miniColors and tagedit
jqueryui::miniColors();
jqueryui::tagedit();
// Modify search filter (and set selected tags)
if ($this->rc->action == 'show' || !$this->rc->action) {
if ($this->rc->task == 'mail' && ($this->rc->action == 'show' || !$this->rc->action)) {
$this->search_filter_mods();
}
}
@ -283,6 +279,18 @@ class kolab_tags_engine
}
}
/**
* Refresh tags list
*/
public function action_refresh()
{
$taglist = $this->backend->list_tags();
$taglist = array_map(array($this, 'parse_tag'), $taglist);
$this->rc->output->set_env('tags', $taglist);
$this->rc->output->command('plugin.kolab_tags', array('refresh' => 1));
}
/**
* Template object building tags list/cloud
*/

View file

@ -15,6 +15,7 @@ $labels['tagactions'] = 'Tag actions...';
$labels['tagadd'] = 'Tag as...';
$labels['tagremove'] = 'Remove tag...';
$labels['untag'] = 'Remove tag';
$labels['notags'] = 'No tags';
$labels['tagremoveall'] = 'Remove all tags';
$labels['add'] = 'Add';
$labels['edit'] = 'Edit';

View file

@ -28,7 +28,7 @@
$(document).ready(function(e) {
// put tags cloud under folders list
var tagcloud = $('#tagcloud').detach();
$('#folderlist-content > ul:last').after(tagcloud.show());
$('#folderlist-content,#notebooks-content').children('ul:first').after(tagcloud.show());
// add tag message menu positions to Mark menu
var menu = $('#tagmessagemenu li').detach();
@ -36,7 +36,7 @@ $(document).ready(function(e) {
// add tags management menu positions to folder actions menu
menu = $('#tagsmenu li').detach();
$('#mailboxoptions-menu ul').append(menu);
$('#mailboxoptions-menu,#notebookactions-menu').find('ul').append(menu);
// Apply tags colors in Elastic-way
rcmail.addEventListener('kolab-tags-update', function() {
@ -58,7 +58,7 @@ $(document).ready(function(e) {
// Ignore tags coloring on lists, handled above, use default method for tagboxes only
rcmail.addEventListener('kolab-tag-color', function(prop) {
if ($(prop.obj).is('li')) {
if ($(prop.obj).is('li:not(.tagedit-listelement)')) {
return false;
}
});

View file

@ -70,6 +70,7 @@ ul.toolbarmenu li span.icon.tagremoveall {
}
.tag-draghelper .tag .count,
#taglist li .count:empty,
#taglist li.inactive .count {
display: none;
}
@ -138,3 +139,86 @@ ul.toolbarmenu li span.icon.tagremoveall {
#tag-selector li.search input {
width: 120px;
}
.tagline {
color: #999;
font-weight: normal;
font-size: 0.9em;
position: relative;
cursor: text;
margin: 4px 0 0 0;
min-height: 31px;
}
.tagline.disabled {
margin-top: 0;
}
.tagline .tagedit-list {
min-height: 31px;
}
.tagline.disabled .tagedit-list {
outline: none;
padding-left: 0;
border: 0;
min-height: 31px;
background: rgba(255,255,255,0.01);
-webkit-box-shadow: none;
-moz-box-shadow: none;
-o-box-shadow: none;
box-shadow: none;
}
.tagline .placeholder {
position: absolute;
top: 6px;
left: 6px;
z-index: 2;
}
.tagline.disabled .placeholder {
left: 0;
}
.tagedit-list {
position: relative;
z-index: 1;
min-height: 32px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.tagedit-list #tagedit-input {
background: none;
}
.tag-draghelper {
z-index: 1000;
}
.tagedit span.tag-element,
.tagedit-list li.tagedit-listelement-old {
background: #ddeef5 !important;
border: 0 !important;
padding: 2px 20px 0 6px !important;
line-height: 1.5 !important;
position: relative !important;
}
.tagedit-list li.tagedit-listelement-old a.tagedit-close,
.tagedit-list li.tagedit-listelement-old a.tagedit-break,
.tagedit-list li.tagedit-listelement-old a.tagedit-delete,
.tagedit-list li.tagedit-listelement-old a.tagedit-save {
position: absolute !important;
top: 1px !important;
right: 0;
background: transparent !important;
text-indent: initial !important;
}
.tag-droppable.ui-droppable-hover {
background-color: #e8e798;
}

View file

@ -31,11 +31,25 @@
$(document).ready(function(e) {
// put tags cloud under folders list
var tagcloud = $('#tagcloud').detach();
$('#mailview-left').append(tagcloud.show());
var containers, tagcloud = $('#tagcloud').detach();
new rcube_splitter({ id:'mailtagsplitter', p1:'#mailboxcontainer', p2:'#tagcloud',
orientation:'h', relative:true, start:242, min:120, size:12, offset:4 }).init();
if (rcmail.env.task == 'mail')
containers = {
sidebar: '#mailview-left',
list: '#mailboxcontainer'
};
else if (rcmail.env.task == 'notes')
containers = {
sidebar: '#sidebar',
list: '#notebooksbox'
};
if (containers) {
$(containers.sidebar).append(tagcloud.show());
new rcube_splitter({ id: rcmail.task + 'tagsplitter', p1:containers.list, p2:'#tagcloud',
orientation:'h', relative:true, start:242, min:120, size:12, offset:4 }).init();
}
// add tag message menu positions to Mark menu
var menu = $('#tagmessagemenu li').detach();

View file

@ -42,6 +42,26 @@
margin: 0 .5rem 0 .2rem;
}
}
.count {
position: absolute;
top: 0;
right: 0;
min-width: 2em;
line-height: 1.4rem;
margin: (@listing-line-height - 1.4rem)/2;
padding: 0 .3em;
border-radius: .4em;
background: @color-list-secondary;
color: #fff;
text-align: center;
font-weight: bold;
html.touch & {
line-height: 2rem;
margin: (@listing-touch-line-height - 2rem)/2;
}
}
}
#tagsform option:before,
@ -93,14 +113,15 @@
.tagbox {
color: #fff;
background-color: @color-main;
background-color: @color-main !important;
border: 0 !important;
border-radius: .25rem;
max-width: 4em;
padding: .1rem .4rem;
margin-right: .2rem;
font-weight: bold;
a {
&:not(.tagedit-listelement) a {
color: inherit;
padding-left: .5rem;
text-decoration: none;
@ -110,3 +131,12 @@
font-size: 1.2rem;
}
}
.tag-droppable.formcontainer {
&.ui-droppable-active {
background-color: @color-black-shade-bg !important;
}
&.ui-droppable-hover {
background-color: @color-list-droptarget-background !important;
}
}