Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
84 KB
Referenced Files
None
Subscribers
None
diff --git a/plugins/kolab_notes/kolab_notes_ui.php b/plugins/kolab_notes/kolab_notes_ui.php
index fa2fe5a7..143bc54b 100644
--- a/plugins/kolab_notes/kolab_notes_ui.php
+++ b/plugins/kolab_notes/kolab_notes_ui.php
@@ -1,352 +1,353 @@
<?php
class kolab_notes_ui
{
private $rc;
private $plugin;
private $ready = false;
function __construct($plugin)
{
$this->plugin = $plugin;
$this->rc = $plugin->rc;
}
/**
* Calendar UI initialization and requests handlers
*/
public function init()
{
if ($this->ready) // already done
return;
// add taskbar button
$this->plugin->add_button(array(
'command' => 'notes',
'class' => 'button-notes',
'classsel' => 'button-notes button-selected',
'innerclass' => 'button-inner',
'label' => 'kolab_notes.navtitle',
), 'taskbar');
$this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/notes.css');
$this->plugin->register_action('print', array($this, 'print_template'));
$this->plugin->register_action('folder-acl', array($this, 'folder_acl'));
$this->ready = true;
}
/**
* Register handler methods for the template engine
*/
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'));
$this->plugin->register_handler('plugin.listing', array($this, 'listing'));
$this->plugin->register_handler('plugin.editform', array($this, 'editform'));
$this->plugin->register_handler('plugin.notetitle', array($this, 'notetitle'));
$this->plugin->register_handler('plugin.detailview', array($this, 'detailview'));
$this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
$this->rc->output->include_script('list.js');
$this->rc->output->include_script('treelist.js');
$this->plugin->include_script('notes.js');
$this->plugin->include_script('jquery.tagedit.js');
$this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/tagedit.css');
// load config options and user prefs relevant for the UI
$settings = array(
'sort_col' => $this->rc->config->get('kolab_notes_sort_col', 'changed'),
'print_template' => $this->rc->url('print'),
);
if ($list = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC)) {
$settings['selected_list'] = $list;
}
if ($uid = rcube_utils::get_input_value('_id', RCUBE_INPUT_GPC)) {
$settings['selected_uid'] = $uid;
}
$lang_codes = array($_SESSION['language']);
if ($pos = strpos($_SESSION['language'], '_')) {
$lang_codes[] = substr($_SESSION['language'], 0, $pos);
}
foreach ($lang_codes as $code) {
if (file_exists(INSTALL_PATH . "program/js/tinymce/langs/$code.js")) {
$lang = $code;
break;
}
}
if (empty($lang)) {
$lang = 'en';
}
$settings['editor'] = array(
'lang' => $lang,
'spellcheck' => intval($this->rc->config->get('enable_spellcheck')),
'spelldict' => intval($this->rc->config->get('spellcheck_dictionary'))
);
$this->rc->output->set_env('kolab_notes_settings', $settings);
$this->rc->output->add_label('save','cancel');
}
public function folders($attrib)
{
$attrib += array('id' => 'rcmkolabnotebooks');
if ($attrib['type'] == 'select') {
$attrib['is_escaped'] = true;
$select = new html_select($attrib);
}
$jsenv = array();
$items = '';
foreach ($this->plugin->get_lists() as $prop) {
unset($prop['user_id']);
$id = $prop['id'];
$class = '';
if (!$prop['virtual'])
$jsenv[$id] = $prop;
if ($attrib['type'] == 'select') {
if ($prop['editable']) {
$select->add($prop['name'], $prop['id']);
}
}
else {
$html_id = rcube_utils::html_identifier($id);
$title = $prop['name'] != $prop['listname'] ? html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET) : '';
if ($prop['virtual'])
$class .= ' virtual';
else if (!$prop['editable'])
$class .= ' readonly';
if ($prop['class_name'])
$class .= ' '.$prop['class_name'];
+ $attr = $prop['virtual'] ? array('tabindex' => '0') : array('href' => $this->rc->url(array('_list' => $id)));
$items .= html::tag('li', array('id' => 'rcmliknb' . $html_id, 'class' => trim($class)),
- html::span(array('class' => 'listname', 'title' => $title), $prop['listname']) .
+ html::a($attr + array('class' => 'listname', 'title' => $title), $prop['listname']) .
html::span(array('class' => 'count'), '')
);
}
}
$this->rc->output->set_env('kolab_notebooks', $jsenv);
$this->rc->output->add_gui_object('notebooks', $attrib['id']);
return $attrib['type'] == 'select' ? $select->show() : html::tag('ul', $attrib, $items, html::$common_attrib);
}
public function listing($attrib)
{
$attrib += array('id' => 'rcmkolabnoteslist');
$this->rc->output->add_gui_object('noteslist', $attrib['id']);
return html::tag('ul', $attrib, '', 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');
$this->rc->output->add_gui_object('noteseditform', $attrib['id']);
$this->rc->output->include_script('tinymce/tinymce.min.js');
- $textarea = new html_textarea(array('name' => 'content', 'id' => 'notecontent', 'cols' => 60, 'rows' => 20, 'tabindex' => 3));
+ $textarea = new html_textarea(array('name' => 'content', 'id' => 'notecontent', 'cols' => 60, 'rows' => 20, 'tabindex' => 0));
return html::tag('form', $attrib, $textarea->show(), array_merge(html::$common_attrib, array('action')));
}
public function detailview($attrib)
{
$attrib += array('id' => 'rcmkolabnotesdetailview');
$this->rc->output->add_gui_object('notesdetailview', $attrib['id']);
return html::div($attrib, '');
}
public function notetitle($attrib)
{
$attrib += array('id' => 'rcmkolabnotestitle');
$this->rc->output->add_gui_object('noteviewtitle', $attrib['id']);
- $summary = new html_inputfield(array('name' => 'summary', 'class' => 'notetitle inline-edit', 'size' => 60, 'tabindex' => 1));
+ $summary = new html_inputfield(array('name' => 'summary', 'class' => 'notetitle inline-edit', 'size' => 60, 'tabindex' => 0));
$html = $summary->show();
$html .= html::div(array('class' => 'tagline tagedit', 'style' => 'display:none'), '&nbsp;');
$html .= html::div(array('class' => 'dates', 'style' => 'display:none'),
html::label(array(), $this->plugin->gettext('created')) .
html::span('notecreated', '') .
html::label(array(), $this->plugin->gettext('changed')) .
html::span('notechanged', '')
);
return html::div($attrib, $html);
}
public function attachments_list($attrib)
{
$attrib += array('id' => 'rcmkolabnotesattachmentslist');
$this->rc->output->add_gui_object('notesattachmentslist', $attrib['id']);
return html::tag('ul', $attrib, '', html::$common_attrib);
}
/**
* Render edit for notes lists (folders)
*/
public function list_editform($action, $list, $folder)
{
if (is_object($folder)) {
$folder_name = $folder->name; // UTF7
}
else {
$folder_name = '';
}
$hidden_fields[] = array('name' => 'oldname', 'value' => $folder_name);
$storage = $this->rc->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$form = array();
if (strlen($folder_name)) {
$options = $storage->folder_info($folder_name);
$path_imap = explode($delim, $folder_name);
array_pop($path_imap); // pop off name part
$path_imap = implode($path_imap, $delim);
}
else {
$path_imap = '';
$options = array();
}
// General tab
$form['properties'] = array(
'name' => $this->rc->gettext('properties'),
'fields' => array(),
);
// folder name (default field)
$input_name = new html_inputfield(array('name' => 'name', 'id' => 'noteslist-name', 'size' => 20));
$form['properties']['fields']['name'] = array(
'label' => $this->plugin->gettext('listname'),
'value' => $input_name->show($list['editname'], array('disabled' => ($options['norename'] || $options['protected']))),
- 'id' => 'folder-name',
+ 'id' => 'noteslist-name',
);
// prevent user from moving folder
if (!empty($options) && ($options['norename'] || $options['protected'])) {
$hidden_fields[] = array('name' => 'parent', 'value' => $path_imap);
}
else {
- $select = kolab_storage::folder_selector('note', array('name' => 'parent'), $folder_name);
+ $select = kolab_storage::folder_selector('note', array('name' => 'parent', 'id' => 'parent-folder'), $folder_name);
$form['properties']['fields']['path'] = array(
'label' => $this->plugin->gettext('parentfolder'),
'value' => $select->show(strlen($folder_name) ? $path_imap : ''),
+ 'id' => 'parent-folder',
);
}
// add folder ACL tab
if ($action != 'form-new') {
$form['sharing'] = array(
'name' => Q($this->plugin->gettext('tabsharing')),
'content' => html::tag('iframe', array(
'src' => $this->rc->url(array('_action' => 'folder-acl', '_folder' => $folder_name, 'framed' => 1)),
'width' => '100%',
'height' => 280,
'border' => 0,
'style' => 'border:0'),
'')
);
}
$form_html = '';
if (is_array($hidden_fields)) {
foreach ($hidden_fields as $field) {
$hiddenfield = new html_hiddenfield($field);
$form_html .= $hiddenfield->show() . "\n";
}
}
// create form output
foreach ($form as $tab) {
if (is_array($tab['fields']) && empty($tab['content'])) {
$table = new html_table(array('cols' => 2));
foreach ($tab['fields'] as $col => $colprop) {
- $colprop['id'] = '_'.$col;
$label = !empty($colprop['label']) ? $colprop['label'] : $this->plugin->gettext($col);
$table->add('title', html::label($colprop['id'], Q($label)));
$table->add(null, $colprop['value']);
}
$content = $table->show();
}
else {
$content = $tab['content'];
}
if (!empty($content)) {
$form_html .= html::tag('fieldset', null, html::tag('legend', null, Q($tab['name'])) . $content) . "\n";
}
}
return html::tag('form', array('action' => "#", 'method' => "post", 'id' => "noteslistpropform"), $form_html);
}
/**
* Handler to render ACL form for a notes folder
*/
public function folder_acl()
{
$this->plugin->require_plugin('acl');
$this->rc->output->add_handler('folderacl', array($this, 'folder_acl_form'));
$this->rc->output->send('kolab_notes.kolabacl');
}
/**
* Handler for ACL form template object
*/
public function folder_acl_form()
{
$folder = rcube_utils::get_input_value('_folder', RCUBE_INPUT_GPC);
if (strlen($folder)) {
$storage = $this->rc->get_storage();
$options = $storage->folder_info($folder);
// get sharing UI from acl plugin
$acl = $this->rc->plugins->exec_hook('folder_form',
array('form' => array(), 'options' => $options, 'name' => $folder));
}
return $acl['form']['sharing']['content'] ?: html::div('hint', $this->plugin->gettext('aclnorights'));
}
/**
* Render the template for printing with placeholders
*/
public function print_template()
{
$this->rc->output->reset(true);
echo $this->rc->output->parse('kolab_notes.print', false, false);
exit;
}
}
diff --git a/plugins/kolab_notes/localization/en_US.inc b/plugins/kolab_notes/localization/en_US.inc
index 71a26cd4..74e65377 100644
--- a/plugins/kolab_notes/localization/en_US.inc
+++ b/plugins/kolab_notes/localization/en_US.inc
@@ -1,48 +1,50 @@
<?php
$labels = array();
$labels['navtitle'] = 'Notes';
$labels['tags'] = 'Tags';
$labels['lists'] = 'Notebooks';
$labels['notes'] = 'Notes';
$labels['create'] = 'New Note';
$labels['createnote'] = 'Create a new note';
$labels['send'] = 'Send';
$labels['sendnote'] = 'Send note by email';
$labels['newnote'] = 'New Note';
$labels['notags'] = 'No tags';
$labels['removetag'] = 'Remove tag';
$labels['created'] = 'Created';
$labels['changed'] = 'Last Modified';
$labels['title'] = 'Title';
$labels['now'] = 'Now';
$labels['sortby'] = 'Sort by';
$labels['newnotebook'] = 'Create a new notebook';
$labels['addnotebook'] = 'Add notebook';
$labels['editlist'] = 'Edit Notebook';
$labels['listname'] = 'Name';
$labels['tabsharing'] = 'Sharing';
$labels['discard'] = 'Discard';
$labels['abort'] = 'Abort';
$labels['unsavedchanges'] = 'Unsaved Changes!';
$labels['appendnote'] = 'Add a Note';
$labels['editnote'] = 'Edit Note';
$labels['savein'] = 'Save in';
$labels['savingdata'] = 'Saving data...';
$labels['recordnotfound'] = 'Record not found';
+$labels['norecordsfound'] = 'No notes found';
$labels['nochanges'] = 'No changes to be saved';
$labels['entertitle'] = 'Please enter a title for this note!';
$labels['deletenotesconfirm'] = 'Do you really want to delete the selected notes?';
$labels['deletenotebookconfirm'] = 'Do you really want to delete this notebook with all its notes? This action cannot be undone.';
$labels['discardunsavedchanges'] = 'The current note has not yet been saved. Discard the changes?';
$labels['invalidlistproperties'] = 'Invalid notebook properties! Please set a valid name.';
$labels['entertitle'] = 'Please enter a title for this note.';
$labels['aclnorights'] = 'You do not have administrator rights for this notebook.';
$labels['arialabelnoteslist'] = 'List of notes';
$labels['arialabelnotesearchform'] = 'Notes search form';
$labels['arialabelnotesquicksearchbox'] = 'Notes search input';
$labels['arialabelnotessortmenu'] = 'Notes list sorting options';
$labels['arialabelnotesoptionsmenu'] = 'Notebook actions menu';
$labels['arialabelnotebookform'] = 'Notebook properties';
+$labels['arialabelmessagereferences'] = 'Linked email messages';
diff --git a/plugins/kolab_notes/notes.js b/plugins/kolab_notes/notes.js
index c52e0b92..5f29ba46 100644
--- a/plugins/kolab_notes/notes.js
+++ b/plugins/kolab_notes/notes.js
@@ -1,1348 +1,1375 @@
/**
* Client scripts for the Kolab Notes plugin
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
function rcube_kolab_notes_ui(settings)
{
/* private vars */
var ui_loading = false;
var saving_lock;
var search_query;
var folder_drop_target;
var notebookslist;
var noteslist;
var notesdata = {};
var tagsfilter = [];
var tags = [];
var search_request;
var search_query;
var tag_draghelper;
+ var render_no_focus;
var me = this;
/* public members */
this.selected_list;
this.selected_note;
this.notebooks = rcmail.env.kolab_notebooks || {};
/**
* initialize the notes UI
*/
function init()
{
// register button commands
rcmail.register_command('createnote', function(){
warn_unsaved_changes(function(){ edit_note(null, 'new'); })
}, false);
rcmail.register_command('list-create', function(){ list_edit_dialog(null); }, true);
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('list-sort', list_set_sort, true);
rcmail.register_command('save', save_note, true);
rcmail.register_command('delete', delete_notes, false);
rcmail.register_command('search', quicksearch, true);
rcmail.register_command('reset-search', reset_search, true);
rcmail.register_command('sendnote', send_note, false);
rcmail.register_command('print', print_note, false);
// register server callbacks
rcmail.addEventListener('plugin.data_ready', data_ready);
rcmail.addEventListener('plugin.render_note', render_note);
rcmail.addEventListener('plugin.update_note', update_note);
rcmail.addEventListener('plugin.update_list', list_update);
rcmail.addEventListener('plugin.destroy_list', list_destroy);
rcmail.addEventListener('plugin.unlock_saving', function(){
if (saving_lock) {
rcmail.set_busy(false, null, saving_lock);
}
if (rcmail.gui_objects.noteseditform) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, false);
}
});
// initialize folder selectors
var li, id;
for (id in me.notebooks) {
if (me.notebooks[id].editable && (!settings.selected_list || (me.notebooks[id].active && !me.notebooks[me.selected_list].active))) {
settings.selected_list = id;
}
}
notebookslist = new rcube_treelist_widget(rcmail.gui_objects.notebooks, {
id_prefix: 'rcmliknb',
selectable: true,
check_droptarget: function(node) {
var list = me.notebooks[node.id];
return !node.virtual && list.editable && node.id != me.selected_list;
}
});
notebookslist.addEventListener('select', function(node) {
var id = node.id;
if (me.notebooks[id] && id != me.selected_list) {
warn_unsaved_changes(function(){
rcmail.enable_command('createnote', 'list-edit', 'list-remove', me.notebooks[id].editable);
fetch_notes(id); // sets me.selected_list
},
function(){
// restore previous selection
notebookslist.select(me.selected_list);
});
}
});
+ $(rcmail.gui_objects.notebooks).on('click', 'li a', function(e) {
+ var id = String($(this).closest('li').attr('id')).replace(/^rcmliknb/, '');
+ notebookslist.select(id);
+ e.preventDefault();
+ return false;
+ });
+
// register dbl-click handler to open list edit dialog
- $(rcmail.gui_objects.notebooks).on('dblclick', 'li:not(.virtual)', function(e){
- var id = String(this.id).replace(/^rcmliknb/, '');
+ $(rcmail.gui_objects.notebooks).on('dblclick', 'li:not(.virtual) a', function(e) {
+ var id = String($(this).closest('li').attr('id')).replace(/^rcmliknb/, '');
if (me.notebooks[id] && me.notebooks[id].editable) {
list_edit_dialog(id);
}
// clear text selection (from dbl-clicking)
var sel = window.getSelection ? window.getSelection() : document.selection;
if (sel && sel.removeAllRanges) {
sel.removeAllRanges();
}
else if (sel && sel.empty) {
sel.empty();
}
e.preventDefault();
return false;
});
// initialize notes list widget
if (rcmail.gui_objects.noteslist) {
noteslist = new rcube_list_widget(rcmail.gui_objects.noteslist,
{ multiselect:true, draggable:true, keyboard:true });
noteslist.addEventListener('select', function(list) {
+ render_no_focus = rcube_event._last_keyboard_event && $(list.list).has(rcube_event._last_keyboard_event.target);
var selection_changed = list.selection.length != 1 || !me.selected_note || list.selection[0] != me.selected_note.id;
selection_changed && warn_unsaved_changes(function(){
var note;
if (noteslist.selection.length == 1 && (note = notesdata[noteslist.selection[0]])) {
edit_note(note.uid, 'edit');
}
else {
reset_view();
}
},
function(){
// TODO: previous restore selection
list.select(me.selected_note.id);
});
rcmail.enable_command('delete', me.notebooks[me.selected_list] && me.notebooks[me.selected_list].editable && list.selection.length > 0);
rcmail.enable_command('sendnote', list.selection.length > 0);
rcmail.enable_command('print', list.selection.length == 1);
})
.addEventListener('dragstart', function(e) {
folder_drop_target = null;
notebookslist.drag_start();
})
.addEventListener('dragmove', function(e) {
folder_drop_target = notebookslist.intersects(rcube_event.get_mouse_pos(e), true);
})
.addEventListener('dragend', function(e) {
notebookslist.drag_end();
// move dragged notes to this folder
if (folder_drop_target) {
noteslist.draglayer.hide();
move_notes(folder_drop_target);
noteslist.clear_selection();
reset_view();
}
folder_drop_target = null;
})
.init().focus();
}
if (settings.sort_col) {
$('#notessortmenu a.by-' + settings.sort_col).addClass('selected');
}
// click-handler on tags list
- $(rcmail.gui_objects.notestagslist).on('click', function(e){
+ $(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', this).removeClass('selected');
+ $('li', rcmail.gui_objects.notestagslist).removeClass('selected').attr('aria-checked', 'false');
tagsfilter = [];
}
// add tag to filter
if (index < 0) {
- item.addClass('selected');
+ item.addClass('selected').attr('aria-checked', 'true');
tagsfilter.push(tag);
}
else if (shift) {
- item.removeClass('selected');
+ 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)
}
}
this.init = init;
/**
*
*/
function init_dialog()
{
rcmail.register_command('save', save_note, true);
rcmail.addEventListener('plugin.render_note', render_note);
rcmail.addEventListener('plugin.update_note', function(data){
data.id = rcmail.html_identifier_encode(data.uid);
notesdata[data.id] = data;
render_note(data);
});
rcmail.addEventListener('plugin.unlock_saving', function(){
if (saving_lock) {
rcmail.set_busy(false, null, saving_lock);
}
if (rcmail.gui_objects.noteseditform) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, false);
}
});
var id;
for (id in me.notebooks) {
if (me.notebooks[id].editable) {
me.selected_list = id;
break;
}
}
init_editor();
if (settings.selected_uid) {
me.selected_list = settings.selected_list;
edit_note(settings.selected_uid);
}
else {
setTimeout(function(){
me.selected_note = $.extend({
list: me.selected_list,
uid: null,
title: rcmail.gettext('newnote','kolab_notes'),
description: '',
categories: [],
created: rcmail.gettext('now', 'kolab_notes'),
changed: rcmail.gettext('now', 'kolab_notes')
}, rcmail.env.kolab_notes_template || {});
render_note(me.selected_note);
}, 100);
}
}
this.init_dialog = init_dialog;
/**
* initialize tinyMCE editor
*/
function init_editor()
{
var editor_conf = {
selector: '#notecontent',
theme: 'modern',
language: settings.editor.lang,
content_css: 'program/js/tinymce/roundcube/content.css?v1',
plugins: 'autolink charmap code link paste tabfocus searchreplace table textcolor',
toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify '
+ '| bullist numlist outdent indent blockquote | forecolor backcolor fontselect fontsizeselect '
+ '| link unlink table charmap | code searchreplace undo redo',
menubar: false,
statusbar: false,
toolbar_items_size: 'small',
extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]',
relative_urls: false,
remove_script_host: false,
convert_urls: false,
image_description: false,
paste_webkit_style: "color font-size font-family",
paste_data_images: true,
//spellchecker_rpc_url: '../../../../../?_task=utils&_action=spell_html&_remote=1',
//spellchecker_language: rcmail.env.spell_lang,
accessibility_focus: false,
+ tabfocus_elements: [':prev','btn-save-note'],
setup: function(ed) {
// make links open on shift-click
ed.on('click', function(e) {
var link = $(e.target).closest('a');
if (link.length && e.shiftKey) {
if (!bw.mz) window.open(link.get(0).href, '_blank');
return false;
}
});
}
};
// support external configuration settings e.g. from skin
if (window.rcmail_editor_settings)
$.extend(editor_conf, window.rcmail_editor_settings);
tinyMCE.init(editor_conf);
// register click handler for message links
$(rcmail.gui_objects.notesattachmentslist).on('click', 'li a.messagelink', function(){
rcmail.open_window(this.href);
return false;
});
}
/**
* Quote HTML entities
*/
function Q(str)
{
return String(str).replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
/**
* Trim whitespace off the given string
*/
function trim(str)
{
return String(str).replace(/\s+$/, '').replace(/^\s+/, '');
}
/**
*
*/
function edit_note(uid, action)
{
if (!uid) {
if (noteslist)
noteslist.clear_selection();
me.selected_note = {
list: me.selected_list,
uid: null,
title: rcmail.gettext('newnote','kolab_notes'),
description: '',
categories: [],
created: rcmail.gettext('now', 'kolab_notes'),
changed: rcmail.gettext('now', 'kolab_notes')
}
render_note(me.selected_note);
rcmail.enable_command('print', true);
}
else {
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('get', { _list:me.selected_list, _id:uid }, true);
}
}
/**
*
*/
function list_edit_dialog(id)
{
if (!rcmail.gui_containers.notebookeditform) {
return false;
}
// close show dialog first
var $dialog = rcmail.gui_containers.notebookeditform;
if ($dialog.is(':ui-dialog')) {
$dialog.dialog('close');
}
var list = me.notebooks[id] || { name:'', editable:true };
var form, name;
$dialog.html(rcmail.get_label('loading'));
$.ajax({
type: 'GET',
dataType: 'html',
url: rcmail.url('list'),
data: { _do: (list.id ? 'form-edit' : 'form-new'), _list: { id: list.id } },
success: function(data) {
$dialog.html(data);
rcmail.triggerEvent('kolab_notes_editform_load', list);
// resize and reposition dialog window
form = $('#noteslistpropform');
var win = $(window), w = win.width(), h = win.height();
$dialog.dialog('option', { height: Math.min(h-20, form.height()+130), width: Math.min(w-20, form.width()+50) })
.dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
name = $('#noteslist-name').prop('disabled', !list.editable).val(list.editname || list.name);
name.select();
}
});
// dialog buttons
var buttons = {};
buttons[rcmail.gettext('save')] = function() {
// form is not loaded
if (!form || !form.length)
return;
// do some input validation
if (!name.val() || name.val().length < 2) {
alert(rcmail.gettext('invalidlistproperties', 'kolab_notes'));
name.select();
return;
}
// post data to server
var data = form.serializeJSON();
if (list.id)
data.id = list.id;
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('list', { _do: (list.id ? 'edit' : 'new'), _list: data });
$dialog.dialog('close');
};
buttons[rcmail.gettext('cancel')] = function() {
$dialog.dialog('close');
};
// open jquery UI dialog
$dialog.dialog({
modal: true,
resizable: true,
closeOnEscape: false,
- title: rcmail.gettext((list.id ? 'editlist' : 'createlist'), 'kolab_notes'),
+ title: rcmail.gettext((list.id ? 'editlist' : 'newnotebook'), 'kolab_notes'),
open: function() {
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
},
close: function() {
$dialog.html('').dialog('destroy').hide();
},
buttons: buttons,
minWidth: 480,
width: 640,
}).show();
}
/**
* Callback from server after changing list properties
*/
function list_update(prop)
{
if (prop._reload) {
rcmail.redirect(rcmail.url('', { _list: (prop.newid || prop.id) }));
}
else if (prop.newid && prop.newid != prop.id) {
var book = $.extend({}, me.notebooks[prop.id]);
book.id = prop.newid;
book.name = prop.name;
book.listname = prop.listname;
book.editname = prop.editname || prop.name;
me.notebooks[prop.newid] = book;
delete me.notebooks[prop.id];
// update treelist item
var li = $(notebookslist.get_item(prop.id));
$('.listname', li).html(prop.listname);
notebookslist.update(prop.id, { id:book.id, html:li.html() });
// link all loaded note records to the new list id
if (me.selected_list == prop.id) {
me.selected_list = prop.newid;
for (var k in notesdata) {
if (notesdata[k].list == prop.id) {
notesdata[k].list = book.id;
}
}
notebookslist.select(prop.newid);
}
}
}
/**
*
*/
function list_remove(id)
{
var list = me.notebooks[id];
if (list && confirm(rcmail.gettext('deletenotebookconfirm', 'kolab_notes'))) {
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('list', { _do: 'delete', _list: { id: list.id } });
}
}
/**
* Callback from server on list delete command
*/
function list_destroy(prop)
{
if (!me.notebooks[prop.id]) {
return;
}
notebookslist.remove(prop.id);
delete me.notebooks[prop.id];
if (me.selected_list == prop.id) {
for (id in me.notebooks) {
if (me.notebooks[id]) {
notebookslist.select(id);
break;
}
}
}
}
/**
* Change notes list sort order
*/
function list_set_sort(col)
{
if (settings.sort_col != col) {
settings.sort_col = col;
$('#notessortmenu a').removeClass('selected').filter('.by-' + col).addClass('selected');
rcmail.save_pref({ name: 'kolab_notes_sort_col', value: col });
// re-sort table in DOM
$(noteslist.tbody).children().sortElements(function(la, lb){
var a_id = String(la.id).replace(/^rcmrow/, ''),
b_id = String(lb.id).replace(/^rcmrow/, ''),
a = notesdata[a_id],
b = notesdata[b_id];
if (!a || !b) {
return 0;
}
else if (settings.sort_col == 'title') {
return String(a.title).toLowerCase() > String(b.title).toLowerCase() ? 1 : -1;
}
else {
return b.changed_ - a.changed_;
}
});
}
}
/**
* Execute search
*/
function quicksearch()
{
var q;
if (rcmail.gui_objects.qsearchbox && (q = rcmail.gui_objects.qsearchbox.value)) {
var id = 'search-'+q;
// ignore if query didn't change
if (search_request == id)
return;
warn_unsaved_changes(function(){
search_request = id;
search_query = q;
fetch_notes();
},
function(){
reset_search();
});
}
else { // empty search input equals reset
reset_search();
}
}
/**
* Reset search and get back to normal listing
*/
function reset_search()
{
$(rcmail.gui_objects.qsearchbox).val('');
if (search_request) {
search_request = search_query = null;
fetch_notes();
}
}
/**
*
*/
function fetch_notes(id)
{
if (rcmail.busy)
return;
if (id && id != me.selected_list) {
me.selected_list = id;
}
ui_loading = rcmail.set_busy(true, 'loading');
rcmail.http_request('fetch', { _list:me.selected_list, _q:search_query }, true);
reset_view();
noteslist.clear(true);
notesdata = {};
tagsfilter = [];
update_state();
}
function filter_notes()
{
// tagsfilter
var note, tr, match;
for (var id in noteslist.rows) {
tr = noteslist.rows[id].obj;
note = notesdata[id];
match = note.categories && note.categories.length;
for (var i=0; match && note && i < tagsfilter.length; i++) {
if ($.inArray(tagsfilter[i], note.categories) < 0)
match = false;
}
if (match || !tagsfilter.length) {
$(tr).show();
}
else {
$(tr).hide();
}
if (me.selected_note && me.selected_note.uid == note.uid && !match) {
warn_unsaved_changes(function(){
me.selected_note = null;
noteslist.clear_selection();
}, function(){
tagsfilter = [];
filter_notes();
update_tagcloud();
});
}
}
}
/**
*
*/
function data_ready(data)
{
data.data.sort(function(a,b){
if (settings.sort_col == 'title') {
return String(a.title).toLowerCase() > String(b.title).toLowerCase() ? 1 : -1;
}
else {
return b.changed_ - a.changed_;
}
});
var i, id, rec;
for (i=0; data.data && i < data.data.length; i++) {
rec = data.data[i];
rec.id = rcmail.html_identifier_encode(rec.uid);
noteslist.insert_row({
id: 'rcmrow' + rec.id,
cols: [
{ className:'title', innerHTML:Q(rec.title) },
{ className:'date', innerHTML:Q(rec.changed || '') }
]
});
notesdata[rec.id] = rec;
}
render_tagslist(data.tags || [], !data.search)
rcmail.set_busy(false, 'loading', ui_loading);
// select the single result
if (data.data.length == 1) {
noteslist.select(data.data[0].id);
}
else if (settings.selected_uid) {
noteslist.select(rcmail.html_identifier_encode(settings.selected_uid));
delete settings.selected_uid;
}
else if (me.selected_note && notesdata[me.selected_note.id]) {
noteslist.select(me.selected_note.id);
}
+ else if (!data.data.length) {
+ console.log(data);
+ rcmail.display_message(rcmail.gettext('norecordsfound','kolab_notes'), 'info');
+ }
}
/**
*
*/
function render_note(data)
{
rcmail.set_busy(false, 'loading', ui_loading);
if (!data) {
rcmail.display_message(rcmail.get_label('recordnotfound', 'kolab_notes'), 'error');
return;
}
var list = me.notebooks[data.list] || me.notebooks[me.selected_list] || {};
content = $('#notecontent').val(data.description),
readonly = data.readonly || !list.editable,
attachmentslist = $(rcmail.gui_objects.notesattachmentslist).html('');
$('.notetitle', rcmail.gui_objects.noteviewtitle).val(data.title).prop('disabled', readonly);
$('.dates .notecreated', rcmail.gui_objects.noteviewtitle).html(Q(data.created || ''));
$('.dates .notechanged', rcmail.gui_objects.noteviewtitle).html(Q(data.changed || ''));
$(rcmail.gui_objects.notebooks).filter('select').val(list.id);
if (data.created || data.changed) {
$('.dates', rcmail.gui_objects.noteviewtitle).show();
}
// tag-edit line
var tagline = $('.tagline', rcmail.gui_objects.noteviewtitle).empty().show();
$.each(typeof data.categories == 'object' && data.categories.length ? data.categories : [''], function(i,val){
$('<input>')
.attr('name', 'tags[]')
- .attr('tabindex', '2')
+ .attr('tabindex', '0')
.addClass('tag')
.val(val)
.appendTo(tagline);
});
if (!data.categories || !data.categories.length) {
$('<span>').addClass('placeholder').html(rcmail.gettext('notags', 'kolab_notes')).appendTo(tagline);
}
$('.tagline input.tag', rcmail.gui_objects.noteviewtitle).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')
.addClass('message eml')
.append($('<a>')
.attr('href', link.mailurl)
.addClass('messagelink')
.html(Q(link.subject || link.message_id || link.uri))
)
.appendTo(attachmentslist);
/*
if (!readonly && !data._from_mail) {
$('<a>')
.attr('href', '#delete')
.attr('title', rcmail.gettext('delete'))
.addClass('delete')
.html(rcmail.gettext('delete'))
.appendTo(li);
}
*/
});
}
if (!readonly) {
$('.tagedit-list', rcmail.gui_objects.noteviewtitle)
.on('click', function(){ $('.tagline .placeholder').hide(); });
}
if (!data.list)
data.list = list.id;
me.selected_note = data;
me.selected_note.id = rcmail.html_identifier_encode(data.uid);
rcmail.enable_command('save', list.editable && !data.readonly);
var html = data.html || data.description;
// convert plain text to HTML and make URLs clickable
if (!data.html || !html.match(/<(html|body)/)) {
html = text2html(html);
}
var node, editor = tinyMCE.get('notecontent'), is_html = false;
if (!readonly && editor) {
$(rcmail.gui_objects.notesdetailview).hide();
$(rcmail.gui_objects.noteseditform).show();
editor.setContent(html);
node = editor.getContentAreaContainer().childNodes[0];
if (node) node.tabIndex = content.get(0).tabIndex;
- if (me.selected_note.uid)
- editor.getBody().focus();
+ if (me.selected_note.uid) {
+ if (!render_no_focus)
+ editor.getBody().focus();
+ }
else
$('.notetitle', rcmail.gui_objects.noteviewtitle).focus().select();
// read possibly re-formatted content back from editor for later comparison
me.selected_note.description = editor.getContent({ format:'html' });
is_html = true;
}
else {
$(rcmail.gui_objects.noteseditform).hide();
$(rcmail.gui_objects.notesdetailview).html(html).show();
}
+ render_no_focus = false;
+
// notify subscribers
rcmail.triggerEvent('kolab_notes_render', { data:data, readonly:readonly, html:is_html });
if (rcmail.is_framed())
parent.rcmail.triggerEvent('kolab_notes_render', { data:data, readonly:readonly, html:is_html });
// Trigger resize (needed for proper editor resizing)
$(window).resize();
}
/**
* Convert the given plain text to HTML contents to be displayed in editor
*/
function text2html(str)
{
// simple link parser (similar to rcube_string_replacer class in PHP)
var utf_domain = '[^?&@"\'/\\(\\)\\s\\r\\t\\n]+\\.([^\x00-\x2f\x3b-\x40\x5b-\x60\x7b-\x7f]{2,}|xn--[a-z0-9]{2,})',
url1 = '.:;,', url2 = 'a-z0-9%=#@+?&/_~\\[\\]-',
link_pattern = new RegExp('([hf]t+ps?://|www.)('+utf_domain+'(['+url1+']?['+url2+']+)*)?', 'ig'),
link_replace = function(matches, p1, p2) {
var url = (p1 == 'www.' ? 'http://' : '') + p1 + p2;
return '<a href="' + url + '" class="x-templink">' + p1 + p2 + '</a>';
};
return '<pre>' + Q(str).replace(link_pattern, link_replace) + '</pre>';
}
/**
* Open a new window to print the currently selected note
*/
function print_note()
{
var printwin, data;
if (me.selected_note && (printwin = rcmail.open_window(settings.print_template))) {
data = get_save_data();
$(printwin).load(function(){
printwin.document.title = data.title;
$('#notetitle', printwin.document).html(Q(data.title));
$('#notebody', printwin.document).html(data.description);
$('#notetags', printwin.document).html('<span class="tag">' + data.categories.join('</span><span class="tag">') + '</span>');
$('#notecreated', printwin.document).html(Q(me.selected_note.created));
$('#notechanged', printwin.document).html(Q(me.selected_note.changed));
printwin.print();
});
}
}
/**
* Redirect to message compose screen with UIDs of notes to be appended
*/
function send_note()
{
var uids = [];
for (var rec, i=0; i < noteslist.selection.length; i++) {
if (rec = notesdata[noteslist.selection[i]]) {
uids.push(rec.uid);
// TODO: check if rec.uid == me.selected_note.uid and unsaved changes
}
}
if (uids.length) {
rcmail.goto_url('mail/compose', { _with_notes: uids.join(','), _notes_list: me.selected_list }, true);
}
}
/**
*
*/
function render_tagslist(newtags, replace)
{
if (replace) {
tags = newtags;
}
else {
var append = [];
for (var i=0; i < newtags.length; i++) {
if ($.inArray(newtags[i], tags) < 0)
append.push(newtags[i]);
}
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>').attr('rel', tag).data('value', 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();
}
/**
* 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.categories && j < rec.categories.length; j++) {
t = rec.categories[j];
if (typeof counts[t] == 'undefined')
counts[t] = 0;
counts[t]++;
}
});
}
$(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');
+ elem.addClass('selected').attr('aria-checked', 'true');
}
else {
- elem.removeClass('selected');
+ elem.removeClass('selected').attr('aria-checked', 'false');
}
});
}
/**
* Callback from server after saving a note record
*/
function update_note(data)
{
data.id = rcmail.html_identifier_encode(data.uid);
var row, is_new = notesdata[data.id] == undefined
notesdata[data.id] = data;
if (is_new || me.selected_note && data.id == me.selected_note.id) {
render_note(data);
render_tagslist(data.categories || []);
}
else if (data.categories) {
render_tagslist(data.categories);
}
// 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
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
}
}
/**
*
*/
function reset_view()
{
me.selected_note = null;
$('.notetitle', rcmail.gui_objects.noteviewtitle).val('');
$('.tagline, .dates', rcmail.gui_objects.noteviewtitle).hide();
$(rcmail.gui_objects.noteseditform).hide();
$(rcmail.gui_objects.notesdetailview).hide();
$(rcmail.gui_objects.notesattachmentslist).html('');
rcmail.enable_command('save', false);
}
/**
* Collect data from the edit form and submit it to the server
*/
function save_note()
{
if (!me.selected_note) {
return false;
}
var savedata = get_save_data();
// copy links as they're yet immutable
if (me.selected_note.links)
savedata.links = me.selected_note.links;
// add reference to old list if changed
if (me.selected_note.list && savedata.list != me.selected_note.list) {
savedata._fromlist = me.selected_note.list;
}
// do some input validation
if (savedata.title == '') {
alert(rcmail.gettext('entertitle', 'kolab_notes'));
$('.notetitle', rcmail.gui_objects.noteviewtitle).focus();
return false;
}
if (check_change_state(savedata)) {
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: savedata, _do: savedata.uid?'edit':'new' }, true);
}
else {
rcmail.display_message(rcmail.get_label('nochanges', 'kolab_notes'), 'info');
}
}
/**
* Collect updated note properties from edit form for saving
*/
function get_save_data()
{
var editor = tinyMCE.get('notecontent'),
listselect = $('option:selected', rcmail.gui_objects.notebooks);
var savedata = {
title: trim($('.notetitle', rcmail.gui_objects.noteviewtitle).val()),
description: editor ? editor.getContent({ format:'html' }) : $('#notecontent').val(),
list: listselect.length ? listselect.val() : me.selected_note.list || me.selected_list,
uid: me.selected_note.uid,
categories: []
};
// collect tags
$('.tagedit-list input[type="hidden"]', rcmail.gui_objects.noteviewtitle).each(function(i, elem){
if (elem.value)
savedata.categories.push(elem.value);
});
// including the "pending" one in the text box
var newtag = $('#tagedit-input').val();
if (newtag != '') {
savedata.categories.push(newtag);
}
return savedata;
}
/**
* Check if the currently edited note record was changed
*/
function check_change_state(data)
{
if (!me.selected_note || me.selected_note.readonly || !me.notebooks[me.selected_note.list || me.selected_list].editable) {
return false;
}
var savedata = data || get_save_data();
return savedata.title != me.selected_note.title
|| savedata.description != me.selected_note.description
|| savedata.categories.join(',') != (me.selected_note.categories || []).join(',')
|| savedata.list != me.selected_note.list;
}
/**
* Check for unsaved changes and warn the user
*/
function warn_unsaved_changes(ok, nok)
{
if (typeof ok != 'function')
ok = function(){ };
if (typeof nok != 'function')
nok = function(){ };
if (check_change_state()) {
var dialog, buttons = [];
buttons.push({
text: rcmail.gettext('discard', 'kolab_notes'),
click: function() {
dialog.dialog('close');
ok();
}
});
buttons.push({
text: rcmail.gettext('save'),
click: function() {
save_note();
dialog.dialog('close');
ok();
}
});
buttons.push({
text: rcmail.gettext('abort', 'kolab_notes'),
click: function() {
dialog.dialog('close');
nok();
}
});
var options = {
width: 460,
resizable: false,
closeOnEscape: false,
dialogClass: 'warning',
open: function(event, ui) {
$(this).parent().find('.ui-dialog-titlebar-close').hide();
- $(this).parent().find('.ui-button').first().addClass('mainaction').focus();
+ setTimeout(function(){
+ dialog.parent().find('.ui-button:visible').first().addClass('mainaction').focus();
+ }, 10);
}
};
// open jquery UI dialog
dialog = rcmail.show_popup_dialog(
rcmail.gettext('discardunsavedchanges', 'kolab_notes'),
rcmail.gettext('unsavedchanges', 'kolab_notes'),
buttons,
options
);
return false;
}
if (typeof ok == 'function') {
ok();
}
return true;
}
/**
*
*/
function delete_notes()
{
if (!noteslist.selection.length) {
return false;
}
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();
noteslist.clear_selection();
}
}
/**
*
*/
function move_notes(list_id)
{
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];
}
}
if (uids.length) {
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
rcmail.http_post('action', { _data: { uid: uids.join(','), list: me.selected_list, to: list_id }, _do: 'move' }, true);
}
}
/**
* update browser location to remember current view
*/
function update_state()
{
var query = { _list: me.selected_list }
if (settings.selected_uid) {
query._id = settings.selected_uid;
}
if (window.history.replaceState) {
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');
},
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.categories && $.inArray(tag, drop_rec.categories) >= 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.categories)
savedata.categories = [];
savedata.categories.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);
}
}
}
// extend jQuery
// from http://james.padolsey.com/javascript/sorting-elements-with-jquery/
jQuery.fn.sortElements = (function(){
var sort = [].sort;
return function(comparator, getSortable) {
getSortable = getSortable || function(){ return this };
var last = null;
return sort.call(this, comparator).each(function(i){
// at this point the array is sorted, so we can just detach each one from wherever it is, and add it after the last
var node = $(getSortable.call(this));
var parent = node.parent();
if (last) last.after(node);
else parent.prepend(node);
last = node;
});
};
})();
/* notes plugin UI initialization */
var kolabnotes;
window.rcmail && rcmail.addEventListener('init', function(evt) {
kolabnotes = new rcube_kolab_notes_ui(rcmail.env.kolab_notes_settings);
if (rcmail.env.action == 'dialog-ui')
kolabnotes.init_dialog();
else
kolabnotes.init();
});
diff --git a/plugins/kolab_notes/skins/larry/notes.css b/plugins/kolab_notes/skins/larry/notes.css
index f85890ed..d57c2f83 100644
--- a/plugins/kolab_notes/skins/larry/notes.css
+++ b/plugins/kolab_notes/skins/larry/notes.css
@@ -1,420 +1,424 @@
/**
* Kolab Notes plugin styles for skin "Larry"
*
* Copyright (C) 2014, Kolab Systems AG <contact@kolabsys.com>
* Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
*
* The contents are subject to the Creative Commons Attribution-ShareAlike
* License. It is allowed to copy, distribute, transmit and to adapt the work
* by keeping credits to the original autors in the README file.
* 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: 0;
left: 0;
bottom: 0;
width: 240px;
}
.notesview #notestoolbar {
position: absolute;
top: -6px;
left: 0;
width: 100%;
height: 40px;
white-space: nowrap;
}
.notesview #notestoolbar a.button.createnote {
background-image: url('sprites.png');
background-position: center -54px;
}
.notesview #notestoolbar a.button.sendnote {
background-position: left -650px;
}
.notesview #quicksearchbar {
top: 2px;
}
.notesview #searchmenulink {
width: 15px;
}
.notesview #mainview-right {
position: absolute;
bottom: 0;
right: 0;
top: 0;
left: 252px;
}
.notesview #tagsbox {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 240px;
}
.notesview #notebooksbox {
position: absolute;
top: 252px;
left: 0;
width: 100%;
bottom: 0px;
}
.notesview #noteslistbox {
position: absolute;
top: 0;
left: 0;
width: 240px;
bottom: 0px;
}
+.notesview #kolabnoteslist li {
+ border-left: 2px solid transparent;
+}
+
+.notesview #kolabnoteslist.focus li.focused {
+ border-left: 2px solid #739da8;
+}
+
.notesview #kolabnoteslist .title {
display: block;
- padding: 4px 8px;
+ padding: 4px 6px;
overflow: hidden;
text-overflow: ellipsis;
+ outline: none;
}
.notesview #kolabnoteslist .date {
display: block;
- padding: 0px 8px 4px 8px;
+ padding: 0px 6px 4px 6px;
color: #777;
font-weight: normal;
}
.notesview .boxpagenav a.icon.sortoptions {
background: url(sprites.png) center -93px no-repeat;
}
.notesview .toolbarmenu.iconized .selected span.icon {
background: url(sprites.png) -4px -110px no-repeat;
}
.notesview #notedetailsbox {
position: absolute;
top: 0;
left: 252px;
right: 0;
bottom: 0px;
}
.notesdialog #notedetailsbox {
left: 0;
}
.notesview #notedetailsbox .formbuttons {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 8px 12px;
background: #f9f9f9;
}
.notesview #noteform,
.notesview #notedetails {
display: none;
position: absolute;
top: 82px;
left: 0;
bottom: 41px;
width: 100%;
}
.notesview #notedetails {
padding: 8px;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
border-bottom: 1px solid #dfdfdf;
}
.notesdialog #noteform,
.notesdialog #notedetails {
bottom: 30px;
}
.notesview #notedetails pre {
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
margin: 0;
}
.notesview #notecontent {
position: relative;
width: 100%;
height: 100%;
border: 0;
border-radius: 0;
padding: 8px 0 8px 8px;
resize: none;
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
font-size: 12px;
outline: none;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
-webkit-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
-moz-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
-o-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.2);
}
.notesview #notecontent:active,
.notesview #notecontent:focus {
-webkit-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
-moz-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
-o-box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
box-shadow: inset 0 0 3px 2px rgba(71,135,177, 0.9);
}
.notesview .mce-panel {
border: 0;
}
.notesview #notedetailstitle {
height: auto;
}
.notesview #notedetailstitle .tagedit-list,
.notesview #notedetailstitle input.inline-edit,
.notesview #notedetailstitle input.inline-edit:focus {
outline: none;
padding: 0;
margin: 0;
border: 0;
background: rgba(255,255,255,0.01);
-webkit-box-shadow: none;
-moz-box-shadow: none;
-o-box-shadow: none;
box-shadow: none;
}
.notesview #notedetailstitle input.notetitle,
.notesview #notedetailstitle input.notetitle:focus {
width: 100%;
font-size: 14px;
font-weight: bold;
color: #777;
}
.notesview #notedetailstitle .dates,
.notesview #notedetailstitle .tagline,
.notesdialog .notebookselect label {
color: #999;
font-weight: normal;
font-size: 0.9em;
margin-top: 6px;
}
.notesview #notedetailstitle .dates,
.notesview #notedetailstitle .notebookselect {
margin-top: 4px;
margin-bottom: 4px;
}
.notesview #notedetailstitle .tagline {
position: relative;
cursor: text;
+ margin: 6px -2px -2px -2px;
}
.notesview #notedetailstitle .tagline .placeholder {
position: absolute;
top: 4px;
left: 0;
z-index: 1;
}
.notesview #notedetailstitle .tagedit-list {
position: relative;
z-index: 2;
+ padding: 2px;
}
.notesview #notedetailstitle #tagedit-input {
background: none;
}
.notesview .tag-draghelper {
z-index: 1000;
}
.notesview #notedetailstitle .notecreated,
.notesview #notedetailstitle .notechanged {
display: inline-block;
padding-left: 0.4em;
padding-right: 2em;
color: #777;
}
.notesview #notereferences {
position: absolute;
left: 0;
right: 0;
bottom: 41px;
height: 27px;
background: #fff;
padding-left: 10px;
}
.notesdialog #notereferences {
bottom: 0;
}
.notesview #notebooks li {
margin: 0;
height: 20px;
padding: 6px 8px 2px 6px;
display: block;
position: relative;
white-space: nowrap;
}
.notesview #notebooks li.virtual {
height: 12px;
}
-.notesview #notebooks li span.listname {
+.notesview #notebooks li .listname {
display: block;
position: absolute;
- top: 7px;
- left: 9px;
+ top: 1px;
+ left: 2px;
right: 6px;
+ height: 19px;
cursor: default;
- padding-bottom: 2px;
- padding-right: 26px;
+ padding: 4px 26px 2px 6px;
color: #004458;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
-.notesview #notebooks li.virtual span.listname {
+.notesview #notebooks li.virtual .listname {
color: #aaa;
- top: 3px;
+ top: 0;
+ padding: 1px 8px;
}
.notesview #notebooks li.readonly,
.notesview #notebooks li.shared,
.notesview #notebooks li.other {
background-image: url('folder_icons.png');
background-position: right -1000px;
background-repeat: no-repeat;
}
.notesview #notebooks li.readonly {
background-position: 98% -21px;
}
.notesview #notebooks li.other {
background-position: 98% -52px;
}
.notesview #notebooks li.other.readonly {
background-position: 98% -77px;
}
.notesview #notebooks li.shared {
background-position: 98% -103px;
}
.notesview #notebooks li.shared.readonly {
background-position: 98% -130px;
}
-.notesview #notebooks li.other.readonly span.listname,
-.notesview #notebooks li.shared.readonly span.listname {
+.notesview #notebooks li.other.readonly .listname,
+.notesview #notebooks li.shared.readonly .listname {
padding-right: 36px;
}
-.notesview #notebooks li.selected > a {
- background-color: transparent;
-}
-
.notesview .uidialog .tabbed {
margin-top: -12px;
}
-.notesview .uidialog .propform fieldset.tab {
- display: block;
- background: #efefef;
- margin-top: 0.5em;
- padding: 0.5em 1em;
+.notesview .uidialog .propform fieldset.ui-tabs-panel {
min-height: 290px;
}
.notesview .uidialog .propform #noteslist-name {
width: 20em;
}
.notesdialog #notesdialogheader {
height: auto;
}
#kolabnotesinlinegui {
border: 0;
padding: 0;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.notesview .attachmentslist li.message.eml {
display: inline-block;
background-image: url(sprites.png);
background-position: -6px -128px;
margin-right: 1em;
}
.notesview .attachmentslist li.message a.messagelink {
padding-left: 24px;
}
ul.toolbarmenu li .appendnote span.icon {
background-image: url(sprites.png);
background-position: -4px -170px;
}
div.kolabmessagenotes {
margin: 8px 8px;
padding: 4px 8px;
border: 1px solid #dfdfdf;
background: #fafafa;
border-radius: 4px;
}
div.kolabmessagenotes a.kolabnotesref {
display: inline-block;
color: #333;
font-weight: bold;
padding: 3px 15px 2px 22px;
text-shadow: 0px 1px 1px #fff;
text-decoration: none;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 12em;
background: url(sprites.png) -6px -151px no-repeat;
}
\ No newline at end of file
diff --git a/plugins/kolab_notes/skins/larry/templates/notes.html b/plugins/kolab_notes/skins/larry/templates/notes.html
index 2904cf0b..1647da33 100644
--- a/plugins/kolab_notes/skins/larry/templates/notes.html
+++ b/plugins/kolab_notes/skins/larry/templates/notes.html
@@ -1,159 +1,160 @@
<roundcube:object name="doctype" value="html5" />
<html>
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
</head>
<body class="notesview noscroll">
<roundcube:include file="/includes/header.html" />
<div id="mainscreen">
<h1 class="voice"><roundcube:label name="kolab_notes.navtitle" /></h1>
<div id="notestoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
<roundcube:button command="createnote" type="link" class="button createnote disabled" classAct="button createnote" classSel="button createnote pressed" label="kolab_notes.create" title="kolab_notes.createnote" />
<roundcube:button command="print" type="link" class="button print disabled" classAct="button print" classSel="button print pressed" label="print" title="print" />
<roundcube:button command="sendnote" type="link" class="button sendnote disabled" classAct="button sendnote" classSel="button sendnote pressed" label="kolab_notes.send" title="kolab_notes.sendnote" />
<roundcube:container name="toolbar" id="notestoolbar" />
</div>
<div id="quicksearchbar" role="search" aria-labelledby="aria-label-searchform">
<h2 id="aria-label-searchform" class="voice"><roundcube:label name="kolab_notes.arialabelnotesearchform" /></h2>
<label for="quicksearchbox" class="voice"><roundcube:label name="kolab_notes.arialabelnotesquicksearchbox" /></label>
<a id="searchmenulink" class="iconbutton searchoptions" > </a>
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
</div>
<div id="mainscreencontent">
<div id="sidebar">
<div id="tagsbox" class="uibox listbox">
- <h2 class="boxtitle"><roundcube:label name="kolab_notes.tags" id="taglist" /></h2>
+ <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" />
+ <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">
- <h2 class="boxtitle"><roundcube:label name="kolab_notes.lists" /></h2>
+ <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" /></h2>
<div class="scroller withfooter">
- <roundcube:object name="plugin.notebooks" id="notebooks" class="listing" />
+ <roundcube:object name="plugin.notebooks" id="notebooks" class="listing treelist" />
</div>
<div class="boxfooter">
- <roundcube:button command="list-create" type="link" title="kolab_notes.newnotebook" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="kolab_notes.addnotebook" /><roundcube:button name="notesoptionslink" id="notesoptionsmenulink" type="link" title="kolab_notes.listactions" class="listbutton groupactions" onclick="return UI.toggle_popup('notesoptionsmenu', event)" innerClass="inner" content="&#9881;" aria-haspopup="true" aria-expanded="false" aria-owns="notesoptionsmenu-menu" />
+ <roundcube:button command="list-create" type="link" title="kolab_notes.newnotebook" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" label="kolab_notes.addnotebook" /><roundcube:button name="notesoptionslink" id="notesoptionsmenulink" type="link" title="kolab_notes.listactions" class="listbutton groupactions" onclick="return UI.toggle_popup('notesoptionsmenu', event)" innerClass="inner" label="kolab_notes.listactions" aria-haspopup="true" aria-expanded="false" aria-owns="notesoptionsmenu-menu" />
</div>
</div>
+
+ <div id="notesoptionsmenu" class="popupmenu" aria-hidden="true">
+ <h3 id="aria-label-optionsmenu" class="voice"><roundcube:label name="kolab_notes.arialabelnotesoptionsmenu" /></h3>
+ <ul class="toolbarmenu" id="notesoptionsmenu-menu" role="menu" aria-labelledby="aria-label-optionsmenu">
+ <li role="menuitem"><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
+ <li role="menuitem"><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
+ <li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
+ </ul>
+ </div>
</div>
<div id="mainview-right">
<div id="noteslistbox" class="uibox listbox">
<h3 id="aria-label-noteslist" class="boxtitle"><roundcube:label name="kolab_notes.notes" /></h3>
<div class="scroller withfooter" aria-labelledby="aria-label-noteslist">
<roundcube:object name="plugin.listing" id="kolabnoteslist" class="listing" summary="kolab_notes.arialabelnoteslist" />
</div>
<div class="boxfooter">
<roundcube:button command="delete" type="link" title="delete" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" label="delete" />
- <roundcube:object name="plugin.recordsCountDisplay" class="countdisplay" label="fromtoshort" />
+ <roundcube:object name="plugin.recordsCountDisplay" class="countdisplay" label="fromtoshort" aria-live="polite" aria-relevant="text" />
</div>
<div class="boxpagenav">
<roundcube:button name="notessortmenulink" id="notessortmenulink" type="link" title="kolab_notes.sortby" class="icon sortoptions" onclick="return UI.toggle_popup('notessortmenu', event)" innerClass="inner" content="v" aria-haspopup="true" aria-expanded="false" aria-owns="notessortmenu-menu" />
</div>
</div>
- <div id="notedetailsbox" class="uibox contentbox" role="complementary" aria-labelledby="aria-label-noteform">
+ <div id="notedetailsbox" class="uibox contentbox" role="main" aria-labelledby="aria-label-noteform">
<h3 id="aria-label-noteform" class="voice"><roundcube:label name="kolab_notes.arialabelnoteform" /></h3>
<roundcube:object name="plugin.notetitle" id="notedetailstitle" class="boxtitle" />
<roundcube:object name="plugin.editform" id="noteform" />
<roundcube:object name="plugin.detailview" id="notedetails" class="scroller" />
<div id="notereferences">
- <roundcube:object name="plugin.attachments_list" id="attachment-list" class="attachmentslist" />
+ <h3 id="aria-label-messagereferences" class="voice"><roundcube:label name="kolab_notes.arialabelmessagereferences" /></h3>
+ <roundcube:object name="plugin.attachments_list" id="attachment-list" class="attachmentslist" role="region" aria-labelledby="aria-label-messagereferences" />
</div>
<div class="footerleft formbuttons">
- <roundcube:button command="save" type="input" class="button mainaction" label="save" />
+ <roundcube:button command="save" type="input" class="button mainaction" label="save" id="btn-save-note" />
</div>
</div>
</div>
</div>
</div>
<roundcube:object name="message" id="messagestack" />
-<div id="notesoptionsmenu" class="popupmenu" aria-hidden="true">
- <h3 id="aria-label-optionsmenu" class="voice"><roundcube:label name="kolab_notes.arialabelnotesoptionsmenu" /></h3>
- <ul class="toolbarmenu" id="notesoptionsmenu-menu" role="menu" aria-labelledby="aria-label-optionsmenu">
- <li role="menuitem"><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
- <li role="menuitem"><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
- <li role="menuitem"><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
- </ul>
-</div>
-
<div id="notessortmenu" class="popupmenu" aria-hidden="true">
<h3 id="aria-label-sortmenu" class="voice"><roundcube:label name="kolab_notes.arialabelnotessortmenu" /></h3>
<ul class="toolbarmenu iconized" id="notessortmenu-menu" role="menu" aria-labelledby="aria-label-sortmenu">
<li role="menuitem"><roundcube:button command="list-sort" prop="changed" type="link" label="kolab_notes.changed" class="icon active by-changed" innerclass="icon" /></li>
<li role="menuitem"><roundcube:button command="list-sort" prop="title" type="link" label="kolab_notes.title" class="icon active by-title" innerclass="icon" /></li>
</ul>
</div>
<div id="notebookeditform" class="uidialog" aria-labelledby="aria-label-editform" aria-hidden="true" role="dialog">
<h3 id="aria-label-editform" class="voice"><roundcube:label name="kolab_notes.arialabelnotebookform" /></h3>
<roundcube:container name="notebookeditform" id="notebookeditform" />
<roundcube:label name="loading" />
</div>
<script type="text/javascript">
// UI startup
var UI = new rcube_mail_ui();
$(document).ready(function(e){
UI.init();
rcmail.addEventListener('kolab_notes_editform_load', function(e){
UI.init_tabs($('#notebookeditform > form').addClass('propform tabbed'));
})
new rcube_splitter({ id:'notesviewsplitter', p1:'#sidebar', p2:'#mainview-right',
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()
{
var form = $('#noteform, #notedetails'),
content = $('#notecontent'),
header = $('#notedetailstitle'),
footer = $('#notereferences'),
w, h;
if ($('#attachment-list li').length)
footer.show();
else
footer.hide();
form.css('top', header.outerHeight()+'px');
form.css('bottom', ((footer.is(':visible') ? footer.outerHeight()+4 : 0) + 41) + 'px');
w = form.outerWidth();
h = form.outerHeight();
content.width(w).height(h);
$('#noteform > div.mce-tinymce').width(w);
$('#notecontent_ifr').width(w).height(h - 4 - $('div.mce-toolbar').height());
}
$(window).resize(function(e){
layout_view();
});
});
</script>
</body>
</html>

File Metadata

Mime Type
text/x-diff
Expires
Apr 4 2026, 10:15 AM (4 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18747993
Default Alt Text
(84 KB)

Event Timeline