Initial import of the kolab_notes plugin
This commit is contained in:
parent
6a0a3cb849
commit
e94bdf64a5
7 changed files with 1177 additions and 0 deletions
357
plugins/kolab_notes/kolab_notes.php
Normal file
357
plugins/kolab_notes/kolab_notes.php
Normal file
|
@ -0,0 +1,357 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Kolab notes module
|
||||
*
|
||||
* Adds simple notes management features to the web client
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
class kolab_notes extends rcube_plugin
|
||||
{
|
||||
public $task = '?(?!login|logout).*';
|
||||
public $rc;
|
||||
|
||||
private $ui;
|
||||
private $lists;
|
||||
private $folders;
|
||||
|
||||
/**
|
||||
* Required startup method of a Roundcube plugin
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->require_plugin('libkolab');
|
||||
|
||||
$this->rc = rcube::get_instance();
|
||||
|
||||
$this->register_task('notes');
|
||||
|
||||
// load plugin configuration
|
||||
$this->load_config();
|
||||
|
||||
// proceed initialization in startup hook
|
||||
$this->add_hook('startup', array($this, 'startup'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Startup hook
|
||||
*/
|
||||
public function startup($args)
|
||||
{
|
||||
// the notes module can be enabled/disabled by the kolab_auth plugin
|
||||
if ($this->rc->config->get('notes_disabled', false) || !$this->rc->config->get('notes_enabled', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// load localizations
|
||||
$this->add_texts('localization/', $args['task'] == 'notes' && !$args['action']);
|
||||
|
||||
if ($args['task'] == 'notes') {
|
||||
// register task actions
|
||||
$this->register_action('index', array($this, 'notes_view'));
|
||||
$this->register_action('fetch', array($this, 'notes_fetch'));
|
||||
$this->register_action('get', array($this, 'note_record'));
|
||||
$this->register_action('action', array($this, 'note_action'));
|
||||
}
|
||||
|
||||
if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
|
||||
require_once($this->home . '/kolab_notes_ui.php');
|
||||
$this->ui = new kolab_notes_ui($this);
|
||||
$this->ui->init();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Read available calendars for the current user and store them internally
|
||||
*/
|
||||
private function _read_lists($force = false)
|
||||
{
|
||||
// already read sources
|
||||
if (isset($this->lists) && !$force)
|
||||
return $this->lists;
|
||||
|
||||
// get all folders that have type "task"
|
||||
$folders = kolab_storage::sort_folders(kolab_storage::get_folders('note'));
|
||||
$this->lists = $this->folders = array();
|
||||
|
||||
// find default folder
|
||||
$default_index = 0;
|
||||
foreach ($folders as $i => $folder) {
|
||||
if ($folder->default)
|
||||
$default_index = $i;
|
||||
}
|
||||
|
||||
// put default folder on top of the list
|
||||
if ($default_index > 0) {
|
||||
$default_folder = $folders[$default_index];
|
||||
unset($folders[$default_index]);
|
||||
array_unshift($folders, $default_folder);
|
||||
}
|
||||
|
||||
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
|
||||
$listnames = array();
|
||||
|
||||
// include virtual folders for a full folder tree
|
||||
if (!$this->rc->output->ajax_call && in_array($this->rc->action, array('index','')))
|
||||
$folders = kolab_storage::folder_hierarchy($folders);
|
||||
|
||||
foreach ($folders as $folder) {
|
||||
$utf7name = $folder->name;
|
||||
|
||||
$path_imap = explode($delim, $utf7name);
|
||||
$editname = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP'); // pop off raw name part
|
||||
$path_imap = join($delim, $path_imap);
|
||||
|
||||
$fullname = $folder->get_name();
|
||||
$listname = kolab_storage::folder_displayname($fullname, $listnames);
|
||||
|
||||
// special handling for virtual folders
|
||||
if ($folder->virtual) {
|
||||
$list_id = kolab_storage::folder_id($utf7name);
|
||||
$this->lists[$list_id] = array(
|
||||
'id' => $list_id,
|
||||
'name' => $fullname,
|
||||
'listname' => $listname,
|
||||
'virtual' => true,
|
||||
'editable' => false,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($folder->get_namespace() == 'personal') {
|
||||
$norename = false;
|
||||
$readonly = false;
|
||||
$alarms = true;
|
||||
}
|
||||
else {
|
||||
$alarms = false;
|
||||
$readonly = true;
|
||||
if (($rights = $folder->get_myrights()) && !PEAR::isError($rights)) {
|
||||
if (strpos($rights, 'i') !== false)
|
||||
$readonly = false;
|
||||
}
|
||||
$info = $folder->get_folder_info();
|
||||
$norename = $readonly || $info['norename'] || $info['protected'];
|
||||
}
|
||||
|
||||
$list_id = kolab_storage::folder_id($utf7name);
|
||||
$item = array(
|
||||
'id' => $list_id,
|
||||
'name' => $fullname,
|
||||
'listname' => $listname,
|
||||
'editname' => $editname,
|
||||
'editable' => !$readionly,
|
||||
'norename' => $norename,
|
||||
'parentfolder' => $path_imap,
|
||||
'default' => $folder->default,
|
||||
'class_name' => trim($folder->get_namespace() . ($folder->default ? ' default' : '')),
|
||||
);
|
||||
$this->lists[$item['id']] = $item;
|
||||
$this->folders[$item['id']] = $folder;
|
||||
$this->folders[$folder->name] = $folder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of available folders from this source
|
||||
*/
|
||||
public function get_lists()
|
||||
{
|
||||
$this->_read_lists();
|
||||
|
||||
// attempt to create a default folder for this user
|
||||
if (empty($this->lists)) {
|
||||
#if ($this->create_list(array('name' => 'Tasks', 'color' => '0000CC', 'default' => true)))
|
||||
# $this->_read_lists(true);
|
||||
}
|
||||
|
||||
return $this->lists;
|
||||
}
|
||||
|
||||
|
||||
/******* UI functions ********/
|
||||
|
||||
/**
|
||||
* Render main view of the tasklist task
|
||||
*/
|
||||
public function notes_view()
|
||||
{
|
||||
$this->ui->init();
|
||||
$this->ui->init_templates();
|
||||
$this->rc->output->set_pagetitle($this->gettext('navtitle'));
|
||||
$this->rc->output->send('kolab_notes.notes');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function notes_fetch()
|
||||
{
|
||||
$search = rcube_utils::get_input_value('_q', RCUBE_INPUT_GPC);
|
||||
$list = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC);
|
||||
|
||||
$data = $this->notes_data($this->list_notes($list, $search), $tags);
|
||||
$this->rc->output->command('plugin.data_ready', array('list' => $list, 'search' => $search, 'data' => $data, 'tags' => array_values(array_unique($tags))));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function notes_data($records, &$tags)
|
||||
{
|
||||
$tags = array();
|
||||
|
||||
foreach ($records as $i => $rec) {
|
||||
$this->_client_encode($records[$i]);
|
||||
unset($records[$i]['description']);
|
||||
|
||||
foreach ((array)$reg['categories'] as $tag) {
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$tags = array_unique($tags);
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function list_notes($list_id, $search = null)
|
||||
{
|
||||
$results = array();
|
||||
|
||||
// query Kolab storage
|
||||
$query = array();
|
||||
|
||||
// full text search (only works with cache enabled)
|
||||
if (strlen($search)) {
|
||||
foreach (rcube_utils::normalize_string(mb_strtolower($search), true) as $word) {
|
||||
$query[] = array('words', '~', $word);
|
||||
}
|
||||
}
|
||||
|
||||
$this->_read_lists();
|
||||
if ($folder = $this->folders[$list_id]) {
|
||||
foreach ($folder->select($query) as $record) {
|
||||
$record['list'] = $list_id;
|
||||
$results[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function note_record()
|
||||
{
|
||||
$data = $this->get_note(array(
|
||||
'uid' => rcube_utils::get_input_value('_id', RCUBE_INPUT_GPC),
|
||||
'list' => rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC),
|
||||
));
|
||||
|
||||
// encode for client use
|
||||
if (is_array($data)) {
|
||||
$this->_client_encode($data);
|
||||
}
|
||||
|
||||
$this->rc->output->command('plugin.render_note', $data);
|
||||
}
|
||||
|
||||
public function get_note($note)
|
||||
{
|
||||
if (is_array($note)) {
|
||||
$uid = $note['id'] ?: $note['uid'];
|
||||
$list_id = $note['list'];
|
||||
}
|
||||
else {
|
||||
$uid = $note;
|
||||
}
|
||||
|
||||
$this->_read_lists();
|
||||
if ($list_id) {
|
||||
if ($folder = $this->folders[$list_id]) {
|
||||
return $folder->get_object($uid);
|
||||
}
|
||||
}
|
||||
// iterate over all calendar folders and search for the event ID
|
||||
else {
|
||||
foreach ($this->folders as $list_id => $folder) {
|
||||
if ($result = $folder->get_object($uid)) {
|
||||
$result['list'] = $list_id;
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function _client_encode(&$note)
|
||||
{
|
||||
foreach ($note as $key => $prop) {
|
||||
if ($key[0] == '_') {
|
||||
unset($note[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array('created','changed') as $key) {
|
||||
if (is_object($note[$key]) && $note[$key] instanceof DateTime) {
|
||||
$note[$key.'_'] = $note[$key]->format('U');
|
||||
$note[$key] = $this->rc->format_date($note[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $note;
|
||||
}
|
||||
|
||||
public function note_action()
|
||||
{
|
||||
$action = rcube_utils::get_input_value('_do', RCUBE_INPUT_POST);
|
||||
$note = rcube_utils::get_input_value('_data', RCUBE_INPUT_POST, true);
|
||||
|
||||
$success =false;
|
||||
switch ($action) {
|
||||
case 'save':
|
||||
console($action, $note);
|
||||
sleep(3);
|
||||
$success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// show confirmation/error message
|
||||
if ($success) {
|
||||
$this->rc->output->show_message('successfullysaved', 'confirmation');
|
||||
}
|
||||
else {
|
||||
$this->rc->output->show_message('kolab_notes.errorsaving', 'error');
|
||||
}
|
||||
|
||||
// unlock client
|
||||
$this->rc->output->command('plugin.unlock_saving');
|
||||
// $this->rc->output->command('plugin.update_note', $note);
|
||||
}
|
||||
|
||||
}
|
||||
|
139
plugins/kolab_notes/kolab_notes_ui.php
Normal file
139
plugins/kolab_notes/kolab_notes_ui.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?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->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->rc->output->include_script('list.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');
|
||||
|
||||
// TODO: load config options and user prefs relevant for the UI
|
||||
$settings = array();
|
||||
$this->rc->output->set_env('kolab_notes_settings', $settings);
|
||||
}
|
||||
|
||||
public function folders($attrib)
|
||||
{
|
||||
$attrib += array('id' => 'rcmkolabnotebooks');
|
||||
|
||||
$jsenv = array();
|
||||
$items = '';
|
||||
foreach ($this->plugin->get_lists() as $prop) {
|
||||
unset($prop['user_id']);
|
||||
$id = $prop['id'];
|
||||
|
||||
if (!$prop['virtual'])
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
$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'];
|
||||
|
||||
$items .= html::tag('li', array('id' => 'rcmliknb' . $html_id, 'class' => trim($class)),
|
||||
html::span(array('class' => 'listname', 'title' => $title), Q($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 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']);
|
||||
|
||||
$textarea = new html_textarea(array('name' => 'content', 'id' => 'notecontent', 'cols' => 60, 'rows' => 20, 'tabindex' => 3));
|
||||
return html::tag('form', $attrib, $textarea->show(), array_merge(html::$common_attrib, array('action')));
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
$html = $summary->show();
|
||||
$html .= html::div(array('class' => 'tagline tagedit', 'style' => 'display:none'), ' ');
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
17
plugins/kolab_notes/localization/en_US.inc
Normal file
17
plugins/kolab_notes/localization/en_US.inc
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
$labels = array();
|
||||
$labels['navtitle'] = 'Notes';
|
||||
$labels['tags'] = 'Tags';
|
||||
$labels['lists'] = 'Notebooks';
|
||||
$labels['notes'] = 'Notes';
|
||||
$labels['create'] = 'Create';
|
||||
$labels['newnote'] = 'New Note';
|
||||
$labels['notags'] = 'No tags';
|
||||
$labels['removetag'] = 'Remove tag';
|
||||
$labels['created'] = 'Created';
|
||||
$labels['changed'] = 'Last Modified';
|
||||
|
||||
$labels['savingdata'] = 'Saving data...';
|
||||
$labels['recordnotfound'] = 'Record not found';
|
||||
$labels['entertitle'] = 'Please enter a title for this note!';
|
339
plugins/kolab_notes/notes.js
Normal file
339
plugins/kolab_notes/notes.js
Normal file
|
@ -0,0 +1,339 @@
|
|||
/**
|
||||
* Client scripts for the Kolab Notes plugin
|
||||
*
|
||||
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
function rcube_kolab_notes_ui(settings)
|
||||
{
|
||||
/* private vars */
|
||||
var ui_loading = false;
|
||||
var saving_lock;
|
||||
var search_query;
|
||||
var noteslist;
|
||||
var notesdata = {};
|
||||
var tags = [];
|
||||
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(){ 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('save', save_note, true);
|
||||
rcmail.register_command('search', quicksearch, true);
|
||||
rcmail.register_command('reset-search', reset_search, true);
|
||||
|
||||
// register server callbacks
|
||||
rcmail.addEventListener('plugin.data_ready', data_ready);
|
||||
rcmail.addEventListener('plugin.render_note', render_note);
|
||||
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) {
|
||||
init_folder_li(id);
|
||||
|
||||
if (me.notebooks[id].editable && (!me.selected_list || (me.notebooks[id].active && !me.notebooks[me.selected_list].active))) {
|
||||
me.selected_list = id;
|
||||
}
|
||||
}
|
||||
|
||||
// initialize notes list widget
|
||||
if (rcmail.gui_objects.noteslist) {
|
||||
noteslist = new rcube_list_widget(rcmail.gui_objects.noteslist,
|
||||
{ multiselect:true, draggable:false, keyboard:false });
|
||||
noteslist.addEventListener('select', function(list) {
|
||||
var note;
|
||||
if (list.selection.length == 1 && (note = notesdata[list.selection[0]])) {
|
||||
edit_note(note.uid, 'edit');
|
||||
}
|
||||
else {
|
||||
reset_view();
|
||||
}
|
||||
})
|
||||
.init();
|
||||
}
|
||||
|
||||
if (me.selected_list) {
|
||||
rcmail.enable_command('createnote', true);
|
||||
$('#rcmliknb'+me.selected_list).click();
|
||||
}
|
||||
}
|
||||
this.init = init;
|
||||
|
||||
/**
|
||||
* Quote HTML entities
|
||||
*/
|
||||
function Q(html)
|
||||
{
|
||||
return String(html).replace(/&/g, '"').replace(/</g, '<').replace(/>/g, '>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim whitespace off the given string
|
||||
*/
|
||||
function trim(str)
|
||||
{
|
||||
return String(str).replace(/\s+$/, '').replace(/^\s+/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function init_folder_li(id)
|
||||
{
|
||||
$('#rcmliknb'+id).click(function(e){
|
||||
var id = $(this).data('id');
|
||||
rcmail.enable_command('list-edit', 'list-remove', me.notebooks[id].editable);
|
||||
fetch_notes(id);
|
||||
me.selected_list = id;
|
||||
})
|
||||
.dblclick(function(e){
|
||||
// list_edit_dialog($(this).data('id'));
|
||||
})
|
||||
.data('id', id);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function edit_note(uid, action)
|
||||
{
|
||||
if (!uid) {
|
||||
me.selected_note = { list:me.selected_list, uid:null, title:rcmail.gettext('newnote','kolab_notes'), description:'', categories:[] }
|
||||
render_note(me.selected_note);
|
||||
}
|
||||
else {
|
||||
ui_loading = rcmail.set_busy(true, 'loading');
|
||||
rcmail.http_request('get', { _list:me.selected_list, _id:uid }, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function list_edit_dialog(id)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function list_remove(id)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function quicksearch()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function reset_search()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function fetch_notes(id)
|
||||
{
|
||||
if (rcmail.busy)
|
||||
return;
|
||||
|
||||
if (id) {
|
||||
me.selected_list = id;
|
||||
$('li.selected', rcmail.gui_objects.notebooks).removeClass('selected')
|
||||
$('#rcmliknb'+id).addClass('selected');
|
||||
}
|
||||
|
||||
ui_loading = rcmail.set_busy(true, 'loading');
|
||||
rcmail.http_request('fetch', { _list:me.selected_list, _q:search_query }, true);
|
||||
|
||||
reset_view();
|
||||
noteslist.clear();
|
||||
notesdata = {};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function data_ready(data)
|
||||
{
|
||||
data.data.sort(function(a,b){
|
||||
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;
|
||||
}
|
||||
|
||||
tags = data.tags || [];
|
||||
rcmail.set_busy(false, 'loading', ui_loading);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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]
|
||||
var title = $('.notetitle', rcmail.gui_objects.noteviewtitle).val(data.title);
|
||||
var content = $('#notecontent').val(data.description);
|
||||
$('.dates .notecreated', rcmail.gui_objects.noteviewtitle).html(Q(data.created || ''));
|
||||
$('.dates .notechanged', rcmail.gui_objects.noteviewtitle).html(Q(data.changed || ''));
|
||||
if (data.created || data.changed)
|
||||
$('.dates', rcmail.gui_objects.noteviewtitle).show();
|
||||
|
||||
$(rcmail.gui_objects.noteseditform).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')
|
||||
.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,
|
||||
checkNewEntriesCaseSensitive: false,
|
||||
autocompleteOptions: { source: tags, minLength: 0 },
|
||||
texts: { removeLinkTitle: rcmail.gettext('removetag', 'kolab_notes') }
|
||||
})
|
||||
|
||||
$('.tagedit-list', rcmail.gui_objects.noteviewtitle)
|
||||
.on('click', function(){ $('.tagline .placeholder').hide(); });
|
||||
|
||||
me.selected_note = data;
|
||||
rcmail.enable_command('save', list.editable && !data.readonly);
|
||||
content.select();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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.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 = {
|
||||
title: trim($('.notetitle', rcmail.gui_objects.noteviewtitle).val()),
|
||||
description: $('#notecontent').val(),
|
||||
list: 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);
|
||||
}
|
||||
|
||||
// do some input validation
|
||||
if (savedata.title == '') {
|
||||
alert(rcmail.gettext('entertitle', 'kolab_notes'))
|
||||
return false;
|
||||
}
|
||||
|
||||
rcmail.lock_form(rcmail.gui_objects.noteseditform, true);
|
||||
saving_lock = rcmail.set_busy(true, 'kolab_notes.savingdata');
|
||||
rcmail.http_post('action', { _data: savedata, _do:'save' }, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* notes plugin UI initialization */
|
||||
var kolabnotes;
|
||||
window.rcmail && rcmail.addEventListener('init', function(evt) {
|
||||
kolabnotes = new rcube_kolab_notes_ui(rcmail.env.kolab_notes_settings);
|
||||
kolabnotes.init();
|
||||
});
|
||||
|
227
plugins/kolab_notes/skins/larry/notes.css
Normal file
227
plugins/kolab_notes/skins/larry/notes.css
Normal file
|
@ -0,0 +1,227 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.notesview #sidebar {
|
||||
position: absolute;
|
||||
top: 42px;
|
||||
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 {
|
||||
|
||||
}
|
||||
|
||||
.notesview #taskbar a.button-notes span.button-inner {
|
||||
|
||||
}
|
||||
|
||||
.notesview #taskbar a.button-notes.button-selected span.button-inner {
|
||||
|
||||
}
|
||||
|
||||
.notesview #quicksearchbar {
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.notesview #searchmenulink {
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
.notesview #mainview-right {
|
||||
top: 42px;
|
||||
}
|
||||
|
||||
.notesview #tagsbox {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 242px;
|
||||
}
|
||||
|
||||
.notesview #notebooksbox {
|
||||
position: absolute;
|
||||
top: 300px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.notesview #noteslistbox {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 240px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.notesview #kolabnoteslist .title {
|
||||
display: block;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.notesview #kolabnoteslist .date {
|
||||
display: block;
|
||||
padding: 0px 8px 4px 8px;
|
||||
color: #777;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.notesview #notedetailsbox {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 256px;
|
||||
right: 0;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.notesview #notedetailsbox .formbuttons {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 8px 12px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.notesview #notecontent {
|
||||
position: absolute;
|
||||
top: 82px;
|
||||
left: 0;
|
||||
bottom: 41px;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
padding: 8px 0 8px 8px;
|
||||
resize: none;
|
||||
font-family: monospace;
|
||||
font-size: 9pt;
|
||||
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 #notedetailstitle {
|
||||
height: 68px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .dates {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .tagline {
|
||||
position: relative;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .tagline .placeholder {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .tagedit-list {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle #tagedit-input {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .notecreated,
|
||||
.notesview #notedetailstitle .notechanged {
|
||||
display: inline-block;
|
||||
padding-left: 0.4em;
|
||||
padding-right: 2em;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.notesview #notebooks li {
|
||||
margin: 0;
|
||||
height: 20px;
|
||||
padding: 6px 8px 2px 6px;
|
||||
display: block;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notesview #notebooks li span.listname {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 32px;
|
||||
right: 26px;
|
||||
cursor: default;
|
||||
padding-bottom: 2px;
|
||||
padding-right: 30px;
|
||||
color: #004458;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
1
plugins/kolab_notes/skins/larry/tagedit.css
Symbolic link
1
plugins/kolab_notes/skins/larry/tagedit.css
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../tasklist/skins/larry/tagedit.css
|
97
plugins/kolab_notes/skins/larry/templates/notes.html
Normal file
97
plugins/kolab_notes/skins/larry/templates/notes.html
Normal file
|
@ -0,0 +1,97 @@
|
|||
<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">
|
||||
<div id="notestoolbar" class="toolbar">
|
||||
<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:container name="toolbar" id="notestoolbar" />
|
||||
|
||||
<div id="quicksearchbar">
|
||||
<roundcube:object name="plugin.searchform" id="quicksearchbox" />
|
||||
<a id="searchmenulink" class="iconbutton searchoptions" > </a>
|
||||
<roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="sidebar">
|
||||
<div id="tagsbox" class="uibox listbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="kolab_notes.tags" id="taglist" /></h2>
|
||||
<div class="scroller">
|
||||
<roundcube:object name="plugin.tagslist" id="tagslist" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="notebooksbox" class="uibox listbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="kolab_notes.lists" /></h2>
|
||||
<div class="scroller withfooter">
|
||||
<roundcube:object name="plugin.notebooks" id="notebooks" class="listing" />
|
||||
</div>
|
||||
<div class="boxfooter">
|
||||
<roundcube:button command="list-create" type="link" title="kolab_notes.createlist" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="notesoptionslink" id="notesoptionsmenulink" type="link" title="kolab_notes.listactions" class="listbutton groupactions" onclick="UI.show_popup('notesoptionsmenu', undefined, { above:true });return false" innerClass="inner" content="⚙" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="mainview-right">
|
||||
<div id="noteslistbox" class="uibox listbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="kolab_notes.notes" /></h2>
|
||||
<div class="scroller withfooter">
|
||||
<roundcube:object name="plugin.listing" id="kolabnoteslist" class="listing" />
|
||||
</div>
|
||||
<div class="boxfooter">
|
||||
<roundcube:button command="delete" type="link" title="delete" class="listbutton delete disabled" classAct="listbutton delete" innerClass="inner" content="-" />
|
||||
<roundcube:object name="plugin.recordsCountDisplay" class="countdisplay" label="fromtoshort" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="notedetailsbox" class="uibox contentbox">
|
||||
<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 class="footerleft formbuttons">
|
||||
<roundcube:button command="save" type="input" class="button mainaction" label="save" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<roundcube:object name="message" id="messagestack" />
|
||||
|
||||
<div id="notesoptionsmenu" class="popupmenu">
|
||||
<ul class="toolbarmenu">
|
||||
<li><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
|
||||
<li><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
|
||||
<li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
// UI startup
|
||||
var UI = new rcube_mail_ui();
|
||||
|
||||
$(document).ready(function(e){
|
||||
UI.init();
|
||||
|
||||
new rcube_splitter({ id:'notesviewsplitter', p1:'#sidebar', p2:'#mainview-right',
|
||||
orientation:'v', relative:true, start:240, min:180, size:16, offset:2 }).init();
|
||||
new rcube_splitter({ id:'noteslistsplitter2', p1:'#noteslistbox', p2:'#notedetailsbox',
|
||||
orientation:'v', relative:true, start:242, min:180, size:16, offset:2 }).init();
|
||||
new rcube_splitter({ id:'notesviewsplitterv', p1:'#tagsbox', p2:'#notebooksbox',
|
||||
orientation:'h', relative:true, start:242, min:120, size:16, offset:6 }).init();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Reference in a new issue