Merge branch 'dev/kolab-notes'
This commit is contained in:
commit
5d126a296c
30 changed files with 3301 additions and 216 deletions
|
@ -29,7 +29,7 @@
|
|||
|
||||
class kolab_addressbook extends rcube_plugin
|
||||
{
|
||||
public $task = 'mail|settings|addressbook|calendar|tasks';
|
||||
public $task = '?(?!login|logout).*';
|
||||
|
||||
private $sources;
|
||||
private $rc;
|
||||
|
|
1
plugins/kolab_notes/jquery.tagedit.js
Symbolic link
1
plugins/kolab_notes/jquery.tagedit.js
Symbolic link
|
@ -0,0 +1 @@
|
|||
../tasklist/jquery.tagedit.js
|
797
plugins/kolab_notes/kolab_notes.php
Normal file
797
plugins/kolab_notes/kolab_notes.php
Normal file
|
@ -0,0 +1,797 @@
|
|||
<?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 $allowed_prefs = array('kolab_notes_sort_col');
|
||||
public $rc;
|
||||
|
||||
private $ui;
|
||||
private $lists;
|
||||
private $folders;
|
||||
private $cache = array();
|
||||
|
||||
/**
|
||||
* 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']);
|
||||
$this->rc->load_language($_SESSION['language'], array('notes.notes' => $this->gettext('navtitle'))); // add label for task title
|
||||
|
||||
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'));
|
||||
$this->register_action('list', array($this, 'list_action'));
|
||||
}
|
||||
else if ($args['task'] == 'mail') {
|
||||
$this->add_hook('message_compose', array($this, 'mail_message_compose'));
|
||||
}
|
||||
|
||||
if (!$this->rc->output->ajax_call && (!$this->rc->output->env['framed'] || $args['action'] == 'folder-acl')) {
|
||||
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' => !$readonly,
|
||||
'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');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler to retrieve note records for the given list and/or search query
|
||||
*/
|
||||
public function notes_fetch()
|
||||
{
|
||||
$search = rcube_utils::get_input_value('_q', RCUBE_INPUT_GPC, true);
|
||||
$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($tags)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given note records for delivery to the client
|
||||
*/
|
||||
protected function notes_data($records, &$tags)
|
||||
{
|
||||
$tags = array();
|
||||
|
||||
foreach ($records as $i => $rec) {
|
||||
unset($records[$i]['description']);
|
||||
$this->_client_encode($records[$i]);
|
||||
|
||||
foreach ((array)$rec['categories'] as $tag) {
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$tags = array_unique($tags);
|
||||
return $records;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read note records for the given list from the storage backend
|
||||
*/
|
||||
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)) {
|
||||
$words = array_filter(rcube_utils::normalize_string(mb_strtolower($search), true));
|
||||
foreach ($words as $word) {
|
||||
if (strlen($word) > 2) { // only words > 3 chars are stored in DB
|
||||
$query[] = array('words', '~', $word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->_read_lists();
|
||||
if ($folder = $this->folders[$list_id]) {
|
||||
foreach ($folder->select($query) as $record) {
|
||||
// post-filter search results
|
||||
if (strlen($search)) {
|
||||
$matches = 0;
|
||||
$contents = mb_strtolower(
|
||||
$record['title'] .
|
||||
($this->is_html($record) ? strip_tags($record['description']) : $record['description']) .
|
||||
join(' ', (array)$record['categories'])
|
||||
);
|
||||
foreach ($words as $word) {
|
||||
if (mb_strpos($contents, $word) !== false) {
|
||||
$matches++;
|
||||
}
|
||||
}
|
||||
|
||||
// skip records not matching all search words
|
||||
if ($matches < count($words)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$record['list'] = $list_id;
|
||||
$results[] = $record;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for delivering a full note record to the client
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full note record identified by the given UID + Lolder identifier
|
||||
*/
|
||||
public function get_note($note)
|
||||
{
|
||||
if (is_array($note)) {
|
||||
$uid = $note['uid'] ?: $note['id'];
|
||||
$list_id = $note['list'];
|
||||
}
|
||||
else {
|
||||
$uid = $note;
|
||||
}
|
||||
|
||||
// deliver from in-memory cache
|
||||
$key = $list_id . ':' . $uid;
|
||||
if ($this->cache[$key]) {
|
||||
return $this->cache[$key];
|
||||
}
|
||||
|
||||
$this->_read_lists();
|
||||
if ($list_id) {
|
||||
if ($folder = $this->folders[$list_id]) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to encode the given note record for use in the client
|
||||
*/
|
||||
private function _client_encode(&$note)
|
||||
{
|
||||
foreach ($note as $key => $prop) {
|
||||
if ($key[0] == '_' || $key == 'x-custom') {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
// clean HTML contents
|
||||
if (!empty($note['description']) && $this->is_html($note)) {
|
||||
$note['html'] = $this->_wash_html($note['description']);
|
||||
}
|
||||
|
||||
return $note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for client-initiated actions on a single note record
|
||||
*/
|
||||
public function note_action()
|
||||
{
|
||||
$action = rcube_utils::get_input_value('_do', RCUBE_INPUT_POST);
|
||||
$note = rcube_utils::get_input_value('_data', RCUBE_INPUT_POST, true);
|
||||
|
||||
$success = false;
|
||||
switch ($action) {
|
||||
case 'new':
|
||||
$temp_id = $rec['tempid'];
|
||||
|
||||
case 'edit':
|
||||
if ($success = $this->save_note($note)) {
|
||||
$refresh = $this->get_note($note);
|
||||
$refresh['tempid'] = $temp_id;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'move':
|
||||
$uids = explode(',', $note['uid']);
|
||||
foreach ($uids as $uid) {
|
||||
$note['uid'] = $uid;
|
||||
if (!($success = $this->move_note($note, $note['to']))) {
|
||||
$refresh = $this->get_note($note);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$uids = explode(',', $note['uid']);
|
||||
foreach ($uids as $uid) {
|
||||
$note['uid'] = $uid;
|
||||
if (!($success = $this->delete_note($note))) {
|
||||
$refresh = $this->get_note($note);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// show confirmation/error message
|
||||
if ($success) {
|
||||
$this->rc->output->show_message('successfullysaved', 'confirmation');
|
||||
}
|
||||
else {
|
||||
$this->rc->output->show_message('errorsaving', 'error');
|
||||
}
|
||||
|
||||
// unlock client
|
||||
$this->rc->output->command('plugin.unlock_saving');
|
||||
|
||||
if ($refresh) {
|
||||
$this->rc->output->command('plugin.update_note', $this->_client_encode($refresh));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an note record with the given data
|
||||
*
|
||||
* @param array Hash array with note properties (id, list)
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
private function save_note(&$note)
|
||||
{
|
||||
$this->_read_lists();
|
||||
|
||||
$list_id = $note['list'];
|
||||
if (!$list_id || !($folder = $this->folders[$list_id]))
|
||||
return false;
|
||||
|
||||
// moved from another folder
|
||||
if ($note['_fromlist'] && ($fromfolder = $this->folders[$note['_fromlist']])) {
|
||||
if (!$fromfolder->move($note['uid'], $folder->name))
|
||||
return false;
|
||||
|
||||
unset($note['_fromlist']);
|
||||
}
|
||||
|
||||
// load previous version of this record to merge
|
||||
if ($note['uid']) {
|
||||
$old = $folder->get_object($note['uid']);
|
||||
if (!$old || PEAR::isError($old))
|
||||
return false;
|
||||
|
||||
// merge existing properties if the update isn't complete
|
||||
if (!isset($note['title']) || !isset($note['description']))
|
||||
$note += $old;
|
||||
}
|
||||
|
||||
// generate new note object from input
|
||||
$object = $this->_write_preprocess($note, $old);
|
||||
$saved = $folder->save($object, 'note', $note['uid']);
|
||||
|
||||
if (!$saved) {
|
||||
raise_error(array(
|
||||
'code' => 600, 'type' => 'php',
|
||||
'file' => __FILE__, 'line' => __LINE__,
|
||||
'message' => "Error saving note object to Kolab server"),
|
||||
true, false);
|
||||
$saved = false;
|
||||
}
|
||||
else {
|
||||
$note = $object;
|
||||
$note['list'] = $list_id;
|
||||
|
||||
// cache this in memory for later read
|
||||
$key = $list_id . ':' . $note['uid'];
|
||||
$this->cache[$key] = $note;
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move the given note to another folder
|
||||
*/
|
||||
function move_note($note, $list_id)
|
||||
{
|
||||
$this->_read_lists();
|
||||
$tofolder = $this->folders[$list_id];
|
||||
$fromfolder = $this->folders[$note['list']];
|
||||
|
||||
if ($fromfolder && $tofolder) {
|
||||
return $fromfolder->move($note['uid'], $tofolder->name);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a single note record from the backend
|
||||
*
|
||||
* @param array Hash array with note properties (id, list)
|
||||
* @param boolean Remove record irreversible (mark as deleted otherwise)
|
||||
* @return boolean True on success, False on error
|
||||
*/
|
||||
public function delete_note($note, $force = true)
|
||||
{
|
||||
$this->_read_lists();
|
||||
|
||||
$list_id = $note['list'];
|
||||
if (!$list_id || !($folder = $this->folders[$list_id]))
|
||||
return false;
|
||||
|
||||
return $folder->delete($note['uid'], $force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for client requests to list (aka folder) actions
|
||||
*/
|
||||
public function list_action()
|
||||
{
|
||||
$action = rcube_utils::get_input_value('_do', RCUBE_INPUT_GPC);
|
||||
$list = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC, true);
|
||||
$success = $update_cmd = false;
|
||||
|
||||
switch ($action) {
|
||||
case 'form-new':
|
||||
case 'form-edit':
|
||||
$this->_read_lists();
|
||||
echo $this->ui->list_editform($action, $this->lists[$list['id']], $this->folders[$list['id']]);
|
||||
exit;
|
||||
|
||||
case 'new':
|
||||
$list['type'] = 'note';
|
||||
$list['subscribed'] = true;
|
||||
$folder = kolab_storage::folder_update($list);
|
||||
|
||||
if ($folder === false) {
|
||||
$save_error = $this->gettext(kolab_storage::$last_error);
|
||||
}
|
||||
else {
|
||||
$success = true;
|
||||
$update_cmd = 'plugin.update_list';
|
||||
$list['id'] = kolab_storage::folder_id($folder);
|
||||
$list['_reload'] = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'edit':
|
||||
$this->_read_lists();
|
||||
$oldparent = $this->lists[$list['id']]['parentfolder'];
|
||||
$newfolder = kolab_storage::folder_update($list);
|
||||
|
||||
if ($newfolder === false) {
|
||||
$save_error = $this->gettext(kolab_storage::$last_error);
|
||||
}
|
||||
else {
|
||||
$success = true;
|
||||
$update_cmd = 'plugin.update_list';
|
||||
$list['newid'] = kolab_storage::folder_id($newfolder);
|
||||
$list['_reload'] = $list['parent'] != $oldparent;
|
||||
|
||||
// compose the new display name
|
||||
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
|
||||
$path_imap = explode($delim, $newfolder);
|
||||
$list['name'] = kolab_storage::object_name($newfolder);
|
||||
$list['editname'] = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP');
|
||||
$list['listname'] = str_repeat(' ', count($path_imap)) . '» ' . $list['editname'];
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
$this->_read_lists();
|
||||
$folder = $this->folders[$list['id']];
|
||||
if ($folder && kolab_storage::folder_delete($folder->name)) {
|
||||
$success = true;
|
||||
$update_cmd = 'plugin.destroy_list';
|
||||
}
|
||||
else {
|
||||
$save_error = $this->gettext(kolab_storage::$last_error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$this->rc->output->command('plugin.unlock_saving');
|
||||
|
||||
if ($success) {
|
||||
$this->rc->output->show_message('successfullysaved', 'confirmation');
|
||||
|
||||
if ($update_cmd) {
|
||||
$this->rc->output->command($update_cmd, $list);
|
||||
}
|
||||
}
|
||||
else {
|
||||
$error_msg = $this->gettext('errorsaving') . ($save_error ? ': ' . $save_error :'');
|
||||
$this->rc->output->show_message($error_msg, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to add note attachments to message compose if the according parameter is present.
|
||||
* This completes the 'send note by mail' feature.
|
||||
*/
|
||||
public function mail_message_compose($args)
|
||||
{
|
||||
if (!empty($args['param']['with_notes'])) {
|
||||
$uids = explode(',', $args['param']['with_notes']);
|
||||
$list = $args['param']['notes_list'];
|
||||
$attachments = array();
|
||||
foreach ($uids as $uid) {
|
||||
if ($note = $this->get_note(array('uid' => $uid, 'list' => $list))) {
|
||||
$args['attachments'][] = array(
|
||||
'name' => abbreviate_string($note['title'], 50, ''),
|
||||
'mimetype' => 'message/rfc822',
|
||||
'data' => $this->note2message($note),
|
||||
);
|
||||
|
||||
if (empty($args['param']['subject'])) {
|
||||
$args['param']['subject'] = $note['title'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unset($args['param']['with_notes'], $args['param']['notes_list']);
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the given note is HTML formatted
|
||||
*/
|
||||
private function is_html($note)
|
||||
{
|
||||
// check for opening and closing <html> or <body> tags
|
||||
return (preg_match('/<(html|body)(\s+[a-z]|>)/', $note['description'], $m) && strpos($note['description'], '</'.$m[1].'>') > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an RFC 822 message from the given note
|
||||
*/
|
||||
private function note2message($note)
|
||||
{
|
||||
$message = new Mail_mime("\r\n");
|
||||
|
||||
$message->setParam('text_encoding', '8bit');
|
||||
$message->setParam('html_encoding', 'quoted-printable');
|
||||
$message->setParam('head_encoding', 'quoted-printable');
|
||||
$message->setParam('head_charset', RCUBE_CHARSET);
|
||||
$message->setParam('html_charset', RCUBE_CHARSET);
|
||||
$message->setParam('text_charset', RCUBE_CHARSET);
|
||||
|
||||
$message->headers(array(
|
||||
'Subject' => $note['title'],
|
||||
'Date' => $note['changed']->format('r'),
|
||||
));
|
||||
console($note);
|
||||
if ($this->is_html($note)) {
|
||||
$message->setHTMLBody($note['description']);
|
||||
|
||||
// add a plain text version of the note content as an alternative part.
|
||||
$h2t = new rcube_html2text($note['description'], false, true, 0, RCUBE_CHARSET);
|
||||
$plain_part = rcube_mime::wordwrap($h2t->get_text(), $this->rc->config->get('line_length', 72), "\r\n", false, RCUBE_CHARSET);
|
||||
$plain_part = trim(wordwrap($plain_part, 998, "\r\n", true));
|
||||
|
||||
// make sure all line endings are CRLF
|
||||
$plain_part = preg_replace('/\r?\n/', "\r\n", $plain_part);
|
||||
|
||||
$message->setTXTBody($plain_part);
|
||||
}
|
||||
else {
|
||||
$message->setTXTBody($note['description']);
|
||||
}
|
||||
|
||||
return $message->getMessage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the given note data (submitted by the client) before saving it
|
||||
*/
|
||||
private function _write_preprocess($note, $old = array())
|
||||
{
|
||||
$object = $note;
|
||||
|
||||
// TODO: handle attachments
|
||||
|
||||
// clean up HTML content
|
||||
$object['description'] = $this->_wash_html($note['description']);
|
||||
$is_html = true;
|
||||
|
||||
// try to be smart and convert to plain-text if no real formatting is detected
|
||||
if (preg_match('!<body><(?:p|pre)>(.*)</(?:p|pre)></body>!Uims', $object['description'], $m)) {
|
||||
if (!preg_match('!<(a|b|i|strong|em|p|span|div|pre|li)(\s+[a-z]|>)!im', $m[1], $n) || !strpos($m[1], '</'.$n[1].'>')) {
|
||||
// $converter = new rcube_html2text($m[1], false, true, 0);
|
||||
// $object['description'] = rtrim($converter->get_text());
|
||||
$object['description'] = html_entity_decode(preg_replace('!<br(\s+/)>!', "\n", $m[1]));
|
||||
$is_html = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add proper HTML header, otherwise Kontact renders it as plain text
|
||||
if ($is_html) {
|
||||
$object['description'] = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">'."\n" .
|
||||
str_replace('<head>', '<head><meta name="qrichtext" content="1" />', $object['description']);
|
||||
}
|
||||
|
||||
// copy meta data (starting with _) from old object
|
||||
foreach ((array)$old as $key => $val) {
|
||||
if (!isset($object[$key]) && $key[0] == '_')
|
||||
$object[$key] = $val;
|
||||
}
|
||||
|
||||
// make list of categories unique
|
||||
if (is_array($object['categories'])) {
|
||||
$object['categories'] = array_unique(array_filter($object['categories']));
|
||||
}
|
||||
|
||||
unset($object['list'], $object['tempid'], $object['created'], $object['changed'], $object['created_'], $object['changed_']);
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanity checks/cleanups HTML content
|
||||
*/
|
||||
private function _wash_html($html)
|
||||
{
|
||||
// Add header with charset spec., washtml cannot work without that
|
||||
$html = '<html><head>'
|
||||
. '<meta http-equiv="Content-Type" content="text/html; charset='.RCUBE_CHARSET.'" />'
|
||||
. '</head><body>' . $html . '</body></html>';
|
||||
|
||||
// clean HTML with washtml by Frederic Motte
|
||||
$wash_opts = array(
|
||||
'show_washed' => false,
|
||||
'allow_remote' => 1,
|
||||
'charset' => RCUBE_CHARSET,
|
||||
'html_elements' => array('html', 'head', 'meta', 'body', 'link'),
|
||||
'html_attribs' => array('rel', 'type', 'name', 'http-equiv'),
|
||||
);
|
||||
|
||||
// initialize HTML washer
|
||||
$washer = new rcube_washtml($wash_opts);
|
||||
|
||||
$washer->add_callback('form', array($this, '_washtml_callback'));
|
||||
$washer->add_callback('a', array($this, '_washtml_callback'));
|
||||
|
||||
// Remove non-UTF8 characters
|
||||
$html = rcube_charset::clean($html);
|
||||
|
||||
$html = $washer->wash($html);
|
||||
|
||||
// remove unwanted comments (produced by washtml)
|
||||
$html = preg_replace('/<!--[^>]+-->/', '', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function for washtml cleaning class
|
||||
*/
|
||||
public function _washtml_callback($tagname, $attrib, $content, $washtml)
|
||||
{
|
||||
switch ($tagname) {
|
||||
case 'form':
|
||||
$out = html::div('form', $content);
|
||||
break;
|
||||
|
||||
case 'a':
|
||||
// strip temporary link tags from plain-text markup
|
||||
$attrib = html::parse_attrib_string($attrib);
|
||||
if (!empty($attrib['class']) && strpos($attrib['class'], 'x-templink') !== false) {
|
||||
// remove link entirely
|
||||
if (strpos($attrib['href'], html_entity_decode($content)) !== false) {
|
||||
$out = $content;
|
||||
break;
|
||||
}
|
||||
$attrib['class'] = trim(str_replace('x-templink', '', $attrib['class']));
|
||||
}
|
||||
$out = html::a($attrib, $content);
|
||||
break;
|
||||
|
||||
default:
|
||||
$out = '';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
}
|
||||
|
321
plugins/kolab_notes/kolab_notes_ui.php
Normal file
321
plugins/kolab_notes/kolab_notes_ui.php
Normal file
|
@ -0,0 +1,321 @@
|
|||
<?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->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 (!empty($_REQUEST['_list'])) {
|
||||
$settings['selected_list'] = rcube_utils::get_input_value('_list', RCUBE_INPUT_GPC);
|
||||
}
|
||||
|
||||
// TinyMCE uses two-letter lang codes, with exception of Chinese
|
||||
$lang = strtolower($_SESSION['language']);
|
||||
$lang = strpos($lang, 'zh_') === 0 ? str_replace('_', '-', $lang) : substr($lang, 0, 2);
|
||||
|
||||
if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js')) {
|
||||
$lang = 'en';
|
||||
}
|
||||
|
||||
$settings['editor'] = array(
|
||||
'lang' => $lang,
|
||||
'editor_css' => $this->plugin->url($this->plugin->local_skin_path() . '/editor.css'),
|
||||
'spellcheck' => intval($this->rc->config->get('enable_spellcheck')),
|
||||
'spelldict' => intval($this->rc->config->get('spellcheck_dictionary'))
|
||||
);
|
||||
|
||||
$this->rc->output->set_env('kolab_notes_settings', $settings);
|
||||
|
||||
$this->rc->output->add_label('save','cancel');
|
||||
}
|
||||
|
||||
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'];
|
||||
$class = '';
|
||||
|
||||
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), $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']);
|
||||
$this->rc->output->include_script('tiny_mce/tiny_mce.js');
|
||||
|
||||
$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 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));
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
);
|
||||
|
||||
// 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);
|
||||
$form['properties']['fields']['path'] = array(
|
||||
'label' => $this->plugin->gettext('parentfolder'),
|
||||
'value' => $select->show(strlen($folder_name) ? $path_imap : ''),
|
||||
);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
38
plugins/kolab_notes/localization/en_US.inc
Normal file
38
plugins/kolab_notes/localization/en_US.inc
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?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['createlist'] = 'New Notebook';
|
||||
$labels['editlist'] = 'Edit Notebook';
|
||||
$labels['listname'] = 'Name';
|
||||
$labels['tabsharing'] = 'Sharing';
|
||||
$labels['discard'] = 'Discard';
|
||||
$labels['abort'] = 'Abort';
|
||||
$labels['unsavedchanges'] = 'Unsaved Changes!';
|
||||
|
||||
$labels['savingdata'] = 'Saving data...';
|
||||
$labels['recordnotfound'] = 'Record not 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.';
|
||||
|
1206
plugins/kolab_notes/notes.js
Normal file
1206
plugins/kolab_notes/notes.js
Normal file
File diff suppressed because it is too large
Load diff
29
plugins/kolab_notes/skins/larry/editor.css
Normal file
29
plugins/kolab_notes/skins/larry/editor.css
Normal file
|
@ -0,0 +1,29 @@
|
|||
/* This file contains the CSS data for the editable area(iframe) of TinyMCE */
|
||||
|
||||
body, td, pre {
|
||||
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #FFF;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
pre
|
||||
{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: -moz-pre-wrap !important;
|
||||
white-space: pre-wrap !important;
|
||||
white-space: pre;
|
||||
word-wrap: break-word; /* IE (and Safari) */
|
||||
}
|
||||
|
||||
blockquote
|
||||
{
|
||||
padding-left: 5px;
|
||||
border-left: #1010ff 2px solid;
|
||||
margin-left: 5px;
|
||||
width: 100%;
|
||||
}
|
1
plugins/kolab_notes/skins/larry/folder_icons.png
Symbolic link
1
plugins/kolab_notes/skins/larry/folder_icons.png
Symbolic link
|
@ -0,0 +1 @@
|
|||
../../../kolab_addressbook/skins/larry/folder_icons.png
|
339
plugins/kolab_notes/skins/larry/notes.css
Normal file
339
plugins/kolab_notes/skins/larry/notes.css
Normal file
|
@ -0,0 +1,339 @@
|
|||
/**
|
||||
* 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: 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 {
|
||||
background-image: url('sprites.png');
|
||||
background-position: center -54px;
|
||||
}
|
||||
|
||||
.notesview #notestoolbar a.button.sendnote {
|
||||
background-position: left -650px;
|
||||
}
|
||||
|
||||
.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;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.notesview #kolabnoteslist .date {
|
||||
display: block;
|
||||
padding: 0px 8px 4px 8px;
|
||||
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) -5px -109px no-repeat;
|
||||
}
|
||||
|
||||
.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 #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;
|
||||
}
|
||||
|
||||
.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 .defaultSkin table.mceLayout {
|
||||
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 {
|
||||
color: #999;
|
||||
font-weight: normal;
|
||||
font-size: 0.9em;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.notesview #notedetailstitle .dates {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.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 .tag-draghelper {
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.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.virtual {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
.notesview #notebooks li span.listname {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
left: 9px;
|
||||
right: 6px;
|
||||
cursor: default;
|
||||
padding-bottom: 2px;
|
||||
padding-right: 26px;
|
||||
color: #004458;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notesview #notebooks li.virtual span.listname {
|
||||
color: #aaa;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
.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 {
|
||||
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;
|
||||
min-height: 290px;
|
||||
}
|
||||
|
||||
.notesview .uidialog .propform #noteslist-name {
|
||||
width: 20em;
|
||||
}
|
43
plugins/kolab_notes/skins/larry/print.css
Normal file
43
plugins/kolab_notes/skins/larry/print.css
Normal file
|
@ -0,0 +1,43 @@
|
|||
body {
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #FFF;
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
#notebody {
|
||||
font-family: "Lucida Grande", Verdana, Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
#notedetailstitle {
|
||||
padding-bottom: 0.5em;
|
||||
margin-bottom: 1em;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
#notedetailstitle #notetags {
|
||||
margin-bottom: 0.4em;
|
||||
font-style: italic;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#notedetailstitle #notetags .tag {
|
||||
margin-right: 0.8em;
|
||||
}
|
||||
|
||||
#notedetailstitle .dates {
|
||||
margin-bottom: 0.2em;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
#notedetailstitle .dates span {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#notedetailstitle h1 {
|
||||
font-size: 14pt;
|
||||
margin: 0 0 0.5em 0;
|
||||
}
|
BIN
plugins/kolab_notes/skins/larry/sprites.png
Normal file
BIN
plugins/kolab_notes/skins/larry/sprites.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
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
|
26
plugins/kolab_notes/skins/larry/templates/kolabacl.html
Normal file
26
plugins/kolab_notes/skins/larry/templates/kolabacl.html
Normal file
|
@ -0,0 +1,26 @@
|
|||
<roundcube:object name="doctype" value="html5" />
|
||||
<html>
|
||||
<head>
|
||||
<title><roundcube:object name="pagetitle" /></title>
|
||||
<roundcube:include file="/includes/links.html" />
|
||||
<style type="text/css" media="screen">
|
||||
|
||||
body.aclform {
|
||||
background: #efefef;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body.aclform .hint {
|
||||
margin: 1em;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body class="iframe aclform">
|
||||
|
||||
<roundcube:object name="folderacl" />
|
||||
|
||||
<roundcube:include file="/includes/footer.html" />
|
||||
|
||||
</body>
|
||||
</html>
|
138
plugins/kolab_notes/skins/larry/templates/notes.html
Normal file
138
plugins/kolab_notes/skins/larry/templates/notes.html
Normal file
|
@ -0,0 +1,138 @@
|
|||
<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: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 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" class="tagcloud" />
|
||||
</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 class="boxpagenav">
|
||||
<roundcube:button name="notessortmenulink" id="notessortmenulink" type="link" title="kolab_notes.sortby" class="icon sortoptions" onclick="UI.show_popup('notessortmenu');return false" innerClass="inner" content="v" />
|
||||
</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>
|
||||
|
||||
<div id="notessortmenu" class="popupmenu">
|
||||
<ul class="toolbarmenu iconized">
|
||||
<li><roundcube:button command="list-sort" prop="changed" type="link" label="kolab_notes.changed" class="icon active by-changed" innerclass="icon" /></li>
|
||||
<li><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">
|
||||
<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:16, offset:2, render:layout_view }).init();
|
||||
new rcube_splitter({ id:'noteslistsplitter2', p1:'#noteslistbox', p2:'#notedetailsbox',
|
||||
orientation:'v', relative:true, start:242, min:180, size:16, offset:2, render:layout_view }).init();
|
||||
new rcube_splitter({ id:'notesviewsplitterv', p1:'#tagsbox', p2:'#notebooksbox',
|
||||
orientation:'h', relative:true, start:242, min:120, size:16, offset:6 }).init();
|
||||
|
||||
function layout_view()
|
||||
{
|
||||
var form = $('#noteform, #notedetails'),
|
||||
content = $('#notecontent'),
|
||||
header = $('#notedetailstitle'),
|
||||
w, h;
|
||||
|
||||
form.css('top', header.outerHeight()+'px');
|
||||
|
||||
w = form.outerWidth();
|
||||
h = form.outerHeight();
|
||||
content.width(w).height(h);
|
||||
|
||||
$('#notecontent_tbl').width(w+'px').height('').css('margin-top', '-1px');
|
||||
$('#notecontent_ifr').width(w+'px').height((h-54)+'px');
|
||||
}
|
||||
|
||||
$(window).resize(function(e){
|
||||
layout_view();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
25
plugins/kolab_notes/skins/larry/templates/print.html
Normal file
25
plugins/kolab_notes/skins/larry/templates/print.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<roundcube:object name="doctype" value="html5" />
|
||||
<html>
|
||||
<head>
|
||||
<title>Print</title>
|
||||
<link rel="stylesheet" type="text/css" href="/this/editor.css" />
|
||||
<link rel="stylesheet" type="text/css" href="/this/print.css" />
|
||||
</head>
|
||||
<body class="notesprint">
|
||||
|
||||
<div id="notedetailstitle">
|
||||
<h1 id="notetitle">#Title</h1>
|
||||
<div id="notetags" class="tagline">#Tags</div>
|
||||
<div class="dates">
|
||||
<label><roundcube:label name="kolab_notes.created" /></label>
|
||||
<span id="notecreated">#Created</span>
|
||||
<label><roundcube:label name="kolab_notes.changed" /></label>
|
||||
<span id="notechanged">#Changed</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="notebody">
|
||||
#Body
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* libkolab database schema
|
||||
*
|
||||
* @version 1.0
|
||||
* @version 1.1
|
||||
* @author Thomas Bruederli
|
||||
* @licence GNU AGPL
|
||||
**/
|
||||
|
@ -29,7 +29,7 @@ CREATE TABLE `kolab_cache_contact` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
|
@ -41,7 +41,8 @@ CREATE TABLE `kolab_cache_contact` (
|
|||
CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `contact_type` (`folder_id`,`type`)
|
||||
INDEX `contact_type` (`folder_id`,`type`),
|
||||
INDEX `contact_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
DROP TABLE IF EXISTS `kolab_cache_event`;
|
||||
|
@ -52,7 +53,7 @@ CREATE TABLE `kolab_cache_event` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
|
@ -60,7 +61,8 @@ CREATE TABLE `kolab_cache_event` (
|
|||
`dtend` DATETIME,
|
||||
CONSTRAINT `fk_kolab_cache_event_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`)
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `event_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
DROP TABLE IF EXISTS `kolab_cache_task`;
|
||||
|
@ -71,7 +73,7 @@ CREATE TABLE `kolab_cache_task` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
|
@ -79,7 +81,8 @@ CREATE TABLE `kolab_cache_task` (
|
|||
`dtend` DATETIME,
|
||||
CONSTRAINT `fk_kolab_cache_task_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`)
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `task_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
DROP TABLE IF EXISTS `kolab_cache_journal`;
|
||||
|
@ -90,7 +93,7 @@ CREATE TABLE `kolab_cache_journal` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
|
@ -98,7 +101,8 @@ CREATE TABLE `kolab_cache_journal` (
|
|||
`dtend` DATETIME,
|
||||
CONSTRAINT `fk_kolab_cache_journal_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`)
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `journal_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
DROP TABLE IF EXISTS `kolab_cache_note`;
|
||||
|
@ -109,13 +113,14 @@ CREATE TABLE `kolab_cache_note` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`)
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `note_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
DROP TABLE IF EXISTS `kolab_cache_file`;
|
||||
|
@ -126,7 +131,7 @@ CREATE TABLE `kolab_cache_file` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
|
@ -134,7 +139,8 @@ CREATE TABLE `kolab_cache_file` (
|
|||
CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `folder_filename` (`folder_id`, `filename`)
|
||||
INDEX `folder_filename` (`folder_id`, `filename`),
|
||||
INDEX `file_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
DROP TABLE IF EXISTS `kolab_cache_configuration`;
|
||||
|
@ -145,7 +151,7 @@ CREATE TABLE `kolab_cache_configuration` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
|
@ -153,7 +159,8 @@ CREATE TABLE `kolab_cache_configuration` (
|
|||
CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `configuration_type` (`folder_id`,`type`)
|
||||
INDEX `configuration_type` (`folder_id`,`type`),
|
||||
INDEX `configuration_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
DROP TABLE IF EXISTS `kolab_cache_freebusy`;
|
||||
|
@ -164,7 +171,7 @@ CREATE TABLE `kolab_cache_freebusy` (
|
|||
`uid` VARCHAR(128) CHARACTER SET ascii NOT NULL,
|
||||
`created` DATETIME DEFAULT NULL,
|
||||
`changed` DATETIME DEFAULT NULL,
|
||||
`data` TEXT NOT NULL,
|
||||
`data` LONGTEXT NOT NULL,
|
||||
`xml` LONGBLOB NOT NULL,
|
||||
`tags` VARCHAR(255) NOT NULL,
|
||||
`words` TEXT NOT NULL,
|
||||
|
@ -172,7 +179,8 @@ CREATE TABLE `kolab_cache_freebusy` (
|
|||
`dtend` DATETIME,
|
||||
CONSTRAINT `fk_kolab_cache_freebusy_folder` FOREIGN KEY (`folder_id`)
|
||||
REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
PRIMARY KEY(`folder_id`,`msguid`)
|
||||
PRIMARY KEY(`folder_id`,`msguid`),
|
||||
INDEX `freebusy_uid2msguid` (`folder_id`,`uid`,`msguid`)
|
||||
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
|
||||
|
||||
|
||||
|
|
8
plugins/libkolab/SQL/mysql/2014032700.sql
Normal file
8
plugins/libkolab/SQL/mysql/2014032700.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
ALTER TABLE `kolab_cache_configuration` ADD INDEX `configuration_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
||||
ALTER TABLE `kolab_cache_contact` ADD INDEX `contact_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
||||
ALTER TABLE `kolab_cache_event` ADD INDEX `event_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
||||
ALTER TABLE `kolab_cache_task` ADD INDEX `task_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
||||
ALTER TABLE `kolab_cache_journal` ADD INDEX `journal_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
||||
ALTER TABLE `kolab_cache_note` ADD INDEX `note_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
||||
ALTER TABLE `kolab_cache_file` ADD INDEX `file_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
||||
ALTER TABLE `kolab_cache_freebusy` ADD INDEX `freebusy_uid2msguid` (`folder_id`, `uid`, `msguid`);
|
8
plugins/libkolab/SQL/mysql/2014040900.sql
Normal file
8
plugins/libkolab/SQL/mysql/2014040900.sql
Normal file
|
@ -0,0 +1,8 @@
|
|||
ALTER TABLE `kolab_cache_contact` CHANGE `data` `data` LONGTEXT NOT NULL;
|
||||
ALTER TABLE `kolab_cache_event` CHANGE `data` `data` LONGTEXT NOT NULL;
|
||||
ALTER TABLE `kolab_cache_task` CHANGE `data` `data` LONGTEXT NOT NULL;
|
||||
ALTER TABLE `kolab_cache_journal` CHANGE `data` `data` LONGTEXT NOT NULL;
|
||||
ALTER TABLE `kolab_cache_note` CHANGE `data` `data` LONGTEXT NOT NULL;
|
||||
ALTER TABLE `kolab_cache_file` CHANGE `data` `data` LONGTEXT NOT NULL;
|
||||
ALTER TABLE `kolab_cache_configuration` CHANGE `data` `data` LONGTEXT NOT NULL;
|
||||
ALTER TABLE `kolab_cache_freebusy` CHANGE `data` `data` LONGTEXT NOT NULL;
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
class kolab_format_file extends kolab_format
|
||||
{
|
||||
public $CTYPE = 'application/x-vnd.kolab.file';
|
||||
public $CTYPE = 'application/vnd.kolab+xml';
|
||||
|
||||
protected $objclass = 'File';
|
||||
protected $read_func = 'kolabformat::readKolabFile';
|
||||
|
|
|
@ -24,9 +24,11 @@
|
|||
|
||||
class kolab_format_note extends kolab_format
|
||||
{
|
||||
public $CTYPE = 'application/x-vnd.kolab.note';
|
||||
public $CTYPE = 'application/vnd.kolab+xml';
|
||||
public $CTYPEv2 = 'application/x-vnd.kolab.note';
|
||||
|
||||
public static $fulltext_cols = array('title', 'description', 'categories');
|
||||
|
||||
protected $objclass = 'Note';
|
||||
protected $read_func = 'readNote';
|
||||
protected $write_func = 'writeNote';
|
||||
|
@ -114,4 +116,29 @@ class kolab_format_note extends kolab_format
|
|||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for kolab_storage_cache to get words to index for fulltext search
|
||||
*
|
||||
* @return array List of words to save in cache
|
||||
*/
|
||||
public function get_words()
|
||||
{
|
||||
$data = '';
|
||||
foreach (self::$fulltext_cols as $col) {
|
||||
// convert HTML content to plain text
|
||||
if ($col == 'description' && preg_match('/<(html|body)(\s[a-z]|>)/', $this->data[$col], $m) && strpos($this->data[$col], '</'.$m[1].'>')) {
|
||||
$converter = new rcube_html2text($this->data[$col], false, false, 0);
|
||||
$val = $converter->get_text();
|
||||
}
|
||||
else {
|
||||
$val = is_array($this->data[$col]) ? join(' ', $this->data[$col]) : $this->data[$col];
|
||||
}
|
||||
|
||||
if (strlen($val))
|
||||
$data .= $val . ' ';
|
||||
}
|
||||
|
||||
return array_filter(array_unique(rcube_utils::normalize_string($data, true)));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -911,6 +911,22 @@ class kolab_storage_cache
|
|||
*/
|
||||
public function uid2msguid($uid, $deleted = false)
|
||||
{
|
||||
// query local database if available
|
||||
if (!isset($this->uid2msg[$uid]) && $this->ready) {
|
||||
$this->_read_folder_data();
|
||||
|
||||
$sql_result = $this->db->query(
|
||||
"SELECT msguid FROM $this->cache_table ".
|
||||
"WHERE folder_id=? AND uid=?",
|
||||
$this->folder_id,
|
||||
$uid
|
||||
);
|
||||
|
||||
if ($sql_arr = $this->db->fetch_assoc($sql_result)) {
|
||||
$this->uid2msg[$uid] = $sql_arr['msguid'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($this->uid2msg[$uid])) {
|
||||
// use IMAP SEARCH to get the right message
|
||||
$index = $this->imap->search_once($this->folder->name, ($deleted ? '' : 'UNDELETED ') .
|
||||
|
|
|
@ -578,7 +578,7 @@ class kolab_storage_folder
|
|||
|
||||
// get XML part
|
||||
foreach ((array)$message->attachments as $part) {
|
||||
if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z]+\+)?xml!', $part->mimetype))) {
|
||||
if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z.]+\+)?xml!', $part->mimetype))) {
|
||||
$xml = $part->body ? $part->body : $message->get_part_content($part->mime_id);
|
||||
}
|
||||
else if ($part->filename || $part->content_id) {
|
||||
|
|
194
plugins/tasklist/jquery.tagedit.js
Executable file → Normal file
194
plugins/tasklist/jquery.tagedit.js
Executable file → Normal file
|
@ -5,13 +5,14 @@
|
|||
* Examples and documentation at: tagedit.webwork-albrecht.de
|
||||
*
|
||||
* Copyright (c) 2010 Oliver Albrecht <info@webwork-albrecht.de>
|
||||
* Copyright (c) 2012 Thomas Brüderli <thomas@roundcube.net>
|
||||
*
|
||||
* License:
|
||||
* This work is licensed under a MIT License
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* @author Oliver Albrecht Mial: info@webwork-albrecht.de Twitter: @webworka
|
||||
* @version 1.2.1 (11/2011)
|
||||
* @version 1.5.1 (10/2013)
|
||||
* Requires: jQuery v1.4+, jQueryUI v1.8+, jQuerry.autoGrowInput
|
||||
*
|
||||
* Example of usage:
|
||||
|
@ -52,6 +53,7 @@
|
|||
options = $.extend(true, {
|
||||
// default options here
|
||||
autocompleteURL: null,
|
||||
checkToDeleteURL: null,
|
||||
deletedPostfix: '-d',
|
||||
addedPostfix: '-a',
|
||||
additionalListClass: '',
|
||||
|
@ -67,14 +69,15 @@
|
|||
}
|
||||
},
|
||||
breakKeyCodes: [ 13, 44 ],
|
||||
checkNewEntriesCaseSensitive: false,
|
||||
checkNewEntriesCaseSensitive: false,
|
||||
texts: {
|
||||
removeLinkTitle: 'Remove from list.',
|
||||
saveEditLinkTitle: 'Save changes.',
|
||||
deleteLinkTitle: 'Delete this tag from database.',
|
||||
deleteConfirmation: 'Are you sure to delete this entry?',
|
||||
deletedElementTitle: 'This Element will be deleted.',
|
||||
breakEditLinkTitle: 'Cancel'
|
||||
breakEditLinkTitle: 'Cancel',
|
||||
forceDeleteConfirmation: 'There are more records using this tag, are you sure do you want to remove it?'
|
||||
},
|
||||
tabindex: false
|
||||
}, options || {});
|
||||
|
@ -133,7 +136,8 @@
|
|||
html += '<li class="tagedit-listelement tagedit-listelement-old">';
|
||||
html += '<span dir="'+options.direction+'">' + $(this).val() + '</span>';
|
||||
html += '<input type="hidden" name="'+baseName+'['+elementId+']" value="'+$(this).val()+'" />';
|
||||
html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'">x</a>';
|
||||
if (options.allowDelete)
|
||||
html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'">x</a>';
|
||||
html += '</li>';
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +159,8 @@
|
|||
// put an input field at the End
|
||||
// Put an empty element at the end
|
||||
html = '<li class="tagedit-listelement tagedit-listelement-new">';
|
||||
html += '<input type="text" name="'+baseName+'[]" value="" id="tagedit-input" disabled="disabled" class="tagedit-input-disabled" dir="'+options.direction+'"/>';
|
||||
if (options.allowAdd)
|
||||
html += '<input type="text" name="'+baseName+'[]" value="" id="tagedit-input" disabled="disabled" class="tagedit-input-disabled" dir="'+options.direction+'"/>';
|
||||
html += '</li>';
|
||||
html += '</ul>';
|
||||
|
||||
|
@ -169,16 +174,16 @@
|
|||
.each(function() {
|
||||
$(this).autoGrowInput({comfortZone: 15, minWidth: 15, maxWidth: 20000});
|
||||
|
||||
// Event ist triggert in case of choosing an item from the autocomplete, or finish the input
|
||||
// Event is triggert in case of choosing an item from the autocomplete, or finish the input
|
||||
$(this).bind('transformToTag', function(event, id) {
|
||||
var oldValue = (typeof id != 'undefined' && id.length > 0);
|
||||
var oldValue = (typeof id != 'undefined' && (id.length > 0 || id > 0));
|
||||
|
||||
var checkAutocomplete = oldValue == true? false : true;
|
||||
var checkAutocomplete = oldValue == true || options.autocompleteOptions.noCheck ? false : true;
|
||||
// check if the Value ist new
|
||||
var isNewResult = isNew($(this).val(), checkAutocomplete);
|
||||
if(isNewResult[0] === true || isNewResult[1] != null) {
|
||||
if(isNewResult[0] === true || (isNewResult[0] === false && typeof isNewResult[1] == 'string')) {
|
||||
|
||||
if(oldValue == false && isNewResult[1] != null) {
|
||||
if(oldValue == false && typeof isNewResult[1] == 'string') {
|
||||
oldValue = true;
|
||||
id = isNewResult[1];
|
||||
}
|
||||
|
@ -199,7 +204,8 @@
|
|||
|
||||
// close autocomplete
|
||||
if(options.autocompleteOptions.source) {
|
||||
$(this).autocomplete( "close" );
|
||||
if($(this).is(':ui-autocomplete'))
|
||||
$(this).autocomplete( "close" );
|
||||
}
|
||||
|
||||
})
|
||||
|
@ -288,7 +294,7 @@
|
|||
}
|
||||
return false;
|
||||
})
|
||||
// forward focus event
|
||||
// forward focus event (on tabbing through the form)
|
||||
.focus(function(e){ $(this).click(); })
|
||||
}
|
||||
|
||||
|
@ -321,7 +327,7 @@
|
|||
}
|
||||
|
||||
textfield.remove();
|
||||
$(this).find('a.tagedit-save, a.tagedit-break, a.tagedit-delete, tester').remove(); // Workaround. This normaly has to be done by autogrow Plugin
|
||||
$(this).find('a.tagedit-save, a.tagedit-break, a.tagedit-delete').remove(); // Workaround. This normaly has to be done by autogrow Plugin
|
||||
$(this).removeClass('tagedit-listelement-edit').unbind('finishEdit');
|
||||
return false;
|
||||
});
|
||||
|
@ -356,7 +362,16 @@
|
|||
.click(function() {
|
||||
window.clearTimeout(closeTimer);
|
||||
if(confirm(options.texts.deleteConfirmation)) {
|
||||
markAsDeleted($(this).parent());
|
||||
var canDelete = checkToDelete($(this).parent());
|
||||
if (!canDelete && confirm(options.texts.forceDeleteConfirmation)) {
|
||||
markAsDeleted($(this).parent());
|
||||
}
|
||||
|
||||
if(canDelete) {
|
||||
markAsDeleted($(this).parent());
|
||||
}
|
||||
|
||||
$(this).parent().find(':text').trigger('finishEdit', [true]);
|
||||
}
|
||||
else {
|
||||
$(this).parent().find(':text').trigger('finishEdit', [true]);
|
||||
|
@ -386,6 +401,42 @@
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the tag select to be deleted is used by other records using an Ajax request.
|
||||
*
|
||||
* @param element
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function checkToDelete(element) {
|
||||
// if no URL is provide will not verify
|
||||
if(options.checkToDeleteURL === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var inputName = element.find('input:hidden').attr('name');
|
||||
var idPattern = new RegExp('\\d');
|
||||
var tagId = inputName.match(idPattern);
|
||||
var checkResult = false;
|
||||
|
||||
$.ajax({
|
||||
async : false,
|
||||
url : options.checkToDeleteURL,
|
||||
dataType: 'json',
|
||||
type : 'POST',
|
||||
data : { 'tagId' : tagId},
|
||||
complete: function (XMLHttpRequest, textStatus) {
|
||||
|
||||
// Expected JSON Object: { "success": Boolean, "allowDelete": Boolean}
|
||||
var result = $.parseJSON(XMLHttpRequest.responseText);
|
||||
if(result.success === true){
|
||||
checkResult = result.allowDelete;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a single Tag as deleted.
|
||||
*
|
||||
|
@ -451,12 +502,11 @@
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// If there is an entry for that already in the autocomplete, don't use it (Check could be case sensitive or not)
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
var label = typeof result[i] == 'string' ? result[i] : result[i].label;
|
||||
if (options.checkNewEntriesCaseSensitive == false)
|
||||
label = label.toLowerCase();
|
||||
var resultValue = result[i].label? result[i].label : result[i];
|
||||
var label = options.checkNewEntriesCaseSensitive == true? resultValue : resultValue.toLowerCase();
|
||||
if (label == compareValue) {
|
||||
isNew = false;
|
||||
autoCompleteId = typeof result[i] == 'string' ? i : result[i].id;
|
||||
|
@ -476,60 +526,60 @@
|
|||
// See related thread: http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields
|
||||
|
||||
$.fn.autoGrowInput = function(o) {
|
||||
|
||||
o = $.extend({
|
||||
maxWidth: 1000,
|
||||
minWidth: 0,
|
||||
comfortZone: 70
|
||||
}, o);
|
||||
|
||||
this.filter('input:text').each(function(){
|
||||
|
||||
var minWidth = o.minWidth || $(this).width(),
|
||||
val = '',
|
||||
input = $(this),
|
||||
testSubject = $('<tester/>').css({
|
||||
position: 'absolute',
|
||||
top: -9999,
|
||||
left: -9999,
|
||||
width: 'auto',
|
||||
fontSize: input.css('fontSize'),
|
||||
fontFamily: input.css('fontFamily'),
|
||||
fontWeight: input.css('fontWeight'),
|
||||
letterSpacing: input.css('letterSpacing'),
|
||||
whiteSpace: 'nowrap'
|
||||
}),
|
||||
check = function() {
|
||||
|
||||
if (val === (val = input.val())) {return;}
|
||||
|
||||
// Enter new content into testSubject
|
||||
var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
|
||||
testSubject.html(escaped);
|
||||
|
||||
// Calculate new width + whether to change
|
||||
var testerWidth = testSubject.width(),
|
||||
newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
|
||||
currentWidth = input.width(),
|
||||
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|
||||
|| (newWidth > minWidth && newWidth < o.maxWidth);
|
||||
|
||||
// Animate width
|
||||
if (isValidWidthChange) {
|
||||
input.width(newWidth);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
testSubject.insertAfter(input);
|
||||
|
||||
$(this).bind('keyup keydown blur update', check);
|
||||
|
||||
check();
|
||||
});
|
||||
|
||||
return this;
|
||||
|
||||
o = $.extend({
|
||||
maxWidth: 1000,
|
||||
minWidth: 0,
|
||||
comfortZone: 70
|
||||
}, o);
|
||||
|
||||
this.filter('input:text').each(function(){
|
||||
|
||||
var minWidth = o.minWidth || $(this).width(),
|
||||
val = '',
|
||||
input = $(this),
|
||||
testSubject = $('<tester/>').css({
|
||||
position: 'absolute',
|
||||
top: -9999,
|
||||
left: -9999,
|
||||
width: 'auto',
|
||||
fontSize: input.css('fontSize'),
|
||||
fontFamily: input.css('fontFamily'),
|
||||
fontWeight: input.css('fontWeight'),
|
||||
letterSpacing: input.css('letterSpacing'),
|
||||
whiteSpace: 'nowrap'
|
||||
}),
|
||||
check = function() {
|
||||
|
||||
if (val === (val = input.val())) {return;}
|
||||
|
||||
// Enter new content into testSubject
|
||||
var escaped = val.replace(/&/g, '&').replace(/\s/g,' ').replace(/</g, '<').replace(/>/g, '>');
|
||||
testSubject.html(escaped);
|
||||
|
||||
// Calculate new width + whether to change
|
||||
var testerWidth = testSubject.width(),
|
||||
newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
|
||||
currentWidth = input.width(),
|
||||
isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
|
||||
|| (newWidth > minWidth && newWidth < o.maxWidth);
|
||||
|
||||
// Animate width
|
||||
if (isValidWidthChange) {
|
||||
input.width(newWidth);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
testSubject.insertAfter(input);
|
||||
|
||||
$(this).bind('keyup keydown blur update', check);
|
||||
|
||||
check();
|
||||
});
|
||||
|
||||
return this;
|
||||
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
})(jQuery);
|
|
@ -55,7 +55,7 @@ html.ie7 #taskselector li.selected.overdue .count {
|
|||
color: #fff;
|
||||
}
|
||||
|
||||
html.ie7 #tagslist li,
|
||||
html.ie7 .tagcloud li,
|
||||
html.ie7 #taskselector li {
|
||||
float: left;
|
||||
}
|
||||
|
|
113
plugins/tasklist/skins/larry/tagedit.css
Normal file
113
plugins/tasklist/skins/larry/tagedit.css
Normal file
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* Styles of the tagedit inputsforms
|
||||
*/
|
||||
.tagedit-list {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 4px 4px 0 5px;
|
||||
overflow: auto;
|
||||
min-height: 26px;
|
||||
background: #fff;
|
||||
border: 1px solid #b2b2b2;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
-moz-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
-webkit-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
-o-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
}
|
||||
.tagedit-list li.tagedit-listelement {
|
||||
list-style-type: none;
|
||||
float: left;
|
||||
margin: 0 4px 4px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* New Item input */
|
||||
.tagedit-list li.tagedit-listelement-new input {
|
||||
border: 0;
|
||||
height: 100%;
|
||||
padding: 4px 1px;
|
||||
width: 15px;
|
||||
background: #fff;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
-o-box-shadow: none;
|
||||
}
|
||||
.tagedit-list li.tagedit-listelement-new input:focus {
|
||||
box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
-o-box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
.tagedit-list li.tagedit-listelement-new input.tagedit-input-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Item that is put to the List */
|
||||
.tagedit span.tag-element,
|
||||
.tagedit-list li.tagedit-listelement-old {
|
||||
padding: 3px 6px 1px 6px;
|
||||
background: #ddeef5;
|
||||
background: -moz-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#edf6fa), color-stop(100%,#d6e9f3));
|
||||
background: -o-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
background: -ms-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
background: linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
border: 1px solid #c2dae5;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
color: #0d5165;
|
||||
}
|
||||
|
||||
.tagedit span.tag-element {
|
||||
margin-right: 0.6em;
|
||||
padding: 2px 6px;
|
||||
/* cursor: pointer; */
|
||||
}
|
||||
|
||||
.tagedit span.tag-element.inherit {
|
||||
color: #666;
|
||||
background: #f2f2f2;
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-close,
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-break,
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-delete,
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-save {
|
||||
text-indent: -2000px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 -4px 0 6px;
|
||||
background: url('data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAOCAYAAAD0f5bSAAAAgUlEQVQoz2NgQAKzdxwWAOIEIG5AwiC+AAM2AJQIAOL3QPwfCwaJB6BrSMChGB0nwDQYwATP3nn4f+Ge4ygKQXyQOJKYAUjTepjAm09fwBimEUTDxJA0rWdANxWmaMXB0xiGwDADurthGkEAmwbqaCLFeWQFBOlBTlbkkp2MSE2wAA8R50rWvqeRAAAAAElFTkSuQmCC') left 1px no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tagedit-list li.tagedit-listelement-old span {
|
||||
display: inline-block;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
/** Special hacks for IE7 **/
|
||||
|
||||
html.ie7 .tagedit span.tag-element,
|
||||
html.ie7 .tagedit-list li.tagedit-listelement-old {
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#edf6fa', endColorstr='#d6e9f3', GradientType=0);
|
||||
}
|
||||
|
||||
html.ie7 .tagedit-list li.tagedit-listelement span {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
html.ie7 .tagedit-list li.tagedit-listelement-old a.tagedit-close {
|
||||
left: 5px;
|
||||
}
|
||||
|
|
@ -86,7 +86,7 @@ body.attachmentwin #topnav .topright {
|
|||
padding-right: 0.3em;
|
||||
}
|
||||
|
||||
#tagslist li,
|
||||
.tagcloud li,
|
||||
#taskselector li a {
|
||||
display: inline-block;
|
||||
color: #004458;
|
||||
|
@ -117,7 +117,7 @@ body.attachmentwin #topnav .topright {
|
|||
color: #97b3bf;
|
||||
}
|
||||
|
||||
#tagslist li.selected,
|
||||
.tagcloud li.selected,
|
||||
#taskselector li.selected a {
|
||||
color: #fff;
|
||||
background: #005d76;
|
||||
|
@ -180,13 +180,13 @@ body.attachmentwin #topnav .topright {
|
|||
border-color: #ff3800 transparent;
|
||||
}
|
||||
|
||||
#tagslist {
|
||||
.tagcloud {
|
||||
padding: 0;
|
||||
margin: 6px;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#tagslist li {
|
||||
.tagcloud li {
|
||||
display: inline-block;
|
||||
color: #004458;
|
||||
padding-right: 0.2em;
|
||||
|
@ -196,14 +196,14 @@ body.attachmentwin #topnav .topright {
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
#tagslist li.inactive {
|
||||
.tagcloud li.inactive {
|
||||
color: #89b3be;
|
||||
padding-right: 0.6em;
|
||||
font-size: 80%;
|
||||
/* display: none; */
|
||||
}
|
||||
|
||||
#tagslist li .count {
|
||||
.tagcloud li .count {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
margin-left: 5px;
|
||||
|
@ -221,7 +221,7 @@ body.attachmentwin #topnav .topright {
|
|||
}
|
||||
|
||||
.tag-draghelper .tag .count,
|
||||
#tagslist li.inactive .count {
|
||||
.tagcloud li.inactive .count {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -852,99 +852,6 @@ label.block {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Styles of the tagedit inputsforms
|
||||
*/
|
||||
.tagedit-list {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 4px 4px 0 5px;
|
||||
overflow: auto;
|
||||
min-height: 26px;
|
||||
background: #fff;
|
||||
border: 1px solid #b2b2b2;
|
||||
border-radius: 4px;
|
||||
box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
-moz-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
-webkit-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
-o-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
|
||||
}
|
||||
.tagedit-list li.tagedit-listelement {
|
||||
list-style-type: none;
|
||||
float: left;
|
||||
margin: 0 4px 4px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* New Item input */
|
||||
.tagedit-list li.tagedit-listelement-new input {
|
||||
border: 0;
|
||||
height: 100%;
|
||||
padding: 4px 1px;
|
||||
width: 15px;
|
||||
background: #fff;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
-o-box-shadow: none;
|
||||
}
|
||||
.tagedit-list li.tagedit-listelement-new input:focus {
|
||||
box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
-o-box-shadow: none;
|
||||
outline: none;
|
||||
}
|
||||
.tagedit-list li.tagedit-listelement-new input.tagedit-input-disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Item that is put to the List */
|
||||
.form-section span.tag-element,
|
||||
.tagedit-list li.tagedit-listelement-old {
|
||||
padding: 3px 0 1px 6px;
|
||||
background: #ddeef5;
|
||||
background: -moz-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#edf6fa), color-stop(100%,#d6e9f3));
|
||||
background: -o-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
background: -ms-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
background: linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
|
||||
border: 1px solid #c2dae5;
|
||||
-moz-border-radius: 4px;
|
||||
-webkit-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
color: #0d5165;
|
||||
}
|
||||
|
||||
.form-section span.tag-element {
|
||||
margin-right: 0.6em;
|
||||
padding: 2px 6px;
|
||||
/* cursor: pointer; */
|
||||
}
|
||||
|
||||
.form-section span.tag-element.inherit {
|
||||
color: #666;
|
||||
background: #f2f2f2;
|
||||
border-color: #ddd;
|
||||
}
|
||||
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-close,
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-break,
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-delete,
|
||||
.tagedit-list li.tagedit-listelement-old a.tagedit-save {
|
||||
text-indent: -2000px;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0 2px 0 6px;
|
||||
background: url(sprites.png) -2px -122px no-repeat;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
/** Special hacks for IE7 **/
|
||||
/** They need to be in this file to also affect the task-create dialog embedded in mail view **/
|
||||
|
||||
|
@ -952,17 +859,3 @@ html.ie7 #taskedit-completeness-slider {
|
|||
display: inline;
|
||||
}
|
||||
|
||||
html.ie7 .form-section span.tag-element,
|
||||
html.ie7 .tagedit-list li.tagedit-listelement-old {
|
||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#edf6fa', endColorstr='#d6e9f3', GradientType=0);
|
||||
}
|
||||
|
||||
html.ie7 .tagedit-list li.tagedit-listelement span {
|
||||
position: relative;
|
||||
top: -3px;
|
||||
}
|
||||
|
||||
html.ie7 .tagedit-list li.tagedit-listelement-old a.tagedit-close {
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div id="tagsbox" class="uibox listbox">
|
||||
<h2 class="boxtitle"><roundcube:label name="tasklist.tags" id="taglist" /></h2>
|
||||
<div class="scroller">
|
||||
<roundcube:object name="plugin.tagslist" id="tagslist" />
|
||||
<roundcube:object name="plugin.tagslist" id="tagslist" class="tagcloud" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1205,7 +1205,7 @@ function rcube_tasklist_ui(settings)
|
|||
animSpeed: 100,
|
||||
allowEdit: false,
|
||||
checkNewEntriesCaseSensitive: false,
|
||||
autocompleteOptions: { source: tags, minLength: 0 },
|
||||
autocompleteOptions: { source: tags, minLength: 0, noCheck: true },
|
||||
texts: { removeLinkTitle: rcmail.gettext('removetag', 'tasklist') }
|
||||
});
|
||||
|
||||
|
@ -1341,9 +1341,6 @@ function rcube_tasklist_ui(settings)
|
|||
resizable: (!bw.ie6 && !bw.ie7), // disable for performance reasons
|
||||
closeOnEscape: false,
|
||||
title: rcmail.gettext((action == 'edit' ? 'edittask' : 'newtask'), 'tasklist'),
|
||||
open: function() {
|
||||
$dialog.parent().find('.ui-dialog-buttonset .ui-button').first().addClass('mainaction');
|
||||
},
|
||||
close: function() {
|
||||
editform.hide().appendTo(document.body);
|
||||
$dialog.dialog('destroy').remove();
|
||||
|
|
|
@ -782,11 +782,9 @@ class tasklist extends rcube_plugin
|
|||
|
||||
$this->ui->init_templates();
|
||||
echo $this->api->output->parse('tasklist.taskedit', false, false);
|
||||
echo html::tag('link', array('rel' => 'stylesheet', 'type' => 'text/css', 'href' => $this->url($this->local_skin_path() . '/tagedit.css'), 'nl' => true));
|
||||
echo html::tag('script', array('type' => 'text/javascript'),
|
||||
"rcmail.set_env('tasklists', " . json_encode($this->api->output->env['tasklists']) . ");\n".
|
||||
// "rcmail.set_env('deleteicon', '" . $this->api->output->env['deleteicon'] . "');\n".
|
||||
// "rcmail.set_env('cancelicon', '" . $this->api->output->env['cancelicon'] . "');\n".
|
||||
// "rcmail.set_env('loadingicon', '" . $this->api->output->env['loadingicon'] . "');\n".
|
||||
"rcmail.add_label(" . json_encode($texts) . ");\n"
|
||||
);
|
||||
exit;
|
||||
|
|
|
@ -80,6 +80,8 @@ class tasklist_ui
|
|||
|
||||
$this->plugin->include_script('jquery.tagedit.js');
|
||||
$this->plugin->include_script('tasklist.js');
|
||||
|
||||
$this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/tagedit.css');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue