From 91e3227e6499ce859736fc996733899bc72d18c7 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Mon, 31 Mar 2014 19:03:29 +0200 Subject: [PATCH] Implement creation and deletion of notes; create icons for Larry theme --- plugins/kolab_notes/kolab_notes.php | 82 ++++++++++++++++---- plugins/kolab_notes/kolab_notes_ui.php | 2 +- plugins/kolab_notes/localization/en_US.inc | 1 + plugins/kolab_notes/notes.js | 57 +++++++++++--- plugins/kolab_notes/skins/larry/notes.css | 28 ++++--- plugins/kolab_notes/skins/larry/sprites.png | Bin 0 -> 2067 bytes 6 files changed, 133 insertions(+), 37 deletions(-) create mode 100644 plugins/kolab_notes/skins/larry/sprites.png diff --git a/plugins/kolab_notes/kolab_notes.php b/plugins/kolab_notes/kolab_notes.php index b92e71be..87a7a7e5 100644 --- a/plugins/kolab_notes/kolab_notes.php +++ b/plugins/kolab_notes/kolab_notes.php @@ -32,6 +32,7 @@ class kolab_notes extends rcube_plugin private $ui; private $lists; private $folders; + private $cache = array(); /** * Required startup method of a Roundcube plugin @@ -202,7 +203,7 @@ class kolab_notes extends rcube_plugin } /** - * + * Handler to retrieve note records for the given list and/or search query */ public function notes_fetch() { @@ -214,7 +215,7 @@ class kolab_notes extends rcube_plugin } /** - * + * Convert the given note records for delivery to the client */ protected function notes_data($records, &$tags) { @@ -234,7 +235,7 @@ class kolab_notes extends rcube_plugin } /** - * + * Read note records for the given list from the storage backend */ protected function list_notes($list_id, $search = null) { @@ -261,6 +262,9 @@ class kolab_notes extends rcube_plugin return $results; } + /** + * Handler for delivering a full note record to the client + */ public function note_record() { $data = $this->get_note(array( @@ -276,6 +280,9 @@ class kolab_notes extends rcube_plugin $this->rc->output->command('plugin.render_note', $data); } + /** + * Get the full note record identified by the given UID + Lolder identifier + */ public function get_note($note) { if (is_array($note)) { @@ -286,6 +293,12 @@ class kolab_notes extends rcube_plugin $uid = $note; } + // deliver from in-memory cache + $key = $list_id . ':' . $uid; + if ($this->cache[$key]) { + return $this->cache[$key]; + } + $this->_read_lists(); if ($list_id) { if ($folder = $this->folders[$list_id]) { @@ -306,7 +319,7 @@ class kolab_notes extends rcube_plugin } /** - * + * Helper method to encode the given note record for use in the client */ private function _client_encode(&$note) { @@ -331,6 +344,9 @@ class kolab_notes extends rcube_plugin return $note; } + /** + * Handler for client-initiated actions on a single note record + */ public function note_action() { $action = rcube_utils::get_input_value('_do', RCUBE_INPUT_POST); @@ -347,6 +363,17 @@ class kolab_notes extends rcube_plugin $refresh['tempid'] = $temp_id; } break; + + case 'delete': + $uids = explode(',', $note['uid']); + foreach ($uids as $uid) { + $note['uid'] = $uid; + if (!($success = $this->delete_note($note))) { + $refresh = $this->get_note($note); + break; + } + } + break; } // show confirmation/error message @@ -354,7 +381,7 @@ class kolab_notes extends rcube_plugin $this->rc->output->show_message('successfullysaved', 'confirmation'); } else { - $this->rc->output->show_message('kolab_notes.errorsaving', 'error'); + $this->rc->output->show_message('errorsaving', 'error'); } // unlock client @@ -368,10 +395,10 @@ class kolab_notes extends rcube_plugin /** * Update an note record with the given data * - * @param array Hash array with note properties + * @param array Hash array with note properties (id, list) * @return boolean True on success, False on error */ - private function save_note($note) + private function save_note(&$note) { $this->_read_lists(); @@ -413,12 +440,33 @@ class kolab_notes extends rcube_plugin else { $note = $object; $note['list'] = $list_id; - // TODO: cache this in memory for later read + + // cache this in memory for later read + $key = $list_id . ':' . $note['uid']; + $this->cache[$key] = $note; } return $saved; } + /** + * Remove a single note record from the backend + * + * @param array Hash array with note properties (id, list) + * @param boolean Remove record irreversible (mark as deleted otherwise) + * @return boolean True on success, False on error + */ + public function delete_note($note, $force = true) + { + $this->_read_lists(); + + $list_id = $note['list']; + if (!$list_id || !($folder = $this->folders[$list_id])) + return false; + + return $folder->delete($note['uid'], $force); + } + /** * Process the given note data (submitted by the client) before saving it @@ -431,6 +479,7 @@ class kolab_notes extends rcube_plugin // clean up HTML content $object['description'] = $this->_wash_html($note['description']); + $is_html = true; // try to be smart and convert to plain-text if no real formatting is detected if (preg_match('!
(.*)
!ims', $object['description'], $m)) { @@ -438,9 +487,16 @@ class kolab_notes extends rcube_plugin // $converter = new rcube_html2text($m[1], false, true, 0); // $object['description'] = rtrim($converter->get_text()); $object['description'] = preg_replace('!!', "\n", $m[1]); + $is_html = false; } } + // Add proper HTML header, otherwise Kontact renders it as plain text + if ($is_html) { + $object['description'] = ''."\n" . + str_replace('', '', $object['description']); + } + // copy meta data (starting with _) from old object foreach ((array)$old as $key => $val) { if (!isset($object[$key]) && $key[0] == '_') @@ -461,13 +517,13 @@ class kolab_notes extends rcube_plugin . '' . '' . $html . ''; - // clean HTML with washhtml by Frederic Motte + // clean HTML with washtml by Frederic Motte $wash_opts = array( 'show_washed' => false, 'allow_remote' => 1, 'charset' => RCUBE_CHARSET, - 'html_elements' => array('html', 'body', 'link'), - 'html_attribs' => array('rel', 'type'), + 'html_elements' => array('html', 'head', 'meta', 'body', 'link'), + 'html_attribs' => array('rel', 'type', 'name', 'http-equiv'), ); // initialize HTML washer @@ -476,7 +532,7 @@ class kolab_notes extends rcube_plugin //$washer->add_callback('form', 'rcmail_washtml_callback'); //$washer->add_callback('style', 'rcmail_washtml_callback'); - // Remove non-UTF8 characters (#1487813) + // Remove non-UTF8 characters $html = rcube_charset::clean($html); $html = $washer->wash($html); @@ -486,6 +542,6 @@ class kolab_notes extends rcube_plugin return $html; } - + } diff --git a/plugins/kolab_notes/kolab_notes_ui.php b/plugins/kolab_notes/kolab_notes_ui.php index 264288d4..f3821400 100644 --- a/plugins/kolab_notes/kolab_notes_ui.php +++ b/plugins/kolab_notes/kolab_notes_ui.php @@ -67,7 +67,7 @@ class kolab_notes_ui $settings['editor'] = array( 'lang' => $lang, - 'editor_css' => $this->plugin->url() . $this->plugin->local_skin_path() . '/editor.css', + 'editor_css' => $this->plugin->url($this->plugin->local_skin_path() . '/editor.css'), 'spellcheck' => intval($this->rc->config->get('enable_spellcheck')), 'spelldict' => intval($this->rc->config->get('spellcheck_dictionary')) ); diff --git a/plugins/kolab_notes/localization/en_US.inc b/plugins/kolab_notes/localization/en_US.inc index 131c6c60..4301d1a7 100644 --- a/plugins/kolab_notes/localization/en_US.inc +++ b/plugins/kolab_notes/localization/en_US.inc @@ -15,3 +15,4 @@ $labels['changed'] = 'Last Modified'; $labels['savingdata'] = 'Saving data...'; $labels['recordnotfound'] = 'Record not found'; $labels['entertitle'] = 'Please enter a title for this note!'; +$labels['deletenotesconfirm'] = 'Do you really want to delete the selected notes?'; diff --git a/plugins/kolab_notes/notes.js b/plugins/kolab_notes/notes.js index d2b9319d..ff3aef88 100644 --- a/plugins/kolab_notes/notes.js +++ b/plugins/kolab_notes/notes.js @@ -43,11 +43,11 @@ function rcube_kolab_notes_ui(settings) { // register button commands rcmail.register_command('createnote', function(){ edit_note(null, 'new'); }, false); - rcmail.register_command('list-create', function(){ list_edit_dialog(null); }, true); + rcmail.register_command('list-create', function(){ list_edit_dialog(null); }, false); rcmail.register_command('list-edit', function(){ list_edit_dialog(me.selected_list); }, false); rcmail.register_command('list-remove', function(){ list_remove(me.selected_list); }, false); rcmail.register_command('save', save_note, true); - rcmail.register_command('delete', delete_note, true); + rcmail.register_command('delete', delete_note, false); rcmail.register_command('search', quicksearch, true); rcmail.register_command('reset-search', reset_search, true); @@ -87,6 +87,8 @@ function rcube_kolab_notes_ui(settings) else { reset_view(); } + + rcmail.enable_command('delete', me.notebooks[me.selected_list] && me.notebooks[me.selected_list].editable && list.selection.length > 0); }) .init(); } @@ -293,7 +295,7 @@ function rcube_kolab_notes_ui(settings) tr = noteslist.rows[id].obj; note = notesdata[id]; match = note.categories && note.categories.length; - for (var i=0; match && i < tagsfilter.length; i++) { + for (var i=0; match && note && i < tagsfilter.length; i++) { if ($.inArray(tagsfilter[i], note.categories) < 0) match = false; } @@ -306,7 +308,8 @@ function rcube_kolab_notes_ui(settings) } if (me.selected_note && me.selected_note.uid == note.uid && !match) { - reset_view(); + noteslist.clear_selection(); +// reset_view(); } } } @@ -389,7 +392,8 @@ function rcube_kolab_notes_ui(settings) .on('click', function(){ $('.tagline .placeholder').hide(); }); me.selected_note = data; - rcmail.enable_command('save', 'delete', list.editable && !data.readonly); + me.selected_note.id = rcmail.html_identifier_encode(data.uid); + rcmail.enable_command('save', list.editable && !data.readonly); var html, node, editor = tinyMCE.get('notecontent'); if (editor) { @@ -495,12 +499,25 @@ function rcube_kolab_notes_ui(settings) function update_note(data) { data.id = rcmail.html_identifier_encode(data.uid); + + var row, is_new = notesdata[data.id] == undefined notesdata[data.id] = data; render_note(data); + // add list item on top + if (is_new) { + noteslist.insert_row({ + id: 'rcmrow' + data.id, + cols: [ + { className:'title', innerHTML:Q(data.title) }, + { className:'date', innerHTML:Q(data.changed || '') } + ] + }, true); + + noteslist.select(data.id); + } // update list item - var row = noteslist.rows[data.id]; - if (row) { + else if (row = noteslist.rows[data.id]) { $('.title', row.obj).html(Q(data.title)); $('.date', row.obj).html(Q(data.changed || '')); // TODO: move to top @@ -515,11 +532,10 @@ function rcube_kolab_notes_ui(settings) function reset_view() { me.selected_note = null; - noteslist.clear_selection(); $('.notetitle', rcmail.gui_objects.noteviewtitle).val(''); $('.tagline, .dates', rcmail.gui_objects.noteviewtitle).hide(); $(rcmail.gui_objects.noteseditform).hide(); - rcmail.enable_command('save', 'delete', false); + rcmail.enable_command('save', false); } /** @@ -564,12 +580,29 @@ function rcube_kolab_notes_ui(settings) function delete_note() { - if (!me.selected_note) { + if (!noteslist.selection.length) { return false; } - alert(me.selected_note.title) - reset_view(); + if (confirm(rcmail.gettext('deletenotesconfirm','kolab_notes'))) { + var rec, id, uids = []; + for (var i=0; i < noteslist.selection.length; i++) { + id = noteslist.selection[i]; + rec = notesdata[id]; + if (rec) { + noteslist.remove_row(id); + uids.push(rec.uid); + delete notesdata[id]; + } + } + + saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata'); + rcmail.http_post('action', { _data: { uid: uids.join(','), list: me.selected_list }, _do: 'delete' }, true); + + reset_view(); + update_tagcloud(); + } + } } diff --git a/plugins/kolab_notes/skins/larry/notes.css b/plugins/kolab_notes/skins/larry/notes.css index a3ba1589..747e0d19 100644 --- a/plugins/kolab_notes/skins/larry/notes.css +++ b/plugins/kolab_notes/skins/larry/notes.css @@ -10,6 +10,18 @@ * See http://creativecommons.org/licenses/by-sa/3.0/ for details. */ + +#taskbar a.button-notes span.button-inner { + background-image: url('sprites.png'); + background-position: 0 0; +} + +#taskbar a.button-notes:hover span.button-inner, +#taskbar a.button-notes.button-selected span.button-inner { + background-image: url('sprites.png'); + background-position: 0 -26px; +} + .notesview #sidebar { position: absolute; top: 42px; @@ -28,15 +40,8 @@ } .notesview #notestoolbar a.button.createnote { - -} - -.notesview #taskbar a.button-notes span.button-inner { - -} - -.notesview #taskbar a.button-notes.button-selected span.button-inner { - + background-image: url('sprites.png'); + background-position: center -54px; } .notesview #quicksearchbar { @@ -146,7 +151,7 @@ } .notesview #notedetailstitle { - height: 68px; + height: auto; } .notesview #notedetailstitle .tagedit-list, @@ -180,7 +185,8 @@ } .notesview #notedetailstitle .dates { - margin-top: 0; + margin-top: 4px; + margin-bottom: 4px; } .notesview #notedetailstitle .tagline { diff --git a/plugins/kolab_notes/skins/larry/sprites.png b/plugins/kolab_notes/skins/larry/sprites.png new file mode 100644 index 0000000000000000000000000000000000000000..cee37c3fc878c31ba62c1092ca9e1b25ffbd133e GIT binary patch literal 2067 zcmV+u2<-QXP)%~SzFo@I;jj@vW2Y<8?Qd2F|c;ZDi zwRb5IEs3$(KUz}s4w{szC9OR!*0yP-D&DQFwh)UfyX?ZU?5}s9nZDgyeC+Of>|Rad z`zD{v%-i?oGc%ug-+AAeWjT&x9)u~F2V|(JPo6wkyLa#2pU7=6Au$h4X=&-60BJb*{eB9X7pB3yN~J0(E-u~!f>(f9AS|)L zYPBv86sD%8xX#Yb8HGaOMN!a05Zqs8lai9u)z#HnoleK%ImTkKkopYKh&8jbv${Z` zs;cViOE6@u66Q?$#fuj!y1Tmzi3EIonur<$WR|mm0yxhg`517BBxZrsfq{WLN~Q9! z&*!6~K(0q77GfBpBM|!l3x9uJJc#|! z_^1SpYvR@8T#ZIko{*5Bji#~N?Vcs~$BS46;&!A@iWur|09Cw*$AELdr$D54gafSn z{aNuK_Cn(hXqgrmT9|)qY^-zj>ecTe?*Y`;u3f8#o7@gritt()pm)r~c?N^wxqy}) zkB9Mkz0COdxEX~1049NqrAwEd!teAqyz@kFN`MCa#LFO=SOcGZ3J!lIai^r$m}r*a z;o-rM=kq`qXjE`@I(YEl^{%e24?!>^iMYAB`H3S(j-2i5>w5>q+z{tr2W|q>d^ss} zIV5)Azyb4#6DPWny#hES5#wP~V`F1EDzrgc1@G_~EC7C>Qmt0|2+_#Mh%rD&d~|DT zD5P4ez8TefT~vJpQD($mw4_V<(a0k;6Rn91d8Z*MQZR6L|!=FozBn($Rs>o>TE%c%u z&LuIDNK`TmNEa1J*oXS=7!}N6?iXRDMG|WEf00DpCzc+xJ}v4@Wf|*xmFYB+NI>7` z{ELVr*pNtK+~Hc`He}{;QM#I79}h+nY&;@~!K%E6ERul6M$o^a66Y(E9c8}+P4zn#(OiYBc#!z33wah_G3 z^4tuV19c@}1@K#~^CAgv*78#hFIs^W$d#JRxR6&tc&GX5;v=S33tu&~%XVBnbGs%AUUe0}`=qtYS?4)|Teo+ti&y5p?npZ!rIi8=Ji1rSrB zc~f04ny+t?Fn_uqcmldjzD?B8fYv|8hRZSXjn97)K;GBMN8(D2EQ9XmLZ{;I?U1qCVj`S~>cRf(~+$lw@> zrlN^!Yip~wY}wLQUtb>)b#--Zn>KAKAVeEBY$&LysUhXIZQHiBVM{Ta>;{n(WqO_b zj$OiM7hD!W_$>CVda)sjPn^g~%+1Y_*l36>eK} z?uB8CN(-7n0dZDV*7Ip;X^dX4k4o%tIG8|mk&%&+NcJ&t;6Yj0t*tvj4s#W0dy43qq!Lx(;`QUy@!=;-(}PH$GCo~t-pd^LpP zX4puA-{xn~fKMwbD!Q1+X6I82{rKZ;a&q!@vc>3hI=$q2)JTHg45#TdjIU6#;b@QQ zjJm`{5}_w6&@6DqSdB_ng>IZKl*j$H3l}aNB?LJ+IUhpj8@P6hl*RTqm?wr=`B!N5 zXlTQD%qwG92>~T;X=x!6=>Vr_VqhNe?%lih;+!ELnh~O~K;wen&v>tQVs;ObQRwgQ z|1lad)dU2=Jh76Jl3pZQ5z>12l)P50Aa2DgbP2_07B$rC*RTH*^jZ}%)C--UQ;l(U zOf^nTO=YxNEpZx2Vl@`zQ&Li>#u$mP$Mk3{naM$oacsa4F9wr7s{@Xssi}!-%wG!& zqtWYa3Xl1R#gMLD zySAFwSGvoDizF`akL%X0