Page MenuHomePhorge

No OneTemporary

Size
538 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/CHANGELOG b/CHANGELOG
index 48ff235c4..dc9d805bd 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,2693 +1,2695 @@
CHANGELOG Roundcube Webmail
===========================
- SMTP GSSAPI support via krb_authentication plugin (#6417)
- Removed referer_check option (#6440)
- Update to TinyMCE 4.8.2
- Plugin API: Added 'raise_error' hook (#6199)
- Managesieve: Added support for 'editheader' extension - RFC5293 (#5954)
- Password: Added 'modoboa' driver (#6361)
- Password: Fix bug where password_dovecotpw_with_method setting could be ignored (#6436)
- Password: Fix bug where new users could skip forced password change (#6434)
+- Elastic: On mobile/tablet jump to the list on folder selection (#6415)
+- Elastic: Various improvements on mail compose screen (#6413)
- Elastic: Support new-line char as a separator for pasted recipients (#6460)
- Elastic: Improved UX of search dialogs (#6416)
- Elastic: Fix unwanted thread expanding when selecting a collapsed thread in non-mobile mode (#6445)
- Log errors caused by low pcre.backtrack_limit when sending a mail message (#6433)
- Fix bug where autocomplete list could be displayed out of screen (#6469)
- Fix style/navigation on error page depending on authentication state (#6362)
- Fix so invalid smtp_helo_host is never used, fallback to localhost (#6408)
- Fix custom logo size in Elastic (#6424)
- Fix listing the same attachment multiple times on forwarded messages
- Fix compatibility with MySQL 8 - error on 'system' table use
- Managesieve: Fix bug where show_real_foldernames setting wasn't respected (#6422)
- New_user_identity: Fix %fu/%u vars substitution in user specific LDAP params (#6419)
- Fix support for "allow-from <uri>" in "x_frame_options" config option (#6449)
- Fix bug where valid content between HTML comments could have been skipped in some cases (#6464)
- Fix multiple VCard field search (#6466)
- Fix session issue on long running requests (#6470)
RELEASE 1.4-beta
----------------
- Added new skin with mobile support - the Elastic
- Support Redis cache
- Email Resent (Bounce) feature (#4985)
- Improved Mailvelope integration
- Added private key listing and generating to identity settings
- Enable encrypt & sign option if Mailvelope supports it
- Allow contacts without an email address (#5079)
- Support SMTPUTF8 and relax email address validation to support unicode in local part (#5120)
- Support for IMAP folders that cannot contain both folders and messages (#5057)
- Update to jQuery-3.3.1
- Update to jQuery-minicolors 2.2.6
- Update to TinyMCE 4.7.13
- Remove sample PHP configuration from .htaccess and .user.ini files (#5850)
- Extend skin_logo setting to allow per skin logos (#6272)
- Use Masterminds/HTML5 parser for better HTML5 support (#5761)
- Add More actions button in Contacts toolbar with Copy/Move actions (#6081)
- Display an error when clicking disabled link to register protocol handler (#6079)
- Add option trusted_host_patterns (#6009, #5752)
- Support additional connect parameters in PostgreSQL database wrapper
- Use UI dialogs instead of confirm() and alert() where possible
- Display value of the SMTP message size limit in the error message (#6032)
- Show message flagged status in message view (#5080)
- Skip redundant INSERT query on successful logon when using PHP7
- Replace display_version with display_product_version (#5904)
- Extend disabled_actions config so it accepts also button names (#5903)
- Handle remote stylesheets the same as remote images, ask the user to allow them (#5994)
- Add Message-ID to the sendmail log (#5871)
- Add option to hide folders in share/other-user namespace or outside of the personal namespace root (#5073)
- Archive: Fix archiving by sender address on cyrus-imap
- Archive: Style Archive folder also on folder selector and folder manager lists
- Archive: Add Thunderbird compatible Month option (#5623)
- Archive: Create archive folder automatically if it's configured, but does not exist (#6076)
- Enigma: Add button to send mail unencrypted if no key was found (#5913)
- Enigma: Add options to set PGP cipher/digest algorithms (#5645)
- Enigma: Multi-host support
- Managesieve: Add ability to disable filter sets and other actions (#5496, #5898)
- Managesieve: Add option managesieve_forward to enable settings dialog for simple forwarding (#6021)
- Managesieve: Support filter action with custom IMAP flags (#6011)
- Managesieve: Support 'mime' extension tests - RFC5703 (#5832)
- Managesieve: Support GSSAPI authentication with krb_authentication plugin (#5779)
- Managesieve: Support enabling the plugin for specified hosts only (#6292)
- Password: Support host variables in password_db_dsn option (#5955)
- Password: Automatic virtualmin domain setting, removed password_virtualmin_format option (#5759)
- Password: Added password_username_format option (#5766)
- subscriptions_option: show \\Noselect folders greyed out (#5621)
- zipdownload: Added option to define size limit for multiple messages download (#5696)
- vcard_attachments: Add possibility to send contact vCard from Contacts toolbar (#6080)
- Changed defaults for smtp_user (%u), smtp_pass (%p) and smtp_port (587)
- Composer: Fix certificate validation errors by using packagist only (#5148)
- Add --get and --extract arguments and CACHEDIR env-variable support to install-jsdeps.sh (#5882)
- Support _filter and _scope as GET arguments for opening mail UI (#5825)
- Various improvements for templating engine and skin behaviours
- Support conditional include
- Support for 'link' objects
- Support including files with path relative to templates directory
- Use <button> instead of <input> for submit button on logon screen
- Support skin localization (#5853)
- Reset onerror on images if placeholder does not exist to prevent from requests storm
- Unified and simplified code for loading content frame for responses and identities
- Display contact import and advanced search in popup dialogs
- Display a dialog for mail import with supported format description and upload size hint
- Make possible to set (some) config options from a skin
- Added optional checkbox selection for the list widget
- Make 'compose' command always enabled
- Add .log suffix to all log file names, add option log_file_ext to control this (#313)
- Return "401 Unauthorized" status when login fails (#5663)
- Support both comma and semicolon as recipient separator, drop recipients_separator option (#5092)
- Plugin API: Added 'show_bytes' hook (#5001)
- Add option to not indent quoted text on top-posting reply (#5105)
- Removed global $CONFIG variable
- Removed debug_level setting
- Support AUTHENTICATE LOGIN for IMAP connections (#5563)
- Support LDAP GSSAPI authentication (#5703)
- Localized timezone selector (#4983)
- Use 7bit encoding for ISO-2022-* charsets in sent mail (#5640)
- Handle inline images also inside multipart/mixed messages (#5905)
- Allow style tags in HTML editor on composed/reply messages (#5751)
- Use Github API as a fallback to fetch js dependencies to workaround throttling issues (#6248)
- Show confirm dialog when moving folders using drag and drop (#6119)
- Fix bug where new_user_dialog email check could have been circumvented by deleting / abandoning session (#5929)
- Fix skin extending for assets (#5115)
- Fix handling of forwarded messages inside of a TNEF message (#5632)
- Fix bug where attachment size wasn't visible when the filename was too long (#6033)
- Fix checking table columns when there's more schemas/databases in postgres/mysql (#6047)
- Fix css conflicts in user interface and e-mail content (#5891)
- Fix duplicated signature when using Back button in Chrome (#5809)
- Fix touch event issue on messages list in IE/Edge (#5781)
- Fix so links over images are not removed in plain text signatures converted from HTML (#4473)
- Fix various issues when downloading files with names containing non-ascii chars, use RFC 2231 (#5772)
- Fix PHP warnings on dummy QUOTA responses in Courier-IMAP 4.17.1 (#6374)
- Fix so fallback from BINARY to BODY FETCH is used also on [PARSE] errors in dovecot 2.3 (#6383)
- Enigma: Fix deleting keys with authentication subkeys (#6381)
- Fix invalid regular expressions that throw warnings on PHP 7.3 (#6398)
- Fix so Classic skin splitter does not escape out of window (#6397)
- Fix XSS issue in handling invalid style tag content (#6410)
RELEASE 1.3.7
-------------
- Fix PHP Warning: Use of undefined constant IDNA_DEFAULT on systems without php-intl (#6244)
- Fix bug where some parts of quota information could have been ignored (#6280)
- Fix bug where some escape sequences in html styles could bypass security checks
- Fix bug where some forbidden characters on Cyrus-IMAP were not prevented from use in folder names
- Fix bug where only attachments with the same name would be ignored on zip download (#6301)
- Fix bug where unicode contact names could have been broken/emptied or caused DB errors (#6299)
- Fix bug where after "mark all folders as read" action message counters were not reset (#6307)
- Enigma: [EFAIL] Don't decrypt PGP messages with no MDC protection (#6289)
- Fix bug where some HTML comments could have been malformed by HTML parser (#6333)
RELEASE 1.3.6
-------------
- Fix parsing date strings (e.g. from a Date: mail header) with comments (#6216)
- Fix PHP 7.2: count(): Parameter must be an array in enchant-based spellchecker (#6234)
- Fix possible IMAP command injection and type juggling vulnerabilities (#6229)
- Enigma: Fix key selection for signing
- Enigma: Enable keypair generation on Internet Explorer 11
- Fix check_request() bypass in places using get_uids() [CVE-2018-9846] (#6238)
- Fix bug where usernames without domain part could be malformed or converted to lower-case on logon (#6224)
RELEASE 1.3.5
-------------
- Managesieve: Fix bug where text: syntax was forced for strings longer than 1024 characters (#6143)
- Managesieve: Fix missing Save button in Edit Filter Set page of Classic skin (#6154)
- Fix duplicated labels in Test SMTP Config section (#6166)
- Fix PHP Warning: exif_read_data(...): Illegal IFD size (#6169)
- Enigma: Fix key generation in Safari by upgrade to OpenPGP 2.6.2 (#6149)
- Fix security issue in remote content blocking on HTML image and style tags (#6178)
- Added 9pt and 11pt to the list of font sizes in HTML editor
- Fix handling encoding of HTML tags in "inline" JSON output (#6207)
- Fix bug where some unix timestamps were not handled correctly by rcube_utils::anytodatetime() (#6212)
RELEASE 1.3.4
-------------
- Fix bug where contacts search could skip some records (#6130)
- Fix possible information leak - add more strict sql error check on user creation (#6125)
- Fix a couple of warnings on PHP 7.2 (#6098)
- Fix broken long filenames when using imap4d server - workaround server bug (#6048)
- Fix so temp_dir misconfiguration prints an error to the log (#6045)
- Fix untagged COPYUID responses handling - again (#5982)
- Fix PHP warning "idn_to_utf8(): INTL_IDNA_VARIANT_2003 is deprecated" with PHP 7.2 (#6075)
- Fix bug where Archive folder wasn't auto-created on login with create_default_folders=true
- Fix performance issue when parsing malformed and long Date header (#6087)
- Fix syntax error in mssql.initial.sql (#6097)
- Fix bug where contacts export by selection returned no more than 10 entries (#6103)
- Fix searching contacts by address in LDAP source (#6084)
- Fix X-Frame-Options:ALLOW-FROM support, remove custom click-jacking protection (#6057)
RELEASE 1.3.3
-------------
- Fix decoding of mailto: links with + character in HTML messages (#6020)
- Fix false reporting of failed upgrade in installto.sh (#6019)
- Fix file disclosure vulnerability caused by insufficient input validation [CVE-2017-16651] (#6026)
- Fix mangled non-ASCII characters in links in HTML messages (#6028)
RELEASE 1.3.2
-------------
- Fix bug where pink image was used instead of a thumbnail when image resize fails (#5933)
- Fix so files size/count limit is verified (client-side) also on drag-n-drop uploads (#5940)
- Fix invalid template loading on a message error in preview frame (#5941)
- Fix bug where HTML messages could have been rendered empty on some systems (#5957)
- Fix wording of "Mark previewed messages as read" to "Mark messages as read" (#5952)
- Enigma: Fix decryption of messages encoded with non-ascii charset (#5962)
- Fix missing cursor in HTML editor on mail reply (#5969)
- Fix (again) bug where image data URIs in css style were treated as evil/remote in mail preview (#5580)
- Fix bug where mail search could return empty result on servers without SORT capability (#5973)
- Fix bug where assets_path wasn't added to some watermark frames
- Fix so untagged COPYUID responses are also supported according to RFC6851 (#5982)
- Fix issue caused by non-default session.cookie_lifetime setting (#5961)
- Fix Edge encoding bug when pasting text into the HTML editor, update to TinyMCE 4.5.8 (#5885)
- Fix handling of unknown Content-Disposition type (#6002)
- Fix truncated folder name on messages list in multi-folder mode, for folders with non-ascii characters (#6004)
- Fix bug where removing the last subfolder did not hide toggle button on its parent record (#6007)
- Fix bug where ghost messages could be added to the list after fast delete (#5941)
RELEASE 1.3.1
-------------
- Add Preferences > Mailbox View > Main Options > Layout (#5829)
- Password: Fix compatibility with PHP 7+ in cpanel_webmail driver (#5820)
- Managesieve: Fix parsing dot-staffed lines in multiline text (#5838)
- Managesieve: Fix AM/PM suffix in vacation time selectors
- Managesieve: Fix bug where 'exists' operator was reset to 'contains' (#5899)
- Remove non-printable characters from filenames on download/display (#5880)
- Fix decoding non-ascii attachment names from TNEF attachments (#5646, #5799)
- Fix uninitialized string offset in rcube_utils::bin2ascii() and make sure rcube_utils::random_bytes() result has always requested length (#5788)
- Fix bug where HTML messages with @media styles could moddify style of page body (#5811)
- Fix style issue on selected and unfocused message that is part of a thread (#5798)
- Fix bug where a.button style from managesieve plugin could impact other elements (#5800)
- Fix position of selected icon for (Mailvelope) Encrypt button
- Fix fatal error when using DMY- or MDY-based date format in PostgreSQL (#5808)
- Fix bug where errors were not printed when using bin/update.sh (#5834)
- Fix PHP 7.2 warnings on count() use (#5845)
- Fix bug where Chrome could not upload the same file that was selected before (#5854)
- Fix duplicate messages on the list after deleting messages on the next to the last page (#5862)
- Fix bug where messages count was not updated after delete when imap_cache is set (#5872)
- Fix potential XSS vulnerability with malformed HTML message markup
- Fix sending message with "Too many public recipients" dialog buttons (#5924)
- Bring back double-click behavior on the message list which was removed in 1.3.0 (#5823)
- Enigma: Fix decrypting an encrypted+signed message when signature verification fails (#5914)
RELEASE 1.3.0
-------------
- Update to TinyMCE 4.5.7
- Fix bug where invalid recipients could be silently discarded (#5739)
- Fix conflict with _gid cookie of Google Analytics (#5748)
- Print error from CLI scripts when system/exec function is disabled (#5744)
- Fix bug where comment notation within style tag would cause the whole style to be ignored (#5747)
- Fix bug where it wasn't possible to scroll folders list in Edge (#5750)
- Fix folders list sorting on Windows - if php-intl is available (#5732)
- Fix addressbook searching by gender (#5757)
- Fix prevention from using % and * characters in folder name (#5762)
- Fix POST parameter reflection in default_charset selector (#5768)
- Enigma: Fix compatibility with assets_dir
- Managesieve: Skip redundant LISTSCRIPTS command
- Fix SQL syntax error on MariaDB 10.2 (#5774)
- Fix bug where zipdownload ignored files with the same name (#5777)
- Fix bug where it wasn't possible to set timezone to auto-detected value (#5782)
RELEASE 1.3-rc
--------------
- "Flattened" the larry theme: fresher look by removing shadows and gradients
- Support logging to php://stdout (#5721)
- Add support for DelSp=Yes in format=flowed messages (#5702)
- Update to jQuery 3.2.1
- Update to TinyMCE 4.5.6
- Plugin API: Call message_part_structure hook for sub-parts of multipart/alternative message (#5678)
- Enigma: Always use detached signatures (#5624)
- Enigma: Fix handling of messages with nested PGP encrypted parts (#5634)
- Minimize unwanted message loading in preview frame on drag (#5616)
- Fix failing database schema check in all engines except mysql (#5730)
- Fix autocomplete popup closing with click outside the input, don't handle Tab key as Enter (#5606)
- Fix jsdeps.json synchronization on update, warn about missing requirements of install-jsdeps.sh (#5598)
- Fix missing thread expand icon on search result in widescreen mode (#5613)
- Fix bug where image data URIs in css style were treated as evil/remote in mail preview (#5580)
- Fix bug where external content in src attribute of input/video tags was not secured (#5583)
- Fix PHP error on update of a contact with multiple email addresses when using PHP 7.1 (#5587)
- Fix bug where mail content frame couldn't be reset in some corner cases (#5608)
- Fix bug where some classic skin images were not displayed in IE/Edge (#5614)
- Fix bug where signature couldn't be added above the quote in Firefox 51 (#5628)
- Fix regression where groups with email address were resolved to its members' addresses
- Fix update of group name in the contacts list header on group rename (#5648)
- Add rewrite rule to disable access to /vendor/bin folder in .htaccess (#5630)
- Fix bug where it was too easy accidentally move a folder when using the subscription checkbox (#5655)
- Managesieve: Fix parser issue with empty lines between comments (#5657)
- Managesieve: Fix possible defect in handling \r\n in scripts (#5685)
- Fix/rephrase "unsaved changes" warning when cancelling a draft (#5610)
- Fix XSS issue in handling of a style tag inside of an svg element [CVE-2017-6820]
- Fix bug where settings/upload.inc could not be used by plugins (#5694)
- Fix regression in LDAP fuzzy search where it always used prefix search instead (#5713)
- Fix bug where namespace prefix could not be truncated on folders list if show_real_foldernames=true (#5695)
- Fix undesired effects when postgres database uses different timezone than PHP host (#5708)
- Installer: Fix DB schema initialization on MS SQL Server
- Fix bug where base_dn setting was ignored inside group_filters (#5720)
- Password: Fix security issue in virtualmin and sasl drivers [CVE-2017-8114]
RELEASE 1.3-beta
----------------
- Nicely handle contact deletion on contact edit (#5522)
- vcard_attachments: Add possibility to attach contact vCard to composed message (#4997)
- Preserve message internal/received date on import in mbox format (#5559)
- Zipdownload: Fix date format in mbox "From line"
- Possibility to display QR code for contacts data (#5030)
- Added identicon plugin
- Widescreen layout aka three column view (#5093)
- Unify automatic marking as \Seen in preview pane, full-page and extwin views (#5071)
- Disable double-click on the list when preview pane is on (#5199)
- Support hostname and hostname:port in force_https option (#5511)
- Support ALLOW-FROM in x_frame_options (#5122)
- Allow to omit a subject when sending an email (#5068)
- Warn about too many disclosed recipients in composed email [max_disclosed_recipients] (#5132)
- identity_select: Support Received header (#5085)
- Plugin API: Added get_compose_responses hook (#5457)
- Display error when trying to upload more files than specified in max_file_uploads (#5483)
- Add missing sql upgrade file for 'ip' column resize in session table (#5465)
- Do not show inline images of unsupported mimetype (#5463)
- Password: Added replacement variables support in password_pop_host (#5539)
- Password: Don't store passwords in temp files when using dovecotpw (#5531)
- Password: Added LDAP PPolicy driver (#5364)
- Password: Added cpanel_webmail driver (#5549)
- Password: Added possibility to nicely redirect from other plugins on password expiration (#5468)
- Implement separate action to mark all messages in a folder as \Seen (#5006)
- Implement marking as \Seen in all folders or in a folder and its subfolders (#5076)
- Archive: Don't reload messages list when it's not needed (#5225)
- Archive: Add option to automatically mark archived messages as \Seen (#5142)
- Improve randomness of password salts and random hashes (#5266)
- Password/cPanel: Add support for hash authentication and reseller accounts (#5252)
- Support host-specific imap_conn_options/smtp_conn_options/managesieve_conn_options (#5136)
- Center and scale images in attachment preview frame (#5421)
- Added max_message_size option enforced when attaching files to a composed message (#4993)
- Added Search button in quick search menus (#5312)
- Implement "one click" attachment/messages/photo upload (#5024)
- Squirrelmail_usercopy: Add option to define character set of data files
- Removed useless 'created' column from 'session' table (#5389)
- Dropped legacy browsers support (#5167)
- Removed legacy_browser plugin
- Removed hacks for IE < 10
- Update to jQuery 3.1.1 and jQuery-UI 1.12.0
- compile .min.js files with ECMASCRIPT5 option
- Require PHP >= 5.4
- Add possibility to preview and download attachments in mail compose (#5053)
- Add possibility to rename attachments in mail compose (#4996)
- Remove backward compatibility "layer" of bc.php (#4902)
- Support WEBP images in mail messages (#5362)
- Support MathML in HTML message preview (#5182)
- Rename Addressbook to Contacts (#5233)
- Remove PHP mail() support, smtp_server is required now (#5340)
- Display full message subject in onmouseover on truncated subject in mail view (#5346)
- Enigma: Support GnuPG 2.1 (#5313)
- Enigma: Support key generation for multiple identities (#5383)
- Enigma: Import keys from key-server(s) (#5286)
- Enigma: Search missing public keys on a key-server in mail compose (#5286)
- Enigma: Delete user keys when using deluser.sh script
- Enigma: Fix redundant list-secret-keys/list-public-keys calls on signing/encryption
- Enigma: Implement PGP encryption and signing in one go (#5302)
- Enigma: Display signature verification status for encrypted+signed messages (#5302)
- Display different attachment icon on encrypted messages
- Display different confirmation text when moving messages to Trash (#5220)
- Indicate that a collapsed thread has flagged children (#5013)
- Implemented message/rfc822 attachment preview
- Update to jsTimezoneDetect 1.0.6
- Managesieve: Add (optional) RAW script editor (#5414)
- Managesieve: Add option to automatically set vacation :from address (#5428)
- Managesieve: Support 'string' test from variables extension [RFC 5229] (#5248)
- Managesieve: Support 'duplicate' extension [RFC 7352]
- Managesieve: Unhide advanced rule controls if there are inputs with errors
- Managesieve: Display warning message when filter form contains errors
- Control search engine crawlers via X-Robots-Tag header instead of <meta> and robots.txt (#5098)
- Fixed redundancy in sql caching system and compatibility with Galera Cluster (#5439)
- Removed redundant 'created' column from cache and cache_shared tables
- Removed use of redundant data records
- Added missing primary keys (dictionary, cache, cache_shared tables)
- Fix so templating system does not mess with external (e.g. email) content (#5499)
- Fix redundant keep-alive/refresh after session error on compose page (#5500)
- Managesieve: Fix handling of scripts with nested rules (#5540)
- Fix variable substitution in ldap host for some use-cases, e.g. new_user_identity (#5544)
- Enigma: Fix PHP fatal error when decrypting a message with invalid signature (#5555)
- Fix adding images to new identity signatures
- Fix rsync error handling in installto.sh script (#5562)
- Fix some advanced search issues with multiple addressbooks (#5572)
- Fix so group/addressbook selection is retained on page refresh
RELEASE 1.2.3
-------------
- Searching in both contacts and groups when LDAP addressbook with group_filters option is used
- Fix vulnerability in handling of mail()'s 5th argument
- Fix To: header encoding in mail sent with mail() method (#5475)
- Fix flickering of header topline in min-mode (#5426)
- Fix bug where folders list would scroll to top when clicking on subscription checkbox (#5447)
- Fix decoding of GB2312/GBK text when iconv is not installed (#5448)
- Fix regression where creation of default folders wasn't functioning without prefix (#5460)
- Enigma: Fix bug where last records on keys list were hidden (#5461)
- Enigma: Fix key search with keyword containing non-ascii characters (#5459)
- Fix bug where deleting folders with subfolders could fail in some cases (#5466)
- Fix bug where IMAP password could be exposed via error message (#5472)
- Fix bug where it wasn't possible to store more that 2MB objects in memcache/apc,
Added memcache_max_allowed_packet and apc_max_allowed_packet settings (#5452)
- Fix "Illegal string offset" warning in rcube::log_bug() on PHP 7.1 (#5508)
- Fix storing "empty" values in rcube_cache/rcube_cache_shared (#5519)
- Fix missing content check when image resize fails on attachment thumbnail generation (#5485)
- Fix displaying attached images with wrong Content-Type specified (#5527)
RELEASE 1.2.2
-------------
- Enigma: Add possibility to configure gpg-agent binary location (enigma_pgp_agent)
- Enigma: Fix signature verification with some IMAP servers, e.g. Gmail, DBMail (#5371)
- Enigma: Make recipient key searches case-insensitive (#5434)
- Fix regression in resizing JPEG images with Imagick (#5376)
- Managesieve: Fix parsing of vacation date-time with non-default date_format (#5372)
- Use SymLinksIfOwnerMatch in .htaccess instead of FollowSymLinks disabled on some hosts for security reasons (#5370)
- Wash position:fixed style in HTML mail for better security (#5264)
- Fix bug where memcache_debug didn't work for session operations
- Fix bug where Message-ID domain part was tied to username instead of current identity (#5385)
- Fix bug where blocked.gif couldn't be attached to reply/forward with insecure content
- Fix E_DEPRECATED warning when using Auth_SASL::factory() (#5401)
- Fix bug where names of downloaded files could be malformed when derived from the message subject (#5404)
- Fix so "All" messages selection is resetted on search reset (#5413)
- Fix bug where folder creation could fail if personal namespace contained more than one entry (#5403)
- Fix error causing empty INBOX listing in Firefox when using an URL with user:password specified (#5400)
- Fix PHP warning when handling shared namespace with empty prefix (#5420)
- Fix so folders list is scrolled to the selected folder on page load (#5424)
- Fix so when moving to Trash we make sure the folder exists (#5192)
- Fix displaying size of attachments with zero size
- Fix so "Action disabled" error uses more appropriate 404 code (#5440)
RELEASE 1.2.1
-------------
- Update TinyMCE to version 4.3.13 (#5309)
- Fix bug where errors could have been not logged when per_user_logging=true
- Fix bug where message list columns could be in wrong order after column drag-n-drop and list sorting
- Fix so minified publickey.js (with cache-buster) is used when available (#5254)
- Fix (replace) application/x-tar file extension test as it might not exist in nginx config (#5253)
- Fix PHP warning when password_hosts is set, but is not an array (#5260)
- Fix redundant keep-alive requests when session_lifetime is greater than ~20000 (#5273)
- Fix so subfolders of INBOX can be set as Archive (#5274)
- Fix bug where multi-folder search could choose a wrong folder in "this and subfolders" scope (#5282)
- Fix bug where multi-folder search didn't work for unsubscribed INBOX (#5259)
- Fix bug where "no body" alert could be displayed when sending mailvelope email
- Enigma: Fix keys import from inside of an encrypted message (#5285)
- Enigma: Fix malformed signed messages with force_7bit=true (#5292)
- Enigma: Add possibility to configure gpg binary location (enigma_pgp_binary)
- Enigma: Add possibility to export private keys (#5321)
- Fix searching by email address in contacts with multiple addresses (#5291)
- Fix handling of --delete argument in moduserprefs.sh script (#5296)
- Workaround PHP issue by calling closelog() on script shutdown when using log_driver=syslog (#5289)
- Fix so upgrade script makes sure program/lib directory does not contain old libraries (#5287)
- Fix subscription checkbox state on error in folder subscribe/unsubscribe action (#5243)
- Fix bug where microsecond format in logged date didn't work in some cases
- Fix conflict in new_user_dialog and password_force_new_user settings (#5275)
- Don't create multipart/alternative messages with empty text/plain part (#5283)
- Use contact_search_name format in popup on results in compose contacts search
- Fix handling of 'mailto' and 'error' arguments in message_before_send hook (#5347)
- Fix missing localization of HTML editor when assets_dir != INSTALL_PATH
- Fix handling of blockquote tags with mixed case on html2text conversion (#5363)
- Fix javascript errors in IE on page with iframe that points to another domain
RELEASE 1.2.0
-------------
- Enigma: Added enigma_debug option
- Fix message list multi-select/deselect issue (#5219)
- Fix bug where getting HTML editor content could steal focus from other form controls (#5223)
- Fix bug where contact search menu fields where always unchecked in Larry skin
- Fix autoloading of 'html' class
- Fix bug where Encrypt button appears when switching editor to HTML (#5235)
- Fix XSS issue in href attribute on area tag (#5240)
RELEASE 1.2-rc
--------------
- Managesieve: Refactored script parser to be 100x faster
- Enigma: added option to force users to use signing/encryption
- Enigma: Added option to attach public keys to sent mail (#5152)
- Enigma: Handle messages with text before an encrypted block (#5149)
- Enigma: Handle encrypted/signed content inside message/rfc822 attachments
- Enigma: Fix missing html/plain switch on multipart/signed messages (#4963)
- Enigma: Disable format=flowed for signed plain text messages (#4960)
- Enigma: Fix handling of encrypted + signed messages (#4950)
- Enigma: Fix invalid boundary use in signed messages structure
- Enable use of TLSv1.1 and TLSv1.2 for IMAP (#4955)
- Save copy of original .htaccess file when using installto.sh script (#4947)
- Fix regression where some message attachments could be missing on edit/forward (#4939)
- Fix regression in displaying contents of message/rfc822 parts (#4937)
- Fix handling of message/rfc822 attachments on replies and forwards (#4938)
- Fix PDF support detection in Firefox > 19 (#4941)
- Fix path traversal vulnerability in setting a skin [CVE-2015-8770] (#4945)
- Fix so drag-n-drop of text (e.g. recipient addresses) on compose page actually works (#4944)
- Fix .htaccess rewrite rules to not block .well-known URIs (#4943)
- Fix mail view scaling on iOS (#4915)
- Fix PHP7 warning "session_start(): Session callback expects true/false return value" (#4948)
- Fix XSS issue in SVG images handling [CVE-2015-8864, CVE-2016-4068] (#4949)
- Fix missing language name in "Add to Dictionary" request in HTML mode (#4951)
- Fix (again) security issue in DBMail driver of password plugin [CVE-2015-2181] (#4958)
- Fix bug where Archive/Junk buttons were not active after page jump with select=all mode (#4961)
- Fix bug in long recipients list parsing for cases where recipient name contained @-char (#4964)
- Plugin API: Added addressbook_export hook
- Fix additional_message_headers plugin compatibility with Mail_Mime >= 1.9 (#4966)
- Hide DSN option in Preferences when smtp_server is not used (#4967)
- Fix handling of body parameter in mail compose request
- Protect download urls against CSRF using unique request tokens [CVE-2016-4069] (#4957)
- newmail_notifier: Refactor desktop notifications
- Fix so contactlist_fields option can be set via config file
- Fix so SPECIAL-USE assignments are forced only until user sets special folders (#4782)
- Fix performance in reverting order of THREAD result
- Fix converting mail addresses with @www. into mailto links (#5197)
RELEASE 1.2-beta
----------------
- Update TinyMCE to version 4.2
- Added support for Redis session handler
- Removed some deprecated methods: https://github.com/roundcube/roundcubemail/commit/454b0b1c
- Remove backward compatibility "layer" of bc.php (#4902)
- Add possibility to define date format in write operations for ldap attributes (#3956)
- Display attachment size in compose (#1329)
- Added possibility to drag-n-drop attachments from mail preview to compose window
- Implemented mail messages searching with predefined date interval
- PGP encryption support via Mailvelope integration
- PGP encryption support via Enigma plugin
- PHP7 compatibility fixes (#4836)
- Security: Added brute-force attack prevention via login rate limit (#4922)
- Security: Added options to validate username/password on logon (#4884)
- Security: Improve randomness of security tokens (#4899)
- Security: Use random security tokens instead of hashes based on encryption key (#4829)
- Security: Improved encrypt/decrypt methods with option to choose the cipher_method (#4492)
- Make optional adding of standard signature separator - sig_separator (#3276)
- Optimize folder_size() on Cyrus IMAP by using special folder annotation (#4894)
- Make optional hidding of folders with name starting with a dot - imap_skip_hidden_folders (#4870)
- Add option to enable HTML editor always, except when replying to plain text messages (#4352)
- Emoticons: Added option to switch on/off emoticons in compose editor (#2076)
- Emoticons: Added option to switch on/off emoticons in plain text messages
- Emoticons: All emoticons-related functionality is handled by the plugin now
- Installer: Add button to save generated config file in system temp directory (#3553)
- Remove common subject prefixes Re:, Re[x]:, Re-x: on reply (#4882)
- Added GSSAPI/Kerberos authentication plugin - krb_authentication
- Password: Allow temporarily disabling the plugin functionality with a notice
- Require Mbstring and OpenSSL extensions (#5166)
- Add --config and --type options to moduserprefs.sh script (#4651)
- Implemented memcache_debug and apc_debug options
- Installer: Remove system() function use (#4695)
- Password plugin: Added 'kpasswd' driver by Peter Allgeyer
- Add initdb.sh to create database from initial.sql script with prefix support (#4722)
- Plugin API: Added disabled_plugins an disabled_buttons options in html_editor hook
- Plugin API: Added html2text hook
- Plugin API: Added message_part_body hook
- Plugin API: Added message_ready hook
- Plugin API: Add special onload() method to execute plugin actions before startup (session and GUI initialization)
- Implemented UI element to jump to specified page of the messages list (#1677)
- Fix searching of contacts to allow remote images for known senders (#4886)
- Fix bug where clicking date column with 'arrival' sorting would switch to sorting by 'date' (#4690)
- Fix bug where message content could overlap attachments list in Larry skin (#4876)
- Fix so microseconds macro (u) in log_date_format works (#4855)
- Fix so unrecognized TNEF attachments are displayed on the list of attachments (#5138)
- Fix so database_attachments::cleanup() does not remove attachments from other sessions (#4907)
- Fix responses list update issue after response name change (#4917)
- Fix bug where message preview was unintentionally reset on check-recent action (#4921)
- Fix bug where HTML messages with invalid/excessive css styles couldn't be displayed (#4905)
- Fix redundant blank lines when using HTML and top posting (#4927)
- Fix redundant blank lines on start of text after html to text conversion (#4928)
- Fix HTML sanitizer to skip <!-- node type X --> in output (#4932)
- Fix invalid LDAP query in ACL user autocompletion (#4934)
RELEASE 1.1.3
-------------
- Fix closing of nested menus (#4854)
- Fix so E_DEPRECATED errors from PEAR libs are ignored by error_reporting change (#4770)
- Fix compatibility with PHP 5.3 in rcube_ldap class (#4842)
- Get rid of Mail_mimeDecode package dependency (#4836)
- Fix "Importing..." message does not hide on error (#4840)
- Fix Compose action in addressbook for results from multiple addressbooks (#4834)
- Fix bug where some messages in multi-folder search couldn't be viewed/printed/downloaded (#4843)
- Fix unintentional messages list page change on page switch in compose addressbook (#4844)
- Fix race-condition in saving user preferences and loading plugin config (#4845)
- Fix so plain text signature field uses monospace font (#4848)
- Fix so links with href == content aren't added to links list on html to text conversion (#4847)
- Fix handling of non-break spaces in html to text conversion (#4849)
- Fix self-reply detection issues (#4852)
- Fix multi-folder search result sorting by arrival date (#4858)
- Fix so *-request@ addresses in Sender: header are also ignored on reply-all (#4860)
- Update to TinyMCE 4.1.10 (#5164)
- Fix draft removal after a message is sent and storing sent message is disabled (#4869)
- Fix so imap folder attribute comparisons are case-insensitive (#4868)
- Fix bug where new messages weren't added to the list in search mode
- Fix wrong positioning of message list header on page scroll in Webkit browsers (#4646)
- Fix some javascript errors in rare situations (#4853)
- Fix error when using back button after sending an email (#4628)
- Fix removing signature when switching to identity with an empty sig in HTML mode (#4872)
- Disable links list generation on html-to-text conversion of identities or composed message (#4850)
- Fix "washing" of style elements wrapped into many lines
- Fix so input field (e.g. search box) does not loose focus on list load (#4862)
- Fix so css of one html part does not apply to other text parts on message display (#4887)
- Fix XSS issue in drag-n-drop file uploads [CVE-2015-8105] (#4900)
- Fix handling of plus character in mailto: links (#4891)
- Fix so adding CC/BCC recipients from the sidebar unhides compose form fields in Classic skin (#4874)
- Fix so gc.sh script removes also expired sessions from sql database (#4893)
- Fix support for Mozilla-based browsers, e.g. Pale Moon (#4895)
- Fix various issues with Turkish (and similar) locales (#4896)
- Fix so In-Reply-To header is set also for MDN receipts (#4897)
- Fix missing HTTP_X_FORWARDED_FOR address in generated Received header
- Fix issue where Content-Length of some attachments could be set to wrong value causing browser errors (#4877)
RELEASE 1.1.2
-------------
- Add new plugin hook 'identity_create_after' providing the ID of the inserted identity (#4807)
- Add option to place signature at bottom of the quoted text even in top-posting mode [sig_below]
- Fix handling of %-encoded entities in mailto: URLs (#4799)
- Fix zipped messages downloads after selecting all messages in a folder (#4797)
- Fix vpopmaild driver of password plugin
- Fix PHP warning: Non-static method PEAR::setErrorHandling() should not be called statically (#4798)
- Fix tables listing routine on mysql and postgres so it skips system or other database tables and views (#4796)
- Fix message list header in classic skin on window resize in Internet Explorer (#4732)
- Fix so text/calendar parts are listed as attachments even if not marked as such (#4795)
- Fix lack of signature separator for plain text signatures in html mode (#4802)
- Fix font artifact in Google Chrome on Windows (#4803)
- Fix bug where forced extwin page reload could exit from the extwin mode (#4801)
- Fix bug where some unrelated attachments in multipart/related message were not listed (#4805)
- Fix mouseup event handling when dragging a list record (#4808)
- Fix bug where preview_pane setting wasn't always saved into user preferences (#4809)
- Fix bug where messages count was not updated after message move/delete with skip_deleted=false (#4814)
- Fix security issue in contact photo handling (#4817)
- Fix possible memcache/apc cache data consistency issues (#4820)
- Fix bug where imap_conn_options were ignored in IMAP connection test (#4822)
- Fix bug where some files could have "executable" extension when stored in temp folder (#4815)
- Fix attached file path unsetting in database_attachments plugin (#4823)
- Fix issues when using moduserprefs.sh without --user argument (#4825)
- Fix potential info disclosure issue by protecting directory access (#4816)
- Fix blank image in html_signature when saving identity changes (#4833)
- Installer: Use openssl_random_pseudo_bytes() (if available) to generate des_key (#4827)
- Fix XSS vulnerability in _mbox argument handling (#4837)
RELEASE 1.1.1
-------------
- ACL: Allow other plugins to adjust the list of permissions and groups to edit
- Add possibility to print contact information (of a single contact)
- Add possibility to configure max_allowed_packet value for all database engines (#4772)
- Improved handling of storage errors after message is sent
- Update to TinyMCE 4.1.9
- Unified request* event arguments handling, added support for _unlock and _action parameters
- Security: Generate random hash for the per-user local storage prefix (#4768)
- Fix refreshing of drafts list when sending a message which was saved in meantime (#4745)
- Fix saving/sending emoticon images when assets_dir is set
- Fix PHP fatal error when visiting Vacation interface and there's no sieve script yet (#4778)
- Fix setting max packet size for DB caches and check packet size also in shared cache
- Fix needless security warning on BMP attachments display (#4771)
- Fix handling of some improper constructs in format=flowed text as per the RFC3676[4.5] (#4773)
- Fix performance of rcube_db_mysql::get_variable()
- Fix missing or not up-to-date CATEGORIES entry in vCard export (#4766)
- Fix fatal errors on systems without mbstring extension or mb_regex_encoding() function (#4769)
- Fix cursor position on reply below the quote in HTML mode (#4759)
- Fix so "over quota" errors are displayed also in message compose page
- Fix duplicate entries suppression in autocomplete result (#4776)
- Fix "Non-static method PEAR::isError() should not be called statically" errors (#4770)
- Fix parsing invalid HTML messages with BOM after <!DOCTYPE> (#4777)
- Fix duplicate entry on timezones list in rcube_config::timezone_name_from_abbr() (#4779)
- Fix so localized folder name is displayed in multi-folder search result (#4750)
- Fix javascript error after creating a folder which is a subfolder of another one (#4781)
- Fix bug where subject of sent/saved message was removed if mbstring wasn't installed (#4780)
- Fix missing vcard_attachment icon on messages list (#4783)
- Fix storing signatures with big images in MySQL database (#4785)
- Fix Opera browser detection in javascript (#4786)
- Fix so search filter, scope and fields are reset on folder change
- Fix rows count when messages search fails (#4760)
- Fix bug where spellchecking in HTML editor do not work after switching editor type more than once (#4789)
- Fix bug where TinyMCE area height was too small on slow network connection (#4788)
- Fix backtick character handling in sql queries (#4790)
- Fix redirect URL for attachments loaded in an iframe when behind a proxy (#4724)
- Fix menu container references to point to the actual <ul> element (#4791)
- Fix javascripts errors in IE8 - lack of Event.which, focusing a hidden element (#4793)
RELEASE 1.1.0
-------------
- Make SMTP error log more verbose - include server response and error code
- Fix download options menu (added by zipdownload plugin) in classic skin (#4740)
- Fix blocked.gif image usage with assets_dir set
- Fix bug where max_group_members was ignored when adding a new contact (#4733)
- Hide MDN and DSN options in compose if disabled by admin (#4735)
- Fix checks based on window.ActiveXObject in IE > 10
- Fix XSS issue in style attribute handling [CVE-2015-1433] (#4739)
- Fix bug where Drafts list wasn't updated on draft-save action in new window (#4737)
- Fix so "set as default" option is hidden if identities_level > 1 (#4738)
- Fix bug where search was reset after returning from compose visited for reply
- Fix javascript error in "IE 8.0/Tablet PC" browser (#4730)
- Fix bug where Reply-To address was ignored on reply to messages sent by self (#4742)
- Fix bug where empty fieldmap config entries caused empty results of ldap search (#4741)
- Fix bug where drafts list wasn't refreshed after draft message was sent from another window (#4745)
- Fix keyboard navigation and css in datepicker widget across many Firefox versions
- Fix false warning when opening attached text/plain files (#4748)
- Fix bug where signature could have been inserted twice after plain-to-html switch (#4746)
- Fix security issue in DBMail driver of password plugin (#4757)
- Enable FollowSymLinks option in .htaccess file which is required by rewrite rules (#4754)
- Fix so JSON.parse() errors on localStorage items are ignored (#4752)
RELEASE 1.1-rc
--------------
- Update jQuery to version 2.1.3
- Allow to override any config option through env variables
- Improve system security by using optional special URL with security token - use_secure_urls
- Allow to define separate server/path for image/js/css files - assets_url/assets_dir
- Sync vendor folder if exists in source package (#4700)
- Avoid useless reloading list when resetting search with active filter (#4654)
- Fix invalid folder selection if clicked while busy (#4709)
- Fix import of multiple contact email addresses from Outlook-csv format (#4714)
- Fix drag-n-drop to folders expanded while dragging (#4708)
- Fix import of multiple contact groups from Google-csv format (#4710)
- Fix import of contacts with multiple email addresses from Google-csv format (#4719)
- Fix bugs where CSRF attacks were still possible on some requests [CVE-2014-9587]
- Fix some rcube_utils::anytodatetime() corner cases with timezone mismatches (#4712)
- Improve move-to and contact-export button in classic skin (#4713)
- Fix wrong icon for download button in classic skin
- Fix bug where sent message was saved in Sent folder even if disabled by user (#4729)
RELEASE 1.1-beta
----------------
- Fix skin path handling in plugin context (#4111)
- Prevent memory exhaustion on image resizing with GD on Windows (#4580)
- Add plugin hook for database table name lookups as requested in #4538
- Added Oracle database support
- Support contacts import in GMail CSV format
- Added namespace filter in Folder Manager
- Added folder searching in Folder Manager
- Fix restoring draft messages from localStorage if editor mode differs (#4631)
- Added config option/user preference to disable saving messages in localStorage (#4606)
- Added config option 'imap_log_session' to enable Roundcube <-> IMAP session ID logging
- Added config option 'log_session_id' to control the length of the session identifier in logs
- Implemented 'storage_connected' API hook after successful IMAP login (#4638)
- Intergrate Net_LDAP3 and rcube_ldap_generic classes
- Add option (disabled_actions) to disable UI elements/actions (#4478)
- Support password encryption using openssl extension (#4614)
- Create/rename groups in UI dialogs (#4592)
- Added 'contact_search_name' option to define autocompletion entry format
- Display quota information for current folder not INBOX only (#3442)
- Support images in HTML signatures (#3917)
- Display full quota information in popup (#2103, #2746)
- Mail compose: Selecting contact inserts recipient to previously focused input - to/cc/bcc accordingly (#4487)
- Close "no subject" prompt with Enter key (#4463)
- Password: Add option to force new users to change their password (#2963)
- Improve support for screen readers and assistive technology using WCAG 2.0 and WAI ARIA standards
- Enable basic keyboard navigation throughout the UI (#3333)
- Select/scroll to previously selected message when returning from message page (#4146)
- Display a warning if popup window was blocked (#4472)
- Remove (was: ...) from message subject on reply (#4359)
- Update to TinyMCE 4.1 (#4168)
- Enable autolink plugin in TinyMCE (#4029)
- Support image operations with Imagick extension (#4498)
- Support upload progress with session.upload_progress and PECL uploadprogress module (#3934)
- Make identity name field optional (#4435)
- Utility script to remove user records from the local database
- Plugin API: Added message_saved hook (#4503)
- Plugin API: Added imap_search_before hook
- Support messages import from zip archives
- Zipdownload: Added mbox format support (#2354)
- Drop support for IE6, move IE7/IE8 support to legacy_browser plugin
- Update to jQuery-2.1.1
- Search across multiple folders (#1676)
- Improve UI integration of ACL settings
- Drop support for PHP < 5.3.7
- Set In-Reply-To and References for forwarded messages (#4465)
- Removed redundant default_folders config option (#4500)
- Implemented IMAP SPECIAL-USE extension support [RFC6154] (#3326)
- Optimize some framed pages content for better performance (#4517)
- Improve text messages display and conversion to HTML (#4091)
- Don't remove links when html signature is converted to text (#4473)
- Fix page title when using search filter (#4636)
- Fix mbox files import
- Fix some character sets detection (#4694)
- Fix so attachment charset is set in headers of forward/draft message (#4676)
- Fix bug where wrong charset could be used for text attachment preview page (#4674)
RELEASE 1.0.5
-------------
- Fix wrong icon for download button in classic skin
- Fix checks based on window.ActiveXObject in IE > 10
- Fix XSS issue in style attribute handling (#4739)
- Fix bug where Drafts list wasn't updated on draft-save action in new window (#4737)
- Fix so "set as default" option is hidden if identities_level > 1 (#4738)
- Fix javascript error in "IE 8.0/Tablet PC" browser (#4730)
- Fix bug where empty fieldmap config entries caused empty results of ldap search (#4741)
- Fix bug where sent message was saved in Sent folder even if disabled by user (#4729)
RELEASE 1.0.4
-------------
- Disable TinyMCE contextmenu plugin as there are more cons than pros in using it (#4684)
- Fix bug where show_real_foldernames setting wasn't honored on compose page (#4705)
- Fix issue where Archive folder wasn't protected in Folder Manager (#4706)
- Fix compatibility with PHP 5.2. in rcube_imap_generic (#4682)
- Fix setting flags on servers with no PERMANENTFLAGS response (#4667)
- Fix regression in SHAA password generation in ldap driver of password plugin (#4670)
- Fix displaying of HTML messages with absolutely positioned elements in Larry skin (#4672)
- Fix font style display issue in HTML messages with styled <span> elements (#4671)
- Fix download of attachments that are part of TNEF message (#4668)
- Fix handling of uuencoded messages if messages_cache is enabled (#4675)
- Fix handling of base64-encoded attachments with extra spaces (#4678)
- Fix handling of UNKNOWN-CTE response, try do decode content client-side (#4650)
- Fix bug where creating subfolders in shared folders wasn't possible without ACL extension (#4680)
- Fix reply scrolling issue with text mode and start message below the quote (#4681)
- Fix possible issues in skin/skin_path config handling (#4689)
- Fix lack of delimiter for recipient addresses in smtp_log (#4703)
- Fix generation of Blowfish-based password hashes (#4721)
- Fix bugs where CSRF attacks were still possible on some requests [CVE-2014-9587]
RELEASE 1.0.3
-------------
- Initialize HTML editor before restoring a message from localStorage (#4631)
- Add 'sig_max_lines' config option to default config file (#5162)
- Add config option to specify IMAP connection socket parameters - imap_conn_options (#4589)
- Add option to set default message list mode - default_list_mode (#3157)
- Enable contextmenu plugin for TinyMCE editor (#3062)
- Fix insert-signature command in external compose window if opened from inline compose screen (#4663)
- Fix some mime-type to extension mapping checks in Installer (#4610)
- Fix errors when using localStorage in Safari's private browsing mode (#4619)
- Fix bug where $Forwarded flag was being set even if server didn't support it (#4621)
- Fix various iCloud vCard issues, added fallback for external photos (#4617)
- Fix invalid Content-Type header when send_format_flowed=false (#4616)
- Fix errors when adding/updating contacts in active search (#4630)
- Fix incorrect thumbnail rotation with GD and exif orientation data (#4641)
- Fix contacts list update after adding/deleting/moving a contact (#4640, #4644)
- Fix handling of email addresses with quoted domain part (#4647)
- Fix comm_path update on task switch (#4648)
- Fix error in MSSQL update script 2013061000.sql (#4658)
- Fix validation of email addresses with IDNA domains (#4661)
RELEASE 1.0.2
-------------
- Fix storing unsaved drafts in localStorage (#4529)
- Add configurable LDAP_OPT_DEREF option (#4546)
- Fix so when switching editor mode original version of signature is used (#4032)
- Fix unintentional draft autosave request if autosave is disabled (#4550)
- Fix malformed References: header in send/saved mail (#4552)
- Fix handling unicode characters in links (#4555)
- Fix incorrect handling of HTML comments in messages sanitization code (#4558)
- Fix so current page is reset on list-mode change (#4561)
- Fix so responses menu hides on click in classic skin (#4566)
- Fix unintentional line-height style modification in HTML messages (#4567)
- Fix broken normalize_string(), add support for ISO-8859-2 (#4568)
- Support csv contacts import in German localization (#4570)
- Fix so message list and counters are updated when a message is opened in new window (#4569)
- Fix malformed recipient name when composing a message by clicking on mailto link (#4583)
- Fix list reload after sending message in another window (#4576)
- Fix so address format errors are ignored when saving a draft (#4594)
- Fix incorrect label translation in return receipt (#4598)
- Fix security issue in delete-response action - allow only ajax request
- Fix Delete button state after deleting identity/response (#4603)
- Fix bug where contacts with no email address were listed on compose addressbook (#4602)
- Fix images import from various vCard formats (#4604)
- Fix sorting messages by size on servers without SORT capability (#4608)
RELEASE 1.0.1
-------------
- Support 'error' and 'body_file' return attribs in 'message_before_send' hook (#4467)
- Apply user-specific replacements to group's base_dn property (#4512)
- Fix missing email address when importing contacts from outlook csv (#4535)
- Fix bug where "With attachment" option in search filter wasn't selected after return from mail view (#4508)
- Fix "washing" of unicoded style attributes (#4510)
- Fix unintentional redirect from compose page in Webkit browsers (#4516)
- Fix messages index cache update under some conditions (e.g. proxy) (#4505)
- Fix lack of translation of special folders in some configurations (#4520)
- Fix XSS issue in plain text spellchecker (#4524)
- Fix invalid page title for some folders (1489804)
- Fix redundant alert message on over-size uploads (#4528)
- Fix next message display after removing a message (#4521)
- Fix missing Mail-Followup-To header in sent mail (#4534)
- Fix error when spell-checking an empty text (#4536)
- Avoid popupmenus being closed when scrollbar is clicked (#4537)
- Add proxy_whitelist configuration option (#4496)
- Fix identities_level=4 handling in new_user_dialog plugin (#4540)
- Fix various db_prefix issues (#4539)
- Fix too small length of users.preferences column data type on MySQL
- Fix redundant warning when switching from html to text in empty editor (#4530)
- Fix invalid host validation on login (#4541)
- Fix IMAP connection test in installer so it is aware of imap_auth_type (#4502)
RELEASE 1.0.0
-------------
- Added toolbar button to move message in message view
- Fix style of disabled protocol handler link on IE (#4460)
- Fix message import dialog when no file is selected (#4488)
- Fix opening compose screen in new window after saving as draft (#4479)
- Fix directories check in Installer on Windows (#4462)
- Fix issue when default_addressbook option is set to integer value (#4379)
- Fix Opera > 15 detection (#4455)
- Fix security issue in DomainFactory driver of Password plugin
- Fix invalid X-Draft-Info on forwarded message draft (#4464)
- Fix regression in handling of 'attachments' result in message_compose hook (#4474)
- Fix issue where msgexport.sh printed the message to STDOUT instead of a file (#4476)
- Fix fatal error in database_attachments plugin under some conditions (#4495)
RELEASE 1.0-rc
--------------
- Small CSS fix with message notice boxes in Larry skin (#4429)
- Include groups in contacts search on mail compose (#4186)
- Add mime-type mapping for .7z files (#4436)
- Invoke update scripts with php to circumvent execution restrictions (#4330)
- Fix drag & drop message/contact moving on touch device (#4395)
- Fix canned responses in HTML mode (#4446)
- Check/create default folders on every login not only the first (#4391)
- Update to jQuery-1.11.0 and jQuery-UI-1.9.2
- Support SMTP socket context options via new config option 'smtp_conn_options'
- Fix compatibility with PHP 5.2 in html.php file (#4438)
- Remove expand/collapse with plus/minus keys (on numeric keypad) (#4437)
- Fix issue where filesystem path was added to all-attachments (zip) file (#4433)
- Fix case-sensitivity of email addresses handling on compose (#1899)
- Don't alter Message-ID of a draft when sending (#4381)
- Fix issue where deprecated syntax for HTML lists was not handled properly (#3975)
- Display different icons when Trash folder is empty or full (#2108)
- Remember last position of more headers switch (#3660)
- Fix so message flags modified by another client are applied on the list on refresh (#1639)
- Fix broken text/* attachments when forwarding/editing a message (#4393)
- Improved minified files handling, added css minification (#3041)
- Fix handling of X-Forwarded-For header with multiple addresses (#4424)
- Fix border issue on folders list in classic skin (#4419)
- Implemented menu actions to copy/move messages, added folder-selector widget (#863)
- Fix security rules in .htaccess preventing access to base URL without the ending slash (#4422)
- Fix regression where only first new folder was placed in correct place on the list (#4418)
- Fix issue where children of selected and collapsed thread were skipped on various actions (#4410)
- Fix issue where groups were not deleted when "Replace entire addressbook" option on contacts import was used (#4388)
- Fix unreliable mimetype tests in Installer (#4408)
- Fix performance of listing writeable folders (#4406)
RELEASE 1.0-beta
----------------
- Fix handling of invalid closing tags in HTML messages (#4403)
- Set real content-type for file downloads (#4400)
- Update TinyMCE to version 3.5.10 (#4401)
- Fix keyboard navigation in list widgets (#4367)
- Allow plugins to grab the reference of opened windows (#4383)
- Larry skin: Improved status message display for better visibility (#4115)
- Fix Internet Explorer 11 detection (#4397)
- Fix date column width to fit the widest possible date format (#4354)
- Move certain user preference options to a collapsed "advanced" block (#4015)
- Add file type icons for Powerpoint and Open Office presentations (#4269)
- Fix operations on folders with trailing spaces in name (#4387)
- Improve identity selection based on From: header (#4360)
- Fix issue where mails with inline images of the same name contained only the first image multiple times (#4378)
- Use left/right arrow keys to collapse/expand thread and spacebar to select a row, change Ctrl key behavior (#4367)
- Fix an issue where using arrow keys to go up a list can result in selected message being under headers (#4375)
- Fix an issue where Home/End keys don't focus list row properly, don't scrollTo properly (#4370)
- Add an option to disable smart Reply-List behaviour - reply_all_mode (#3953)
- Fix an issue where pressing minus key on contacts list was hiding list records (#4368)
- Fix an issue where shift + arrow-up key wasn't selecting all messages in collapsed thread (#4371)
- Added icon for priority column in messages list header (#4275)
- New feature "Canned Responses" to save and recall boilerplate text snippets
- Fix HTML part detection when encapsulated inside multipart/signed (#4357)
- Add spellchecker backend for the After the Deadline service
- Replace markdown-style [1] link indexes in plain text email bodies
- Improved mailto: link arguments handling (#4351)
- Use DOMDocument LIBXML_PARSEHUGE and LIBXML_COMPACT options if possible (#4316)
- Support HTTP_HOST, SERVER_NAME and SERVER_ADDR values in include_host_config feature
- Make default font size for HTML messages configurable (request #118)
- Fix XSS issue in addressbook group name field [CVE-2013-5646] (#4337)
- After message is sent refresh messages list of replied message folder (#4282)
- Add option force specified domain in user login - username_domain_forced (#4290)
- Add option to import Vcards with group assignments
- Save groups membership in Vcard export (#3801)
- Workaround broken PHP function timezone_name_from_abbr (#4289)
- Make cached message size limit configurable - messages_cache_threshold (#4326)
- Log also failed logins to userlogins log
- Add temp_dir_ttl configuration option (#4318)
- Allow setting INBOX as Sent folder (#4264)
- Fix replacement variables in user-specific base_dn in some LDAP requests (#4299)
- Fix image scaling issues when image has only one dimension smaller than the limit (#4296)
- Fix issue where uploaded photo was lost when contact form did not validate (#4296)
- Move identity selection based on non-standard headers into (new) identity_select plugin (#3835)
- Fix downloading binary files with (wrong) text/* content-type (#4292)
- Respect HTTP_X_FORWARDED_FOR and HTTP_X_REAL_IP variables for session IP check
- Simplified configuration by merging it into one file + defaults (#3156)
- Make message list header stay on top when scrolling (#353)
- Add support for 'enchant' spellcheck engine
- Check filetype detection in installer and update script (#4252)
- Fix folder names truncation in Classic skin (#4265)
- Make possible to disable some (broken) IMAP extensions with imap_disable_caps option (#4245)
- Contacts drag-n-drop default action is to move contacts (#3962)
- Added possibility to choose to move or copy contacts from drag-n-drop menu (#3962)
- Fix Close link and remove About link on error pages (#4201)
- Improved/unified attachment preview screen, added print button
- Fix lack of space between searchfiler and quicksearchbar in Larry skin (#4233)
- Cache LDAP's user_specific search and use vlv for better performance (#4247)
- LDAP: auto-detect and use VLV indices for all search operations
- LDAP: additional group configuration options for address books
- LDAP: separated address book implementation from a generic LDAP wrapper class
- Allow address books to browse a multi-level group hierarchy in the contacts list
- Fix session issues when local and database time differs (#2401)
- Fix thread cache syncronization/validation (#4150)
- Added feature to import messages to the currently selected folder
- Add option show_real_foldernames to disable localization of special folders
- Fix database cache expunge issues (#4229)
- Fix date format issues on MS SQL Server (#4078)
- Add imap_cache_ttl option to configure TTL of imap_cache
- Make LDAP cache engine configurable via ldap_cache and ldap_cache_ttl options
- Fix "duplicate entry" errors on inserts to imap cache tables (#4228)
- Improved handling of Reply-To/Bcc addresses of identity in compose form (#4142)
- Added user preference to open all popups as standard windows
- Implemented shared cache (rcube_cache_shared)
- Change Reply-All button label/title when mailing list is detected (#4092)
- Fix SMTP connection using IPv6 address in smtp_server option (#4147)
- Added attachment_reminder plugin
- Make PHP code eval() free, use create_function()
- Add option to display email address together with a name in mail preview (#3952)
- Support CSV import from Atmail (#4161)
- Add db_prefix configuration option in place of db_table_*/db_sequence_* options
- Make possible to use db_prefix for schema initialization in Installer (#4175)
- Fix updatedb.sh script so it recognizes also table prefix for external DDL files
- Fix parsing invalid date string (#4155)
- Add "with attachment" option to messages list filter (#1795)
- Call resize handler in intervals to prevent lags and double onresize calls in Chrome (#4137)
- Add rel="noreferrer" for links in displayed messages (#4976)
- Add ability to toggle between HTML and text while viewing a message (#3005)
- Remove "HTML message" from attachments list while viewing a message in text mode (#3005)
- Support IMAP MOVE extension [RFC 6851]
- Add attachment menu with Open and Download options (#4116)
- Display user-friendly message on IMAP "over quota" errors (#914)
- Extended archive plugin with user-configurable options to store messages into subfolders
- Fix export of selected contacts from search result (#4070)
- Feature to export only selected contacts from addressbook (by Phil Weir)
RELEASE 0.9.5
-------------
- Fix failing vCard import when email address field contains spaces (#4363)
- Fix default spell-check configuration after Google suspended their spell service
- Fix vulnerability in handling _session argument of utils/save-prefs [CVE-2013-6172] (#4362)
- Fix iframe onload for upload errors handling (#4361)
- Fix address matching in Return-Path header on identity selection (#4358)
- Fix text wrapping issue with long unwrappable lines (#4356)
- Fixed issues where HTML comments inside style tag would hang Internet Explorer
- Hide Delivery Status Notification option when smtp_server is unset (#4339)
- Display full attachment name using title attribute when name is too long to display (#4328)
- Fix attachment icon issue when rare font/language is used (#4334)
- Fix expanded thread root message styling after refreshing messages list (#4335)
- Fix issue where From address was removed from Cc and Bcc fields when editing a draft (#4327)
- Fix error_reporting directive check (#4331)
- Fix de_DE localization of "About" label in Help plugin (#4333)
RELEASE 0.9.4
-------------
- Make identities matching case insensitive (#1881)
- Fix issue where too big message data was stored in cache causing sql errors (#4325)
- Fix iframe scrollbars on webkit desktop browsers (#4319)
- Fix issue where legacy config was overridden by default config (#4305)
- Fix newmail_notifier issue where favicon wasn't changed back to default (#4324)
- Fix setting of Junk and NonJunk flags by markasjunk plugin (#4303)
- Fix lack of Reply-To address in header of forwarded message body (#4314)
- Fix bugs when invoking contact creation form when read-only addressbook is selected (#4313)
- Fix identity selection on reply (#4308)
- Fix so additional headers are added to all messages sent (#4302)
- Fix display issue after moving folder in Folder Manager (#4310)
- Fix handling of non-default date formats (#4311)
- Fix unquoted path in PREG expression on Windows (#4307)
- Fix wrong close tag in /template/mail.html (#4312)
RELEASE 0.9.3
-------------
- Fix setting refresh_interval to "Never" in Preferences (#4304)
- Fixed iframe scrolling on touch devices
- Optimized message list for touch devices
- Fix purge action in folder manager (#4300)
- Fix base URL resolving on attribute values with no quotes (#4297)
- Fix wrong handling of links with '|' character (#4298)
- Fix colorspace issue on image conversion using ImageMagick (#4294)
- Fix XSS vulnerability when editing a message "as new" or draft [CVE-2013-5645] (#4283)
- Fix XSS vulnerability when saving HTML signatures [CVE-2013-5645] (#4283)
- Fix rewrite rule in .htaccess (#4278)
- Fix detecting Turkish language in ISO-8859-9 encoding (#4284)
- Fix identity-selection using Return-Path headers (#4279)
- Fix parsing of links with ... in URL (#4251)
- Fix compose priority selector when opening in new window (#4286)
- Fix bug where signature wasn't changed on identity selection when editing a draft (#4272)
- Fix IMAP SETMETADATA parameters quoting (#4274)
- Fix "could not load message" error on valid empty message body (#4271)
- Fix handling of message/rfc822 attachments on message forward and edit (#4262)
- Fix parsing of square bracket characters in IMAP response strings (#4267)
- Don't clear References and in-Reply-To when a message is "edited as new" (#4263)
- Fix messages list sorting with THREAD=REFS
- Remove deprecated (in PHP 5.5) PREG /e modifier usage (#4239)
- Fix empty messages list when register_globals is enabled (#4232)
- Fix so valid and set date.timezone is not required by installer checks (#4242)
- Canonize boolean ini_get() results (#4249)
- Fix so install do not fail when one of DB driver checks fails but other drivers exist (#4240)
- Fix so exported vCard specifies encoding in v3-compatible format (#4244)
RELEASE 0.9.2
-------------
- Fix image thumbnails display in print mode (#4220)
- Fix height of message headers block (#4200)
- Fix timeout issue on drag&drop uploads (#4238)
- Fix default sorting of threaded list when THREAD=REFS isn't supported
- Fix list mode switch to 'List' after saving list settings in Larry skin (#4236)
- Fix error when there's no writeable addressbook source (#4235)
- Fix zipdownload plugin issue with filenames charset (#4231)
- Fix so non-inline images aren't skipped on forward (#4230)
- Fix "null" instead of empty string on messages list in IE10 (#4227)
- Fix legacy options handling
- Fix so bounces addresses in Sender headers are skipped on Reply-All (#4140)
- Fix bug where serialized strings were truncated in PDO::quote() (#4226)
- Fix displaying messages with invalid self-closing HTML tags (#4223)
- Fix PHP warning when responding to a message with many Return-Path headers (#4222)
- Fix unintentional compose window resize (#4206)
- Fix performance regression in text wrapping function (#4219)
- Fix connection to posgtres db using unix socket (#4218)
- Fix handling of comma when adding contact from contacts widget (#4199)
- Fix bug where a message was opened in both preview pane and new window on double-click (#4212)
- Fix fatal error when xdebug.max_nesting_level was exceeded in rcube_washtml (#4202)
- Fix PHP warning in html_table::set_row_attribs() in PHP 5.4 (#4194)
- Fix invalid option selected in default_font selector when font is unset (#4204)
- Fix displaying contact with ID divisible by 100 in sql addressbook (#4211)
- Fix browser warnings on PDF plugin detection (#4209)
- Fix fatal error when parsing UUencoded messages (#4210)
RELEASE 0.9.1
-------------
- Better German labels for from/to to avoid conflicts with 'sender' (#4188)
- Fix problem where security warning was displayed for valid images with image/jpg type (#4196)
- Fix handling of invalid email addresses in headers (#4193)
- Fix IMAP connection issue with default_socket_timeout < 0 and imap_timeout < 0 (#4191)
- Fix various PHP code bugs found using static analysis (#4190)
- Fix backslash character handling on vCard import (#4189)
- Fix csv import from Thunderbird with French localization (#4170)
- Fix messages list focus issue in Opera and Webkit (#4169)
- Fix Reply-To header handling in Reply-All action (#4157)
- Fix so Sender: address is added to Cc: field on reply to all (#4140)
- Fix so addressbook_search_mode works also for group search (#4183)
- Fix removal of a contact from a group in LDAP addressbook (#4185)
- Include SQL query in the log on SQL error (#4172)
- Fix handling untagged responses in IMAP FETCH - "could not load message" error (#4180)
- Fix very small window size in Chrome (#4087)
- Fix list page reset when viewing a message in Larry skin (#4182)
- Fix min_refresh_interval handling on preferences save (#4179)
- Fix PDF support detection for Firefox PDF.js (#4113)
- Fix possible collision in generated thumbnail cache key (#4177)
- Fix exit code on bootsrap errors in CLI mode (#4160)
- Fix error handling in CLI mode, use STDERR and non-empty exit code (#5161)
- Fix error when using check_referer=true
- Fix incorrect handling of some specific links (#4171)
- Fix incorrect handling of leading spaces in text wrapping
- Fix unintentional messages list jumps on click in Internet Explorer (#4167)
- Fix list of required configuration options (#4166)
- Fix DB error when creating a new contact and a group is selected (#4164)
- Fix handling of deprecated boolean value of reply_mode option (#4165)
RELEASE 0.9.0
-------------
- Fix display of HTML entities in protected folder name (#4159)
- Set minimal permissions to temp files (#4131)
- Improve content check for embedded images without filename (#4151)
- Fix handling of invalid characters in message headers and output (#4153)
- Fix selecting collapsed rows on select-all (#4156)
- Avoid race-conditions with concurrent attachment uploads (#3739)
- Fix possible header duplicates when using additional headers (#4154)
- Fix session issues with use_https=true (#4125)
- Fix blockquote width in sent mail (#4152)
- Fix keyboard events on list widgets in Internet Explorer (#4148)
RELEASE 0.9-rc2
---------------
- Fix security issue in save-pref command
- Remove sig_above configuration option, use reply_mode only (#4135)
- Refresh current folder in opener window after draft save or message sent (#4132)
- Fix saving draft just after entering compose window (#4141)
- Fix javascript error in IE9 when loading form with placeholders into an iframe (#4138)
- Fix handling of some conditional comment tags in HTML message (#4136)
- Fix so forward as attachment works if additional attachment is added by message_compose hook (#4134)
- Better handling of session errors in ajax requests (#4105)
- Fix HTML part detection for some specific message structures (#4130)
- Don't show fake address - phishing prevention (#4120)
- Fix forward as attachment bug with editormode != 1 (#4129)
- Fix LIMIT/OFFSET queries handling on MS SQL Server (#4123)
- Fix so task name can really contain all from a-z0-9_- characters (#4095)
- Fix javascript errors when working in a page opened with taget="_blank"
- Mention SQLite database format change in UPGRADING file (#4122)
- Increase maxlength to 254 chars for email input fields in addressbook (#4126)
- Fix thumbnail size when GD extension is used for image resize (#4124)
- Display notice that message is encrypted also for application/pkcs7-mime messages (#3815)
RELEASE 0.9-rc
--------------
- Fix plain text spellchecker incorrect highlighting in non-ASCII text (#4114)
- Add workaround for invalid message charset detection by IMAP servers (#4112)
- Fix NUL characters in content-type of ms-tnef attachment (#4108)
- Fix regression in handling LDAP contact identifiers (#4104)
- Updated translations from Transifex
- Fix buggy error template in a frame (#4092)
- Add addressbook widget on compose page in classic skin
- Add search box to compose address book widget (#3710)
- Fix login in case when default_host is an array with one element (#4085)
- Use LDAP fallback hosts on connect + bind instead of ldap_connect() only.
- Add config option for LDAP bind timeout (sets LDAP_OPT_NETWORK_TIMEOUT option)
- Submit Addressbook advanced search form with Enter key (#3843)
- Also block remote images in HTML part view (#4013)
- Improved database schema upgrade procedure, added updatedb.sh script
- Force autocommit mode in mysql database driver (#4068)
RELEASE 0.9-beta
----------------
- Fix searching by date in address book (#4058)
- Improve charset detection by prioritizing charset according to user language (#2032)
- Fix handling of escaped separator in vCard file (#4064)
- Add option to use envelope From address for MDN responses (#4052)
- Add possibility to search in message body only (#3977)
- Support "multipart/relative" as an alias for "multipart/related" type (#4057)
- Display PGP/MIME signature attachments as "Digital Signature" (#3845)
- Workaround UW-IMAP bug where hierarchy separator is added to the shared folder name (#4051)
- Fix version comparisons with -stable suffix (#4050)
- Add unsupported alternative parts to attachments list (#4046)
- Add Compose button on message view page (#3959)
- Display 'Sender' header in message preview
- Plugin API: Added message_before_send hook
- Fix contact copy/add-to-group operations on search result (#4042)
- Use matching identity in MDN response (#4043)
- Fix handling of signatures on draft edit (#3996)
- Fix so compacting of non-empty folder is possible also when messages list is empty (#4039)
- Allow forwarding of multiple emails (#2941)
- Fix big memory consumption of DB layer (#4037)
- Fix broken message/part bodies when FETCH response contains more untagged lines (#4020)
- Fix empty email on identities list after identity update (#4018)
- Add new identities_level: (4) one identity with possibility to edit only signature
- Use Delivered-To and Envelope-To headers for identity selection (#4024, #3835)
- Fix XSS vulnerability using Flash files (#4014)
- Always save drafts with format=flowed in order to keep original line wraps (#3997)
- Select default_addressbook on the list in Address Book (#3624)
- Fix so mobile phone has TYPE=CELL in exported vCard (#4004)
- Support contacts import from CSV file (#2605)
- Improved keep-alive action. Now the interval is based on session_lifetime (#3799)
- Added cross-task 'refresh' request for system state updates (#3799)
- Renamed config options: keep_alive to refresh_interval, min_keep_alive to min_refresh_interval
- Fix handling of text/enriched content on message reply/forward/edit
- Option to display attached images as thumbnails below message body
- Upgraded to jQuery 1.8.3 and jQuery UI 1.9.1
- Add config option to automatically generate LDAP attributes for new entries
- Add user settings to open message view and compose form in new windows (#1886)
- Better client-side timezone detection using the jsTimezoneDetect library (#3947)
- Add option to disable saving sent mail in Sent folder - no_save_sent_messages (#3923)
- Fix handling dont_override with message_sort_col and message_sort_order settings (#3970)
- Fix handling of URLs with asterisk characters (#3969)
- Remove automatic to-lowercase conversion of usernames (#3941)
- Plugin API: Add 'email_list' argument for identities data in user_create hook
- Integrated zipdownload plugin to download all attachments (#617)
- Fix HTML special characters handling in message list/header display (#3812)
- List related text/html part as attachment in plain text mode (#3918)
- Use IMAP BINARY (RFC3516) extension to fetch message/part bodies
- Fix folder creation under public namespace root (#3910)
- Fix so "Edit as new" on draft creates a new message (#3924)
- Fix invalid error message on deleting mail from read only folder (#3929)
- Replace data URIs of images (pasted in HTML editor) with inline attachments (#3795)
- Remove (too big) min-width on mail screen
- Added template object 'frame'
- Add option to enable HTML editor on forwarding (#3807)
- Add option to not include original message on reply, rename option top_posting to reply_mode (#1615)
- Added session_path config option and unified cookies settings in javascript
- Added "Undeleted" option to messages list filter
- Rewritten test scripts for PHPUnit
- Add new DB abstraction layer based on PHP PDO, supporting SQLite3 (#3668)
- Removed PEAR::MDB2 package
- Removed users.alias column, added option ('user_aliases')
to use email address from identities as username (#3851)
- Removed redundant cache.cache_id column (#3817)
- Fix order of attachments in sent mail (#3740)
- Fix Shift + delete button does not permanently delete messages (#3598)
- Add Content-Length for attachments where possible (#1880)
- Fix attachment sizes in message print page and attachment preview page (#3805)
- Add mail attachments using drag & drop on HTML5 enabled browsers
- Add workaround for invalid BODYSTRUCTURE response - parse message with Mail_mimeDecode package (#1966)
- Display Tiff as Jpeg in browsers without Tiff support (#3757)
- Don't display Pdf/Tiff/Flash attachments inline without browser support (#3757, #3394)
- Add is_escaped attribute for html_select and html_textarea (#3782)
- Fix issue where draft auto-save wasn't executed after some inactivity time
- Add vCard import from multiple files at once (#3458)
- Roundcube Framework:
Add possibility to replace IMAP driver with custom class
Add IMAP auto-connection feature, improving performance with caching enabled
Replace imap_init hook with storage_init (with additional 'driver' argument)
Improved performance by caching IMAP server's capabilities in session
Unified global functions naming (rcube_ prefix)
Better classes separation
Framework files moved to lib/Roundcube
RELEASE 0.8.5
-------------
- Fix #countcontrols issue in IE<=8 when text is very long (#4060)
- Fix unwanted horizontal scrollbar in message preview header (#4044)
- Add workaround for IE<=8 bug where Content-Disposition:inline was ignored (#4028)
- Fix XSS vulnerability in vbscript: and data:text links handling [CVE-2012-6121] (#4033)
- Fix absolute positioning in HTML messages (#4007)
- Fix cache (in)validation after setting \Deleted flag
- Fix keybord events on messages list in opera browser (#4011)
- Fix selection of collapsed thread rows (#3978)
- Fix wrapping of quoted text with format=flowed (#3561)
RELEASE 0.8.4
-------------
- Fix regression where unintentional page reload was done after request abort (#3999)
- Fix XSS vulnerability in handling of text/enriched messages (#4000)
- Fix handling of 'media' attribute on linked css (#3989)
- Fix excessive LFs at the end of composed message with top_posting=true (#3995)
- Fix bug where leading blanks were stripped from quoted lines (#3994)
RELEASE 0.8.3
-------------
- Fix AREA links handling (#3992)
- Fix possible HTTP DoS on error in keep-alive requests (#3983)
- Fix compatybility with MDB2 2.5.0b4 (#3982)
- Fix a bug where saving a message in INBOX wasn't possible
- Fix HTML part detection in messages with attachments (#3976)
- Fix bug where wrong words were highlighted on spell-before-send check
- Fix scrolling quirk in email preview frame using Opera 12 (#3973)
- Fix displaying of multipart/alternative messages with empty parts (#3961)
- Fix threaded list sorting on PHP < 5.2.9 (#3960)
- Fix Warning: htmlspecialchars(): charset `RCMAIL_CHARSET' not supported warning in Installer (#3958)
RELEASE 0.8.2
-------------
- Fix XSS vulnerability from HTTP User-Agent header (#3954)
- Force fonts in compose fields to be all the same (#3926)
- Fix handling vCard entries with TEL;TYPE=CELL (#3949)
- Fix error where session wasn't updated after folder rename/delete (#3928)
- Fix PLAIN authentication for some IMAP servers (#3916)
- Fix encoding vCard file when contains PHOTO;ENCODING=b (#3922)
- Fix focus issue in IE when selecting message row (#3881)
- Add full headers view in message preview window (#3823)
- Fix message display page issues - unified with message preview (#3856, #3895)
- Fix displaying all headers when they contain malformed characters (#3911)
- Fix decoding of HTML messages with UTF-16 charset specified (#3902)
- Fix quota capability detection so it can be overwritten by a plugin (#3903)
- Fix identity selection on reply (#3516)
- Fix Larry's messages list filter in IE (#3890)
- Fix more IE issues by disabling Compat. mode with X-UA-Compatible meta tag (#3886)
- Fix setting locales under Solaris - use additional .UTF-8 suffix (#3887)
- Fix email address validation for addresses with IP address in domain part
- Fix Larry skin issues in IE7 compat. mode (#3879)
- Fix so subscribed non-existing/non-accessible shared folder can be unsubscribed
RELEASE 0.8.1
-------------
- Fix bug where domain name was converted to lower-case even with login_lc=false (#3859)
- Fix lower-casing email address on replies (#3863)
- Fix line separator in exported messages (#3866)
- Fix XSS issue where plain signatures wasn't secured in HTML mode [CVE-2012-4668] (#3875)
- Fix XSS issue where href="javascript:" wasn't secured [CVE-2012-3508] (#3875)
- Fix impossible to create message with empty plain text part (#3873)
- Fix stripped apostrophes when replying in plain text to HTML message (#3869)
- Fix inactive Save search option after advanced search (#3870)
- Fix Remove from group option is active for contact search result (#3871)
- Disable autocapitalization in login form on iPad/iPhone (#3872)
- Fix focus on the list when list row is clicked (#3865)
- Added separate From and To columns apart from smart From/To column (#2970)
- Fix fallback to Larry skin when configured skin isn't available (#3857)
- Fix (workaround) delete operations with some versions of memcache (#3858)
- Fix (disable) request validation for spell and spell_html actions
RELEASE 0.8.0
-------------
- Don't show product version on login screen (can be enabled by config)
- Renamed old default skin to 'classic'. Larry is the new default skin.
- Support connections to memcached socket file (#3848)
- Enable TinyMCE inlinepopups plugin
- Update to TinyMCE 3.5.6
- Correctly escape localized labels in javascript variable (#3842)
- Update Net_SMTP/Auth_SASL packages to fix Digest-MD5/Cram-MD5 authentication (#3846)
- Don't add attachments content into reply/forward/draft message body (#3837)
- Fix 'no connection' errors on page unloads (#3832)
- Plugin API: Add 'unauthenticated' hook (#3545)
- Show explicit error message when provided hostname is invalid (#3834)
- Fix wrong compose screen elements focus in IE9 (#3826)
- Fix fatal error when date.timezone isn't set (#3831)
- Update to TinyMCE 3.5.4.1
- Better icons with distinct shapes for priority columns (#3706)
- Show dedicated icon for multipart/report messages (#3813)
- Properly hide text of icon links/buttons (#3820)
- Fix handling of unitless CSS size values in HTML message (#3821)
- Fix removing contact photo using LDAP addressbook (#3737)
- Fix storing X-ANNIVERSARY date in vCard format (#3816)
- Update to Mail_Mime-1.8.5 (#3810)
- Fix XSS vulnerability in message subject handling using Larry skin [CVE-2012-3507] (#3809)
- Fix handling of links with various URI schemes e.g. "skype:" (#3521)
- Fix handling of links inside PRE elements on html to text conversion
- Fix indexing of links on html to text conversion
- Decode header value in rcube_mime::get() by default (#3803)
- Fix errors with enabled PHP magic_quotes_sybase option (#3798)
- Fix SQL query for contacts listing on MS SQL Server (#3797)
- Fix window.resize handler on IE8 and Opera (#3758)
- Don't let error message popups cover the login form (#3794)
- Update to TinyMCE 3.5.2
- Don't show errors when moving contacts into groups they are already in (#3788)
- Make folders with unread messages in subfolders bold again (#2892)
- Abbreviate long attachment file names with ellipsis (#3793)
- Fix html2text conversion of strong|b|a|th|h tags when used in upper case
- Add listcontrols template container in Larry skin (#3792)
- Fix host autoselection when default_host is an array (#3790)
- Move messages forwarding mode setting into Preferences
- Fix HTML entities handling in HTML editor (#3780)
- Fix listing shared folders on Courier IMAP (#3767)
RELEASE 0.8-rc
--------------
- Added new translations in Belarusian, Interlingua and Malayalam
- Flipped compose options arrow (#3772)
- Fix handling of large uuencode attachments (#3771)
- Fix handling of "usemap" attribute (#3770)
- Fix handling of some HTML tags e.g. IMG (#3769)
- Use similar language as a fallback for plugin localization (#3726)
- Fix issue where signature wasn't re-added on draft compose (#3659)
- Update to TinyMCE 3.5 (#3762)
- Fixed multi-threaded autocompletion when number of threads > number of sources
- Allow to configure the number of values allowed for each LDAP attribute
- Support for serialized LDAP address values (usually delimited with a $)
- Less restrictive session auth checks, repeat keep-alive requests on failure (#3755)
- Fix redirect to mail/compose on re-login (#3585)
- Add IE8 hack for messages list issue (#3317)
- Fix handling errors on draft auto-save
- Fix importing vCard photo with ENCODING param specified (#3746)
- Support multiple name/email pairs for Bcc and Reply-To identity settings (#3752)
- Set flexible width to login form fields (#3735)
- Fix re-draw bug on list columns change in IE8 (#3318)
- Allow mass-removal of addresses from a group (#3259)
- Fix removing all contacts on import to LDAP addressbook
- Fix so "Back" from compose/show doesn't reset search request (#3594)
- Add option to delete messages instead of moving to Trash when in Junk folder (#2805)
- Fix invisible cursor when replying to a html message (#3100)
- Reset IP stored in session when destroying session data (#3485)
- Fix bug where memory_limit = -1 wasn't handled properly
- Support LDAP RFC2256's country object class read/write (#3535)
- Upgraded to jQuery 1.7.2
- Image resize with GD extension (#3712)
- Fix lack of warning when switching task in compose window (#3725)
- Fix bug where it wasn't possible to enter ( or & characters in autocomplete fields
- Request all needed fields from address book backends (#3721)
- Unified (single) spellchecker button
- Scroll long lists on drag&drop (#2249)
- Copy all skins in installto script (#3705)
RELEASE 0.8-beta
----------------
- Upgraded to jQuery 1.7.1 (#3673) and jQuery UI 1.8.18
- Add Russian to the spellchecker languages list (#3542)
- Remember custom skin selection after logout (#3688)
- Make sure About tab is always the last tab (#3609)
- Fix issue with folder creation under INBOX. namespace (#3683)
- Added mailto: protocol handler registration link in User Preferences (#2729)
- Handle identity details box with an iframe (#3066)
- Fix issue where some text from original message was missing on reply (#3675)
- Fix autoselect_host() for login (#3639)
- Changed license to GNU GPLv3+ with exceptions for skins & plugins
- Added address book widget on compose screen
- Use proper timezones from PHP's internal timezonedb (#1973)
- Add separate pagesize setting for mail messages and contacts (#3617)
- Deprecate $DB, $USER, $IMAP global variables, Use $RCMAIL instead
- Add option to set default font for HTML message (#894)
- Fix issues with big memory allocation of IMAP results
- Prevent from memory_limit exceeding when trying to parse big messages bodies (#3164)
- Add possibility to add SASL mechanisms for SMTP in smtp_connect hook (#3399)
- Mark (with different color) folders with recent messages (#2479)
- Added About tab in Settings
- TinyMCE updated to 3.4.6
RELEASE 0.7.2
-------------
- Fix encoding of attachment with comma in name (#3717)
- Fix handling of % character in IMAP protocol (#3711)
- Fix duplicate names handling in addressbook searches (#3704)
- Fix displaying of HTML messages from Disqus (#3702)
- Disable E_STRICT warnings on PHP 5.4
- Prevent from folder selection on virtual folder collapsing (#3681)
- Fix automatic unsubscribe of non-existent folders
- Fix double-quotes handling in recipient names
- User configurable setting how to display contact names in list
- Make contacts list sorting configurable for the admin/user
- Fix parse errors in DDL files for MS SQL Server
- Revert SORT=DISPLAY support, removed by mistake (#3664)
- Add lost translation label in de_DE (#3654)
- Fix drafts update issues when edited from preview pane (#3653)
- Fix wrong variable name in rcube_ldap.php (#3643)
- Make mime type detection based on filename extension to be case-insensitive
- Fix failure on MySQL database upgrade from 0.7 - text column can't have default value (#3642)
RELEASE 0.7.1
-------------
- Fix bug in handling of base href and inline content (#3634)
- Fix SQL Error when saving a contact with many email addresses (#3630)
- Fix strict email address searching if contact has more than one address
- Remove duplicated 'organization' label (#3631)
- Fix so editor selector is hidden when 'htmleditor' is listed in 'dont_override'
- Fix wrong (long) label usage (#3627)
- Fix handling of INBOX's subfolders in special folders config (#3623)
- Add ifModule statement for setting Options -Indexes in .htaccess file (#3620)
- Fix crashes with eAccelerator (#3608)
- Fix searching on IMAP servers without CHARSET specifier support (#3619)
- Fix expanding folders during drag&drop (#3611)
- Fix wrong postgres sequence name in upgrade from 0.6
- Fix broken CREATE INDEX queries in SQLite DDL files (#3607)
RELEASE 0.7
-----------
- Make Roundcube render the Email Standards Project Acid Test correctly
- Replace prompt() with jQuery UI dialog (#1603)
- Fix navigation in messages search results
- Improved handling of some malformed values encoded with quoted-printable (#3590)
- Add possibility to do LDAP bind before searching for bind DN
- Fix handling of empty <U> tags in HTML messages (#3584)
- Add content filter for embedded attachments to protect from XSS on IE [CVE-2012-1253] (#3372)
- Use strpos() instead of strstr() when possible (#3581)
- Fix handling HTML entities when converting HTML to text (#3582)
- Fix fit_string_to_size() renders browser and ui unresponsive (#3577)
- Fix handling of invalid characters in request (#3536)
- Fix merging some configuration options in update.sh script (#2181)
- Fix so TEXT key will remove all HEADER keys in IMAP SEARCH (#3578)
- Fix handling contact photo url with https:// prefix (#3575)
- Fix possible infinite redirect on attachment preview (#3572)
- Improved clickjacking protection for browsers which don't support X-Frame-Options headers
- Fixed bug where similar folder names were highlighted wrong (#3345)
- Fixed bug in handling link with '!' character in it (#3569)
- Fixed bug where session ID's length was limited to 40 characters (#3570)
- TinyMCE security issue: removed moxieplayer (embedding flv and mp4 is not supported anymore)
RELEASE 0.7-beta
----------------
- Fix handling of HTML form elements in messages (#1604)
- Fix regression in setting recipient to self when replying to a Sent message (#3101)
- Fix listing of folders in hidden namespaces (#2895)
- Don't consider \Noselect flag when building folders tree (#3448)
- Fix sorting autocomplete results (#3504)
- Add option to set session name (#2630)
- Add option to skip alternative email addresses in autocompletion
- Fix inconsistent behaviour of Compose button in Drafts folder, add Edit button for drafts
- Fix problem with parsing HTML message body with non-unicode characters (#3312)
- Add option to define matching method for addressbook search (#2720, #3378)
- Make email recipients separator configurable
- Fix so folders with \Noinferiors attribute aren't listed in parent selector
- Fix handling of curly brackets in URLs (#3555)
- Fix handling of dates (birthday/anniversary) in contact data (#3552)
- Fix error on opening searched LDAP contact (#3550)
- Fix redundant line break in flowed format (#3551)
- Fix IDN address validation issue (#3544)
- Fix JS error when dst_active checkbox doesn't exist (#3540)
- Autocomplete LDAP records when adding contacts from mail (#3498)
- Plugin API: added 'ready' hook (#3492)
- Ignore DSN request when it isn't supported by SMTP server (#3300)
- Make sure LDAP name fields aren't arrays (#3523)
- Fixed imap test to non-default port when using ssl (#3532)
- Force all files to be overwritten when updating (#3531)
- Fix issue where it wasn't possible to change list view mode in folder manager for INBOX (#3522)
- Fix namespace handling in special folders settings (#3527)
- Disable time limit for CLI scripts (#3524)
- Fix misleading display when chaning editor type (#3519)
- Add loading indicator on contact delete
- Fix bug where after delete message rows can be added to the list of another folder (#3263)
- Add notice on autocompletion that not all records were displayed
- Add option 'searchonly' for LDAP address books
- Add Priority filter to the messages list
- Cache synchronization using QRESYNC/CONDSTORE
- Trigger 'new_messages' hook for all checked folders (#3503)
- Make date/time format user configurable; drop 'date_today' config option
- Fix setting title for truncated subject in IE (#3141)
- Fix displaying multipart/alternative messages with only one part (#3400)
- Rewritten messages caching:
Indexes are stored in a separate table, so there's no need to store all messages in a folder
Added threads data caching
Flags are stored separately, so flag change doesn't cause DELETE+INSERT, just UPDATE
- Improved FETCH response handling
- Improvements in response tokenization method
- Use 'From' and 'To' labels instead of 'Sender' and 'Recipient'
- Fix username case-insensitivity issue in MySQL (#3462)
- Addressbook Saved Searches
- Added spellchecker exceptions dictionary (shared or per-user)
- Added possibility to ignore words containing caps, numbers, symbols (spellcheck_ignore_* options)
- Added 'priority' column on messages list (#2884)
- Localize forwarded message header (#3487)
RELEASE 0.6
-----------
- Fix bug where the last identity is used on reply (#3516)
- Fix locked folder rename option on servers supporting RFC2086 only (#3508)
- Fix session race conditions when composing new messages
- Fix encoding of LDAP contacts identifiers (#3501)
- jQuery 1.6.4
- Fix handling of binary attachments encoded with quoted-printable (#3494)
- Fix text-overflow:ellipsis issues on messages list in FF7 and Webkit (#3490)
- Fix handling of links with IP address
- Fix compacting folder resets message list filter (#3499)
RELEASE 0.6-rc
----------------
- Send X-Frame-Options headers to protect from clickjacking (#3079)
- Fallback to mail_domain in LDAP variable replacements; added 'host' to 'user_create' hook arguments (#3464)
- Fixed wrong vCard type parameter mobile (#3496)
- Fixed vCard WORKFAX issue (#3476)
- Add vCard's Profile URL support (#3491)
- jQuery 1.6.3
- Fix imap_cache setting to values other than 'db' (#3489)
- Fix handling of attachments inside message/rfc822 parts (#3466)
- Make list of mimetypes that open in preview window configurable (#3175)
- Added plugin hook 'message_part_get' for attachment downloads
- Added unique connection identifier to IMAP debug messages
- Fix image type check for contact photo uploads
RELEASE 0.6-beta
----------------
- Fixed selecting identity on reply/forward (#3434)
- Add option to hide selected LDAP addressbook on the list
- Add client-side checking of uploaded files size
- Add newlines between organization, department, jobtitle (#3468)
- Recalculate date when replying to a message and localize the cite header (#3212)
- Fix handling of email addresses with quoted local part (#3401)
- Fix EOL character in vCard exports (#3357)
- Added optional "multithreading" autocomplete feature
- Plugin API: Added 'config_get' hook
- Fixed new_user_identity plugin to work with updated rcube_ldap class (#3443)
- Plugin API: added folder_delete and folder_rename hooks
- Added possibility to undo last contact delete operation
- Fix sorting of contact groups after group create (#3258)
- Add optional textual upload progress indicator (#2330)
- Fix parsing URLs containing commas (#3425)
- Added vertical splitter for books/groups list in addressbook (#3389)
- Improved namespace roots handling in folder manager
- Added searching in all addressbook sources
- Added addressbook source selection in contacts import
- Implement LDAPv3 Virtual List View (VLV) for paged results listing
- Use 'address_template' config option when adding a new address block (#3406)
- Added addressbook advanced search
- Add popup with basic fields selection for addressbook search
- Case-insensitive matching in autocompletion (#3398)
- Added option to force spellchecking before sending a message (#1862)
- Fix handling of "<" character in contact data, search fields and folder names (#3349)
- Fix saving "<" character in identity name and organization fields (#3349)
- Added option to specify to which address book add new contacts
- Added plugin hook for keep-alive requests
- Store user preferences in session when write-master is not available and session is stored in memcache, write them later
- Improve performence of folder manager operations
- Fix default_port option handling in Installer when config.inc.php file exists (#3390)
- Removed option focus_on_new_message, added newmail_notifier plugin
- Added general rcube_cache class with Memcache and APC support
- Improved caching performance by skipping writes of unchanged data
- Option enable_caching replaced by imap_cache and messages_cache options
- Fix WORKFAX saving in address book (#3380)
- Add forward-as-attachment feature
- jQuery-1.6.2 (#5158, #3154)
- Improve display name composition when saving contacts (#3153)
- Fix problems with subfolders of INBOX folder on some IMAP servers (#3247)
- Fix handling of folders that doesn't belong to any namespace (#3184)
- Enable multiselection for attachments uploading in capable browsers (#2266)
- Add possibility to change HTML editor configuration by skin
- Fix a bug where selecting too many contacts would produce too large URI request (#3369)
- Improve performance by including files with absolute path (#3337)
- Move folder name truncation to client/skin (#1822)
- Added plugin hook for request token creation
- Replace LDAP vars in group queries (#3329)
- Fix vcard folding with uncode characters (#3353)
- Keep all submitted data if contact form validation fails (#3350)
- Handle uncode strings in rcube_addressbook::normalize_string() (#3351)
- Fix handling of debug_level=4 in ajax requests (#3327)
- Enable TinyMCE's contextmenu (#3062)
- Allow multiple concurrent compose sessions
- New config option for custom logo
- Allow skins to define/override texts with <roundcube:label />
- Add simple ACL rights/namespace handling in folder manager
- Force IE to send referers (#3306)
- Better display of vcard import results (#1861)
- Improved vcard import
- Interactive update script with improved DB schema check
- Fix problem with contactgroupmembers table creation on MySQL 4.x, add index on contact_id column
- Add LDAP SASL bind and proxy authentication (#2810)
- Replying to a sent message puts the old recipient as the new recipient (#3101)
- Fulltext search over (almost) all data for contacts
- Extend address book with rich contact information
RELEASE 0.5.4
-------------
- Fix XSS vulnerability in UI messages [CVE-2011-2937] (#3469)
RELEASE 0.5.3
-------------
- Fix identities "reply-to" and "bcc" fields have a bogus value when left empty (#3405)
- Fix issue which cases IMAP disconnection when encrypt() method was used (#3374)
- Fix some CSS issues in Settings for Internet Explorer
- Fixed handling of folder with name "0" in folder selector
- Fix bug where messages were deleted instead moved to trash folder after Shift key was used (#3376)
- Fix relative URLs handling according to a <base> in HTML (#3368)
- Fix handling of top-level domains with more than 5 chars or unicode chars (#3366)
- Fix usage of non-standard HTTP error codes (#3297)
- Fix PHP warning on mistaken in_array() usage (#3375)
RELEASE 0.5.2
-------------
- TinyMCE 3.4.2 now compatible with IE9
- PEAR::Net_SMTP 1.5.2, fixed timeout issue (#3332)
- Fix bug where template name without plugin prefix was used in render_page hook
- Support 'abort' and 'result' response in 'preferences_save' hook, add error handling
- Fix bug where some content would cause hang on html2text conversion (#3348)
- Improve space-stuffing handling in format=flowed messages (#3346)
- Fix bug where some dates would produce SQL error in MySQL (#3342)
- Added workaround for some IMAP server with broken STATUS response (#3344)
- Fix bug where default_charset was not used for text messages (#3328)
- Stateless request tokens. No keep-alive necessary on login page (#3325)
- Force names of unique constraints in PostgreSQL DDL
- Add code for prevention from IMAP connection hangs when server closes socket unexpectedly
- Remove redundant DELETE query (for old session deletion) on login
- Get around unreliable rand() and mt_rand() in session ID generation (#2516)
- Fix some emails are not shown using Cyrus IMAP (#3316)
- Fix handling of mime-encoded words with non-integral number of octets in a word (#3301)
- Fix parsing links with non-printable characters inside (#3305)
- Fixed de_CH Localization bugs (#3279)
- Add variable for 'Today' label in date_today option (#2394)
- Fix dont_override setting does not override existing user preferences (#3205)
- Use only one from IMAP authentication methods to prevent login delays (1487784)
- Support strftime format in date_today option
- Fix SQL query in rcube_user::query() so it uses index on MySQL again
- Removed redundant </form> tags from contact add/edit pages
- Fix CSS error in contact details screen on IE7 (#3281)
RELEASE 0.5.1
-------------
- Fix handling of attachments with invalid content type (#3275)
- Add workaround for DBMail's bug http://www.dbmail.org/mantis/view.php?id=881 (#3274)
- Use IMAP's ID extension (RFC2971) to print more info into debug log
- Security: add optional referer check to prevent CSRF in GET requests
- Fix email_dns_check setting not used for identities/contacts (#3251)
- Fix ICANN example addresses doesn't validate (#3253)
- Security: protect login form submission from CSRF [CVE-2011-1491]
- Security: prevent from relaying malicious requests through modcss.inc [CVE-2011-1492]
- Fix handling of non-image attachments in multipart/related messages (#3261)
- Fix IDNA support when IDN/INTL modules are in use (#3253)
- Fix handling of invalid HTML comments in messages (#3269)
- Fix parsing FETCH response for very long headers (#3264)
- Fix add/remove columns in message list when message_sort_order isn't set (#3262)
- Check mime headers before attempt to parse them (#3256)
- Quote header values in show_additional_headers plugin (#3255)
- Fix settings UI on IE 6 (#3246)
- Remove double borders in folder listing (#3236)
- Separate full message headers UI element from headers table (#3238)
- Add part MIME ID to message_part_* hooks (#3241)
- Improve parsing of MS Outlook vCards (#3239)
- Updated PEAR::Net_Socket to 1.0.10
- Updated PEAR::Net_IDNA2 to 0.1.1
- Fix handling of comments inside an email address spec. (#3210)
- Show full mail subject as title when hovering a cut subject link (#3141)
- Fix randomly disappearing folders list in IE (#3231)
- Fix list column add/removal in IE (#3230)
- Fix login redirect issues (#3221)
- Require PHP 5.2.1 or greater
- Fix %h/%z variables in username_domain option (#3228)
- Workaround for setting charset in case of malformed bodystructure response (#3227)
- Fix impossible to subscribe to protected folders (#3199)
- Fix setting timezone in Preferences (#3232)
RELEASE 0.5
-----------
- Fix double-login/session issue (#3124)
- Wrap HTML parts with <html><body> and add Doctype declaration (#3119)
- Make rcube_autoload silently skip unknown classes (#3128)
- Fix charset detection in vcards with encoded values (#1934)
- Better CSS cursors for splitters (#2954)
- Show the same message only once (#3186)
- Fix namespaces handling (#3192)
- Add handling of multifolder METADATA/ANNOTATION responses
- Fix handling of INBOX when personal namespace prefix is non-empty (#3200)
- Fix handling square brackets in links (#3209)
- Add description of 'use_https' option in main.inc.php.dist file
RELEASE 0.5-RC
--------------
- Plugin API: Add 'pass' argument in 'authenticate' hook (#3147)
- Fix attachments of type message/rfc822 are not listed on attachments list
- Add 'login_lc' config option for case-insensitive authentication (#3131)
- Fix window is blur'ed in IE when selecting a message (#3161)
- Fix cursor position on compose form in Webkit browsers (#2796)
- Fix setting charset of attachment filenames (#3136)
- Allow setting autocomplete attribute for all inputs separately (#3158)
- New Folder Manager UI
- Fix invalid Request when creating a folder (#3165)
- Add folder size and quota indicator in folder manager (#2112)
- Add possibility to move a subfolder into root folder (#2890)
- Fix copying all messages in a folder copies only messages from current page
- Improve performance of moving or copying of all messages in a folder
- Fix plaintext versions of HTML messages don't contain placeholders for emotions (#1657)
- Improve performance of folder rename and delete actions
- Better support for READ-ONLY and NOPERM responses handling (#3108)
- Add confirmation message on purge/expunge command response
- Fix handling of untagged responses for AUTHENTICATE command (#3171)
- Add username and IP address to log message on unsuccessful login (#3176)
- Improved Mail-Followup-To and Mail-Reply-To headers handling
- Fix charset conversion for text attachments without charset specification (#3181)
RELEASE 0.5-BETA
----------------
- Make session data storage more robust against garbage session data (#3148)
- Config option for autocomplete on login screen
- Allow plugin templates to include local files (#3146)
- List groups in address detail view and allow to subscribe/unsubscribe from there (#2862)
- Messages caching: performance improvements, fixed syncing, fixes related with #2857
- Add link to identities in compose window (#2843)
- Add Internationalized Domain Name (IDNA) support (#729)
- Add option to automatically send read notifications for known senders (#2199)
- Add option to "Return receipt" will be always checked (#2571)
- Fix HTML to plain text conversion doesn't handle citation blocks (#2992)
- Use custom sorting when SORT is disabled by IMAP admin (#3020)
- Allow setting some washtml options from plugin (#2727)
- Add option do bind for an individual LDAP address book (#3048)
- Change reply prefix to display email address only if sender name doesn't exist (#2709)
- Plugin API: improved 'abort' flag handling, added 'result' item in some hooks (#2988)
- Fix mailto optional params in plain text messages aren't handled (#3071)
- Add Reply-to-List feature (#977)
- Add Mail-Followup-To/Mail-Reply-To support (#1937)
- Fix confirmation message isn't displayed after sending mail on Chrome (#2437)
- Fix keyboard doesn't work with autocomplete list with Chrome (#3073)
- Improve tabs to fixed width and add tabs in identities info (#3030)
- Add unique index on users.username+users.mail_host
- Make htmleditor option more consistent and add option to use HTML on reply to HTML message (#2164)
- Use empty envelope sender address for message disposition notifications (RFC 2298.3)
- Support SMTP Delivery Status Notifications - RFC 3461 (#2409)
- Use css sprite image for messages list
- Add (different) attachment icon for messages of type multipart/report (#2426)
- Prevent from inserting empty link when composing HTML message (#3007)
- Add caching support in id2uid and uid2id functions (#3065)
- Add SASL proxy authentication for SMTP (#2811)
- Improve displaying of UI messages (#3033)
- Fix double e-mail filed in identity form (#3088)
- Display IMAP errors for LIST/THREAD/SEARCH commands (#2981)
- Add LITERAL+ (IMAP4 non-synchronizing literals) support (RFC 2088)
- Add separate column for message status icon (#2788)
- Add ACL extension support into IMAP classes (RFC 4314)
- Add ANNOTATEMORE extension support into IMAP classes (draft-daboo-imap-annotatemore)
- Add METADATA extension support into IMAP classes (RFC 5464)
- Fix decoding of e-mail address strings in message headers (#3097)
- Fix handling of attachments when Content-Disposition is not inline nor attachment (#3086)
- Improve performance of unseen messages counting (#3090)
- Improve performance of messages counting using ESEARCH extension (RFC4731)
- Add LIST-STATUS support in rcube_imap_generic class (RFC 5819)
- Add SASL-IR support in IMAP (RFC 4959)
- Add LOGINDISABLED support (RFC 2595)
- Add support for AUTH=PLAIN in IMAP authentication
- Re-implemented SMTP proxy authentication support
- Add support for IMAP proxy authentication (#2808)
- Add support for AUTH=DIGEST-MD5 in IMAP (RFC 2831)
- Fix parent folder with unread subfolder not bold when message is open (#3104)
- Add basic IMAP LIST's \Noselect option support
- Add support for selection options from LIST-EXTENDED extension (RFC 5258)
- Don't list subscribed but non-existent folders (#2474)
- Fix handling of URLs with tilde (~) or semicolon (;) character (#3110, #3111)
- Plugin API: added 'contact_form' hook
- Add SORT=DISPLAY support (RFC 5957)
- Plugin API: add possibility to disable plugin in AJAX mode, 'noajax' property
- Plugin API: add possibility to disable plugin in framed mode, 'noframe' property
- Improve performance of setting IMAP flags using .SILENT suffix
- Improve performance of message cache status checking with skip_disabled=true
- Support contact's email addresses up to 255 characters long (#3116)
- Add option to place replies in the folder of the message being replied to (#2248)
- Add missing confirmation/error messages on contact/group/message actions (#2935)
- Add 'loading' message on message move/copy/delete/mark actions
- Improve responsiveness of messages displaying (#3039)
- Add option for minimum length of autocomplete's string (#2625)
- Fix operations on messages in unsubscribed folder (#3126)
- Add support for shared folders (#525)
- Fix handling of folders with name "0" (#3133)
- Fix handling of folders with "<>" characters in name
- jQuery 1.4.4
- Fix handling of HTML entity strings in plain text messages
- Fix focused elements aren't unfocused when clicking on the list (#3137)
- Fix error in MSSQL DDL scripts (#3130)
- Lock submit button in onsubmit event on login page (#3078)
- Don't set attachment's charset in Content-type header (#3136)
- Fix handling of message bodies (quoted-printable encoded) with NULL characters (#2448)
- Add workaround for MSOE's multipart/related messages with non-related attachments
RELEASE 0.4.2
-------------
- Fix handling of backslash as IMAP delimiter
- Fix charset replacement in HTML message bodies (#3067)
- Fix: contact group input is empty when using rename action more than once on the same group record
- Fix "Server Error! (Not Found)" when using utils/save-pref action (#3069)
- Fix handling of Thunderbird's vCards (#3070)
RELEASE 0.4.1
-------------
- Fix space-stuffing in format=flowed messages (#3064)
- Fix msgexport.sh now using the new imap wrapper
- Avoid displaying password on shell (#3010)
- Only lower-case user name if first login attempt failed (#2600)
- Make alias setting in squirrelmail_usercopy plugin configurable (patch by pommi, #3056)
- Prevent from saving a non-existing skin path in user prefs (#3004)
- Improve handling of single-part messages with bogus BODYSTRUCTURE (#2976)
- Fix path to SQL files when using pgsql/mysqli/sqlsrv drivers (#2979)
- Fix upgrade script for SQLite (#2980)
- Fixes in SQL init script + added update script for MSSQL database
- Remove redundant date in syslog messages (#3008)
- Fix contacts list page controls when a group is selected (#3009)
- Fix SMTP test in Installer (#3014)
- Fix "Select all" causes message to be opened in folder with exactly one message (#2987)
- Fix Tab key doesn't work in HTML editor in Google Chrome (#2995)
- Fix TinyMCE uses zh_CN when zh_TW locale is set (#2998)
- Fix TinyMCE buttons are hidden in Opera (#2993)
- Fix JS error on IE when trying to send HTML message with enabled spellchecker (#3006)
- Display inline images with known extensions and non-image content-type (#3002)
- Fix "Threaded" checkbox after subfolder creation (#2997)
- Fix timezone string in sent mail (#3021)
- Show disabled checkboxes for protected folders instead of dots (#1898)
- Added fieldsets in Identity form, added 'identity_form' hook
- Re-added 'Close' button in upload form (#2999, #2917)
- Fix handling of charsets with LATIN-* label
- Fix messages background image handling in some cases (#3043)
- Fix format=flowed handling (#3042)
- Fix when IMAP connection fails in 'get' action session shouldn't be destroyed (#3046)
- Fix list_cols is not updated after column dragging (#3050)
- Support %z variable in host configuration options (#3054)
RELEASE 0.4
-----------
- Fix disappearing upload form disappears when user selects a file on Safari (#2917)
- Don't replace error messages with loading info (#2534)
- Fix JS errors on compose mode switch (#2952)
- Fix message structure parsing when it lacks optional fields (#2960)
- Include all recipients in sendmail log
- Support HTTP_X_FORWARDED_PROTO header for HTTPS detecting (#2950)
- Fix default IMAP port configuration (#2948)
- Create Sent folder when starting to compose a new message (#2900)
- Fix handling of messages with Content-Type: application/* and no filename (#840)
- Improved compose screen: resizable body and attachments list, vertical splitter, options menu
- Fix RC forgets search results (#722)
- TinyMCE 3.3.7
- Improve parsing of styled empty tags in HTML messages (#2908)
- Add %dc variable support in base_dn/bind_dn config (#2881)
- Add button to hide/unhide the preview pane (#955)
- Fix no-cache headers on https to prevent content caching by proxies (#2897)
- Fix attachment filenames broken with TNEF decoder using long filenames (#2894)
- Use user's timezone in Date header, not server's timezone (#2393)
- Add option to set separate footer for HTML messages (#2784)
- Add real SMTP error description to displayed error messages (#2233)
- Fix some IMAP errors handling when opening the message (#1848)
- Fix related parts aren't displayed when got mimetype other than image/* (#2629)
- Multiple identity and database support for squirrelmail_usercopy plugin (#2686)
- Support dynamic hostname (%d/%n) variables in configuration options (#1843)
- Add 'messages_list' hook (#2504)
- Add request* event triggers in http_post/http_request (#2340)
- Fix use RFC-compliant line-delimiter when saving messages on IMAP (#2828)
- Add 'imap_timeout' option (#2869)
- Fix forwarding of messages with winmail attachments
- Fix handling of uuencoded attachments in message body (#2163)
- Added list_mailboxes hook in rcube_imap::list_unsubscribed() (#2791)
- Fix wrong message on file upload error (#2839)
- Add support for data URI scheme [RFC2397] (#2851)
- Added 'actionbefore', 'actionafter', 'responsebefore', 'responseafter' events
- Fix double-addition of e-mail domain to content ID in HTML images
- Read and send messages with format=flowed (#1052), fixes word wrapping issues (#2703)
- Fix duplicated attachments when forwarding a message (#2670)
- Fix message/rfc822 attachments containing only attachments are not parsed properly (#2854)
- Fix %00 character in winmail.dat attachments names (#2850)
- Fix handling errors of folder deletion (#2821)
- Parse untagged CAPABILITY response for LOGIN command (#2853)
- Renamed all php-cli scripts to use .sh extension
- Some files from /bin + spellchecking actions moved to the new 'utils' task
- Added thread tree icons
- Extend contact groups support (#2802)
- Fix check-recent action issues and performance (#2690)
- Fix messages order after checking for recent (#1249)
- Fix autocomplete shows entries without email (#2640)
- Fix listupdate event doesn't trigger on search response (#2824)
- Fix select_all_mode value after selecting a message (#2834)
- Set focus to editor on reply in HTML mode (#2768)
- Fix composing in HTML jumps cursor to body instead of recipients (#2796)
- Allow columns order change per user - drag&drop (#2124)
- Add References header in read receipt (#2801)
- Fix database constraint violation when opening a message (#2814)
- Add 'loading' message while login is in progress (#2790)
- Fix quota_zero_as_unlimited (#2786)
- Fix folder subscription checking (#2804)
- Fix INBOX appears (sometimes) twice in mailbox list (#2794)
- Fix listing of attachments of some types e.g. "x-epoc/x-sisx-app" (#2779)
- Fix DB Schema checking when some db_table_* options are not set (#2780)
RELEASE 0.4-beta
----------------
- Add sizelimit and timelimit variables in LDAP config (#2704)
- Hide IMAP host dropdown when single host is defined (#2553)
- Add images pre-loading on login page (#623)
- Add HTTP_X_REAL_IP and HTTP_X_FORWARDED_FOR to successful logins log (#2634)
- Fix setting spellcheck languages with extended codes (#2747)
- Fix messages list scrolling in FF3.6 (#2657)
- Fix quicksearch input focus (#2770)
- Always set changed date when flagging a DB record as deleted + provide a cleanup script
- Fix address book/group selection (#2760)
- Assign newly created contacts to the active group (#2764)
- Added option not to mark messages as read when viewed in preview pane (#1513)
- Allow plugins modify the Sent folder when composing (#2708)
- Added optional (max_recipients) support to restrict total number of recipients per message (#1167)
- Re-organize editor buttons, add blockquote and search buttons
- Make possible to write inside or after a quoted html message (#1878)
- Fix bugs on unexpected IMAP connection close (#2449, #2507)
- Iloha's imap.inc rewritten into rcube_imap_generic class
- Added contact groups in address book (not finished yet)
- Added PageUp/PageDown/Home/End keys support on lists (#2627)
- Added possibility to select all messages in a folder (#1312)
- Added 'imap_force_caps' option for after-login CAPABILITY checking (#2087)
- Password: Support dovecotpw encryption
- TinyMCE 3.3.1
- Implemented messages copying using drag&drop + SHIFT (#863)
- Improved performance of folders operations (#2689)
- Fix blocked.gif attachment is not attached to the message (#2685)
- Managesieve: import from Horde-INGO
- Managesieve: support for more than one match (#2362)
- Managesieve: support for selectively disabling rules within a single sieve script (#2198)
- Threaded message listing now available
- Added sorting by ARRIVAL and CC
- Message list columns configurable by the user
- Removed 'index_sort' option, now we're using empty 'message_sort_col' for this
- virtuser_query: support other identity data (#2413)
- Options virtuser_* replaced with virtuser_* plugins
- Plugin API: Implemented 'email2user' and 'user2email' hooks
- Fix forwarding message omits CC header (#2538)
- Add 'default_charset' option to user preferences (#1855)
- Add 'delete_always' option to user preferences
- Support/Require tls:// prefix in 'smtp_server' option for TLS connections
- Fix inconsistent behaviour of 'delete_always' option (#2533)
- Fix deleting all messages from last list page (#2528)
- Flag original messages when sending a draft (#2458)
- Changed signature separator when top-posting (#2555)
- Let the admin define defaults for search modifiers (#2211)
- Fix long e-mail addresses validation (#2641)
- Remember search modifiers in user prefs (#2411)
- Added force_7bit option to force MIME encoding of plain/text messages (#2679)
- Use case sensitive check when checking for default folders (#2567)
- Fix checking for new mail: now checks unseen count of inbox (#2123)
- Improve performance by avoiding unnecessary updates to the session table (#2552)
- Fix invalid <font> tags which cause HTML message rendering problems (#2687)
- Fix CVE-2010-0464: Disable DNS prefetching (#2639)
- Fix Received headers to behave better with SpamAssassin (#2682)
- Password: Make passwords encoding consistent with core, add 'password_charset' global option (#2658)
- Fix adding contacts SQL error on mysql (#2645)
- Squirrelmail_usercopy: support reply-to field (#2678)
- Fix IE spellcheck suggestion popup issue (#2656)
- Fix email address auto-completion shows regexp pattern (#2498)
- Fix merging of configuration parameters: user prefs always survive (#2584)
- Fix quota indicator value after folder purge/expunge (#2671)
- Fix external mailto links support for use as protocol handler (#2328)
- Fix attachment excessive memory use, support messages of any size (#1245)
- Fix setting task name according to auth state
- Password: fix vpopmaild driver (#2662)
- Add workaround for MySQL bug [http://bugs.mysql.com/bug.php?id=46293] (#2659)
- Fix quoted text wrapping when replying to an HTML email in plain text (#897)
- Fix handling of extended mailto links (with params) (#2573)
- Fix sorting by date of messages without date header on servers without SORT (#2521)
- Fix inconsistency when not using default table names (#2652)
- Fix folder rename/delete buttons do not appear on creation of first folder (#2653)
- Fix character set conversion fails on systems where iconv doesn't accept //IGNORE (#2590)
- Log in performance: Create default folders on first login only
- Import contacts into the selected address book (by Phil Weir)
- Add support for MDB2's 'sqlsrv' driver (#2602)
- Use jQuery-1.4
- Removed problematic browser-caching of messages
- Fix incompatybility with suhosin.executor.disable_emodifier (#2549)
- Use PLAIN auth when CRAM fails and imap_auth_type='check' (#2587)
- Fix removal of <title> tag from HTML messages (#2629)
- Fix 'force_https' to specified port when URL contains a port number (#2612)
- Fix to-text converting of HTML entities inside b/strong/th/hX tags (#2621)
- Bug in spellchecker suggestions when server charset != UTF8 (#2607)
- Managesieve: Fix requires generation for multiple actions (#2603)
- Fix LDAP problem with special characters in RDN (#2548)
- Improved handling of message parts of type message/rfc822
- Plugin API: added 'quota' hook
- Fix parsing conditional comments in HTML messages (#2569)
- Use built-in json_encode() for proper JSON format in AJAX replies
- Allow setting only selected params in 'message_compose' hook (#2543)
- Plugin API: added 'message_compose_body' hook (#2520)
- Fix counters of all folders are checked in 'getunread' action with check_all_folders disabled (#2399)
- Fix displaying alternative parts in messages of type message/rfc822 (#2488)
- Fix possible messages exposure when using Roundcube behind a proxy (#2516)
- Fix unicode para and line separators in javascript response (#2542)
- Additional_message_headers: allow unsetting headers, support plugin's config file (#2505)
- Fix displaying of hidden directories in skins list (#2535)
- Fix open_basedir restriction error when reading skins list (#2537)
- Fix pasting from Office apps into html editor (#2508)
- Fix empty <a> tags parsing (#2509)
- Don't cut off attachment names when using non-RFC2231 encoding (#1912)
- Allow inserting signatures above replied message body (#991)
- Managesieve 2.0: multi-script support
- Fix imap_auth_type regression (#2502)
RELEASE 0.3.1
------------------
- Specify toolbar container in compose template (#2489)
- Fix $_SERVER['HTTPS'] check for SSL forcing on IIS (#2486)
- Avoid unnecessary page loads for selected tab (#2324)
- Fix quota indicator issues by content generation on client-size (#2454, #2470)
- Don't display disabled sections in Settings (#2380)
- Added server-side e-mail address validation with 'email_dns_check' option (#2175)
- Fix login page loading into an iframe when session expires (#2253)
- Allow setting port number in 'force_https' option (#2373)
- Option 'force_https' replaced by 'force_https' plugin
- Fix IE issue with non-UTF-8 characters in AJAX response (#2422)
- Partially fixed "empty body" issue by showing raw body of malformed message (#2427)
- Fix importing/sending to email address with whitespace (#2467)
- Added XIMSS (CommuniGate) driver for Password plugin
- Fix newly attached files are not saved in drafts w/o editing any text (#2457)
- Added attachment upload indicator with parallel upload (#2344)
- Use default_charset for bodies of messages without charset definition (#2446)
- Password: added cPanel driver
- Fix return to first page from e-mail screen (#2385)
- Fix handling HTML comments in HTML messages (#2448)
- Fix folder/messagelist controls alignment - icons used (#2356)
- Fix LDAP addressbook shows 'Contact not found' error sometimes (#2438)
- Fix cache status checking + improve cache operations performance (#2384)
- Prevent from setting INBOX as any of special folders (#2390)
- Fix regular expression for e-mail address (#2417)
- Fix Received header format
- Implemented sorting by message index - added 'index_sort' option (#2240)
- Fix dl() use in installer (#2415)
- Added 'ldap_debug' option
- Fix "Empty startup greeting" bug (#2369)
- Fix setting user name in 'new_user_identity' plugin (#2405)
- Fix incorrect count of new messages in folder list when using multiple IMAP clients (#2289)
- Fix all folders checking for new messages with disabled caching (#2399)
- Support skins in 'archive' and 'markasjunk' plugins
- Added 'html_editor' hook (#2353)
- Fix DB constraint violation when populating messages cache (#2338)
- Password: added password strength options (#2348)
- Fix LDAP partial result warning (#1928)
- Fix delete in message view deletes permanently with flag_for_deletion=true (#2382)
- Use faster/secure mt_rand() (#2376)
- Fix roundcube hangs on empty inbox with bincimapd (#2375)
- Fix wrong headers for IE on servers without $_SERVER['HTTPS'] (#2232)
- Force IE style headers for attachments in non-HTTPS session, 'use_https' option (#2023)
- Check 'post_max_size' for upload max filesize (#2372)
- Password Plugin: Fix %d inserts username instead of domain (#2371)
- Fix rcube_mdb2::affected_rows() (#2366)
RELEASE 0.3-stable
------------------
- Fix gn and givenName should be synonymous in LDAP addressbook (#2208)
- Add mail_domain to LDAP email entries without @ sign (#1652)
- Fix saving empty values in LDAP contact data (#2113)
- Fix LDAP contact update when RDN field is changed (#2119)
- Fix LDAP attributes case senitivity problems (#2155)
- Fix LDAP addressbook browsing when only one directory is used (#2314)
- Fix endless loop on error response for APPEND command (#2346)
- Don't require date.timezone setting in installer (#2284)
- Fix date sorting problem with Courier IMAP server (#2351)
- Unselect pressed buttons on mouse up (#2283)
- Don't set php_value error_log in .htaccess but mention in INSTALL (#2230)
- Fix too small status/flag/attachment columns in Safari 4 (#2349)
- Fix selection disabling while dragging splitter in webkit browsers (#2342)
- Added 'new_messages' plugin hook (#2298)
- Added 'logout_after' plugin hook (#2333)
- Added 'message_compose' hook
- Added 'imap_connect' hook (#2256)
- Fix vcard_attachments plugin (#2326)
- Updated PEAR::Auth_SASL to 1.0.3 version
- Use sequence names only with PostgreSQL (#2310)
- Re-designed User Preferences interface
- Fix MS SQL DDL (#2312)
- Fix rcube_mdb2.php: call to setCharset not implemented in mssql driver (#2311)
- Added 'display_next' option
- Fix rcube_mdb2::unixtimestamp for MS SQL (#2308)
- Fix HTML washing to respect character encoding
- Fix endless loop in iil_C_Login() with Courier IMAP (#2303)
- Fix #messagemenu display on IE (#2299)
- Speedup UI by using sprites for (toolbar) buttons
- Fix charset names with X- prefix handling
- Fix displaying of HTML messages with unknown/malformed tags (#2296)
RELEASE 0.3-RC1
---------------
- Fix import of vCard entries with params (#1857)
- Fix HTML messages output with empty block elements (#2271)
- Use request tokens to protect POST requests from CSRF [CVE-2009-4076, CVE-2009-4077]
- Added hook when killing a session
- Added hook to write_log function (#2268)
- Performance improvements by use UID commands (#2046)
- Fix HTML editor tabIndex setting (#2269)
- Added 'imap_debug' and 'smtp_debug' options
- Support strftime's format modifiers in date_* options (#1354)
- Support %h variable in 'smtp_server' option (#2101)
- Show SMTP errors in browser (#2233)
- Allow WBR tag in HTML message (#2259)
- Use spl_autoload_register() instead of __autoload (#2250)
- Add hook for identities listing (#2257)
- Trigger hook 'smtp_connect' when opening an SMTP connection (#2255)
- Added config option to enforce HTTPS connections
- Fix non-unicode characters caching in unicode database (#1209)
- Performance improvements of messages caching
- Fix empty Date header issue (#2229)
- Open collapsed folders during drag & drop (#2221)
- Fixed link text replacements (#2120)
- Also trigger 'insertrow' events on page load (#2151)
- No link on subject in IE browsers (#1438)
- Fixed filename encoding according to RFC2231 (#2192)
- Added message Edit feature (#727, #1101)
- Fix message Etag generation for counter issues (#1996)
- Fix messages searching on MailEnable IMAP (#2097)
- Fixed many 'skip_deleted' issues (#2006)
- Fixed messages list sorting on servers without SORT capability
- Colorized signatures in plain text messages
- Reviewed/fixed skip_deleted/read_when_deleted/flag_for_deletion options handling in UI
- Fix displaying of big maximum upload filesize (#2205)
- Added possibility to invert messages selection
- After move/delete from 'show' action display next message instead of messages list (#2203)
- Fixed problem with double quote at the end of folder name (#2200)
- Speedup UI by using CSS sprites and etags/expires/deflate in Apache config (#1397,#2128)
- Support UID EXPUNGE: remove only moved/deleted messages
- Add drag cancelling with ESC key (#1036)
- Support initial identity name from virtuser_query (#807)
- Added message menu, removed Print and Source buttons
- Added possibility to save message as .eml file (#2178)
- Added 1 minute interval in autosave options (#2173)
- Support UTF-7 encoding in messages (#2156)
- Better support for malformed character names (#2093)
RELEASE 0.3-BETA
----------------
- Plugin API + jQuery engine
- Added possibility to encrypt received header, option 'http_received_header_encrypt',
added some more logic in encrypt/decrypt functions for security
- Fix Answered/Forwarded flag setting for messages in subfolders
- Fix autocomplete problem with capital letters (#2122)
- Support UUencode content encoding (#2163)
- Minimize chance of race condition in session handling (#1260)
- Fix session handling on non-session SQL query error (#2078)
- Fix html editor mode setting when reopening draft message (#2158)
- Added quick search box menu (#1010)
- Fix wrong column sort order icons (#2149)
- Updated TinyMCE to 3.2.3 version
- Fix attachment names encoding when charset isn't specified in attachment part (#1483)
- Fix message normal priority problem (#2146)
- Fix autocomplete spinning wheel does not disappear (#2132)
- Added log_date_format option (#2060)
- Fix text wrapping in HTML editor after switching from plain text to HTML (#1917)
- Fix auto-complete function hangs with plus sign (#2141)
- Fix AJAX requests errors handler (#1503)
- Speed up message list displaying on IE
- Fix read/write database recognition (#2137)
RELEASE 0.2.2
-------------
- Fix quicksearchbox look in Chrome and Konqueror (#1380)
- Fix UTF-8 byte-order mark removing (#1911)
- Fix folders subscribtions on Konqueror (#1380)
- Fix debug console on Konqueror and Safari
- Fix messagelist focus issue when modifying status of selected messages (#2134)
- Support STARTTLS in IMAP connection (#1714)
- Fix DEL key problem in search boxes (#1923)
- Support several e-mail addresses per user from virtuser_file (#2036)
- Fix drag&drop with scrolling on IE (#2117)
- Fix adding signature separator in html mode (#1768)
- Fix opening attachment marks message as read (#2131)
- Fix 'temp_dir' does not support relative path under Windows (#1157)
- Fix "Initialize Database" button missing from installer (#2130)
- Fix compose window doesn't fit 1024x768 window (#1807)
- Fix service not available error when pressing back from compose dialog (#1942)
- Fix using mail() on Windows (#2111)
- Fix word wrapping in message-part's <PRE>s for printing (#2118)
- Fix incorrect word wrapping in outgoing plaintext multibyte messages (#2062)
- Fix double footer in HTML message with embedded images
- Fix TNEF implementation bug (#2107)
- Fix incorrect row id parsing for LDAP contacts list (#2116)
- Fix 'mode' parameter in sqlite DSN (#2106)
RELEASE 0.2.1
------------------
- Use US-ASCII as failover when Unicode searching fails (#2097)
- Fix errors handling in IMAP command continuations (#2097)
- Fix FETCH result parsing for servers returning flags at the end of result (#2098)
- Fix datetime columns defaults in mysql's DDL (#2012)
- Fix attaching more than nine inline images (#2094)
- Support 'UNICODE-1-1-UTF-7' alias for UTF-7 encoding (#2093)
- Fix mime-type detection using a hard-coded map (#1735)
- Don't return empty string if charset conversion failed (#2092)
- Disable concurrent autocomplete query results display (#2082)
- Fix new lines stripped from message footer (#2088)
- Fix IE problem with mouse click autocomplete (#2080)
- Fix html body washing on reply/forward + fix attachments handling (#2034)
- Fix multiple recipients input parsing (#2077)
- Fix replying to message with html attachment (#2034)
- Use default_charset for messages without specified charset (#2027, #1484961)
- Support non-standard "GMT-XXXX" literal in date header (#2074)
- Added TNEF support to decode MS Outlook attachments (winmail.dat)
- Fix "value continuation" MIME headers by adding required semicolon (#2073)
- Fix pressing select all/unread multiple times (#2069)
- Fix selecting all unread does not honor new messages (#2070)
- Fix some base64 encoded attachments handling (#2071)
- Support NGINX as IMAP backend: better BAD response handling (#2066)
- Performance fix: don't fetch attachment parts headers twice to parse filename
- Fix checking for recent messages on various IMAP servers (#2055)
- Performance fix: Don't fetch quota and recent messages in "message view" mode
- Fix displaying of alternative-inside-alternative messages (#2061)
- Fix MDNSent flag checking, use arbitrary keywords (asterisk) flag (#2059)
- Fix creation of folders with '&' sign in name
- Fix parsing of email addresses without angle brackets (#2048)
- Save spellcheck corrections when switching from plain to html editor (and spellchecking is on)
- Fix large search results on server without SORT capability (#2031)
- Get rid of preg_replace() with eval modifier and create_function usage (#2042)
- Bring back <base> and <link> tags in HTML messages
- Fix XSS vulnerability through background attributes [CVE-2009-0413]
- Fix problems with backslash as IMAP hierarchy delimiter (#1116)
- Secure vcard export by getting rid of preg's 'e' modifier use (#2045)
- Fix authentication when submitting form with existing session (#2037)
- Allow absolute URLs to images in HTML messages/sigs (#2029)
- Fix message body which contains both inline attachments and emotions
- Fix SQL query execution errors handling in rcube_mdb2 class (#1907)
- Fix address names with '@' sign handling (#2022)
- Improve messages display performance
- Fix messages searching with 'to:' modifier
RELEASE 0.2-STABLE
------------------
- Fix mark popup in IE 7 (#1785)
- Fix line-break issue when copy & paste in Firefox (#1832)
- Fix autocomplete "unknown server error" (#2008)
- Fix STARTTLS before AUTH in SMTP connection (#1415)
- Support multiple quota values in QUOTAROOT resonse (#1999)
- Only abbreviate file name for IE < 7 browsers (#1548)
- Performance: allow setting imap rootdir and delimiter before connect (#1628)
- Fix sorting of folders with more than 2 levels (#1953)
- Fix search results page jumps in LDAP addressbook (#1689)
- Fix empty line before the signature in IE (#1769)
- Fix horizontal scrollbar in preview pane on IE (#1228)
- Add Robots meta tag in login page and installer (#1385)
- Added 'show_images' option, removed 'addrbook_show_images' (#1977)
- Option to check for new mails in all folders (#1053)
- Don't set client busy when checking for new messages (#1706)
- Allow UTF-8 folder names in config (#1960)
- Add junk_mbox option configuration in installer (#1960)
- Do serverside addressbook queries for autocompletion (#1925)
- Allow setting attachment col position in 'list_cols' option
- Allow override 'list_cols' via skin (#1958)
- Fix 'cache' table cleanup on session destroy (#1913)
- Increase speed of session destroy and garbage clean up
- Fix session timeout when DB server got clock skew (#1890)
- Fix handling of some malformed messages (#1099)
- Speed up raw message body handling
- Better HTML entities conversion in html2text (#1916)
- Fix big memory consumption and speed up searching on servers without SORT capability
- Fix setting locale to tr_TR, ku and az_AZ (#1872)
- Use SORT for searching on servers with SORT capability
- Added message status filter
- Fix empty file sending (#1801)
- Improved searching with many criterias (calling one SEARCH command)
- Fix HTML editor initialization on IE (#1731)
- Add warning when switching editor mode from html to plain (#1888)
- Make identities list scrollable (#1930)
- Fix problem with numeric folder names (#1922)
- Added BYE response simple support to prevent from endless loops in imap.inc (#777)
- Fix unread message unintentionally marked as read if read_when_deleted=true (#1819)
- Remove port number from SERVER_NAME in smtp_helo_host (#1915)
- Don't send disposition notification receipts for messages marked as 'read' (#1918)
- Added 'keep_alive' and 'min_keep_alive' options (#1777)
- Added option 'identities_level', removed 'multiple_identities'
- Allow deleting identities when multiple_identities=false (#1840)
- Added option focus_on_new_message (#1789)
- Fix html2text class autoloading on Windows (#1904)
- Fix html signature formatting when identity save error occurred (#1833)
- Add feedback and set busy when moving folder (#1897)
- Fix 'Empty' link visibility for some languages e.g. Slovak (#1889)
- Fix messages count bar overlapping (#1703)
- Fix adding signature in drafts compose mode (#1884)
- Fix iil_C_Sort() to support very long and/or divided responses (#1713)
- Fix matching case sensitivity when setting identity on reply (#1881)
- Prefer default identity on reply
- Fix imap searching on ISMail server (#1870)
- Add css class for flagged messages (#1868)
- Write username instead of id in sendmail log (#1879)
- Fix htmlspecialchars() use for PHP version < 5.2.3 (#1877)
- Fix js keywords escaping in json_serialize() for IE/Opera (#1874)
- Added bin/killcache.php script (#1839)
- Add support for SJIS, GB2312, BIG5 in rc_detect_encoding()
- Fix vCard file encoding detection for non-UTF-8 strings (#1820)
- Add 'skip_deleted' option in User Preferences (#1850)
- Minimize "inline" javascript scripts use (#1838)
- Fix css class setting for folders with names matching defined classes names (#1772)
- Fix race conditions when changing mailbox
- Fix spellchecking when switching to html editor (#1779)
- Fix compose window width/height (#1807)
- Allow calling msgimport.sh/msgexport.sh from any directory (#1837)
- Localized filesize units (#1760)
- Better handling of "no identity" and "no email in identity" situations (#1592)
- Added 'mime_param_folding' option with possibility to choose long/non-ascii attachment names encoding eg. to be readable in MS Outlook/OE (#1743)
- Added "advanced options" feature in User Preferences
- Fix unread counter when displaying cached massage in preview panel (#1720)
- Fix htmleditor spellchecking on MS Windows (#1808)
- Fix problem with non-ascii attachment names in Mail_mime (#1700, #1576)
- Fix language autodetection (#1812)
- Fix button label in folders management (#1816)
- Fix collapsed folder not indicating unread msgs count of all subfolders (#1814)
- Fix handling of apostrophes in filenames decoded according to rfc2231
RELEASE 0.2-BETA
----------------
- Made config files location configurable (#1664)
- Reduced memory footprint when forwarding attachments (#1764)
- Allow and use spellcheck attribute for input/textarea fields (#1545)
- Added icons for forwarded/forwarded+replied messages (#1691)
- Added Reply-To to forwarded emails (#1739)
- Display progress message for folders create/delete/rename (#1774)
- Smart Tags and NOBR tag support in html messages (#1780, #1748)
- Redesign of the identities settings (#836)
- Add config option to disable creation/deletion of identities (#1139)
- Added 'sendmail_delay' option to restrict messages sending interval (#1135)
- Added vertical splitter for folders list resizing
- Added possibility to view all headers in message view
- Fixed splitter drag/resize on Opera (#1626)
- Fixed quota img height/width setting from template (#1396)
- Refactor drag & drop functionality. Don't rely on browser events anymore (#1108)
- Insert "virtual" folders in subscription list (#1333)
- Added link to open message in new window
- Enable export of address book contacts as vCard
- Add feature to import contacts from vcard files (#395)
- Respect Content-Location headers in multipart/related messages according to RFC2110 (#1464)
- Allowed max. attachment size now indicated in compose screen (#1523)
- Also capture backspace key in list mode (#1186)
- Allow application/pgp parts to be displayed (#1309)
- Correctly handle options in mailto-links (#1671)
- Immediately save sort_col/sort_order in user prefs (#1698)
- Truncate very long (above 50 characters) attachment filenames when displaying
- Allow to auto-detect client language if none set (#1095)
- Auto-detect the client timezone (user configurable)
- Add RFC2231 header value continuations support for attachment filenames + hack for servers that not support that feature
- Fix Reply-To header displaying (#1738)
- Mark form buttons that provide the most obvious operation (mainaction)
- Added option 'quota_zero_as_unlimited' (#1206)
- Added PRE handling in html2text class (#1301)
- Added folder hierarchy collapsing
- Added options to use syslog instead of log file (#1389)
- Added Logging & Debugging section in Installer
- Fix In-Reply-To and References headers when composing saved draft message (#1718)
- Fix html message charset conversion for charsets with underline (#1717)
- Fix buttons status after contacts deletion (#1675)
- Fix escaping of To: and From: fields when building message body for reply or forward in the HTML editor (#1432)
- Use current mailbox name in template (#1690)
- Better fix for skipping untagged responses (#1694)
- Added pspell support patch by Kris Steinhoff (#781)
- Enable spellchecker for HTML editor (#1589)
- Respect spellcheck_uri in tinyMCE spellchecker (#941)
- Case insensitive contacts searching using PostgreSQL (#1692)
- Make default imap folders configurable for each user (#1558)
- Save outgoing mail to selectable folder (#1324581)
- Fix hiding of mark menu when clicking th button again (#1463)
- Use long date format in print mode (#1643)
- Updated TinyMCE to version 3.1.0.1
- Re-enable autocomplete attribute for login form (#1661)
- Check PERMANENTFLAGS before saving $MDNSent flag (#1478, #1485163)
- Added flag column on messages list (#1220)
- Patched Mail/MimePart.php (http://pear.php.net/bugs/bug.php?id=14232)
- Allow trash/junk subfolders to be purged (#1568)
- Store compose parameters in session and redirect to a unique URL
- Fixed CRAM-MD5 authentication (#1364)
- Fixed forwarding messages with one HTML attachment (#1103)
- Fixed encoding of message/rfc822 attachments and image/pjpeg handling (#1439)
- Added option to select skin in user preferences
- Added option to configure displaying of attached images below the message body
- Added option to display images in messages from known senders (#1204)
- User preferences grouped in more fieldsets
- Fix corrupted MIME headers of messages in Sent folder (#1587)
- Fixed bug in MDB2 package: http://pear.php.net/bugs/bug.php?id=14124
- Use keypress instead of keydown to select list's row (#1362)
- Don't call expunge and don't remove message row after message move if flag_for_deletion is set to true (#1505)
RELEASE 0.2-ALPHA
-----------------
- Added option to disable autocompletion from selected LDAP address books (#1445)
- TLS support in LDAP connections: 'use_tls' property (#1581)
- Fixed removing messages from search set after deleting them (#1583)
- imap.inc: Fixed iil_C_FetchStructureString() to handle many
literal strings in response (#1483)
- Support for subfolders in default/protected folders (#1250)
- Disallowed delimiter in folder name (#1351)
- Support " and \ in folder names
- Escape \ in login (#1214)
- Better HTML sanitization with the DOM-based washtml script (#1276)
- Fixed sorting of folders with non-ascii characters
- Fixed Mysql DDL for default identities creation (#1554)
- In Preferences added possibility to configure 'read_when_deleted',
'mdn_requests', 'flag_for_deletion' options
- Made IMAP auth type configurable (#683)
- Fixed empty values with FROM_UNIXTIME() in rcube_mdb2 (#1540)
- Fixed attachment list on IE 6/7 (#1355)
- Fixed JavaScript in compose.html that shows cc/bcc fields if populated
- Make password input fields of type password in installer (#1417)
- Don't attempt to delete cache entries if enable_caching is FALSE (#1537)
- Optimized messages sorting on servers without sort capability (#1535)
- Corrected message headers decoding when charset isn't specified and improved
support for native languages (#1536, #1534)
- Expanded LDAP configuration options to support LDAP server writes.
- Installer: encode special characters in DB username/password (#1529)
- Fixed management of folders with national characters in names (#1526, #1504)
- Fixed identities saving when using MDB2 pgsql driver (#1525)
- Fixed BCC header reset (#1501)
- Improved messages list performance - patch from Justin Heesemann
- Append skin_path to images location only when it starts with '/' sign (#1398)
- Fix IMAP response in message body when message has no body (#1479)
- Fixed non-RFC dates formatting (#1429)
- Fixed typo in set_charset() (#1498)
- Decode entities when inserting HTML signature to plain text message (#1497)
- HTML editing is now working with PHP5 updates and TinyMCE v3.0.6
- Fixed signature loading on Windows (#1169)
- Added language support to HTML editing (#1401)
- Fixed remove signature when replying (#446)
- Fixed problem with line with a space at the end (#1440)
- Fixed <!DOCTYPE> tag filtering (#1066)
- Fixed <?xml> tag filtering (#1075)
- Added sections (fieldset+label) in Settings interface
- Mark as read in one action with message preview (#1486)
- Deleted redundant quota reads (#1486)
- Added options for empty trash and expunge inbox on logout (#707)
- Removed lines wrapping when displaying message
- Fixed month localization
- Changed codebase to PHP5 with autoloader
RELEASE 0.1.1
-------------
- Clear selection when selecting single item (#1461)
- Remove hard-coded image size in skin templates (#1423)
- Database schema improvements (dropped unnecessary indexes)
- Fixed creating a new folder with a comma in its name (#1263)
- Fixed sorting of messages when default mailbox is empty (#1020)
- Improve message previewpane - less loading (#1019)
- Fixed login form autoompletion (#1378)
- Fixed virtuser_query option for mdb2 backend (#1409)
- Fixed attachment resoting from Drafts when message body was empty (#1144)
- Fixed usage of ob_gzhandler (#1390)
- Fixed message part window in IE6 (#1211)
- Fixed decoding of mime-encoded strings (#938)
- Fixed some iconv/mb_string problems (#1202)
- Correctly quote mailbox name when using in URL (#1016)
- Fixed "headers already sent" errors (#1399)
RELEASE 0.1-STABLE
------------------
- Added interactive installer script
- Fix folder adding/renaming inspired by #1349
- Localize folder name in page title (#1338)
- Fix code using wrong variable name (#818)
- Allow to send mail with BCC recipients only
- condense TinyMCE toolbar down to one line, removing table buttons (#1306)
- Add function to mark the selected messages as read/unread (#641)
- Also do charset decoding as suggested in RFC 2231 (fix #1022)
- Show message count in folder list and hint when creating a subfolder
- Distinguish ssl and tls for imap connections (#1252)
- Added some charset aliases to fix typical mis-labelling (#1185)
- Remember decision to display images for a certain message during session (#1310)
- Truncate attachment filenames to 55 characters due to an IE bug (#1313)
- Make sending of read receipts configurable
- Respect config when localize folder names (#1280)
- Also respect receipt and priority settings when re-opening a draft message
- Remember search results (closes #722), patch by the_glu
- Add Received header on outgoing mail
- Upgrade to TinyMCE 2.1.3
- Allow inserting image attachments into HTML messages while composing (#1179)
- Implement Message-Disposition-Notification (Receipts)
- Fix overriding of session vars when register_globals is on (#1255)
- Fix bug with case-sensitive folder names (#973)
- Don't create default folders by default
- Fixed some potential security risks (audited by Andris)
- Only show new messages if they match the current search (#925)
- Switch to/from when searcing in Sent folder (#1177)
- Correctly read the References header (#1236)
- Unset old cookie before sending a new value (#1232)
- Correctly decode attachments when downloading them (#1235 and #1484642)
- Suppress IE errors when clearing attachments form (#1043)
- Log error when login fails due to auto_create_user turned off
- Filter linked/imported CSS files (closes #844)
- Improve message compose screen (closes #1060)
- Select next row after removing one from list (#1063)
RELEASE 0.1-RC2
---------------
- Enable drag-&-dropping of folders to a new parent and allow to create subfolders (#637)
- Suppress IE errors when clearing attachments form (#1043)
- Set preferences field in user table to NULL (#1062)
- Log error when login fails due to auto_create_user turned off
- Filter linked/imported CSS files (closes #844)
- Improve message compose screen (closes #1060)
- Select next row after removing one from list (#1063)
- Make smtp HELO/EHLO hostname configurable (#851)
- IPv6 Compatibility (#1023), Patch #1484373
- Unlock interface when message sending fails (#1188)
- Eval PHP code in template includes (if configured)
- Show message when folder is empty. Mo more static text in table (#1068)
- Only display unread count in page title when new messages arrived
- Fixed wrong delete button tooltip (#785)
- Fixed charset encoding bug (#1091)
- Applied patch for LDAP version (#1175)
- Improved XHTML validation
- Fix message list selection (#1174)
- Better fix lowercased usernames (#1120)
- Update pngbehavior Script as suggested in #1134
- Fixed moving/deleting messages when more than 1 is selected
- Applied patch for LDAP contacts listing by Glen Ogilvie
- Applied patch for more address fields in LDAP contacts (#1074)
- Add alternative for getallheaders() (fix #1146)
- Identify mailboxes case-sensitive
- Sort mailbox list case-insensitive (closes #1032)
- Fix display of multipart messages from Apple Mail (closes #823)
- Protect AJAX request from being fetched by a foreign site (XSS)
- Make autocomplete for loginform configurable by the skin template
- Fix compose function from address book (closes #1089)
- Added //IGNORE to iconv call (patch #1086, closes #821)
- Check if mbstring supports charset (#1003 and #1004)
- Prefer iconv over mbstring (as suggested in #1004)
- Check filesize of template includes (#1079)
- Fixed bug with buttons not dimming/enabling properly after switching folders
- Fixed compose window becoming unresponsive after saving a draft (#1132)
- Re-enabled "Back" button in compose window now that bug #1132 is fixed
- Fixed unresponsive interface issue when downloading attachments (#1138)
- Lowered status message time from 5 to 3 seconds to improve responsiveness
- Raised .htaccess upload_max_filesize from 2M to 5M to differ from default php.ini
- Increased "mailboxcontrols" mail.css width from 160 to 170px to fix non-english languages (#1140)
- Fix status message bug #1114 with regard to #1041
- Fix address adding bug reported by David Koblas
- Applied socket error patch by Thomas Mangin
- Pass-by-reference workarround for PHP5 in sendmail.inc
- Fixed buggy imap_root settings (closes #1056)
- Prevent default events on subject links (#1071)
- Use HTTP-POST requests for actions that change state
RELEASE 0.1-RC1
---------------
- Use global filters and bind username/ for Ldap searches (#909)
- Hide quota display if imap server does not support it
- Hide address groups if no LDAP servers configured
- Add link to message subjects (closes #982)
- Better SQL query for contact listing/search (closes #1051)
- Fixed marking as read in preview pane (closes #1048)
- CSS hack to display attachments correctly in IE6
- Wrap message body text (closes #901)
- LDAP access is back in address book (closes #864)
- Added search function for contacts
- New Template parsing and output encoding
- Fixed bugs #884 and #793
- Fixed message moving procedure (closes #1013)
- Fixed display of multiple attachments (closes #647)
- Fixed check for new messages (closes #1015)
- List attachments without filename
- New session authentication: Change sessid cookie when login, authentication with sessauth cookie is now configurable.
Should close bugs #774 and #1484299
- Correctly translate mailbox names (closes #993)
- Quote e-mail address links (closes #1007)
- Updated PEAR::Mail_mime package
- Accept single quotes for HTML attributes when modifying message body (thanks Jason)
- Sanitize input for new users/identities (thanks Colin Alston)
- Don't download HTML message parts
- Convert HTML parts to plaintext if 'prefer_html' is off
- Correctly parse message/rfc822 parts (closes #838)
- Also use user_id for unique key in messages table (closes #857)
- Hide contacts drop down on blur (closes #946)
- Make entries in contacts drop down clickable
- Turn off browser autocompletion on login page
- Quote <? in text/html message parts
- Hide border around radio buttons
- Applied patch for attachment download by crichardson (closes #943)
- Fixed bug in Postgres DB handling (closes #852)
- Fixed bug of invalid calls to fetchRow() in rcube_db.inc (closes #996)
- Fixed array_merge bug (closes #997)
- Fixed flag for deletion in list view (closes #987)
- Finally support semicolons as recipient separator (closes ##976)
- Fixed message headers (subject) encoding
- check if safe mode is on or not (closes #990)
- Show "no subject" in message list if subject is missing (closes #971)
- Solved page caching of message preview (closes #905)
- Only use gzip compression if configured (closes #967)
- Fixed priority selector issue (#903)
- Fixed some CSS issues in default skin (closes #951 and #911)
- Prevent from double quoting of numeric HTML character references (closes #978)
- Fixed display of HTML message attachments (closes #927)
- Applied patch for preview caching (closes #933)
- Added error handling for attachment uploads
- Use multibyte safe string functions where necessary (closes #798)
- Applied security patch to validate the submitted host value (by Kees Cook)
- Applied security patch to validate input values when deleting contacts (by Kees Cook)
- Applied security patch that sanitizes emoticon paths when attaching them (by Kees Cook)
- Applied a patch to more aggressively sanitize a HTML message
- Visualize blocked images in HTML messages
- Fixed wrong message listing when showing search results (closes #890)
- Show remote images when opening HTML message part as attachment
- Improve memory usage when sending mail (closes #871)
- Mark messages as read once the preview is loaded (closes #1484132)
- Include smtp final response in log (closes #862)
- Corrected date string in sent message header (closes #887)
- Correclty choose "To" column in sent and draft mailboxes (closes #769)
- Changed srong tooltips for message browse buttons (closes #757)
- Fixed signature delimiter character to be standard (Bug #830)
- Fixed XSS vulnerability (Bug #877)
- Remove newlines from mail headers (Bug #827)
- Selection issues when moving/deleting (Bug #837)
- Applied patch of Clement Moulin for imap host auto-selection
- ISO-encode IMAP password for plaintext login (Bugs #792 & #723)
- Fixed folder name encoding in subscription list (Bug #879)
- Fixed JS errors in identity list (Bug #885)
- Translate foldernames in folder form (closes #879)
- Added first and last buttons to message list, address book
and message detail
- Pressing Shift-Del bypasses Trash folder
- Enable purge command for Junk folder
- Fetch all aliases if virtuser_query is used instead
- Re-enabled multi select of contacts (Bug #817)
- Enable contact editing right after creation (Bug #644)
- Correct UTF-7 to UTF-8 conversion if mbstring is not available
- Fixed IMAP fetch of message body (Bug #819)
- Fixed safe_mode problems (Bug #539)
- Fixed wrong header encoding (Bug #1483976)
- Made automatic draft saving configurable
- Fixed JS bug when renaming folders (Bug #799)
- Added quota display as image (by Brett Patterson)
- Corrected creation of a message-id
- New indentation for quoted message text
- Improved HTML validity
- Fixed URL character set (Ticket #616)
- Fixed saving of contact into MySQL from LDAP query results (Ticket #681)
- Fixed folder renaming: unsubscribe before rename (Bug #750)
- Finalized new message parsing (+ chaching)
- Fixed wrong usage of mbstring (Bug #645)
- Set default spelling language (Ticket #764)
- Added support for Nox Spell Server
- Re-built message parsing (Bug #422)
Now based on the message structure delivered by the IMAP server.
- Fixed some XSS and SQL injection issues
- Fixed charset problems with folder renaming
diff --git a/plugins/vcard_attachments/vcardattach.js b/plugins/vcard_attachments/vcardattach.js
index edd92ed20..0db18d6ab 100644
--- a/plugins/vcard_attachments/vcardattach.js
+++ b/plugins/vcard_attachments/vcardattach.js
@@ -1,85 +1,105 @@
/**
* vcard_attachments plugin script
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (c) 2012-2017, The Roundcube Dev Team
*
* The JavaScript code in this page is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*/
function plugin_vcard_save_contact(mime_id)
{
var lock = rcmail.set_busy(true, 'loading');
rcmail.http_post('plugin.savevcard', { _uid: rcmail.env.uid, _mbox: rcmail.env.mailbox, _part: mime_id }, lock);
return false;
}
function plugin_vcard_insertrow(data)
{
if (data.row.ctype.match(/^(text\/vcard|text\/x-vcard|text\/directory)$/i)) {
$(data.row.obj).find('.attachment > .attachment').addClass('vcard');
}
}
function plugin_vcard_attach()
{
var id, n, contacts = [],
ts = new Date().getTime(),
args = {_uploadid: ts, _id: rcmail.env.compose_id || null},
selection = rcmail.contact_list.get_selection();
for (n=0; n < selection.length; n++) {
if (rcmail.env.task == 'addressbook') {
id = selection[n];
contacts.push(rcmail.env.source + '-' + id + '-0');
}
else {
id = selection[n];
if (id && id.charAt(0) != 'E' && rcmail.env.contactdata[id])
contacts.push(id);
}
}
if (!contacts.length)
return false;
args._uri = 'vcard://' + contacts.join(',');
if (rcmail.env.task == 'addressbook') {
args._attach_vcard = 1;
rcmail.open_compose_step(args);
}
else {
// add to attachments list
if (!rcmail.add2attachment_list(ts, {name: '', html: rcmail.get_label('attaching'), classname: 'uploading', complete: false}))
rcmail.file_upload_id = rcmail.set_busy(true, 'attaching');
rcmail.http_post('upload', args);
}
}
window.rcmail && rcmail.addEventListener('init', function(evt) {
if (rcmail.gui_objects.messagelist)
rcmail.addEventListener('insertrow', function(data, evt) { plugin_vcard_insertrow(data); });
if ((rcmail.env.action == 'compose' || (rcmail.env.task == 'addressbook' && rcmail.env.action == '')) && rcmail.gui_objects.contactslist) {
- if (rcmail.env.action == 'compose')
+ if (rcmail.env.action == 'compose') {
rcmail.env.compose_commands.push('attach-vcard');
+ // Elastic: add "Attach vCard" button to the attachments widget
+ if (window.UI && UI.recipient_selector) {
+ var button, form = $('#compose-attachments > div');
+ button = $('<button class="btn btn-secondary attach vcard">')
+ .attr('tabindex', $('button,input', form).first().attr('tabindex') || 0)
+ .text(rcmail.gettext('vcard_attachments.attachvcard'))
+ .appendTo(form)
+ .click(function() {
+ UI.recipient_selector('', {
+ title: 'vcard_attachments.attachvcard',
+ button: 'vcard_attachments.attachvcard',
+ button_class: 'attach',
+ focus: button,
+ multiselect: false,
+ action: function() { rcmail.command('attach-vcard'); }
+ });
+ });
+ }
+ }
+
rcmail.register_command('attach-vcard', function() { plugin_vcard_attach(); });
rcmail.contact_list.addEventListener('select', function(list) {
// TODO: support attaching more than one at once
var selection = list.get_selection();
rcmail.enable_command('attach-vcard', selection.length == 1 && selection[0].charAt(0) != 'E');
});
}
});
diff --git a/program/js/editor.js b/program/js/editor.js
index 9ee78d628..ee5d935fe 100644
--- a/program/js/editor.js
+++ b/program/js/editor.js
@@ -1,846 +1,849 @@
/**
* Roundcube editor js library
*
* This file is part of the Roundcube Webmail client
*
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (c) 2006-2014, The Roundcube Dev Team
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* @licend The above is the entire license notice
* for the JavaScript code in this file.
*
* @author Eric Stadtherr <estadtherr@gmail.com>
* @author Aleksander Machniak <alec@alec.pl>
*/
/**
* Roundcube Text Editor Widget class
* @constructor
*/
function rcube_text_editor(config, id)
{
var ref = this,
abs_url = location.href.replace(/[?#].*$/, '').replace(/\/$/, ''),
conf = {
selector: '#' + ($('#' + id).is('.mce_editor') ? id : 'fake-editor-id'),
cache_suffix: 's=4080200',
theme: 'modern',
language: config.lang,
content_css: rcmail.assets_path('program/resources/tinymce/content.css'),
menubar: false,
statusbar: false,
toolbar_items_size: 'small',
extended_valid_elements: 'font[face|size|color|style],span[id|class|align|style]',
fontsize_formats: '8pt 9pt 10pt 11pt 12pt 14pt 18pt 24pt 36pt',
valid_children: '+body[style]',
relative_urls: false,
remove_script_host: false,
convert_urls: false, // #1486944
image_description: false,
paste_webkit_style: "color font-size font-family",
paste_data_images: true,
browser_spellcheck: true,
anchor_bottom: false,
anchor_top: false
};
// register spellchecker for plain text editor
this.spellcheck_observer = function() {};
if (config.spellchecker) {
this.spellchecker = config.spellchecker;
if (config.spellcheck_observer) {
this.spellchecker.spelling_state_observer = this.spellcheck_observer = config.spellcheck_observer;
}
}
// secure spellchecker requests with Roundcube token
// Note: must be registered only once (#1490311)
if (!tinymce.registered_request_token) {
tinymce.registered_request_token = true;
tinymce.util.XHR.on('beforeSend', function(e) {
e.xhr.setRequestHeader('X-Roundcube-Request', rcmail.env.request_token);
});
}
// minimal editor
if (config.mode == 'identity') {
$.extend(conf, {
plugins: 'autolink charmap code colorpicker hr image link paste tabfocus textcolor',
toolbar: 'bold italic underline alignleft aligncenter alignright alignjustify'
+ ' | outdent indent charmap hr link unlink image code forecolor'
+ ' | fontselect fontsizeselect',
file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); },
file_browser_callback_types: 'image'
});
}
// full-featured editor
else {
$.extend(conf, {
plugins: 'autolink charmap code colorpicker directionality link lists image media nonbreaking'
+ ' paste table tabfocus textcolor searchreplace spellchecker',
toolbar: 'bold italic underline | alignleft aligncenter alignright alignjustify'
+ ' | bullist numlist outdent indent ltr rtl blockquote | forecolor backcolor | fontselect fontsizeselect'
+ ' | link unlink table | $extra charmap image media | code searchreplace undo redo',
spellchecker_rpc_url: abs_url + '/?_task=utils&_action=spell_html&_remote=1',
spellchecker_language: rcmail.env.spell_lang,
accessibility_focus: false,
file_browser_callback: function(name, url, type, win) { ref.file_browser_callback(name, url, type); },
// @todo: support more than image (types: file, image, media)
file_browser_callback_types: 'image media'
});
}
// add TinyMCE plugins/buttons from Roundcube plugin
$.each(config.extra_plugins || [], function() {
if (conf.plugins.indexOf(this) < 0)
conf.plugins = conf.plugins + ' ' + this;
});
$.each(config.extra_buttons || [], function() {
if (conf.toolbar.indexOf(this) < 0)
conf.toolbar = conf.toolbar.replace('$extra', '$extra ' + this);
});
// disable TinyMCE plugins/buttons from Roundcube plugin
$.each(config.disabled_plugins || [], function() {
conf.plugins = conf.plugins.replace(this, '');
});
$.each(config.disabled_buttons || [], function() {
conf.toolbar = conf.toolbar.replace(this, '');
});
conf.toolbar = conf.toolbar.replace('$extra', '').replace(/\|\s+\|/g, '|');
// support external configuration settings e.g. from skin
if (window.rcmail_editor_settings)
$.extend(conf, window.rcmail_editor_settings);
conf.setup = function(ed) {
ed.on('init', function(ed) { ref.init_callback(ed); });
// add handler for spellcheck button state update
ed.on('SpellcheckStart SpellcheckEnd', function(args) {
ref.spellcheck_active = args.type == 'spellcheckstart';
ref.spellcheck_observer();
});
ed.on('keypress', function() {
rcmail.compose_type_activity++;
});
// make links open on shift-click
ed.on('click', function(e) {
var link = $(e.target).closest('a');
if (link.length && e.shiftKey) {
window.open(link.get(0).href, '_blank');
return false;
}
});
ed.on('focus blur', function(e) {
$(ed.getContainer()).toggleClass('focused');
});
+
+ if (conf.setup_callback)
+ conf.setup_callback(ed);
};
rcmail.triggerEvent('editor-init', {config: conf, ref: ref});
// textarea identifier
this.id = id;
// reference to active editor (if in HTML mode)
this.editor = null;
tinymce.init(conf);
// react to real individual tinyMCE editor init
this.init_callback = function(event)
{
this.editor = event.target;
rcmail.triggerEvent('editor-load', {config: conf, ref: ref});
if (rcmail.env.action == 'compose') {
var area = $('#' + this.id),
height = $('div.mce-toolbar-grp:first', area.parent()).height();
// the editor might be still not fully loaded, making the editing area
// inaccessible, wait and try again (#1490310)
if (height > 200 || height > area.height()) {
return setTimeout(function () { ref.init_callback(event); }, 300);
}
var css = {},
elem = rcube_find_object('_from'),
fe = rcmail.env.compose_focus_elem;
if (rcmail.env.default_font)
css['font-family'] = rcmail.env.default_font;
if (rcmail.env.default_font_size)
css['font-size'] = rcmail.env.default_font_size;
if (css['font-family'] || css['font-size'])
$(this.editor.getBody()).css(css);
if (elem && elem.type == 'select-one') {
// insert signature (only for the first time)
if (!rcmail.env.identities_initialized)
rcmail.change_identity(elem);
// Focus previously focused element
if (fe && fe.id != this.id && fe.nodeName != 'BODY') {
window.focus(); // for WebKit (#1486674)
fe.focus();
rcmail.env.compose_focus_elem = null;
}
}
}
// set tabIndex and set focus to element that was focused before
ref.tabindex(ref.force_focus || (fe && fe.id == ref.id));
// Trigger resize (needed for proper editor resizing in some browsers)
$(window).resize();
};
// set tabIndex on tinymce editor
this.tabindex = function(focus)
{
if (rcmail.env.task == 'mail' && this.editor) {
var textarea = this.editor.getElement(),
node = this.editor.getContentAreaContainer().childNodes[0];
if (textarea && node)
node.tabIndex = textarea.tabIndex;
// find :prev and :next elements to get focus when tabbing away
if (textarea.tabIndex > 0) {
var x = null,
tabfocus_elements = [':prev',':next'],
el = tinymce.DOM.select('*[tabindex='+textarea.tabIndex+']:not(iframe)');
tinymce.each(el, function(e, i) { if (e.id == ref.id) { x = i; return false; } });
if (x !== null) {
if (el[x-1] && el[x-1].id) {
tabfocus_elements[0] = el[x-1].id;
}
if (el[x+1] && el[x+1].id) {
tabfocus_elements[1] = el[x+1].id;
}
this.editor.settings.tabfocus_elements = tabfocus_elements.join(',');
}
}
// ContentEditable reset fixes invisible cursor issue in Firefox < 25
if (bw.mz && bw.vendver < 25)
$(this.editor.getBody()).prop('contenteditable', false).prop('contenteditable', true);
}
if (focus)
this.focus();
};
// focus the editor
this.focus = function()
{
$(this.editor || ('#' + this.id)).focus();
this.force_focus = false;
};
// switch html/plain mode
this.toggle = function(ishtml, noconvert)
{
var curr, content, result,
// these non-printable chars are not removed on text2html and html2text
// we can use them as temp signature replacement
sig_mark = "\u0002\u0003",
input = $('#' + this.id),
signature = rcmail.env.identity ? rcmail.env.signatures[rcmail.env.identity] : null,
is_sig = signature && signature.text && signature.text.length > 1;
// apply spellcheck changes if spell checker is active
this.spellcheck_stop();
if (ishtml) {
content = input.val();
// replace current text signature with temp mark
if (is_sig) {
content = content.replace(/\r\n/, "\n");
content = content.replace(signature.text.replace(/\r\n/, "\n"), sig_mark);
}
var init_editor = function(data) {
// replace signature mark with html version of the signature
if (is_sig)
data = data.replace(sig_mark, '<div id="_rc_sig">' + signature.html + '</div>');
ref.force_focus = true;
input.val(data);
tinymce.execCommand('mceAddEditor', false, ref.id);
};
// convert to html
if (!noconvert) {
result = rcmail.plain2html(content, init_editor);
}
else {
init_editor(content);
result = true;
}
}
else if (this.editor) {
if (is_sig) {
// get current version of signature, we'll need it in
// case of html2text conversion abort
if (curr = this.editor.dom.get('_rc_sig'))
curr = curr.innerHTML;
// replace current signature with some non-printable characters
// we use non-printable characters, because this replacement
// is visible to the user
// doing this after getContent() would be hard
this.editor.dom.setHTML('_rc_sig', sig_mark);
}
// get html content
content = this.editor.getContent();
var init_plaintext = function(data) {
tinymce.execCommand('mceRemoveEditor', false, ref.id);
ref.editor = null;
// replace signture mark with text version of the signature
if (is_sig)
data = data.replace(sig_mark, "\n" + signature.text);
input.val(data).focus();
rcmail.set_caret_pos(input.get(0), 0);
};
// convert html to text
if (!noconvert) {
result = rcmail.html2plain(content, init_plaintext);
}
else {
init_plaintext(input.val());
result = true;
}
// bring back current signature
if (!result && curr)
this.editor.dom.setHTML('_rc_sig', curr);
}
return result;
};
// start spellchecker
this.spellcheck_start = function()
{
if (this.editor) {
tinymce.execCommand('mceSpellCheck', true);
this.spellcheck_observer();
}
else if (this.spellchecker && this.spellchecker.spellCheck) {
this.spellchecker.spellCheck();
}
};
// stop spellchecker
this.spellcheck_stop = function()
{
var ed = this.editor;
if (ed) {
if (ed.plugins && ed.plugins.spellchecker && this.spellcheck_active) {
ed.execCommand('mceSpellCheck', false);
this.spellcheck_observer();
}
}
else if (ed = this.spellchecker) {
if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found')
$(ed.spell_span).trigger('click');
}
};
// spellchecker state
this.spellcheck_state = function()
{
var ed;
if (this.editor)
return this.spellcheck_active;
else if ((ed = this.spellchecker) && ed.state)
return ed.state != 'ready' && ed.state != 'no_error_found';
};
// resume spellchecking, highlight provided mispellings without a new ajax request
this.spellcheck_resume = function(data)
{
var ed = this.editor;
if (ed) {
ed.plugins.spellchecker.markErrors(data);
}
else if (ed = this.spellchecker) {
ed.prepare(false, true);
ed.processData(data);
}
};
// get selected (spellchecker) language
this.get_language = function()
{
if (this.editor) {
return this.editor.settings.spellchecker_language || rcmail.env.spell_lang;
}
else if (this.spellchecker) {
return GOOGIE_CUR_LANG;
}
};
// set language for spellchecking
this.set_language = function(lang)
{
var ed = this.editor;
if (ed) {
ed.settings.spellchecker_language = lang;
}
if (ed = this.spellchecker) {
ed.setCurrentLanguage(lang);
}
};
// replace selection with text snippet
// input can be a string or object with 'text' and 'html' properties
this.replace = function(input)
{
var format, ed = this.editor;
if (!input)
return false;
// insert into tinymce editor
if (ed) {
ed.getWin().focus(); // correct focus in IE & Chrome
if ($.type(input) == 'object' && ('html' in input)) {
input = input.html;
format = 'html';
}
else {
if ($.type(input) == 'object')
input = input.text || '';
input = rcmail.quote_html(input).replace(/\r?\n/g, '<br/>');
format = 'text';
}
ed.selection.setContent(input, {format: format});
}
// replace selection in compose textarea
else if (ed = rcube_find_object(this.id)) {
var selection = $(ed).is(':focus') ? rcmail.get_input_selection(ed) : {start: 0, end: 0},
value = ed.value,
pre = value.substring(0, selection.start),
end = value.substring(selection.end, value.length);
if ($.type(input) == 'object')
input = input.text || '';
// insert response text
ed.value = pre + input + end;
// set caret after inserted text
rcmail.set_caret_pos(ed, selection.start + input.length);
ed.focus();
}
};
// Fill the editor with specified content
// TODO: support format conversion
this.set_content = function(content)
{
if (this.editor) {
this.editor.setContent(content);
this.editor.getWin().focus();
}
else if (ed = rcube_find_object(this.id)) {
$(ed).val(content).focus();
}
};
// get selected text (if no selection returns all text) from the editor
this.get_content = function(args)
{
var sigstart, ed = this.editor, text = '', strip = false,
defaults = {refresh: true, selection: false, nosig: false, format: 'html'};
if (!args)
args = defaults;
else
args = $.extend(defaults, args);
// apply spellcheck changes if spell checker is active
if (args.refresh) {
this.spellcheck_stop();
}
// get selected text from tinymce editor
if (ed) {
if (args.selection)
text = ed.selection.getContent({format: args.format});
if (!text) {
text = ed.getContent({format: args.format});
// @todo: strip signature in html mode
strip = args.format == 'text';
}
}
// get selected text from compose textarea
else if (ed = rcube_find_object(this.id)) {
if (args.selection && $(ed).is(':focus')) {
text = rcmail.get_input_selection(ed).text;
}
if (!text) {
text = ed.value;
strip = true;
}
}
// strip off signature
// @todo: make this optional
if (strip && args.nosig) {
sigstart = text.indexOf('-- \n');
if (sigstart > 0) {
text = text.substring(0, sigstart);
}
}
return text;
};
// change user signature text
this.change_signature = function(id, show_sig)
{
var position_element, cursor_pos, p = -1,
input_message = $('#' + this.id),
message = input_message.val(),
sig = rcmail.env.identity;
if (!this.editor) { // plain text mode
// remove the 'old' signature
if (show_sig && sig && rcmail.env.signatures && rcmail.env.signatures[sig]) {
sig = rcmail.env.signatures[sig].text;
sig = sig.replace(/\r\n/g, '\n');
p = rcmail.env.top_posting ? message.indexOf(sig) : message.lastIndexOf(sig);
if (p >= 0)
message = message.substring(0, p) + message.substring(p+sig.length, message.length);
}
// add the new signature string
if (show_sig && rcmail.env.signatures && rcmail.env.signatures[id]) {
sig = rcmail.env.signatures[id].text;
sig = sig.replace(/\r\n/g, '\n');
// in place of removed signature
if (p >= 0) {
message = message.substring(0, p) + sig + message.substring(p, message.length);
cursor_pos = p - 1;
}
// empty message or new-message mode
else if (!message || !rcmail.env.compose_mode) {
cursor_pos = message.length;
message += '\n\n' + sig;
}
else if (rcmail.env.top_posting && !rcmail.env.sig_below) {
// at cursor position
if (pos = rcmail.get_caret_pos(input_message.get(0))) {
message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length);
cursor_pos = pos;
}
// on top
else {
message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, '');
cursor_pos = 0;
}
}
else {
message = message.replace(/[\r\n]+$/, '');
cursor_pos = !rcmail.env.top_posting && message.length ? message.length + 1 : 0;
message += '\n\n' + sig;
}
}
else {
cursor_pos = rcmail.env.top_posting ? 0 : message.length;
}
input_message.val(message);
// move cursor before the signature
rcmail.set_caret_pos(input_message.get(0), cursor_pos);
}
else if (show_sig && rcmail.env.signatures) { // html
var sigElem = this.editor.dom.get('_rc_sig');
// Append the signature as a div within the body
if (!sigElem) {
var body = this.editor.getBody();
sigElem = $('<div id="_rc_sig"></div>').get(0);
// insert at start or at cursor position in top-posting mode
// (but not if the content is empty and not in new-message mode)
if (rcmail.env.top_posting && !rcmail.env.sig_below
&& rcmail.env.compose_mode && (body.childNodes.length > 1 || $(body).text())
) {
this.editor.getWin().focus(); // correct focus in IE & Chrome
var node = this.editor.selection.getNode();
$(sigElem).insertBefore(node.nodeName == 'BODY' ? body.firstChild : node.nextSibling);
$('<p>').append($('<br>')).insertBefore(sigElem);
}
else {
body.appendChild(sigElem);
position_element = rcmail.env.top_posting && rcmail.env.compose_mode ? body.firstChild : $(sigElem).prev();
}
}
sigElem.innerHTML = rcmail.env.signatures[id] ? rcmail.env.signatures[id].html : '';
}
else if (!rcmail.env.top_posting) {
position_element = $(this.editor.getBody()).children().last();
}
// put cursor before signature and scroll the window
if (this.editor && position_element && position_element.length) {
this.editor.selection.setCursorLocation(position_element.get(0));
this.editor.getWin().scroll(0, position_element.offset().top);
}
};
// trigger content save
this.save = function()
{
if (this.editor) {
this.editor.save();
}
};
// focus the editing area
this.focus = function()
{
(this.editor || rcube_find_object(this.id)).focus();
};
// image selector
this.file_browser_callback = function(field_name, url, type)
{
var i, button, elem, cancel, dialog, fn, hint, list = [],
form = $('.upload-form').clone();
// open image selector dialog
this.editor.windowManager.open({
title: rcmail.get_label('select' + type),
width: 500,
html: '<div id="image-selector" class="image-selector file-upload"><ul id="image-selector-list" class="attachmentslist"></ul></div>',
buttons: [{text: 'Cancel', onclick: function() { ref.file_browser_close(); }}]
});
rcmail.env.file_browser_field = field_name;
rcmail.env.file_browser_type = type;
dialog = $('#image-selector');
if (!form.length)
form = this.file_upload_form(rcmail.gui_objects.uploadform);
else
form.find('button,a.button').slice(1).remove(); // need only the first button
button = dialog.prepend(form).find('button,a.button')
.text(rcmail.get_label('add' + type))
.focus();
// fill images list with available images
for (i in rcmail.env.attachments) {
if (elem = ref.file_browser_entry(i, rcmail.env.attachments[i])) {
list.push(elem);
}
}
cancel = dialog.parent().parent().find('button:last').parent();
// Add custom Tab key handlers, tabindex does not work
list = $('#image-selector-list').append(list).on('keydown', 'li', function(e) {
if (e.which == 9) {
if (rcube_event.get_modifier(e) == SHIFT_KEY) {
if (!$(this).prev().focus().length) {
button.focus();
}
}
else if (!$(this).next().focus().length) {
cancel.focus();
}
return false;
}
});
button.keydown(function(e) {
if (e.which == 9) { // Tab
if (rcube_event.get_modifier(e) == SHIFT_KEY || !list.find('li:first').focus().length) {
cancel.focus();
}
return false;
}
if (e.which == 13) { // Enter
this.click();
}
});
cancel.keydown(function(e) {
if (e.which == 9) {
if (rcube_event.get_modifier(e) != SHIFT_KEY || !list.find('li:last').focus().length) {
button.focus();
}
return false;
}
});
// enable drag-n-drop area
if ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData) {
if (!rcmail.env.filedrop) {
rcmail.env.filedrop = {};
}
if (rcmail.gui_objects.filedrop) {
rcmail.env.old_file_drop = rcmail.gui_objects.filedrop;
}
rcmail.gui_objects.filedrop = $('#image-selector');
rcmail.gui_objects.filedrop.addClass('droptarget')
.on('dragover dragleave', function(e) {
e.preventDefault();
e.stopPropagation();
$(this)[(e.type == 'dragover' ? 'addClass' : 'removeClass')]('hover');
})
.get(0).addEventListener('drop', function(e) { return rcmail.file_dropped(e); }, false);
}
// register handler for successful file upload
if (!rcmail.env['file_dialog_event_' + type]) {
rcmail.env['file_dialog_event+' + type] = true;
rcmail.addEventListener('fileuploaded', function(attr) {
var elem;
if (elem = ref.file_browser_entry(attr.name, attr.attachment)) {
list.prepend(elem);
elem.focus();
}
});
}
// @todo: upload progress indicator
};
// close file browser window
this.file_browser_close = function(url)
{
var input = $('#' + rcmail.env.file_browser_field);
if (url)
input.val(url);
this.editor.windowManager.close();
input.focus();
if (rcmail.env.old_file_drop)
rcmail.gui_objects.filedrop = rcmail.env.old_file_drop;
};
// creates file browser entry
this.file_browser_entry = function(file_id, file)
{
if (!file.complete || !file.mimetype) {
return;
}
if (rcmail.file_upload_id) {
rcmail.set_busy(false, null, rcmail.file_upload_id);
}
var rx, img_src;
switch (rcmail.env.file_browser_type) {
case 'image':
rx = /^image\//i;
break;
case 'media':
rx = /^video\//i;
img_src = rcmail.assets_path('program/resources/tinymce/video.png');
break;
default:
return;
}
if (rx.test(file.mimetype)) {
var path = rcmail.env.comm_path + '&_from=' + rcmail.env.action,
action = rcmail.env.compose_id ? '&_id=' + rcmail.env.compose_id + '&_action=display-attachment' : '&_action=upload-display',
href = path + action + '&_file=' + file_id,
img = $('<img>').attr({title: file.name, src: img_src ? img_src : href + '&_thumbnail=1'});
return $('<li>').attr({tabindex: 0})
.data('url', href)
.append($('<span class="img">').append(img))
.append($('<span class="name">').text(file.name))
.click(function() { ref.file_browser_close($(this).data('url')); })
.keydown(function(e) {
if (e.which == 13) {
ref.file_browser_close($(this).data('url'));
}
});
}
};
this.file_upload_form = function(clone_form)
{
var hint = clone_form ? $(clone_form).find('.hint').text() : '',
form = $('<form id="imageuploadform">').attr({method: 'post', enctype: 'multipart/form-data'});
file = $('<input>').attr({name: '_file[]', type: 'file', multiple: true, style: 'opacity:0;height:1px;width:1px'})
.change(function() { rcmail.upload_file(form, 'upload'); }),
wrapper = $('<div class="upload-form">')
.append($('<button>').attr({'class': 'btn btn-secondary attach', href: '#', onclick: "rcmail.upload_input('imageuploadform')"}));
if (hint)
wrapper.prepend($('<div class="hint">').text(hint));
// clone existing upload form
if (clone_form) {
file.attr('name', $('input[type="file"]', clone_form).attr('name'));
form.attr('action', $(clone_form).attr('action'));
}
form.append(file).append($('<input>').attr({type: 'hidden', name: '_token', value: rcmail.env.request_token}));
return wrapper.append(form);
};
}
diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
index decc015c4..6113f4393 100644
--- a/program/lib/Roundcube/rcube_utils.php
+++ b/program/lib/Roundcube/rcube_utils.php
@@ -1,1390 +1,1391 @@
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2012, The Roundcube Dev Team |
| Copyright (C) 2011-2012, Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Utility class providing common functions |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Utility class providing common functions
*
* @package Framework
* @subpackage Utils
*/
class rcube_utils
{
// define constants for input reading
const INPUT_GET = 1;
const INPUT_POST = 2;
const INPUT_COOKIE = 4;
const INPUT_GP = 3; // GET + POST
const INPUT_GPC = 7; // GET + POST + COOKIE
/**
* Helper method to set a cookie with the current path and host settings
*
- * @param string Cookie name
- * @param string Cookie value
- * @param string Expiration time
+ * @param string $name Cookie name
+ * @param string $value Cookie value
+ * @param int $exp Expiration time
+ * @param bool $http_only HTTP Only
*/
- public static function setcookie($name, $value, $exp = 0)
+ public static function setcookie($name, $value, $exp = 0, $http_only = true)
{
if (headers_sent()) {
return;
}
$cookie = session_get_cookie_params();
$secure = $cookie['secure'] || self::https_check();
- setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'], $secure, true);
+ setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'], $secure, $http_only);
}
/**
* E-mail address validation.
*
- * @param string $email Email address
+ * @param string $email Email address
* @param boolean $dns_check True to check dns
*
* @return boolean True on success, False if address is invalid
*/
public static function check_email($email, $dns_check=true)
{
// Check for invalid (control) characters
if (preg_match('/\p{Cc}/u', $email)) {
return false;
}
// Check for length limit specified by RFC 5321 (#1486453)
if (strlen($email) > 254) {
return false;
}
$pos = strrpos($email, '@');
if (!$pos) {
return false;
}
$domain_part = substr($email, $pos + 1);
$local_part = substr($email, 0, $pos);
// quoted-string, make sure all backslashes and quotes are
// escaped
if (substr($local_part,0,1) == '"') {
$local_quoted = preg_replace('/\\\\(\\\\|\")/','', substr($local_part, 1, -1));
if (preg_match('/\\\\|"/', $local_quoted)) {
return false;
}
}
// dot-atom portion, make sure there's no prohibited characters
else if (preg_match('/(^\.|\.\.|\.$)/', $local_part)
|| preg_match('/[\\ ",:;<>@]/', $local_part)
) {
return false;
}
// Validate domain part
if (preg_match('/^\[((IPv6:[0-9a-f:.]+)|([0-9.]+))\]$/i', $domain_part, $matches)) {
return self::check_ip(preg_replace('/^IPv6:/i', '', $matches[1])); // valid IPv4 or IPv6 address
}
else {
// If not an IP address
$domain_array = explode('.', $domain_part);
// Not enough parts to be a valid domain
if (count($domain_array) < 2) {
return false;
}
foreach ($domain_array as $part) {
if (!preg_match('/^((xn--)?([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part)) {
return false;
}
}
// last domain part
$last_part = array_pop($domain_array);
if (strpos($last_part, 'xn--') !== 0 && preg_match('/[^a-zA-Z]/', $last_part)) {
return false;
}
$rcube = rcube::get_instance();
if (!$dns_check || !$rcube->config->get('email_dns_check')) {
return true;
}
// find MX record(s)
if (!function_exists('getmxrr') || getmxrr($domain_part, $mx_records)) {
return true;
}
// find any DNS record
if (!function_exists('checkdnsrr') || checkdnsrr($domain_part, 'ANY')) {
return true;
}
}
return false;
}
/**
* Validates IPv4 or IPv6 address
*
* @param string $ip IP address in v4 or v6 format
*
* @return bool True if the address is valid
*/
public static function check_ip($ip)
{
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
}
/**
* Replacing specials characters to a specific encoding type
*
* @param string Input string
* @param string Encoding type: text|html|xml|js|url
* @param string Replace mode for tags: show|remove|strict
* @param boolean Convert newlines
*
* @return string The quoted string
*/
public static function rep_specialchars_output($str, $enctype = '', $mode = '', $newlines = true)
{
static $html_encode_arr = false;
static $js_rep_table = false;
static $xml_rep_table = false;
if (!is_string($str)) {
$str = strval($str);
}
// encode for HTML output
if ($enctype == 'html') {
if (!$html_encode_arr) {
$html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
unset($html_encode_arr['?']);
}
$encode_arr = $html_encode_arr;
if ($mode == 'remove') {
$str = strip_tags($str);
}
else if ($mode != 'strict') {
// don't replace quotes and html tags
$ltpos = strpos($str, '<');
if ($ltpos !== false && strpos($str, '>', $ltpos) !== false) {
unset($encode_arr['"']);
unset($encode_arr['<']);
unset($encode_arr['>']);
unset($encode_arr['&']);
}
}
$out = strtr($str, $encode_arr);
return $newlines ? nl2br($out) : $out;
}
// if the replace tables for XML and JS are not yet defined
if ($js_rep_table === false) {
$js_rep_table = $xml_rep_table = array();
$xml_rep_table['&'] = '&amp;';
// can be increased to support more charsets
for ($c=160; $c<256; $c++) {
$xml_rep_table[chr($c)] = "&#$c;";
}
$xml_rep_table['"'] = '&quot;';
$js_rep_table['"'] = '\\"';
$js_rep_table["'"] = "\\'";
$js_rep_table["\\"] = "\\\\";
// Unicode line and paragraph separators (#1486310)
$js_rep_table[chr(hexdec('E2')).chr(hexdec('80')).chr(hexdec('A8'))] = '&#8232;';
$js_rep_table[chr(hexdec('E2')).chr(hexdec('80')).chr(hexdec('A9'))] = '&#8233;';
}
// encode for javascript use
if ($enctype == 'js') {
return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
}
// encode for plaintext
if ($enctype == 'text') {
return str_replace("\r\n", "\n", $mode == 'remove' ? strip_tags($str) : $str);
}
if ($enctype == 'url') {
return rawurlencode($str);
}
// encode for XML
if ($enctype == 'xml') {
return strtr($str, $xml_rep_table);
}
// no encoding given -> return original string
return $str;
}
/**
* Read input value and convert it for internal use
* Performs stripslashes() and charset conversion if necessary
*
* @param string Field name to read
* @param int Source to get value from (see self::INPUT_*)
* @param boolean Allow HTML tags in field value
* @param string Charset to convert into
*
* @return string Field value or NULL if not available
*/
public static function get_input_value($fname, $source, $allow_html = false, $charset = null)
{
$value = null;
if (($source & self::INPUT_GET) && isset($_GET[$fname])) {
$value = $_GET[$fname];
}
if (($source & self::INPUT_POST) && isset($_POST[$fname])) {
$value = $_POST[$fname];
}
if (($source & self::INPUT_COOKIE) && isset($_COOKIE[$fname])) {
$value = $_COOKIE[$fname];
}
return self::parse_input_value($value, $allow_html, $charset);
}
/**
* Parse/validate input value. See self::get_input_value()
* Performs stripslashes() and charset conversion if necessary
*
* @param string Input value
* @param boolean Allow HTML tags in field value
* @param string Charset to convert into
*
* @return string Parsed value
*/
public static function parse_input_value($value, $allow_html = false, $charset = null)
{
global $OUTPUT;
if (empty($value)) {
return $value;
}
if (is_array($value)) {
foreach ($value as $idx => $val) {
$value[$idx] = self::parse_input_value($val, $allow_html, $charset);
}
return $value;
}
// remove HTML tags if not allowed
if (!$allow_html) {
$value = strip_tags($value);
}
$output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
// remove invalid characters (#1488124)
if ($output_charset == 'UTF-8') {
$value = rcube_charset::clean($value);
}
// convert to internal charset
if ($charset && $output_charset) {
$value = rcube_charset::convert($value, $output_charset, $charset);
}
return $value;
}
/**
* Convert array of request parameters (prefixed with _)
* to a regular array with non-prefixed keys.
*
* @param int $mode Source to get value from (GPC)
* @param string $ignore PCRE expression to skip parameters by name
* @param boolean $allow_html Allow HTML tags in field value
*
* @return array Hash array with all request parameters
*/
public static function request2param($mode = null, $ignore = 'task|action', $allow_html = false)
{
$out = array();
$src = $mode == self::INPUT_GET ? $_GET : ($mode == self::INPUT_POST ? $_POST : $_REQUEST);
foreach (array_keys($src) as $key) {
$fname = $key[0] == '_' ? substr($key, 1) : $key;
if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) {
$out[$fname] = self::get_input_value($key, $mode, $allow_html);
}
}
return $out;
}
/**
* Convert the given string into a valid HTML identifier
* Same functionality as done in app.js with rcube_webmail.html_identifier()
*/
public static function html_identifier($str, $encode=false)
{
if ($encode) {
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
}
else {
return asciiwords($str, true, '_');
}
}
/**
* Replace all css definitions with #container [def]
* and remove css-inlined scripting, make position style safe
*
* @param string CSS source code
* @param string Container ID to use as prefix
* @param bool Allow remote content
* @param string Prefix to be added to id/class identifier
*
* @return string Modified CSS source
*/
public static function mod_css_styles($source, $container_id, $allow_remote = false, $prefix = '')
{
$last_pos = 0;
$replacements = new rcube_string_replacer;
// ignore the whole block if evil styles are detected
$source = self::xss_entity_decode($source);
$stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
$evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\((?!data:image)' : '');
if (preg_match("/$evilexpr/i", $stripped)) {
return '/* evil! */';
}
$strict_url_regexp = '!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims';
// cut out all contents between { and }
while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
$nested = strpos($source, '{', $pos+1);
if ($nested && $nested < $pos2) // when dealing with nested blocks (e.g. @media), take the inner one
$pos = $nested;
$length = $pos2 - $pos - 1;
$styles = substr($source, $pos+1, $length);
// Convert position:fixed to position:absolute (#5264)
$styles = preg_replace('/position:[\s\r\n]*fixed/i', 'position: absolute', $styles);
// check every line of a style block...
if ($allow_remote) {
$a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
for ($i=0, $len=count($a_styles); $i < $len; $i++) {
$line = $a_styles[$i];
$stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
// allow data:image uri, join with continuation
if (stripos($stripped, 'url(data:image')) {
$a_styles[$i] .= ';' . $a_styles[$i+1];
unset($a_styles[$i+1]);
}
// allow strict url() values only
else if (stripos($stripped, 'url(') && !preg_match($strict_url_regexp, $line)) {
$a_styles = array('/* evil! */');
break;
}
}
$styles = join(";\n", $a_styles);
}
$key = $replacements->add($styles);
$repl = $replacements->get_replacement($key);
$source = substr_replace($source, $repl, $pos+1, $length);
$last_pos = $pos2 - ($length - strlen($repl));
}
// remove html comments
$source = preg_replace('/(^\s*<\!--)|(-->\s*$)/m', '', $source);
// add #container to each tag selector and prefix to id/class identifiers
if ($container_id || $prefix) {
// (?!##str) below is to not match with ##str_replacement_0##
// from rcube_string_replacer used above, this is needed for
// cases like @media { body { position: fixed; } } (#5811)
$regexp = '/(^\s*|,\s*|\}\s*|\{\s*)((?!##str)[a-z0-9\._#\*\[][a-z0-9\._:\(\)#=~ \[\]"\|\>\+\$\^-]*)/im';
$callback = function($matches) use ($container_id, $prefix) {
$replace = $matches[2];
if ($prefix) {
$replace = str_replace(array('.', '#'), array(".$prefix", "#$prefix"), $replace);
}
if ($container_id) {
$replace = "#$container_id " . $replace;
}
return str_replace($matches[2], $replace, $matches[0]);
};
$source = preg_replace_callback($regexp, $callback, $source);
}
// replace body definition because we also stripped off the <body> tag
if ($container_id) {
$regexp = '/#' . preg_quote($container_id, '/') . '\s+body/i';
$source = preg_replace($regexp, "#$container_id", $source);
}
// put block contents back in
$source = $replacements->resolve($source);
return $source;
}
/**
* Generate CSS classes from mimetype and filename extension
*
* @param string $mimetype Mimetype
* @param string $filename Filename
*
* @return string CSS classes separated by space
*/
public static function file2class($mimetype, $filename)
{
$mimetype = strtolower($mimetype);
$filename = strtolower($filename);
list($primary, $secondary) = explode('/', $mimetype);
$classes = array($primary ?: 'unknown');
if ($secondary) {
$classes[] = $secondary;
}
if (preg_match('/\.([a-z0-9]+)$/', $filename, $m)) {
if (!in_array($m[1], $classes)) {
$classes[] = $m[1];
}
}
return join(" ", $classes);
}
/**
* Decode escaped entities used by known XSS exploits.
* See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
*
* @param string CSS content to decode
*
* @return string Decoded string
*/
public static function xss_entity_decode($content)
{
$callback = function($matches) { return chr(hexdec($matches[1])); };
$out = html_entity_decode(html_entity_decode($content));
$out = trim(preg_replace('/(^<!--|-->$)/', '', trim($out)));
$out = preg_replace_callback('/\\\([0-9a-f]{2,6})\s*/i', $callback, $out);
$out = preg_replace('/\\\([^0-9a-f])/i', '\\1', $out);
$out = preg_replace('#/\*.*\*/#Ums', '', $out);
$out = strip_tags($out);
return $out;
}
/**
* Check if we can process not exceeding memory_limit
*
* @param integer Required amount of memory
*
* @return boolean True if memory won't be exceeded, False otherwise
*/
public static function mem_check($need)
{
$mem_limit = parse_bytes(ini_get('memory_limit'));
$memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
}
/**
* Check if working in SSL mode
*
* @param integer $port HTTPS port number
* @param boolean $use_https Enables 'use_https' option checking
*
* @return boolean
*/
public static function https_check($port=null, $use_https=true)
{
if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off') {
return true;
}
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO'])
&& strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https'
&& in_array($_SERVER['REMOTE_ADDR'], rcube::get_instance()->config->get('proxy_whitelist', array()))
) {
return true;
}
if ($port && $_SERVER['SERVER_PORT'] == $port) {
return true;
}
if ($use_https && rcube::get_instance()->config->get('use_https')) {
return true;
}
return false;
}
/**
* Replaces hostname variables.
*
* @param string $name Hostname
* @param string $host Optional IMAP hostname
*
* @return string Hostname
*/
public static function parse_host($name, $host = '')
{
if (!is_string($name)) {
return $name;
}
// %n - host
$n = self::server_name();
// %t - host name without first part, e.g. %n=mail.domain.tld, %t=domain.tld
$t = preg_replace('/^[^\.]+\./', '', $n);
// %d - domain name without first part
$d = preg_replace('/^[^\.]+\./', '', self::server_name('HTTP_HOST'));
// %h - IMAP host
$h = $_SESSION['storage_host'] ?: $host;
// %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
$z = preg_replace('/^[^\.]+\./', '', $h);
// %s - domain name after the '@' from e-mail address provided at login screen.
// Returns FALSE if an invalid email is provided
if (strpos($name, '%s') !== false) {
$user_email = self::idn_to_ascii(self::get_input_value('_user', self::INPUT_POST));
$matches = preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s);
if ($matches < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false) {
return false;
}
}
return str_replace(array('%n', '%t', '%d', '%h', '%z', '%s'), array($n, $t, $d, $h, $z, $s[2]), $name);
}
/**
* Returns the host name after checking it against trusted hostname
* patterns, otherwise returns localhost (and logs a warning)
*
* @param string $type The $_SERVER key, e.g. 'HTTP_HOST', Default: 'SERVER_NAME'.
* @param boolean $strip_port Strip port from the host name
*
* @return string Server name
*/
public static function server_name($type = null, $strip_port = true)
{
$name = $_SERVER[$type ?: 'SERVER_NAME'];
$rcube = rcube::get_instance();
$patterns = (array) $rcube->config->get('trusted_host_patterns');
if ($strip_port) {
$name = preg_replace('/:\d+$/', '', $name);
}
if (empty($patterns) || in_array_nocase($name, $patterns)) {
return $name;
}
if (!empty($name)) {
foreach ($patterns as $pattern) {
if (preg_match("/$pattern/", $name)) {
return $name;
}
}
$rcube->raise_error(array('file' => __FILE__, 'line' => __LINE__,
'message' => "Specified host is not trusted. Using 'localhost'."), true, false);
}
return 'localhost';
}
/**
* Returns remote IP address and forwarded addresses if found
*
* @return string Remote IP address(es)
*/
public static function remote_ip()
{
$address = $_SERVER['REMOTE_ADDR'];
// append the NGINX X-Real-IP header, if set
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
$remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
}
// append the X-Forwarded-For header, if set
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if (!empty($remote_ip)) {
$address .= '(' . implode(',', $remote_ip) . ')';
}
return $address;
}
/**
* Returns the real remote IP address
*
* @return string Remote IP address
*/
public static function remote_addr()
{
// Check if any of the headers are set first to improve performance
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) || !empty($_SERVER['HTTP_X_REAL_IP'])) {
$proxy_whitelist = rcube::get_instance()->config->get('proxy_whitelist', array());
if (in_array($_SERVER['REMOTE_ADDR'], $proxy_whitelist)) {
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
foreach (array_reverse(explode(',', $_SERVER['HTTP_X_FORWARDED_FOR'])) as $forwarded_ip) {
if (!in_array($forwarded_ip, $proxy_whitelist)) {
return $forwarded_ip;
}
}
}
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
return $_SERVER['HTTP_X_REAL_IP'];
}
}
}
if (!empty($_SERVER['REMOTE_ADDR'])) {
return $_SERVER['REMOTE_ADDR'];
}
return '';
}
/**
* Read a specific HTTP request header.
*
* @param string $name Header name
*
* @return mixed Header value or null if not available
*/
public static function request_header($name)
{
if (function_exists('getallheaders')) {
$hdrs = array_change_key_case(getallheaders(), CASE_UPPER);
$key = strtoupper($name);
}
else {
$key = 'HTTP_' . strtoupper(strtr($name, '-', '_'));
$hdrs = array_change_key_case($_SERVER, CASE_UPPER);
}
return $hdrs[$key];
}
/**
* Explode quoted string
*
* @param string Delimiter expression string for preg_match()
* @param string Input string
*
* @return array String items
*/
public static function explode_quoted_string($delimiter, $string)
{
$result = array();
$strlen = strlen($string);
for ($q=$p=$i=0; $i < $strlen; $i++) {
if ($string[$i] == "\"" && $string[$i-1] != "\\") {
$q = $q ? false : true;
}
else if (!$q && preg_match("/$delimiter/", $string[$i])) {
$result[] = substr($string, $p, $i - $p);
$p = $i + 1;
}
}
$result[] = (string) substr($string, $p);
return $result;
}
/**
* Improved equivalent to strtotime()
*
* @param string $date Date string
* @param DateTimeZone $timezone Timezone to use for DateTime object
*
* @return int Unix timestamp
*/
public static function strtotime($date, $timezone = null)
{
$date = self::clean_datestr($date);
$tzname = $timezone ? ' ' . $timezone->getName() : '';
// unix timestamp
if (is_numeric($date)) {
return (int) $date;
}
// It can be very slow when provided string is not a date and very long
if (strlen($date) > 128) {
$date = substr($date, 0, 128);
}
// if date parsing fails, we have a date in non-rfc format.
// remove token from the end and try again
while (($ts = @strtotime($date . $tzname)) === false || $ts < 0) {
if (($pos = strrpos($date, ' ')) === false) {
break;
}
$date = rtrim(substr($date, 0, $pos));
}
return (int) $ts;
}
/**
* Date parsing function that turns the given value into a DateTime object
*
* @param string $date Date string
* @param DateTimeZone $timezone Timezone to use for DateTime object
*
* @return DateTime instance or false on failure
*/
public static function anytodatetime($date, $timezone = null)
{
if ($date instanceof DateTime) {
return $date;
}
$dt = false;
$date = self::clean_datestr($date);
// try to parse string with DateTime first
if (!empty($date)) {
try {
$_date = preg_match('/^[0-9]+$/', $date) ? "@$date" : $date;
$dt = $timezone ? new DateTime($_date, $timezone) : new DateTime($_date);
}
catch (Exception $e) {
// ignore
}
}
// try our advanced strtotime() method
if (!$dt && ($timestamp = self::strtotime($date, $timezone))) {
try {
$dt = new DateTime("@".$timestamp);
if ($timezone) {
$dt->setTimezone($timezone);
}
}
catch (Exception $e) {
// ignore
}
}
return $dt;
}
/**
* Clean up date string for strtotime() input
*
* @param string $date Date string
*
* @return string Date string
*/
public static function clean_datestr($date)
{
$date = trim($date);
// check for MS Outlook vCard date format YYYYMMDD
if (preg_match('/^([12][90]\d\d)([01]\d)([0123]\d)$/', $date, $m)) {
return sprintf('%04d-%02d-%02d 00:00:00', intval($m[1]), intval($m[2]), intval($m[3]));
}
// Clean malformed data
$date = preg_replace(
array(
'/\(.*\)/', // remove RFC comments
'/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal
'/[^a-z0-9\x20\x09:\/\.+-]/i', // remove any invalid characters
'/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names
),
array(
'',
'\\1',
'',
'',
), $date);
$date = trim($date);
// try to fix dd/mm vs. mm/dd discrepancy, we can't do more here
if (preg_match('/^(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{4})(\s.*)?$/', $date, $m)) {
$mdy = $m[2] > 12 && $m[1] <= 12;
$day = $mdy ? $m[2] : $m[1];
$month = $mdy ? $m[1] : $m[2];
$date = sprintf('%04d-%02d-%02d%s', $m[3], $month, $day, $m[4] ?: ' 00:00:00');
}
// I've found that YYYY.MM.DD is recognized wrong, so here's a fix
else if (preg_match('/^(\d{4})\.(\d{1,2})\.(\d{1,2})(\s.*)?$/', $date, $m)) {
$date = sprintf('%04d-%02d-%02d%s', $m[1], $m[2], $m[3], $m[4] ?: ' 00:00:00');
}
return $date;
}
/**
* Turns the given date-only string in defined format into YYYY-MM-DD format.
*
* Supported formats: 'Y/m/d', 'Y.m.d', 'd-m-Y', 'd/m/Y', 'd.m.Y', 'j.n.Y'
*
* @param string $date Date string
* @param string $format Input date format
*
* @return strin Date string in YYYY-MM-DD format, or the original string
* if format is not supported
*/
public static function format_datestr($date, $format)
{
$format_items = preg_split('/[.-\/\\\\]/', $format);
$date_items = preg_split('/[.-\/\\\\]/', $date);
$iso_format = '%04d-%02d-%02d';
if (count($format_items) == 3 && count($date_items) == 3) {
if ($format_items[0] == 'Y') {
$date = sprintf($iso_format, $date_items[0], $date_items[1], $date_items[2]);
}
else if (strpos('dj', $format_items[0]) !== false) {
$date = sprintf($iso_format, $date_items[2], $date_items[1], $date_items[0]);
}
else if (strpos('mn', $format_items[0]) !== false) {
$date = sprintf($iso_format, $date_items[2], $date_items[0], $date_items[1]);
}
}
return $date;
}
/**
* Wrapper for idn_to_ascii with support for e-mail address.
*
* Warning: Domain names may be lowercase'd.
* Warning: An empty string may be returned on invalid domain.
*
* @param string $str Decoded e-mail address
*
* @return string Encoded e-mail address
*/
public static function idn_to_ascii($str)
{
return self::idn_convert($str, true);
}
/**
* Wrapper for idn_to_utf8 with support for e-mail address
*
* @param string $str Decoded e-mail address
*
* @return string Encoded e-mail address
*/
public static function idn_to_utf8($str)
{
return self::idn_convert($str, false);
}
/**
* Convert a string to ascii or utf8 (using IDNA standard)
*
* @param string $input Decoded e-mail address
* @param boolean $is_utf Convert by idn_to_ascii if true and idn_to_utf8 if false
*
* @return string Encoded e-mail address
*/
public static function idn_convert($input, $is_utf = false)
{
if ($at = strpos($input, '@')) {
$user = substr($input, 0, $at);
$domain = substr($input, $at + 1);
}
else {
$user = '';
$domain = $input;
}
// Note that in PHP 7.2/7.3 calling idn_to_* functions with default arguments
// throws a warning, so we have to set the variant explicitely (#6075)
$variant = defined('INTL_IDNA_VARIANT_UTS46') ? INTL_IDNA_VARIANT_UTS46 : null;
$options = 0;
// Because php-intl extension lowercases domains and return false
// on invalid input (#6224), we skip conversion when not needed
// for compatibility with our Net_IDNA2 wrappers in bootstrap.php
if ($is_utf) {
if (preg_match('/[^\x20-\x7E]/', $domain)) {
$domain = idn_to_ascii($domain, $options, $variant);
}
}
else if (preg_match('/(^|\.)xn--/i', $domain)) {
$domain = idn_to_utf8($domain, $options, $variant);
}
if ($domain === false) {
return '';
}
return $at ? $user . '@' . $domain : $domain;
}
/**
* Split the given string into word tokens
*
* @param string Input to tokenize
* @param integer Minimum length of a single token
* @return array List of tokens
*/
public static function tokenize_string($str, $minlen = 2)
{
$expr = array('/[\s;,"\'\/+-]+/ui', '/(\d)[-.\s]+(\d)/u');
$repl = array(' ', '\\1\\2');
if ($minlen > 1) {
$minlen--;
$expr[] = "/(^|\s+)\w{1,$minlen}(\s+|$)/u";
$repl[] = ' ';
}
return array_filter(explode(" ", preg_replace($expr, $repl, $str)));
}
/**
* Normalize the given string for fulltext search.
* Currently only optimized for ISO-8859-1 and ISO-8859-2 characters; to be extended
*
* @param string Input string (UTF-8)
* @param boolean True to return list of words as array
* @param integer Minimum length of tokens
*
* @return mixed Normalized string or a list of normalized tokens
*/
public static function normalize_string($str, $as_array = false, $minlen = 2)
{
// replace 4-byte unicode characters with '?' character,
// these are not supported in default utf-8 charset on mysql,
// the chance we'd need them in searching is very low
$str = preg_replace('/('
. '\xF0[\x90-\xBF][\x80-\xBF]{2}'
. '|[\xF1-\xF3][\x80-\xBF]{3}'
. '|\xF4[\x80-\x8F][\x80-\xBF]{2}'
. ')/', '?', $str);
// split by words
$arr = self::tokenize_string($str, $minlen);
// detect character set
if (utf8_encode(utf8_decode($str)) == $str) {
// ISO-8859-1 (or ASCII)
preg_match_all('/./u', 'äâàåáãæçéêëèïîìíñöôòøõóüûùúýÿ', $keys);
preg_match_all('/./', 'aaaaaaaceeeeiiiinoooooouuuuyy', $values);
$mapping = array_combine($keys[0], $values[0]);
$mapping = array_merge($mapping, array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u'));
}
else if (rcube_charset::convert(rcube_charset::convert($str, 'UTF-8', 'ISO-8859-2'), 'ISO-8859-2', 'UTF-8') == $str) {
// ISO-8859-2
preg_match_all('/./u', 'ąáâäćçčéęëěíîłľĺńňóôöŕřśšşťţůúűüźžżý', $keys);
preg_match_all('/./', 'aaaaccceeeeiilllnnooorrsssttuuuuzzzy', $values);
$mapping = array_combine($keys[0], $values[0]);
$mapping = array_merge($mapping, array('ß' => 'ss', 'ae' => 'a', 'oe' => 'o', 'ue' => 'u'));
}
foreach ($arr as $i => $part) {
$part = mb_strtolower($part);
if (!empty($mapping)) {
$part = strtr($part, $mapping);
}
$arr[$i] = $part;
}
return $as_array ? $arr : join(" ", $arr);
}
/**
* Compare two strings for matching words (order not relevant)
*
* @param string Haystack
* @param string Needle
*
* @return boolean True if match, False otherwise
*/
public static function words_match($haystack, $needle)
{
$a_needle = self::tokenize_string($needle, 1);
$_haystack = join(" ", self::tokenize_string($haystack, 1));
$valid = strlen($_haystack) > 0;
$hits = 0;
foreach ($a_needle as $w) {
if ($valid) {
if (stripos($_haystack, $w) !== false) {
$hits++;
}
}
else if (stripos($haystack, $w) !== false) {
$hits++;
}
}
return $hits >= count($a_needle);
}
/**
* Parse commandline arguments into a hash array
*
* @param array $aliases Argument alias names
*
* @return array Argument values hash
*/
public static function get_opt($aliases = array())
{
$args = array();
$bool = array();
// find boolean (no value) options
foreach ($aliases as $key => $alias) {
if ($pos = strpos($alias, ':')) {
$aliases[$key] = substr($alias, 0, $pos);
$bool[] = $key;
$bool[] = $aliases[$key];
}
}
for ($i=1; $i < count($_SERVER['argv']); $i++) {
$arg = $_SERVER['argv'][$i];
$value = true;
$key = null;
if ($arg[0] == '-') {
$key = preg_replace('/^-+/', '', $arg);
$sp = strpos($arg, '=');
if ($sp > 0) {
$key = substr($key, 0, $sp - 2);
$value = substr($arg, $sp+1);
}
else if (in_array($key, $bool)) {
$value = true;
}
else if (strlen($_SERVER['argv'][$i+1]) && $_SERVER['argv'][$i+1][0] != '-') {
$value = $_SERVER['argv'][++$i];
}
$args[$key] = is_string($value) ? preg_replace(array('/^["\']/', '/["\']$/'), '', $value) : $value;
}
else {
$args[] = $arg;
}
if ($alias = $aliases[$key]) {
$args[$alias] = $args[$key];
}
}
return $args;
}
/**
* Safe password prompt for command line
* from http://blogs.sitepoint.com/2009/05/01/interactive-cli-password-prompt-in-php/
*
* @return string Password
*/
public static function prompt_silent($prompt = "Password:")
{
if (preg_match('/^win/i', PHP_OS)) {
$vbscript = sys_get_temp_dir() . 'prompt_password.vbs';
$vbcontent = 'wscript.echo(InputBox("' . addslashes($prompt) . '", "", "password here"))';
file_put_contents($vbscript, $vbcontent);
$command = "cscript //nologo " . escapeshellarg($vbscript);
$password = rtrim(shell_exec($command));
unlink($vbscript);
return $password;
}
else {
$command = "/usr/bin/env bash -c 'echo OK'";
if (rtrim(shell_exec($command)) !== 'OK') {
echo $prompt;
$pass = trim(fgets(STDIN));
echo chr(8)."\r" . $prompt . str_repeat("*", strlen($pass))."\n";
return $pass;
}
$command = "/usr/bin/env bash -c 'read -s -p \"" . addslashes($prompt) . "\" mypassword && echo \$mypassword'";
$password = rtrim(shell_exec($command));
echo "\n";
return $password;
}
}
/**
* Find out if the string content means true or false
*
* @param string $str Input value
*
* @return boolean Boolean value
*/
public static function get_boolean($str)
{
$str = strtolower($str);
return !in_array($str, array('false', '0', 'no', 'off', 'nein', ''), true);
}
/**
* OS-dependent absolute path detection
*/
public static function is_absolute_path($path)
{
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN') {
return (bool) preg_match('!^[a-z]:[\\\\/]!i', $path);
}
else {
return $path[0] == '/';
}
}
/**
* Resolve relative URL
*
* @param string $url Relative URL
*
* @return string Absolute URL
*/
public static function resolve_url($url)
{
// prepend protocol://hostname:port
if (!preg_match('|^https?://|', $url)) {
$schema = 'http';
$default_port = 80;
if (self::https_check()) {
$schema = 'https';
$default_port = 443;
}
$prefix = $schema . '://' . preg_replace('/:\d+$/', '', $_SERVER['HTTP_HOST']);
if ($_SERVER['SERVER_PORT'] != $default_port && $_SERVER['SERVER_PORT'] != 80) {
$prefix .= ':' . $_SERVER['SERVER_PORT'];
}
$url = $prefix . ($url[0] == '/' ? '' : '/') . $url;
}
return $url;
}
/**
* Generate a random string
*
* @param int $length String length
* @param bool $raw Return RAW data instead of ascii
*
* @return string The generated random string
*/
public static function random_bytes($length, $raw = false)
{
$hextab = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$tabsize = strlen($hextab);
// Use PHP7 true random generator
if ($raw && function_exists('random_bytes')) {
return random_bytes($length);
}
if (!$raw && function_exists('random_int')) {
$result = '';
while ($length-- > 0) {
$result .= $hextab[random_int(0, $tabsize - 1)];
}
return $result;
}
$random = openssl_random_pseudo_bytes($length);
if ($random === false && $length > 0) {
throw new Exception("Failed to get random bytes");
}
if (!$raw) {
for ($x = 0; $x < $length; $x++) {
$random[$x] = $hextab[ord($random[$x]) % $tabsize];
}
}
return $random;
}
/**
* Convert binary data into readable form (containing a-zA-Z0-9 characters)
*
* @param string $input Binary input
*
* @return string Readable output (Base62)
* @deprecated since 1.3.1
*/
public static function bin2ascii($input)
{
$hextab = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
$result = '';
for ($x = 0; $x < strlen($input); $x++) {
$result .= $hextab[ord($input[$x]) % 62];
}
return $result;
}
/**
* Format current date according to specified format.
* This method supports microseconds (u).
*
* @param string $format Date format (default: 'd-M-Y H:i:s O')
*
* @return string Formatted date
*/
public static function date_format($format = null)
{
if (empty($format)) {
$format = 'd-M-Y H:i:s O';
}
if (strpos($format, 'u') !== false) {
$dt = number_format(microtime(true), 6, '.', '');
$dt .= '.' . date_default_timezone_get();
if ($date = date_create_from_format('U.u.e', $dt)) {
return $date->format($format);
}
}
return date($format);
}
/**
* Parses socket options and returns options for specified hostname.
*
* @param array &$options Configured socket options
* @param string $host Hostname
*/
public static function parse_socket_options(&$options, $host = null)
{
if (empty($host) || empty($options)) {
return $options;
}
// get rid of schema and port from the hostname
$host_url = parse_url($host);
if (isset($host_url['host'])) {
$host = $host_url['host'];
}
// find per-host options
if (array_key_exists($host, $options)) {
$options = $options[$host];
}
}
/**
* Get maximum upload size
*
* @return int Maximum size in bytes
*/
public static function max_upload_size()
{
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize) {
$max_filesize = $max_postsize;
}
return $max_filesize;
}
/**
* Detect and log last PREG operation error
*
* @param array $error Error data (line, file, code, message)
* @param bool $terminate Stop script execution
*
* @return bool True on error, False otherwise
*/
public static function preg_error($error = array(), $terminate = false)
{
if (($preg_error = preg_last_error()) != PREG_NO_ERROR) {
$errstr = "PCRE Error: $preg_error.";
if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
$errstr .= " Consider raising pcre.backtrack_limit!";
}
if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
$errstr .= " Consider raising pcre.recursion_limit!";
}
$error = array_merge(array('code' => 620, 'line' => __LINE__, 'file' => __FILE__), $error);
if (!empty($error['message'])) {
$error['message'] .= ' ' . $errstr;
}
else {
$error['message'] = $errstr;
}
rcube::raise_error($error, true, $terminate);
return true;
}
return false;
}
}
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index d4ce1cce2..71ac457e4 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -1,749 +1,751 @@
<?php
/*
+-----------------------------------------------------------------------+
| localization/<lang>/labels.inc |
| |
| Localization file of the Roundcube Webmail client |
| Copyright (C) 2005-2015, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
+-----------------------------------------------------------------------+
For translation see https://www.transifex.com/projects/p/roundcube-webmail/resource/labels/
*/
$labels = array();
// login page
$labels['welcome'] = 'Welcome to $product';
$labels['username'] = 'Username';
$labels['password'] = 'Password';
$labels['server'] = 'Server';
$labels['login'] = 'Login';
// taskbar
$labels['menu'] = 'Menu';
$labels['logout'] = 'Logout';
$labels['mail'] = 'Mail';
$labels['settings'] = 'Settings';
$labels['addressbook'] = 'Address Book';
// mailbox names
$labels['inbox'] = 'Inbox';
$labels['drafts'] = 'Drafts';
$labels['sent'] = 'Sent';
$labels['trash'] = 'Trash';
$labels['junk'] = 'Junk';
$labels['show_real_foldernames'] = 'Show real names for special folders';
// message listing
$labels['subject'] = 'Subject';
$labels['from'] = 'From';
$labels['sender'] = 'Sender';
$labels['to'] = 'To';
$labels['cc'] = 'Cc';
$labels['bcc'] = 'Bcc';
$labels['replyto'] = 'Reply-To';
$labels['followupto'] = 'Followup-To';
$labels['date'] = 'Date';
$labels['size'] = 'Size';
$labels['priority'] = 'Priority';
$labels['organization'] = 'Organization';
$labels['readstatus'] = 'Read status';
$labels['listoptions'] = 'List options...';
$labels['listoptionstitle'] = 'List options';
$labels['mailboxlist'] = 'Folders';
$labels['messagesfromto'] = 'Messages $from to $to of $count';
$labels['threadsfromto'] = 'Threads $from to $to of $count';
$labels['messagenrof'] = 'Message $nr of $count';
$labels['fromtoshort'] = '$from – $to of $count';
$labels['copy'] = 'Copy';
$labels['move'] = 'Move';
$labels['moveto'] = 'Move to...';
$labels['copyto'] = 'Copy to...';
$labels['download'] = 'Download';
$labels['open'] = 'Open';
$labels['showattachment'] = 'Show';
$labels['showanyway'] = 'Show it anyway';
$labels['filename'] = 'File name';
$labels['filesize'] = 'File size';
$labels['addtoaddressbook'] = 'Add to address book';
// weekdays short
$labels['sun'] = 'Sun';
$labels['mon'] = 'Mon';
$labels['tue'] = 'Tue';
$labels['wed'] = 'Wed';
$labels['thu'] = 'Thu';
$labels['fri'] = 'Fri';
$labels['sat'] = 'Sat';
// weekdays long
$labels['sunday'] = 'Sunday';
$labels['monday'] = 'Monday';
$labels['tuesday'] = 'Tuesday';
$labels['wednesday'] = 'Wednesday';
$labels['thursday'] = 'Thursday';
$labels['friday'] = 'Friday';
$labels['saturday'] = 'Saturday';
// months short
$labels['jan'] = 'Jan';
$labels['feb'] = 'Feb';
$labels['mar'] = 'Mar';
$labels['apr'] = 'Apr';
$labels['may'] = 'May';
$labels['jun'] = 'Jun';
$labels['jul'] = 'Jul';
$labels['aug'] = 'Aug';
$labels['sep'] = 'Sep';
$labels['oct'] = 'Oct';
$labels['nov'] = 'Nov';
$labels['dec'] = 'Dec';
// months long
$labels['longjan'] = 'January';
$labels['longfeb'] = 'February';
$labels['longmar'] = 'March';
$labels['longapr'] = 'April';
$labels['longmay'] = 'May';
$labels['longjun'] = 'June';
$labels['longjul'] = 'July';
$labels['longaug'] = 'August';
$labels['longsep'] = 'September';
$labels['longoct'] = 'October';
$labels['longnov'] = 'November';
$labels['longdec'] = 'December';
$labels['today'] = 'Today';
// toolbar buttons
$labels['refresh'] = 'Refresh';
$labels['checkmail'] = 'Check for new messages';
$labels['compose'] = 'Compose';
$labels['writenewmessage'] = 'Create a new message';
$labels['reply'] = 'Reply';
$labels['replytomessage'] = 'Reply to sender';
$labels['replytoallmessage'] = 'Reply to list or to sender and all recipients';
$labels['replyall'] = 'Reply all';
$labels['replylist'] = 'Reply list';
$labels['forward'] = 'Forward';
$labels['forwardinline'] = 'Forward inline';
$labels['forwardattachment'] = 'Forward as attachment';
$labels['forwardmessage'] = 'Forward the message';
$labels['bouncemsg'] = 'Resend (bounce)';
$labels['bounce'] = 'Resend';
$labels['deletemessage'] = 'Delete message';
$labels['movemessagetotrash'] = 'Move message to trash';
$labels['printmessage'] = 'Print this message';
$labels['previousmessage'] = 'Show previous message';
$labels['firstmessage'] = 'Show first message';
$labels['nextmessage'] = 'Show next message';
$labels['lastmessage'] = 'Show last message';
$labels['backtolist'] = 'Back to message list';
$labels['viewsource'] = 'Show source';
$labels['mark'] = 'Mark';
$labels['markmessages'] = 'Mark messages';
$labels['markread'] = 'As read';
$labels['markunread'] = 'As unread';
$labels['markflagged'] = 'As flagged';
$labels['markunflagged'] = 'As unflagged';
$labels['moreactions'] = 'More actions...';
$labels['markallread'] = 'Mark all as read';
$labels['folders-cur'] = 'Selected folder only';
$labels['folders-sub'] = 'Selected folder and its subfolders';
$labels['folders-all'] = 'All folders';
$labels['more'] = 'More';
$labels['back'] = 'Back';
$labels['options'] = 'Options';
$labels['composeoptions'] = 'Compose options';
$labels['optionsandattachments'] = 'Options and attachments';
$labels['actions'] = 'Actions';
$labels['first'] = 'First';
$labels['last'] = 'Last';
$labels['previous'] = 'Previous';
$labels['next'] = 'Next';
$labels['select'] = 'Select';
$labels['selection'] = 'Selection';
$labels['all'] = 'All';
$labels['none'] = 'None';
$labels['currpage'] = 'Current page';
$labels['isread'] = 'Read';
$labels['unread'] = 'Unread';
$labels['flagged'] = 'Flagged';
$labels['unflagged'] = 'Not Flagged';
$labels['unanswered'] = 'Unanswered';
$labels['withattachment'] = 'With attachment';
$labels['deleted'] = 'Deleted';
$labels['undeleted'] = 'Not deleted';
$labels['replied'] = 'Replied';
$labels['forwarded'] = 'Forwarded';
$labels['invert'] = 'Invert';
$labels['filter'] = 'Filter';
$labels['list'] = 'List';
$labels['threads'] = 'Threads';
$labels['expand-all'] = 'Expand All';
$labels['expand-unread'] = 'Expand Unread';
$labels['collapse-all'] = 'Collapse All';
$labels['threaded'] = 'Threaded';
$labels['autoexpand_threads'] = 'Expand message threads';
$labels['do_expand'] = 'all threads';
$labels['expand_only_unread'] = 'only with unread messages';
$labels['fromto'] = 'From/To';
$labels['flag'] = 'Flag';
$labels['attachment'] = 'Attachment';
$labels['nonesort'] = 'None';
$labels['sentdate'] = 'Sent date';
$labels['arrival'] = 'Arrival date';
$labels['asc'] = 'ascending';
$labels['desc'] = 'descending';
$labels['listcolumns'] = 'List columns';
$labels['listsorting'] = 'Sorting column';
$labels['listorder'] = 'Sorting order';
$labels['listmode'] = 'List view mode';
$labels['lmode'] = 'List mode';
$labels['layout'] = 'Layout';
$labels['layoutwidescreen'] = 'Widescreen';
$labels['layoutdesktop'] = 'Desktop';
$labels['layoutlist'] = 'List';
$labels['layoutwidescreendesc'] = 'Widescreen (3-column view)';
$labels['layoutdesktopdesc'] = 'Desktop (wide list and mail preview below)';
$labels['layoutlistdesc'] = 'List (no mail preview)';
$labels['folderactions'] = 'Folder actions...';
$labels['compact'] = 'Compact';
$labels['empty'] = 'Empty';
$labels['importmessages'] = 'Import messages';
$labels['mailimportdesc'] = 'You can upload mail using files in <a href="https://en.wikipedia.org/wiki/Email#Message_format">MIME</a> or <a href="https://en.wikipedia.org/wiki/Mbox">Mbox</a> format.';
$labels['mailimportzip'] = 'Multiple files can be compressed into zip archives.';
$labels['quota'] = 'Disk usage';
$labels['unknown'] = 'unknown';
$labels['unlimited'] = 'unlimited';
$labels['quotatype'] = 'Quota type';
$labels['quotatotal'] = 'Limit';
$labels['quotaused'] = 'Used';
$labels['quotastorage'] = 'Disk space';
$labels['quotamessage'] = 'Messages count';
$labels['shortheaderdate'] = 'On $date';
$labels['shortheaderto'] = 'To $to on $date';
$labels['shortheaderfrom'] = 'From $from on $date';
$labels['quicksearch'] = 'Quick search';
$labels['searchplaceholder'] = 'Search...';
$labels['resetsearch'] = 'Reset search';
$labels['searchmod'] = 'Search modifiers';
$labels['msgtext'] = 'Entire message';
$labels['body'] = 'Body';
$labels['type'] = 'Type';
$labels['namex'] = 'Name';
$labels['searchscope'] = 'Scope';
$labels['currentfolder'] = 'Current folder';
$labels['subfolders'] = 'This and subfolders';
$labels['allfolders'] = 'All folders';
$labels['searchinterval-1W'] = 'older than a week';
$labels['searchinterval-1M'] = 'older than a month';
$labels['searchinterval-1Y'] = 'older than a year';
$labels['searchinterval1W'] = 'younger than a week';
$labels['searchinterval1M'] = 'younger than a month';
$labels['searchinterval1Y'] = 'younger than a year';
$labels['openinextwin'] = 'Open in new window';
$labels['emlsave'] = 'Download (.eml)';
$labels['changeformattext'] = 'Display in plain text format';
$labels['changeformathtml'] = 'Display in HTML format';
// message compose
$labels['editasnew'] = 'Edit as new';
$labels['send'] = 'Send';
$labels['sendmessage'] = 'Send message';
$labels['savemessage'] = 'Save as draft';
$labels['addattachment'] = 'Attach a file';
$labels['charset'] = 'Charset';
$labels['editortype'] = 'Editor type';
$labels['returnreceipt'] = 'Return receipt';
$labels['dsn'] = 'Delivery status notification';
$labels['mailreplyintro'] = 'On $date, $sender wrote:';
$labels['originalmessage'] = 'Original Message';
$labels['selectimage'] = 'Select image';
$labels['addimage'] = 'Add image';
$labels['selectmedia'] = 'Select movie';
$labels['addmedia'] = 'Add movie';
$labels['encrypt'] = 'Encrypt';
$labels['encryptmessage'] = 'Encrypt message';
$labels['encryptmessagemailvelope'] = 'Encrypt message with Mailvelope';
$labels['importpubkeys'] = 'Import public keys';
$labels['encryptedsendialog'] = 'Sending encrypted message';
$labels['encryptandsign'] = 'Encrypt and sign';
$labels['keyid'] = 'Key ID';
$labels['keylength'] = 'Bits';
$labels['keyexpired'] = 'Expired';
$labels['keyrevoked'] = 'Revoked';
$labels['bccinstead'] = 'Use Bcc';
$labels['addheader'] = 'Add recipient (header)';
+$labels['insert'] = 'Insert';
+$labels['insertcontact'] = 'Insert contact(s)';
$labels['recipient'] = 'Recipient';
$labels['recipientedit'] = 'Recipient edit';
$labels['editidents'] = 'Edit identities';
$labels['spellcheck'] = 'Spell';
$labels['checkspelling'] = 'Check spelling';
$labels['resumeediting'] = 'Resume editing';
$labels['revertto'] = 'Revert to';
$labels['restore'] = 'Restore';
$labels['restoremessage'] = 'Restore message?';
$labels['ignore'] = 'Ignore';
$labels['responses'] = 'Responses';
$labels['insertresponse'] = 'Insert a response';
$labels['manageresponses'] = 'Manage responses';
$labels['newresponse'] = 'Create new response';
$labels['addresponse'] = 'Add response';
$labels['editresponse'] = 'Edit response';
$labels['editresponses'] = 'Edit responses';
$labels['responsename'] = 'Name';
$labels['responsetext'] = 'Response Text';
$labels['attach'] = 'Attach';
$labels['attachments'] = 'Attachments';
$labels['upload'] = 'Upload';
$labels['uploadprogress'] = '$percent ($current of $total)';
$labels['close'] = 'Close';
$labels['messageoptions'] = 'Message options...';
$labels['togglecomposeoptions'] = 'Toggle composition options';
$labels['attachmentrename'] = 'Rename attachment';
$labels['low'] = 'Low';
$labels['lowest'] = 'Lowest';
$labels['normal'] = 'Normal';
$labels['high'] = 'High';
$labels['highest'] = 'Highest';
$labels['nosubject'] = '(no subject)';
$labels['showimages'] = 'Display images';
$labels['allow'] = 'Allow';
$labels['alwaysshow'] = 'Always show images from $sender';
$labels['alwaysallow'] = 'Always allow from $sender';
$labels['isdraft'] = 'This is a draft message.';
$labels['andnmore'] = '$nr more...';
$labels['details'] = 'Details';
$labels['headers'] = 'Headers';
$labels['allheaders'] = 'All headers...';
$labels['togglemoreheaders'] = 'Show more message headers';
$labels['togglefullheaders'] = 'Toggle raw message headers';
$labels['htmltoggle'] = 'HTML';
$labels['plaintoggle'] = 'Plain text';
$labels['savesentmessagein'] = 'Save sent message in';
$labels['dontsave'] = 'don\'t save';
$labels['maxuploadsize'] = 'Maximum allowed file size is $size';
$labels['addcc'] = 'Add Cc';
$labels['addbcc'] = 'Add Bcc';
$labels['addreplyto'] = 'Add Reply-To';
$labels['addfollowupto'] = 'Add Followup-To';
// mdn
$labels['mdnrequest'] = 'The sender of this message has asked to be notified when you read this message. Do you wish to notify the sender?';
$labels['receiptread'] = 'Return Receipt (read)';
$labels['yourmessage'] = 'This is a Return Receipt for your message';
$labels['receiptnote'] = 'Note: This receipt only acknowledges that the message was displayed on the recipient\'s computer. There is no guarantee that the recipient has read or understood the message contents.';
$labels['zoomin'] = 'Zoom In';
$labels['zoomout'] = 'Zoom Out';
$labels['rotate'] = 'Rotate';
$labels['increaseimage'] = 'Increase image size';
$labels['decreaseimage'] = 'Decrease image size';
$labels['rotateimage'] = 'Rotate image';
$labels['showtools'] = 'Show image tools';
$labels['hidetools'] = 'Hide image tools';
// address boook
$labels['name'] = 'Display Name';
$labels['firstname'] = 'First Name';
$labels['surname'] = 'Last Name';
$labels['middlename'] = 'Middle Name';
$labels['nameprefix'] = 'Prefix';
$labels['namesuffix'] = 'Suffix';
$labels['nickname'] = 'Nickname';
$labels['jobtitle'] = 'Job Title';
$labels['department'] = 'Department';
$labels['gender'] = 'Gender';
$labels['maidenname'] = 'Maiden Name';
$labels['email'] = 'Email';
$labels['phone'] = 'Phone';
$labels['address'] = 'Address';
$labels['street'] = 'Street';
$labels['locality'] = 'City';
$labels['zipcode'] = 'ZIP Code';
$labels['region'] = 'State/Province';
$labels['country'] = 'Country';
$labels['birthday'] = 'Birthday';
$labels['anniversary'] = 'Anniversary';
$labels['website'] = 'Website';
$labels['instantmessenger'] = 'IM';
$labels['notes'] = 'Notes';
$labels['male'] = 'male';
$labels['female'] = 'female';
$labels['manager'] = 'Manager';
$labels['assistant'] = 'Assistant';
$labels['spouse'] = 'Spouse';
$labels['allfields'] = 'All fields';
$labels['search'] = 'Search';
$labels['searchresult'] = 'Search result';
$labels['advsearch'] = 'Advanced Search';
$labels['advanced'] = 'Advanced';
$labels['other'] = 'Other';
$labels['printcontact'] = 'Print contact';
$labels['qrcode'] = 'QR Code';
$labels['typehome'] = 'Home';
$labels['typework'] = 'Work';
$labels['typeother'] = 'Other';
$labels['typemobile'] = 'Mobile';
$labels['typemain'] = 'Main';
$labels['typehomefax'] = 'Home Fax';
$labels['typeworkfax'] = 'Work Fax';
$labels['typecar'] = 'Car';
$labels['typepager'] = 'Pager';
$labels['typevideo'] = 'Video';
$labels['typeassistant'] = 'Assistant';
$labels['typehomepage'] = 'Home Page';
$labels['typeblog'] = 'Blog';
$labels['typeprofile'] = 'Profile';
$labels['addfield'] = 'Add field...';
$labels['addcontact'] = 'Add contact';
$labels['editcontact'] = 'Edit contact';
$labels['contacts'] = 'Contacts';
$labels['contactproperties'] = 'Contact properties';
$labels['contactnameandorg'] = 'Name and Organization';
$labels['personalinfo'] = 'Personal information';
$labels['personal'] = 'Personal';
$labels['contactphoto'] = 'Contact photo';
$labels['edit'] = 'Edit';
$labels['cancel'] = 'Cancel';
$labels['save'] = 'Save';
$labels['delete'] = 'Delete';
$labels['discard'] = 'Discard';
$labels['continue'] = 'Continue';
$labels['ok'] = 'OK';
$labels['rename'] = 'Rename';
$labels['addphoto'] = 'Add';
$labels['replacephoto'] = 'Replace';
$labels['uploadphoto'] = 'Upload photo';
$labels['newcontact'] = 'Create new contact';
$labels['deletecontact'] = 'Delete selected contacts';
$labels['composeto'] = 'Compose mail to';
$labels['contactsfromto'] = 'Contacts $from to $to of $count';
$labels['print'] = 'Print';
$labels['export'] = 'Export';
$labels['exportall'] = 'Export all';
$labels['exportsel'] = 'Export selected';
$labels['exportvcards'] = 'Export contacts in vCard format';
$labels['newgroup'] = 'Create new group';
$labels['addgroup'] = 'Add group';
$labels['grouprename'] = 'Rename group';
$labels['groupdelete'] = 'Delete group';
$labels['groupassign'] = 'Assign to group...';
$labels['groupremove'] = 'Remove from group';
$labels['groupremoveselected'] = 'Remove selected contacts from group';
$labels['uponelevel'] = 'Up one level';
$labels['previouspage'] = 'Show previous page';
$labels['firstpage'] = 'Show first page';
$labels['nextpage'] = 'Show next page';
$labels['lastpage'] = 'Show last page';
$labels['group'] = 'Group';
$labels['groups'] = 'Groups';
$labels['listgroup'] = 'List group members';
$labels['personaladrbook'] = 'Personal Addresses';
$labels['searchsave'] = 'Save search';
$labels['searchdelete'] = 'Delete search';
$labels['import'] = 'Import';
$labels['importcontacts'] = 'Import contacts';
$labels['importfromfile'] = 'Import from file';
$labels['importtarget'] = 'Add contacts to';
$labels['importreplace'] = 'Replace the entire address book';
$labels['importgroups'] = 'Import group assignments';
$labels['importgroupsall'] = 'All (create groups if necessary)';
$labels['importgroupsexisting'] = 'Only for existing groups';
$labels['importdesc'] = 'You can upload contacts from an existing address book.<br/>We currently support importing addresses from the <a href="https://en.wikipedia.org/wiki/VCard">vCard</a> or CSV (comma-separated) data format.';
$labels['done'] = 'Done';
// settings
$labels['settingsfor'] = 'Settings for';
$labels['about'] = 'About';
$labels['preferences'] = 'Preferences';
$labels['userpreferences'] = 'User preferences';
$labels['editpreferences'] = 'Edit user preferences';
$labels['identities'] = 'Identities';
$labels['manageidentities'] = 'Manage identities';
$labels['newidentity'] = 'Create new identity';
$labels['addidentity'] = 'Add identity';
$labels['editidentity'] = 'Edit identity';
$labels['identityencryption'] = 'Encryption';
$labels['preferhtml'] = 'Display HTML';
$labels['defaultcharset'] = 'Default Character Set';
$labels['htmlmessage'] = 'HTML Message';
$labels['messagepart'] = 'Part';
$labels['digitalsig'] = 'Digital Signature';
$labels['dateformat'] = 'Date format';
$labels['timeformat'] = 'Time format';
$labels['prettydate'] = 'Pretty dates';
$labels['setdefault'] = 'Set default';
$labels['autodetect'] = 'Auto';
$labels['language'] = 'Language';
$labels['timezone'] = 'Time zone';
$labels['pagesize'] = 'Rows per page';
$labels['signature'] = 'Signature';
$labels['dstactive'] = 'Daylight saving time';
$labels['showinextwin'] = 'Open message in a new window';
$labels['composeextwin'] = 'Compose in a new window';
$labels['htmleditor'] = 'Compose HTML messages';
$labels['htmlonreply'] = 'on reply to HTML message';
$labels['htmlonreplyandforward'] = 'on forward or reply to HTML message';
$labels['htmlsignature'] = 'HTML signature';
$labels['showemail'] = 'Show email address with display name';
$labels['previewpane'] = 'Show preview pane';
$labels['skin'] = 'Interface skin';
$labels['logoutclear'] = 'Clear Trash on logout';
$labels['logoutcompact'] = 'Compact Inbox on logout';
$labels['uisettings'] = 'User Interface';
$labels['serversettings'] = 'Server Settings';
$labels['mailboxview'] = 'Mailbox View';
$labels['mdnrequests'] = 'On request for return receipt';
$labels['askuser'] = 'ask me';
$labels['autosend'] = 'send receipt';
$labels['autosendknown'] = 'send receipt to my contacts, otherwise ask me';
$labels['autosendknownignore'] = 'send receipt to my contacts, otherwise ignore';
$labels['ignorerequest'] = 'ignore request';
$labels['readwhendeleted'] = 'Mark the message as read on delete';
$labels['flagfordeletion'] = 'Flag the message for deletion instead of delete';
$labels['skipdeleted'] = 'Do not show deleted messages';
$labels['deletealways'] = 'If moving messages to Trash fails, delete them';
$labels['deletejunk'] = 'Directly delete messages in Junk';
$labels['showremoteimages'] = 'Display remote inline images';
$labels['allowremoteresources'] = 'Allow remote resources (images, styles)';
$labels['fromknownsenders'] = 'from known senders';
$labels['always'] = 'always';
$labels['alwaysbutplain'] = 'always, except when replying to plain text';
$labels['showinlineimages'] = 'Display attached images below the message';
$labels['autosavedraft'] = 'Automatically save draft';
$labels['everynminutes'] = 'every $n minute(s)';
$labels['refreshinterval'] = 'Refresh (check for new messages, etc.)';
$labels['never'] = 'never';
$labels['immediately'] = 'immediately';
$labels['messagesdisplaying'] = 'Displaying Messages';
$labels['messagescomposition'] = 'Composing Messages';
$labels['mimeparamfolding'] = 'Attachment names';
$labels['2231folding'] = 'Full RFC 2231 (Thunderbird)';
$labels['miscfolding'] = 'RFC 2047/2231 (MS Outlook)';
$labels['2047folding'] = 'Full RFC 2047 (other)';
$labels['force7bit'] = 'Use MIME encoding for 8-bit characters';
$labels['savelocalstorage'] = "Save in the browser's local storage (temporarily)";
$labels['advancedoptions'] = 'Advanced options';
$labels['toggleadvancedoptions'] = 'Toggle advanced options';
$labels['focusonnewmessage'] = 'Focus browser window on new message';
$labels['checkallfolders'] = 'Check all folders for new messages';
$labels['displaynext'] = 'After message delete/move display the next message';
$labels['defaultfont'] = 'Default font of HTML message';
$labels['mainoptions'] = 'Main Options';
$labels['browseroptions'] = 'Browser Options';
$labels['section'] = 'Section';
$labels['maintenance'] = 'Maintenance';
$labels['newmessage'] = 'New Message';
$labels['signatureoptions'] = 'Signature Options';
$labels['whenreplying'] = 'When replying';
$labels['replyempty'] = 'do not quote the original message';
$labels['replytopposting'] = 'start new message above the quote';
$labels['replytoppostingnoindent'] = 'start new message above the quote (no indentation)';
$labels['replybottomposting'] = 'start new message below the quote';
$labels['replyremovesignature'] = 'When replying remove original signature from message';
$labels['autoaddsignature'] = 'Automatically add signature';
$labels['newmessageonly'] = 'new message only';
$labels['replyandforwardonly'] = 'replies and forwards only';
$labels['insertsignature'] = 'Insert signature';
$labels['sigbelow'] = 'Place signature below the quoted message';
$labels['sigseparator'] = 'Force standard separator in signatures';
$labels['automarkread'] = 'Mark messages as read';
$labels['afternseconds'] = 'after $n seconds';
$labels['reqmdn'] = 'Always request a return receipt';
$labels['reqdsn'] = 'Always request a delivery status notification';
$labels['replysamefolder'] = 'Place replies in the folder of the message being replied to';
$labels['defaultabook'] = 'Default address book';
$labels['autocompletesingle'] = 'Skip alternative email addresses in autocompletion';
$labels['listnamedisplay'] = 'List contacts as';
$labels['spellcheckbeforesend'] = 'Check spelling before sending a message';
$labels['spellcheckoptions'] = 'Spellcheck Options';
$labels['spellcheckignoresyms'] = 'Ignore words with symbols';
$labels['spellcheckignorenums'] = 'Ignore words with numbers';
$labels['spellcheckignorecaps'] = 'Ignore words with all letters capitalized';
$labels['addtodict'] = 'Add to dictionary';
$labels['mailtoprotohandler'] = 'Register protocol handler for mailto: links';
$labels['standardwindows'] = 'Handle popups as standard windows';
$labels['forwardmode'] = 'Messages forwarding';
$labels['inline'] = 'inline';
$labels['asattachment'] = 'as attachment';
$labels['replyallmode'] = 'Default action of [Reply all] button';
$labels['replyalldefault'] = 'reply to all';
$labels['replyalllist'] = 'reply to mailing list only (if found)';
$labels['folder'] = 'Folder';
$labels['folders'] = 'Folders';
$labels['foldername'] = 'Folder name';
$labels['subscribed'] = 'Subscribed';
$labels['messagecount'] = 'Messages';
$labels['create'] = 'Create';
$labels['createfolder'] = 'Create new folder';
$labels['managefolders'] = 'Manage folders';
$labels['specialfolders'] = 'Special Folders';
$labels['properties'] = 'Properties';
$labels['folderproperties'] = 'Folder properties';
$labels['parentfolder'] = 'Parent folder';
$labels['location'] = 'Location';
$labels['info'] = 'Information';
$labels['getfoldersize'] = 'Click to get folder size';
$labels['changesubscription'] = 'Click to change subscription';
$labels['foldertype'] = 'Folder Type';
$labels['personalfolder'] = 'Private Folder';
$labels['otherfolder'] = 'Other User\'s Folder';
$labels['sharedfolder'] = 'Public Folder';
$labels['findfolders'] = 'Find folders';
$labels['findcontacts'] = 'Find contacts';
$labels['findmail'] = 'Find mail messages';
$labels['namespace.personal'] = 'Personal';
$labels['namespace.other'] = 'Other users';
$labels['namespace.shared'] = 'Shared';
$labels['dualuselabel'] = 'Can contain only';
$labels['dualusemail'] = 'messages';
$labels['dualusefolder'] = 'folders';
$labels['generate'] = 'Generate';
$labels['encryptioncreatekey'] = 'Create a new key pair';
$labels['openmailvelopesettings'] = 'Open Mailvelope Settings';
$labels['encryptionprivkeysinmailvelope'] = 'You have $nr matching private keys stored in your Mailvelope keyring:';
$labels['encryptionnoprivkeysinmailvelope'] = 'This sender identity doesn\'t yet have a PGP private key stored in your Mailvelope extension. Would you like to create one in order to enable encrypted messaging?';
$labels['sortby'] = 'Sort by';
$labels['sortasc'] = 'Sort ascending';
$labels['sortdesc'] = 'Sort descending';
$labels['undo'] = 'Undo';
$labels['installedplugins'] = 'Installed plugins';
$labels['plugin'] = 'Plugin';
$labels['version'] = 'Version';
$labels['source'] = 'Source';
$labels['license'] = 'License';
$labels['support'] = 'Get support';
$labels['savedsearches'] = 'Saved searches';
$labels['dateformatY'] = 'YYYY';
$labels['dateformaty'] = 'YY';
$labels['dateformatm'] = 'MM';
$labels['dateformatd'] = 'DD';
$labels['dateformatj'] = 'D';
$labels['dateformatn'] = 'M';
// units
$labels['B'] = 'B';
$labels['KB'] = 'KB';
$labels['MB'] = 'MB';
$labels['GB'] = 'GB';
// character sets
$labels['unicode'] = 'Unicode';
$labels['english'] = 'English';
$labels['westerneuropean'] = 'Western European';
$labels['easterneuropean'] = 'Eastern European';
$labels['southeasterneuropean'] = 'South-Eastern European';
$labels['baltic'] = 'Baltic';
$labels['cyrillic'] = 'Cyrillic';
$labels['arabic'] = 'Arabic';
$labels['greek'] = 'Greek';
$labels['hebrew'] = 'Hebrew';
$labels['turkish'] = 'Turkish';
$labels['nordic'] = 'Nordic';
$labels['thai'] = 'Thai';
$labels['celtic'] = 'Celtic';
$labels['vietnamese'] = 'Vietnamese';
$labels['japanese'] = 'Japanese';
$labels['korean'] = 'Korean';
$labels['chinese'] = 'Chinese';
// accessibility (voice-only) headings and descriptions
$labels['arialabeltopnav'] = 'Window control';
$labels['arialabeltasknav'] = 'Application tasks';
$labels['arialabeltoolbar'] = 'Application toolbar';
$labels['arialabelactivetask'] = 'Active task';
$labels['arialabelmessagessearchfilter'] = 'Email listing filter';
$labels['arialabelmailsearchform'] = 'Email message search form';
$labels['arialabelcontactsearchform'] = 'Contacts search form';
$labels['arialabelmailquicksearchbox'] = 'Email search input';
$labels['arialabelquicksearchbox'] = 'Search input';
$labels['arialabelfoldersearchfilter'] = 'Folder listing filter';
$labels['arialabelfoldersearchform'] = 'Folder search form';
$labels['arialabelfolderlist'] = 'Email folder selection';
$labels['arialabelmessagelist'] = 'Email Messages Listing';
$labels['arialabelmailpreviewframe'] = 'Message preview';
$labels['arialabelmailboxmenu'] = 'Folder actions menu';
$labels['arialabellistselectmenu'] = 'List selection menu';
$labels['arialabelthreadselectmenu'] = 'Threads listing menu';
$labels['arialabelmessagelistoptions'] = 'Message list display and sorting options';
$labels['arialabelmailimportdialog'] = 'Message import dialog';
$labels['arialabelmessagenav'] = 'Message navigation';
$labels['arialabelmessagebody'] = 'Message Body';
$labels['arialabelmessageactions'] = 'Message actions';
$labels['arialabelcontactquicksearch'] = 'Contacts search form';
$labels['arialabelcontactsearchbox'] = 'Contact search input';
$labels['arialabelmessageheaders'] = 'Message headers';
$labels['arialabelforwardingoptions'] = 'Forwarding options';
$labels['arialabelreplyalloptions'] = 'Reply-all options';
$labels['arialabelmoremessageactions'] = 'More message actions';
$labels['arialabelmorecontactactions'] = 'More contact actions';
$labels['arialabelmarkmessagesas'] = 'Mark selected messages as...';
$labels['arialabelcomposeoptions'] = 'Composition options';
$labels['arialabelresponsesmenu'] = 'Canned responses menu';
$labels['arialabelattachmentuploadform'] = 'Attachment upload form';
$labels['arialabelattachmentmenu'] = 'Attachment options';
$labels['arialabelmailtomenu'] = 'Email address options';
$labels['arialabelattachmentpreview'] = 'Attachment preview';
$labels['ariasummarycomposecontacts'] = 'List of contacts and groups to select as recipients';
$labels['arialabelcontactexportoptions'] = 'Contact export options';
$labels['arialabelabookgroupoptions'] = 'Addressbook/group options';
$labels['arialabelpreferencesform'] = 'Preferences form';
$labels['arialabelidentityeditfrom'] = 'Identity edit form';
$labels['arialabelresonseeditfrom'] = 'Response edit form';
$labels['arialabelsearchterms'] = 'Search terms';
$labels['arialabeldropactionmenu'] = 'Drag-n-Drop action menu';
$labels['arialabelheadersmenu'] = 'Recipient (header) adding menu';
$labels['arialabelimagetools'] = 'Image tools';
$labels['helplistnavigation'] = 'List keyboard navigation';
$labels['helplistkeyboardnavigation'] = "Arrows up/down: Move row focus/selection.
Space: Select focused row.
Shift + up/down: Select additional row above/below.
Ctrl + Space: Add focused row to selection/remove from selection.";
$labels['helplistkeyboardnavmessages'] = "Arrows right/left: expand/collapse message thread (in threads mode only).
Enter: Open the selected/focused message.
Delete: Move selected messages to Trash.";
$labels['helplistkeyboardnavcontacts'] = "Enter: Open the selected/focused contact.";
?>
diff --git a/skins/elastic/styles/colors.less b/skins/elastic/styles/colors.less
index 2aeb30eb3..74a63d74a 100644
--- a/skins/elastic/styles/colors.less
+++ b/skins/elastic/styles/colors.less
@@ -1,203 +1,203 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
@color-main: #37beff;
@color-main-dark: darken(@color-main, 35%);
@color-black: #161b1d;
@color-font: lighten(@color-black, 10%);
@color-link: #00acff;
@color-link-hover: darken(@color-link, 10%);
@color-border: #ddd;
@color-error: #ff5552;
@color-success: #41b849;
@color-warning: #ffd452;
@color-link-secondary: lighten(@color-font, 60%);
@color-black-shade-text: lighten(@color-black, 35%);
@color-black-shade-border: lighten(@color-black, 75%);
@color-black-shade-bg: lighten(@color-black, 85%);
// Layout elements
@color-layout-border: @color-black-shade-border;
@color-layout-header: @color-font;
@color-layout-sidebar-background: #fff;
@color-layout-list-background: #fff;
@color-layout-content-background: #fff;
@color-layout-header-background: #f4f4f4;
@color-layout-footer-background: #fff;
@color-layout-mobile-header-background: @color-layout-header-background;
@color-layout-mobile-footer-background: @color-layout-header-background;
// Task menu
@color-taskmenu-background: #2f3a3f;
@color-taskmenu-button: #fff;
@color-taskmenu-button-selected: @color-taskmenu-button;
@color-taskmenu-button-action: @color-main;
@color-taskmenu-button-special: @color-taskmenu-button;
@color-taskmenu-button-selected-background: lighten(@color-taskmenu-background, 10%);
@color-taskmenu-button-action-background: transparent;
@color-taskmenu-button-special-background: @color-taskmenu-background;
@color-taskmenu-button-hover: #fff;
@color-taskmenu-button-selected-hover: #fff;
@color-taskmenu-button-action-hover: @color-main;
@color-taskmenu-button-special-hover: lighten(@color-taskmenu-button-special, 10%);
@color-taskmenu-button-background-hover: lighten(@color-taskmenu-background, 10%);
@color-taskmenu-button-action-background-hover: @color-taskmenu-button-background-hover;
@color-taskmenu-button-special-background-hover:lighten(@color-taskmenu-button-special-background, 10%);
@color-taskmenu-button-logout-hover: @color-error;
// Toolbar
@color-toolbar-button: @color-font;
@color-toolbar-button-background-hover: darken(@color-layout-header-background, 3%);
@color-searchbar-icon-active: green;
@color-searchbar-background: #fbfbfb;
// Toolbar menu
@color-toolbarmenu-hover: #fff;
@color-toolbarmenu-hover-background: @color-main;
// Listings
@color-list: @color-font;
-@color-list-selected: inherit;
+@color-list-selected: @color-font;
@color-list-selected-background: tint(@color-main, 90%);
@color-list-flagged: @color-error;
@color-list-deleted: lighten(@color-black, 50%);
@color-list-secondary: @color-black-shade-text;
@color-list-droptarget-background: #ffffcc;
@color-list-focus-indicator: lighten(@color-main, 20%);
@color-list-border: @color-black-shade-bg;
@color-list-badge: #fff;
@color-list-badge-background: @color-main;
@color-list-recent: blue;
@color-list-recent-badge: #fff;
@color-list-recent-badge-background: @color-list-recent;
@color-list-pagenav: @color-black-shade-text;
@color-list-icon: fadeout(@color-list-secondary, 25%);
// Drag-n-drop layer
@color-drag-layer: #fff;
@color-drag-layer-background: @color-taskmenu-background;
@color-drag-layer-shadow: @color-black-shade-bg;
// Messages
@color-message: @color-font;
@color-message-border: rgba(0, 0, 0, 0.15);
@color-message-background: fadeout(@color-main, 95%);
@color-message-link: @color-main;
@color-message-information: @color-main;
@color-message-success: @color-success;
@color-message-warning: @color-warning;
@color-message-error: @color-error;
@color-message-loading: #333;
@color-message-shadow: @color-black-shade-bg;
@color-message-error-background: fadeout(@color-message-error, 85%);
@color-message-information-background: fadeout(@color-message-information, 85%);
@color-message-success-background: fadeout(@color-message-success, 85%);
@color-message-warning-background: fadeout(#ffff66, 75%);
// Popovers (menus)
@color-popover-header: @color-black-shade-text;
@color-popover-header-background: transparent;
@color-popover-shadow: @color-black-shade-bg;
@color-popover-separator: @color-black-shade-text;
@color-popover-separator-background: @color-black-shade-bg;
@color-popover-mobile-header: #fff;
@color-popover-mobile-header-background: @color-main-dark;
// Dialogs
@color-dialog-overlay-background: fade(@color-font, 50%);
@color-dialog-header: @color-layout-header;
@color-dialog-header-border: @color-border;
@color-spinner-circle: @color-black-shade-bg;
@color-spinner-item: @color-black-shade-text;
// Forms
@color-input: @color-font;
@color-input-border: rgba(22, 27, 29, 0.15);
@color-input-border-focus: @color-main;
@color-input-border-focus-shadow: fadeout(@color-main, 75);
@color-input-border-invalid: @color-error;
@color-input-border-invalid-shadow: fadeout(@color-error, 75);
@color-input-addon-background: @color-black-shade-bg;
@color-recipient-input-border: @color-input-border;
@color-recipient-input-background: @color-black-shade-bg;
@color-input-placeholder: #bbb;
@color-checkbox: @color-main;
@color-checkbox-checked: @color-main;
@color-checkbox-focus: darken(@color-checkbox, 30%);
@color-checkbox-checked-focus: darken(@color-checkbox-checked, 30%);
@color-form-hint: @color-black-shade-text;
@color-image-upload-background: @color-black-shade-bg;
@color-btn-secondary: #fff;
@color-btn-secondary-background: lighten(@color-black, 50%);
@color-btn-primary: #fff;
@color-btn-primary-background: @color-main;
@color-btn-danger: #fff;
@color-btn-danger-background: @color-error;
@color-quota-background: #fff;
@color-quota-text: @color-black-shade-text;
@color-quota-value: @color-main;
@color-quota-value-warning: @color-error;
@color-blockquote-background: fadeout(@color-black-shade-bg, 50%);
@color-blockquote-0: darken(@color-main, 30%);
@color-blockquote-1: darken(@color-success, 25%);
@color-blockquote-2: darken(@color-error, 20%);
@color-blockquote-0-border: @color-blockquote-0;
@color-blockquote-1-border: @color-blockquote-1;
@color-blockquote-2-border: @color-blockquote-2;
@color-mail-signature: @color-black-shade-text;
@color-mail-headers: @color-black-shade-text;
@color-spellcheck-link: @color-error;
@color-table-border: @color-layout-border;
@color-table-selected: @color-list-selected;
@color-table-selected-background: @color-list-selected-background;
// Datepicker
@color-datepicker-border: @color-layout-border;
@color-datepicker-font: @color-font;
@color-datepicker-highlight: !default;
@color-datepicker-highlight-background: lighten(@color-main, 30%);
@color-datepicker-active: #fff;
@color-datepicker-active-background: @color-main;
// Image tools
@color-image-tools: #fff;
@color-image-tools-background: fadeout(@color-main, 60%);
@color-image-tools-hover: fadeout(@color-main, 50%);
diff --git a/skins/elastic/styles/widgets/buttons.less b/skins/elastic/styles/widgets/buttons.less
index 683bab444..79248c13a 100644
--- a/skins/elastic/styles/widgets/buttons.less
+++ b/skins/elastic/styles/widgets/buttons.less
@@ -1,303 +1,309 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Buttons ***/
a.rcmaddcontact {
display: none;
}
.button.disabled {
opacity: .5;
}
a.button {
text-decoration: none;
&.active {
cursor: pointer;
}
}
/* font-icons */
a.button.icon,
button.btn {
&:before {
&:extend(.font-icon-class);
}
&.sidebar-menu:before,
&.toolbar-menu-button:before,
&.toolbar-list-button:before {
content: @fa-var-ellipsis-v;
width: 1em;
}
&.menu-button:before {
content: @fa-var-bars;
}
&.back-sidebar-button:before,
&.back-content-button:before,
&.back-list-button:before {
content: @fa-var-chevron-left;
}
&.refresh:before {
content: @fa-var-sync;
}
&.generate:before,
&.yes:before,
&.submit:before,
&.continue:before,
&.save:before {
content: @fa-var-check;
}
&.create:before {
content: @fa-var-plus-square;
}
&.edit:before {
content: @fa-var-pencil-alt;
}
&.qrcode:before {
content: @fa-var-qrcode;
}
&.search:before {
content: @fa-var-search;
}
&.filter:before {
content: @fa-var-filter;
font-size: 1.2em; // this icon is too-big in FA5
}
&.import:before {
content: @fa-var-upload;
}
&.export:before {
content: @fa-var-download;
}
&.discard:before,
&.delete:before {
.font-icon-regular(@fa-var-trash-alt);
}
&.next:before {
content: @fa-var-arrow-right;
}
&.restore:before {
content: @fa-var-undo;
}
&.send:before,
&.bounce:before {
content: @fa-var-paper-plane;
}
&.attach:before {
content: @fa-var-paperclip;
}
+ &.attach.vcard:before {
+ content: @fa-var-user;
+ }
&.no:before,
&.close:before,
&.cancel:before {
content: @fa-var-times;
}
&.mark:before {
.font-icon-regular(@fa-var-star);
}
&.back:before {
content: @fa-var-chevron-left;
}
&.remove:before {
content: @fa-var-times;
}
&.unlock:before {
content: @fa-var-unlock;
}
&.help:before {
.font-icon-regular(@fa-var-life-ring);
}
&.folders:before {
content: @fa-var-folder-open;
}
&.tools:before,
&.settings:before {
content: @fa-var-wrench;
}
&.properties:before {
content: @fa-var-info-circle;
}
&.select:before {
.font-icon-regular(@fa-var-check-circle);
}
+ &.insert.recipient:before {
+ content: @fa-var-user-plus;
+ }
}
a.btn,
button.btn {
// FIXME: Maybe button text (and icon) alignment requires some rework
padding-bottom: .5rem;
&:before {
display: inline !important;
float: none !important;
}
}
a.button.icon {
&.dropdown:before {
content: @fa-var-caret-down;
font-size: 1em;
}
& > span.inner {
display: none;
}
}
html.touch {
.btn:focus {
box-shadow: none;
}
}
@floating-action-button-size: 4rem;
.floating-action-buttons {
position: absolute;
right: 0;
bottom: 0;
.footer:not(:empty) + & {
bottom: @layout-footer-small-height;
}
a.button {
display: block;
float: left;
width: @floating-action-button-size;
height: @floating-action-button-size;
border-radius: 50%;
background: @color-main;
color: white;
opacity: .95;
box-shadow: 0 0 5px 5px lighten(@color-main, 35%);
margin: 0 1rem 1rem 0;
&:before {
&:extend(.font-icon-class);
content: @fa-var-plus;
width: @floating-action-button-size;
height: @floating-action-button-size;
line-height: @floating-action-button-size;
}
.inner {
display: none;
}
}
}
/*** Bootstrap button style overrides ***/
.btn-secondary {
color: @color-btn-secondary;
background: @color-btn-secondary-background;
border-color: @color-btn-secondary-background;
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-secondary-background, 50%);
}
&:hover {
background: darken(@color-btn-secondary-background, 5%);
border-color: darken(@color-btn-secondary-background, 7%);
}
&.disabled,
&:disabled {
background: lighten(@color-btn-secondary-background, 20%);
border-color: lighten(@color-btn-secondary-background, 20%);
opacity: 1;
}
&:not(:disabled):not(.disabled) {
&:active,
&.active {
background: darken(@color-btn-secondary-background, 10%);
border-color: darken(@color-btn-secondary-background, 12%);
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-secondary-background, 53%);
}
}
}
}
.btn-primary {
color: @color-btn-primary;
background: @color-btn-primary-background;
border-color: @color-btn-primary-background;
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-primary-background, 50%);
}
&:hover {
background: darken(@color-btn-primary-background, 5%);
border-color: darken(@color-btn-primary-background, 7%);
}
&.disabled,
&:disabled {
background: lighten(@color-btn-primary-background, 20%);
border-color: lighten(@color-btn-primary-background, 20%);
opacity: 1;
}
&:not(:disabled):not(.disabled) {
&:active,
&.active {
background: darken(@color-btn-primary-background, 10%);
border-color: darken(@color-btn-primary-background, 12%);
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-primary-background, 53%);
}
}
}
}
.btn-danger {
color: @color-btn-danger;
background: @color-btn-danger-background;
border-color: @color-btn-danger-background;
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-danger-background, 50%);
}
&:hover {
background: darken(@color-btn-danger-background, 5%);
border-color: darken(@color-btn-danger-background, 7%);
}
&.disabled,
&:disabled {
background: lighten(@color-btn-danger-background, 20%);
border-color: lighten(@color-btn-danger-background, 20%);
opacity: 1;
}
&:not(:disabled):not(.disabled) {
&:active,
&.active {
background: darken(@color-btn-danger-background, 10%);
border-color: darken(@color-btn-danger-background, 12%);
&:focus {
box-shadow: 0 0 0 .2rem fade(@color-btn-danger-background, 53%);
}
}
}
}
diff --git a/skins/elastic/styles/widgets/common.less b/skins/elastic/styles/widgets/common.less
index 7e7500054..2deb67672 100644
--- a/skins/elastic/styles/widgets/common.less
+++ b/skins/elastic/styles/widgets/common.less
@@ -1,535 +1,550 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Common UI elements ***/
.hidden,
.voice {
display: none !important;
}
font.bold {
font-weight: bold;
}
#rcmdraglayer {
min-width: 260px;
width: 260px;
background-color: @color-drag-layer-background;
color: @color-drag-layer;
box-shadow: 3px 3px 5px @color-drag-layer-shadow;
border-radius: .3rem;
z-index: 250;
opacity: .92;
padding: .5rem;
white-space: nowrap;
div {
line-height: 1.6em;
.overflow-ellipsis;
}
}
.frame-content {
padding: 1rem;
h2 {
font-weight: bold;
font-size: 1.5em;
}
h3 {
font-weight: bold;
font-size: 1.25em;
}
}
.listbox {
.scroller {
width: 100%;
overflow-x: hidden;
overflow-y: auto;
}
.navlist {
height: 0;
flex: initial !important;
.listing {
tr:last-child td,
li:last-child {
border-bottom: 0;
}
}
}
+
+ .popup & {
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+
+ .scroller {
+ flex: 1;
+ }
+
+ .footer {
+ border-top: 1px solid @color-layout-border;
+ background-color: @color-searchbar-background;
+ }
+ }
}
.contact-header {
display: flex;
margin-bottom: 1rem;
.contact-photo {
min-width: @layout-contact-icon-width;
}
.contact-head {
margin-left: 1rem;
legend {
display: none;
}
}
}
@image-attachment-size: 250px;
// this is when image thumbnails are enabled
p.image-attachment {
position: relative;
border: 1px solid @color-border;
border-radius: .3rem;
background-color: @color-message-background;
float: left;
margin: .5rem;
min-width: 47%;
min-height: @image-attachment-size;
overflow: hidden;
// use flexbox to center the image
display: flex;
justify-content: center;
@media screen and (max-width: @screen-width-xs) {
float: none;
margin: .5rem 0 .5rem 0;
}
.image-link {
align-self: center;
text-align: center;
margin: 1.6rem .5rem;
}
span {
color: @color-form-hint;
padding: 0 .5rem;
font-size: 90%;
white-space: nowrap;
position: absolute;
line-height: 1.5rem;
}
.image-filename {
.overflow-ellipsis;
left: 0;
top: 0;
right: 0;
padding-right: 4rem;
}
.image-filesize {
right: 0;
top: 0;
}
.attachment-links {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
text-align: center;
a {
text-decoration: none;
display: inline-block;
padding: 0 .5rem;
line-height: 1.5rem;
}
a:before {
&:extend(.font-icon-class);
display: inline-block;
}
a.open:before {
content: @fa-var-external-link-square-alt;
}
a.download:before {
content: @fa-var-download;
}
}
}
// this is when image thumbnails are disabled
fieldset.image-attachment {
margin-top: .5rem;
legend {
color: @color-form-hint;
font-size: .9rem;
border-top: 1px solid lighten(@color-mail-headers, 50%);
margin: 0;
}
img {
max-width: 100%;
}
}
#folder-selector {
overflow-y: auto;
}
.noselect {
user-select: none;
-moz-user-select: none;
-khtml-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.iframe-loader {
width: 100%;
position: absolute;
top: 0;
bottom: 0;
background-color: rgba(255, 255, 255, .95);
display: flex;
align-items: center;
justify-content: center;
z-index: 3;
.spinner {
position: relative;
display: inline-block;
width: 7rem;
height: 7rem;
overflow: hidden;
text-indent: -999em;
color: @color-spinner-circle;
border: 1rem solid;
border-color: currentColor @color-spinner-item currentColor currentColor;
border-radius: 50%;
-webkit-animation: fa-spin 1s infinite linear;
animation: fa-spin 1s infinite linear;
}
}
.footer.toolbar + .iframe-loader {
top: @layout-header-height;
bottom: @layout-header-height;
}
// iOS: Fix scrolling of iframe, display scrollbars on scrollable elements
.ios-scroll {
padding: 0;
-webkit-overflow-scrolling: touch !important;
overflow: scroll !important;
&.iframe-wrapper {
margin-top: 1px; // FIXME: without this scrolling hides the wrapper neighbours' border
}
}
.webkit-scroller {
&::-webkit-scrollbar {
-webkit-appearance: none;
}
&::-webkit-scrollbar:vertical {
width: .6rem;
}
&::-webkit-scrollbar:horizontal {
height: .6rem;
}
&::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, .4);
border-radius: .3rem;
border: 2px solid #fff;
}
}
.quota-widget {
width: 100%;
max-width: 15em;
padding: .5rem 1rem;
display: flex;
align-items: center;
color: @color-quota-text;
&:before {
&:extend(.font-icon-class);
content: @fa-var-hdd;
line-height: 1;
}
.count {
color: @color-quota-text;
font-size: 80%;
order: 2;
}
.bar {
flex: 1;
height: .5rem;
margin: 0 1rem;
background-color: @color-quota-background;
border: 1px solid @color-layout-border;
border-radius: .25rem;
overflow: hidden;
}
.value {
display: block;
background-color: @color-quota-value;
height: 1rem;
opacity: .75;
&.warning {
background-color: @color-quota-value-warning;
}
}
}
.quota-info {
width: 100%;
display: table !important;
td,th {
text-align: center;
white-space: nowrap;
}
th {
border-top: 0;
}
.root {
line-height: 1;
font-style: italic;
color: @color-popover-separator;
background-color: @color-popover-separator-background;
}
th:first-child,
.name {
text-align: left;
}
}
// Make Bootstrap tabs non-wrappable
.nav-tabs {
flex-wrap: nowrap;
.nav-item {
white-space: nowrap;
overflow: hidden;
}
.nav-link {
.overflow-ellipsis;
}
}
.props-table {
td.title {
width: 7em;
}
}
.table-widget {
display: flex;
flex-direction: column;
margin-bottom: .5rem;
border: 1px solid @color-table-border;
& > .content {
overflow-x: auto;
flex-grow: 1;
height: 18.5em;
table th {
border-top: 0;
}
}
& > .footer {
height: 3.5rem;
border-top: 1px solid @color-table-border;
text-align: left;
a.button {
padding: 0;
height: 3.5rem;
}
}
table {
margin: 0;
max-height: 18.5em;
}
// Options table is a table with first column for identifier/description
// and other columns for a state flag. E.g. ACL table
table.options-table {
td,th {
text-align: center;
vertical-align: middle;
&:first-child {
.overflow-ellipsis;
text-align: left;
}
}
tr:last-child td {
border-bottom: 1px solid @color-table-border;
}
tr.selected td {
background-color: @color-table-selected-background;
color: @color-table-selected;
outline: 0;
}
td:not(:first-child) span {
display: inline-block;
line-height: 1.25;
&:before {
&:extend(.font-icon-class);
}
}
td.enabled span:before {
content: @fa-var-check;
}
td.partial span:before {
opacity: .3;
content: @fa-var-check;
}
}
}
table.compact-table {
margin: 0;
*:not(.invalid-feedback) {
font-size: inherit;
}
td {
padding: .25rem;
border: 0;
}
td:first-child {
padding-left: 0;
}
td:last-child {
padding-right: 0;
}
}
table.table {
.checkbox-cell {
width: 3rem;
white-space: nowrap;
overflow: hidden;
text-align: center;
input.icon-checkbox + label {
padding: 0;
&:before {
line-height: 1;
height: 1em;
}
}
}
th.checkbox-cell {
padding: .75rem 0;
max-width: 1rem;
&:before {
&:extend(.font-icon-class);
cursor: pointer;
margin: 0 1rem;
line-height: 1;
}
&.subscription:before {
content: @fa-var-rss-square;
}
&.alarm:before {
.font-icon-regular(@fa-var-bell);
}
&.read:before {
content: @fa-var-eye;
}
&.write:before {
content: @fa-var-pencil-alt;
}
}
.buttons-cell {
width: 1%;
white-space: nowrap;
text-align: center;
a.button:before {
line-height: 1;
float: none;
display: inline-block;
}
@media screen and (min-width: @screen-width-xs) {
a.button .inner {
display: inline;
}
}
}
label {
margin: 0;
display: inline;
}
fieldset.tab-pane & thead th {
border: 0;
}
}
html.touch {
table.table {
th.checkbox-cell:before {
font-size: 1.5rem;
}
}
}
/* Bootstrap's .table style overwrites */
.table {
thead th {
border-width: 1px;
white-space: nowrap;
}
}
diff --git a/skins/elastic/styles/widgets/dialogs.less b/skins/elastic/styles/widgets/dialogs.less
index 834d89a84..c3f9badf8 100644
--- a/skins/elastic/styles/widgets/dialogs.less
+++ b/skins/elastic/styles/widgets/dialogs.less
@@ -1,244 +1,246 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Dialogs and popovers ***/
.popupmenu {
display: none;
padding: 0;
min-width: 180px;
+ height: 100%;
li > a {
width: 100%;
}
&.propform {
overflow: hidden;
margin-top: 1rem;
padding: .25rem; // so overflow do not truncate focus outline on inputs
}
&.simplelist {
min-width: 80px;
}
}
.popup.justified {
display: flex;
justify-content: space-around;
}
.popover-body {
padding: 0;
overflow-x: hidden;
& > .popupmenu {
display: block !important;
}
}
.popover {
box-shadow: 3px 3px 5px @color-popover-shadow;
border-color: @color-layout-border;
padding: 0;
.popover-header {
// On mobile don't display popup arrows and titles
display: none;
}
}
#rcmKSearchpane {
width: auto;
+ height: auto;
li {
padding-right: .5rem;
}
html.layout-small &,
html.layout-phone & {
bottom: auto;
border: 1px solid @color-input-border;
}
html.layout-phone & {
max-width: 100% !important;
}
}
html.layout-small,
html.layout-phone {
.popover:not(.select-menu) {
margin: 0 !important;
padding: 0;
right: 0;
left: initial !important;
bottom: 0;
top: 0;
width: @layout-mobile-menu-width;
transform: none !important;
border-radius: 0;
border: 0;
display: flex;
flex-direction: column;
box-shadow: none;
div.arrow {
display: none;
}
.listing li:last-child {
border-bottom: 1px solid @color-list-border;
}
}
.popover.menu {
left: 0 !important;
}
.popover-overlay {
z-index: 1000;
background-color: @color-dialog-overlay-background;
position: absolute;
top: 0;
bottom: 0;
width: 100%;
}
.popover-header {
display: block;
border-radius: 0;
border: 0;
padding: 0 .5em;
height: @layout-touch-header-height;
min-height: @layout-touch-header-height; // for when it's a flex item
line-height: @layout-touch-header-height;
font-size: @layout-touch-header-font-size;
color: @color-popover-mobile-header;
background-color: @color-popover-mobile-header-background;
&:before {
display: none; // hide the Bootstrap's popover arrow element
}
a {
display: inline-block;
width: 100%;
}
}
.popover-body > * {
max-height: 100% !important;
}
}
html.touch .popover {
.listing {
li a {
line-height: @layout-touch-menu-record-height;
font-size: @layout-touch-menu-record-font-size;
padding: 0 .5em;
&:before {
float: left; // overwrite icon float to have unified element height
}
}
}
}
.select-menu {
max-width: initial;
margin: 0;
.listing li a {
padding-left: .25rem;
outline: 0; // for Android browser
}
.popover-header {
border-radius: .25rem .25rem 0 0 !important;
}
}
/** PGP Key search/import dialog **/
.pgpkeyimport {
div.key {
position: relative;
padding: .5rem 0;
&.revoked,
&.disabled {
color: @color-list-secondary;
}
label {
display: inline-block;
margin-right: 0.5em;
margin-bottom: 0;
&:after {
content: ":";
}
&.keyid {
display: none;
}
}
label + a,
label + span {
line-height: 2.6rem;
margin-right: 1em;
white-space: nowrap;
text-decoration: none;
}
label.keyid + a {
font-weight: bold;
&:before {
&:extend(.font-icon-class);
content: @fa-var-key;
}
}
}
ul.uids {
margin: 0;
padding: 0;
}
li.uid {
border: 0;
padding: .25rem 0 0 1.5em;
line-height: 1.5rem !important;
list-style-type: none;
&:before {
&:extend(.font-icon-class);
content: @fa-var-user;
opacity: 0.25;
font-size: 1em;
line-height: 1.25;
}
}
button.importkey {
position: absolute;
top: .5rem;
right: 0;
}
button:disabled {
display: none;
}
}
diff --git a/skins/elastic/styles/widgets/editor.less b/skins/elastic/styles/widgets/editor.less
index d4ad6b96c..fdb68932c 100644
--- a/skins/elastic/styles/widgets/editor.less
+++ b/skins/elastic/styles/widgets/editor.less
@@ -1,940 +1,941 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Text Editor widget (and TinyMCE editor) ***/
.mce-tinymce {
&.mce-container.mce-panel {
border-radius: .25rem;
border-color: @color-input-border;
overflow: hidden;
}
.mce-btn,
.mce-panel {
background-color: @color-input-addon-background;
}
.mce-panel {
border-color: @color-input-border;
}
}
.mce-top-part::before,
.mce-tinymce,
.mce-window {
box-shadow: none !important;
}
.mce-btn {
&.mce-active {
background: @color-btn-secondary-background !important;
}
}
.mce-window {
&.mce-container {
border: 0;
& :not(.mce-ico) {
.font-family;
}
}
.mce-reset {
background: #fff;
}
.mce-container-body {
&.mce-abs-layout {
overflow: unset;
}
.mce-abs-end {
display: none;
}
}
.mce-window-head {
height: @layout-header-height;
border-bottom: 1px solid @color-dialog-header-border;
padding: 0;
.mce-title {
line-height: @layout-header-height;
font-size: 1.25rem;
padding: 0 3rem 0 1rem;
color: @color-dialog-header;
}
.mce-close {
border: 0;
color: @color-dialog-header;
background: transparent;
right: 0;
top: 0;
position: absolute;
height: (@layout-header-height - .7rem);
width: 1.25em;
margin: .25rem;
padding: .1rem .75rem;
cursor: pointer;
&:before {
&:extend(.font-icon-class);
content: @fa-var-times;
margin: 0;
}
i {
display: none;
}
}
}
.mce-foot {
border: 0;
height: @layout-header-height !important;
position: relative;
@media screen and (max-width: @screen-width-xs) {
border-top: 1px solid @color-dialog-header-border;
}
.mce-container-body {
height: 100% !important;
display: flex;
align-items: center;
justify-content: flex-end; // just 'end' does not work in Chrome
.mce-btn {
position: initial;
margin-right: .5rem;
line-height: 1;
width: auto !important;
height: auto !important;
&:last-child {
margin-right: 1rem;
}
.mce-txt {
line-height: 1.5;
vertical-align: unset;
}
button:before {
&:extend(.font-icon-class);
display: inline;
float: none;
}
}
.mce-abs-end {
position: initial;
width: 1rem;
order: 9;
}
}
.mce-btn {
.btn-secondary;
border-radius: .3rem;
border-color: transparent;
&:focus {
border-color: transparent !important;
color: @color-btn-secondary;
background: @color-btn-secondary-background;
}
&.mce-primary {
.btn-primary;
}
button:hover,
button {
color: @color-btn-secondary;
padding: .5rem .75rem;
}
}
.mce-btn:last-child button:before {
content: @fa-var-times;
}
.mce-btn.mce-primary button:before {
content: @fa-var-check;
}
.mce-search-foot {
div:nth-of-type(2) button:before {
content: @fa-var-search;
}
div:nth-of-type(3) button:before,
div:nth-of-type(4) button:before {
content: @fa-var-pencil-alt;
}
div:nth-of-type(6) button:before {
content: @fa-var-chevron-left;
}
div:nth-of-type(7) button:before {
content: @fa-var-chevron-right;
}
div:nth-of-type(7) button:after {
&:extend(.font-icon-class);
display: inline;
float: none;
margin: 0 0 0 .2rem;
content: @fa-var-chevron-right;
}
@media screen and (min-width: (@screen-width-xs + 1px)) {
div:nth-of-type(6) {
margin-left: .5rem;
}
div:nth-of-type(7) button {
&:before {
display: none;
}
}
}
}
}
// Form elements, let's keep'em in .mce-window to make overwriting simpler
.mce-formitem {
min-width: 450px;
position: unset !important;
& > .mce-container-body {
margin-bottom: .5rem;
& > * {
width: 75% !important;
position: unset !important;
}
& > label {
max-width: 25%;
min-width: 25%;
line-height: 2.5 !important;
}
}
.mce-widget {
border-radius: .25rem;
}
}
.mce-form {
padding: 1rem;
box-sizing: border-box;
.mce-form {
padding: 0;
position: unset !important;
width: 100% !important;
& > .mce-container-body {
flex-wrap: wrap;
height: auto !important;
}
.mce-formitem {
min-width: 100%;
width: 100% !important;
}
}
.mce-container {
height: auto !important;
.mce-container-body {
display: flex;
height: auto !important;
& > input:not([size="5"]) {
position: relative;
left: 0 !important;
flex: 1;
}
}
}
& > .mce-container-body {
box-sizing: border-box;
width: 100% !important;
}
.mce-form-split {
.mce-formitem {
min-width: auto;
& > .mce-container-body {
width: 100% !important;
}
}
}
label {
position: unset;
line-height: 2.5 !important;
height: auto !important;
}
}
.mce-colorpicker {
& + .mce-form {
width: 150px !important;
padding: 0;
.mce-formitem {
min-width: unset;
& + :not(.mce-formitem) {
height: 50px !important;
}
}
}
}
.mce-textbox {
padding: .375rem .75rem !important;
line-height: 1.5;
color: @color-font;
&:not(textarea) {
height: auto !important;
}
&:focus {
color: @color-font;
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow;
}
&[size="5"] {
width: auto !important;
}
&.mce-multiline {
line-height: 1.25;
width: 100% !important;
position: unset;
box-sizing: border-box;
display: block;
}
}
.mce-listbox {
button {
line-height: 1.5;
padding: .375rem .75rem;
}
&:focus {
border-color: @color-input-border-focus !important;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow !important;
}
}
.mce-checkbox {
line-height: 2.5;
i.mce-i-checkbox {
border: 0;
width: auto;
color: @color-checkbox;
text-indent: 0;
&:before {
&:extend(.font-icon-class);
margin: 0;
content: @fa-var-toggle-off;
}
}
&.mce-checked i.mce-i-checkbox:before {
content: @fa-var-toggle-on;
}
&:focus {
i.mce-i-checkbox {
border: 0;
color: @color-checkbox-focus;
}
}
}
.mce-combobox {
display: flex;
input {
border-radius: .3rem 0 0 .3rem;
flex: 1;
&:focus {
z-index: 1;
}
}
&.mce-combobox-fake {
input {
border-radius: .3rem;
}
}
button {
padding: .4rem .6rem;
}
.mce-btn {
border-radius: 0 .3rem .3rem 0;
background: @color-input-addon-background;
&:focus {
- border-color: @color-input-border-focus;
- box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow;
+ background-color: @color-input-border-focus-shadow;
+ border-color: #c5c5c5;
}
}
}
.mce-tabs {
padding-top: 1rem;
margin: 0 1rem;
border-color: @color-layout-border;
.mce-tab {
border-radius: .25rem .25rem 0 0;
padding: .5rem 1rem;
height: auto !important;
border: 1px solid transparent;
color: @color-link;
background: transparent;
margin-bottom: -1px;
&.mce-active {
border: 1px solid @color-layout-border;
border-bottom-color: #fff;
color: @color-font !important;
}
&:not(.mce-active):hover {
border: 1px solid @color-list-border;
border-bottom-color: transparent;
border-bottom: 0;
}
&:focus {
outline: unset !important;
}
}
}
.mce-label {
text-shadow: none;
color: @color-font;
}
}
// Menus and popovers, e.g. color selector, emoticons selector, font selector
.mce-menu,
.mce-floatpanel.mce-popover {
box-shadow: 3px 3px 5px @color-popover-shadow !important;
border-color: @color-layout-border !important;
border-radius: .3rem;
}
.mce-menu {
.mce-menu-item.mce-active {
color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background;
}
.popover-header {
display: none !important;
}
}
div.mce-menubtn.mce-opened {
z-index: 65530 !important; // BUG: https://github.com/tinymce/tinymce/issues/4542
}
#mce-modal-block.mce-in {
background-color: @color-dialog-overlay-background;
opacity: 1 !important;
}
@media screen and (max-width: @screen-width-xs) {
.mce-window {
width: 100% !important;
height: 100% !important;
left: 0 !important;
top: 0 !important;
border-width: 0 !important;
& > .mce-reset {
display: flex;
flex-direction: column;
height: 100%;
}
.mce-window-body {
flex: 1;
overflow-y: auto !important;
}
& > .mce-reset > div,
.mce-container-body {
width: 100% !important;
}
.mce-window-head {
background-color: @color-layout-mobile-header-background;
.mce-title {
font-size: 1rem;
text-align: center;
padding: 0 1rem;
}
.mce-close {
display: none;
}
}
.mce-foot {
background-color: @color-layout-mobile-footer-background;
.mce-container-body {
justify-content: space-evenly;
.mce-btn {
position: initial;
height: 100% !important;
margin: 0;
background: transparent;
border-width: 0;
&:focus {
box-shadow: none;
}
&:hover {
background: transparent;
}
&:last-child {
margin: 0;
}
button {
color: @color-font;
padding: .45rem;
font-size: .9rem;
&:before {
display: block;
float: none;
width: 100%;
margin: 0;
line-height: 1.75;
height: 1.75rem;
}
}
}
.mce-abs-end {
display: none;
}
}
.mce-search-foot {
div:nth-of-type(7) button:after {
display: none;
}
}
}
.mce-form,
.mce-form + .mce-container, // for Embed tab in Media dialog
.mce-formitem,
.mce-combobox,
.mce-panel:not(.mce-popover) {
width: 100% !important;
}
.mce-formitem {
min-width: unset;
}
.mce-form {
& > .mce-container-body {
display: flex;
flex-direction: column;
left: 0;
right: 0;
box-sizing: border-box;
}
.mce-container-body {
height: auto !important;
flex-direction: column;
& > label {
position: unset !important;
width: 100% !important;
max-width: 100%;
}
& > label + * {
position: unset !important;
width: auto !important;
}
& > .mce-checkbox {
position: absolute;
left: 0 !important;
top: 3rem !important;
}
}
}
// FIXME: How to fix the input width in less hacky way?
.mce-combobox input {
max-width: ~"calc(100% - 4rem)";
}
.mce-combobox-fake input {
max-width: ~"calc(100% - 1.7rem)";
}
}
.mce-menu {
width: @layout-mobile-menu-width !important;
right: 0;
top: 0 !important;
left: auto !important;
height: 100% !important;
max-height: unset !important;
padding: 0 !important;
margin: 0 !important;
border-radius: 0;
border: 0 !important;
.popover-header {
display: block !important;
a {
font-size: 1.2rem;
line-height: @layout-touch-header-height;
&:before {
content: @fa-var-times;
}
}
}
.mce-container-body {
width: 100% !important;
}
.mce-menu-item {
height: @layout-touch-menu-record-height;
line-height: @layout-touch-menu-record-height;
padding: 0 .5rem;
border-left: 0;
border-bottom: 1px solid @color-list-border;
margin: 0;
.mce-ico {
font-size: 150%;
padding: 0 .7rem 0 .25rem;
margin-top: -.2rem;
}
.mce-text {
font-size: 1.2rem;
.font-family;
line-height: @layout-touch-menu-record-height;
color: @color-font;
}
.mce-caret {
display: none;
}
&.mce-menu-item-preview {
&.mce-active {
border-left: none;
position: relative;
&:after {
.font-icon-class; // :extend() does not work here
content: @fa-var-check;
position: absolute;
right: .5rem;
top: 0;
color: @color-font;
}
}
}
&.mce-menu-item-expand {
position: relative;
&:after {
.font-icon-class; // :extend() does not work here
content: @fa-var-angle-right;
position: absolute;
right: .5rem;
top: 0;
color: @color-font;
}
}
}
}
.mce-menu-item-sep,
.mce-menu-shortcut {
display: none !important;
}
.mce-charmap-dialog {
position: unset !important;
+ .mce-container {
display: none;
}
}
.mce-charmap {
display: block;
tbody {
display: block;
}
tr {
display: flex;
flex-wrap: wrap;
}
td {
flex: 1;
height: auto !important;
min-width: 17%;
padding: 0 !important;
border: 0 !important;
border-bottom: 1px solid @color-list-border !important;
div {
font-size: 3rem;
line-height: 2;
}
}
}
}
html.touch .mce-grid td {
padding: .5rem;
}
/*** Media file selector for TinyMCE ***/
.image-selector {
margin: 1rem 1rem 1rem 1rem !important;
padding: 1rem .5rem 10rem .5rem !important;
&.droptarget {
border: .2rem dashed @color-table-border;
&:after {
margin-top: 2rem;
}
}
button {
.btn-secondary;
padding: .5rem .75rem;
line-height: 1.5;
position: relative;
&:before {
line-height: 1;
}
}
form {
position: absolute;
top: 0;
}
.attachmentslist {
margin-left: 0;
overflow-x: hidden;
overflow-y: auto;
height: 19.1em;
li {
padding: .25rem;
cursor: pointer;
&:before {
display: none;
}
&:hover,
&:focus {
background: @color-list-selected-background;
}
span.name {
flex: 1;
margin: auto;
padding-left: 1rem;
.overflow-ellipsis;
}
span.img {
height: 80px;
width: 80px;
display: flex;
border: 1px solid @color-list-border;
background: white;
border-radius: .75rem;
overflow: hidden;
}
img {
margin: auto;
}
}
html.layout-phone & {
height: auto;
}
}
}
/*** HTML editor widget ***/
.html-editor {
position: relative;
display: flex;
margin-bottom: .25rem;
& > .nav {
border-color: transparent;
z-index: 1;
position: absolute;
- top: 1px;
right: 1rem;
a.active {
border-color: @color-input-border !important;
&.mode-html {
background-color: @color-input-addon-background;
border-bottom-color: @color-input-addon-background !important;
}
&.mode-plain {
border-bottom-color: #fff !important;
}
}
- a:hover {
+ a:hover,
+ a:focus {
border-bottom-color: transparent;
+ outline: 0;
}
}
& > iframe, // e.g. mailvelope frame
& > .googie_edit_layer,
& > .mce-tinymce,
& > textarea {
margin-top: 2.55rem;
font-family: monospace;
width: 100% !important;
}
& > iframe { // e.g. mailvelope frame
border-radius: .3rem;
border: 1px solid @color-input-border;
min-height: 30em;
}
#composebody_ifr {
min-height: 30em;
}
& > .mce-tinymce.focused {
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow !important;
}
}
/*** GoogieSpell widget ***/
.googie_window {
width: 16rem;
}
.googie_edit_layer {
font-family: monospace;
// from Bootstrap's textarea
padding: .5rem .75rem;
border: 1px solid @color-input-border;
border-radius: .3rem;
line-height: 1.25;
}
.googie_link {
color: @color-spellcheck-link;
text-decoration: underline;
cursor: pointer;
}
.googie_list {
td {
min-width: 8rem;
width: auto;
&.googie_list_onhover {
color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background;
}
.googie_list_revert:before {
&:extend(.font-icon-class);
content: @fa-var-plus-square;
}
.googie_add_to_dict:before {
&:extend(.font-icon-class);
content: @fa-var-plus-square;
}
}
input {
display: inline-block;
margin: .5rem .5rem .5rem 0 !important;
padding: .5rem .75rem !important;
}
}
diff --git a/skins/elastic/styles/widgets/forms.less b/skins/elastic/styles/widgets/forms.less
index 7054b919d..a906944fb 100644
--- a/skins/elastic/styles/widgets/forms.less
+++ b/skins/elastic/styles/widgets/forms.less
@@ -1,1318 +1,1332 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Common form elements ***/
#uploadform {
display: none;
}
form.smart-upload,
input.smart-upload {
visibility: hidden;
width: 1px;
height: 1px;
opacity: 0;
}
.propform {
// TODO: do we need this?
&:not(.popupmenu) {
width: 100%;
}
// This is the way we can have multiple checkboxes in a single form field
.form-check td:not(.title) > label {
display: block;
margin: 0;
line-height: 2rem;
label {
margin-right: .5rem;
}
}
td.datetime {
display: flex;
input:first-child {
margin-right: .5rem;
}
input:last-child {
width: 75%;
}
}
td.rowbuttons {
width: 1%;
white-space: nowrap;
span {
display: none;
}
a {
padding: 0;
line-height: 2.3rem;
height: 2.3rem;
font-size: 1rem;
&:before {
&:extend(.font-icon-class);
content: @fa-var-plus;
@media screen and (min-width: (@screen-width-bs-phone + 1px)) {
margin: 0;
}
}
&.delete:before {
content: @fa-var-trash-alt;
}
&.advanced:before {
content: @fa-var-cog;
}
&:not(:last-child) {
margin-right: .25rem;
}
}
}
td.rowactions {
width: 1%;
.form-control {
width: auto;
}
}
td.rowtargets {
.composite {
input, textarea, select, .multi-input, .input-group {
margin-bottom: .5rem;
}
.input-group {
input, textarea, select, .multi-input {
margin-bottom: 0;
}
}
br {
display: block;
}
}
.input-group {
margin-bottom: .25rem;
*:first-child.input-group-prepend {
text-align: left;
min-width: 7.5em;
& > * {
width: 100%;
}
}
}
& > .advanced {
margin-top: .25rem;
}
}
td.title {
padding-top: 0;
padding-bottom: 0;
}
td > .flexbox {
display: flex;
& > .multi-input {
width: 100%;
margin-left: .25rem;
}
}
.rulerow {
margin-bottom: .5rem;
}
&.groupped {
&.readonly {
legend {
margin: 0;
}
.row.input-group {
margin-bottom: 0 !important;
}
label {
min-width: 7rem;
// Overwrite Bootstrap .input-group-* style to make the label transparent
background-color: transparent;
border: 0;
border-radius: 0;
}
}
.row.input-group {
margin-bottom: .5rem;
flex-wrap: nowrap;
& > *:first-child {
.overflow-ellipsis;
min-width: 8rem;
height: auto;
&:not(select) {
padding: 0;
}
@media screen and (max-width: @screen-width-xs) {
min-width: 6rem;
width: 6rem;
flex-grow: unset;
}
label {
width: 100%;
}
}
& > *:nth-child(2) {
flex-grow: 30;
}
&:last-child {
margin-bottom: 1rem;
}
select {
text-align: left;
height: auto;
}
.content {
padding: 0;
display: flex;
flex-wrap: wrap;
border-radius: 0;
input {
border-radius: 0;
border-color: transparent;
}
.ff_street {
width: 100%;
}
.ff_locality {
width: 75%;
}
.ff_zipcode {
width: 25%;
}
.ff_country, .ff_region {
width: 50%;
}
}
}
.form-control-plaintext {
flex-grow: 1;
border: 0;
}
}
.addfield {
margin: 0;
select {
width: 8rem;
margin-top: .5rem;
}
}
.form-text {
font-size: 90%;
color: @color-form-hint;
}
// Some dialogs may use simple one-row forms like this
&.row.form-group {
margin-left: 0;
margin-right: 0;
label, div {
padding-left: 0;
padding-right: 0;
}
}
// Some forms may use multiple elements that are not part of .input-group
// add proper spacing
select + select,
select + .input-group {
padding-top: .5rem;
}
&.text-only {
margin-bottom: .25rem;
tr {
margin: 0;
}
label {
padding-bottom: 0 !important;
}
@media screen and (max-width: @screen-width-bs-phone) {
tr {
display: table-row;
}
td {
width: auto;
&:first-child {
width: 33%;
}
}
:not(tr).form-group.row {
.col-form-label {
width: 33%;
}
& > :last-child {
width: 67%;
}
}
}
}
}
@media screen and (max-width: @screen-width-bs-phone) {
.propform {
table.compact-table {
overflow-x: initial;
tbody {
display: block;
}
.rowactions > select,
.flexbox > select {
width: 100%;
}
tr {
display: flex;
flex-direction: column;
td {
width: 100%;
padding-right: 0;
padding-left: 0;
&.rowbuttons {
text-align: right;
a {
margin-right: .5rem;
& > span {
display: inline;
}
}
}
}
}
}
}
}
.propform,
.formcontent {
fieldset:not(.tab-pane):nth-of-type(n+2) {
margin-top: 1em;
}
legend {
font-weight: bold;
font-size: 1.2em;
}
label {
-webkit-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
overflow: hidden;
margin-bottom: 0;
}
}
@media screen and (max-width: @screen-width-bs-phone) {
.formcontent .text-only {
.form-group:not(tr) {
margin-bottom: .25rem;
.col-form-label {
width: 33%;
& + span {
width: 67%;
}
}
}
}
html.iframe .formcontent > .propform {
padding: .25rem;
}
}
// Forms fixes for IE and Edge
html.ms .propform {
.row:not(.form-check) > td {
display: flex;
}
@media screen and (min-width: @screen-width-bs-phone) {
.row.form-check > td {
display: flex;
}
}
}
.formcontainer {
display: flex;
flex-direction: column;
justify-content:flex-start;
overflow-y: hidden !important;
.formcontent {
overflow-x: hidden;
overflow-y: auto;
}
.formbuttons {
padding: 0.5rem 1rem;
button {
margin-right: .5rem;
}
}
// Prevent button truncation on tablets
html.iframe.ipad &,
html.iframe.webkit.tablet & {
.formbuttons {
min-height: 4rem;
}
}
// We don't need buttons element on small devices
html.layout-small &,
html.layout-phone & {
.formbuttons {
display: none;
}
}
}
.formcontent {
&:not(.popupmenu) {
padding: 1rem;
}
.row {
margin-right: 0; // without these the form is too wide causing horizontal scrollbar appearence
margin-left: 0;
// Note: We never use odd numbers here
.col, .col-2, .col-4, .col-6, .col-8, .col-10, .col-12,
.col-sm, .col-sm-2, .col-sm-4, .col-sm-6, .col-sm-8, .col-sm-10, .col-sm-12 {
// overwrite Bootstrap's redundant padding
padding: 0;
}
.form-control-plaintext,
label.col-form-label {
padding: .375rem .375rem .375rem 0;
}
.form-control-plaintext {
padding-bottom: 0;
border: 0;
}
@media screen and (max-width: @screen-width-bs-phone) {
&.form-group {
& > td label {
padding-bottom: 0;
}
}
}
}
.row.form-check {
padding: 0; // without these e.g. inputs in compose screen are not aligned properly
display: flex; // https://github.com/twbs/bootstrap/issues/22348
flex-wrap: nowrap;
// fixed checkbox vertical alignment
td:not(.col-form-label) {
padding: 0;
}
// alignment fixes needed because we do not stick precissely to Bootstrap's form structure
@media screen and (max-width: @screen-width-bs-phone) {
.col-6 {
max-width: 100%;
flex: auto;
}
& > *:last-child {
width: 1%;
min-width: 2.2em; // for icon-checkbox
padding-right: 15px;
}
&.with-link > *:last-child {
min-width: 8rem;
}
}
.form-check-input {
margin: 0; // fixes checkbox alignment with other inputs in a form
margin-top: .5rem;
margin-bottom: .5rem;
& + label {
display: inline;
}
}
td > label {
padding-bottom: 0;
}
}
.nav-tabs {
margin-bottom: 1rem;
&:empty {
display: none;
}
}
.hint {
font-style: italic;
color: @color-form-hint;
}
// RAW (CodeMirror) editor
&.raweditor {
height: 100%;
form {
height: 100%;
}
textarea {
font-family: monospace;
height: 100%;
}
.CodeMirror {
border: 1px solid @color-input-border;
border-radius: .3rem;
height: 100%;
color: @color-font;
}
.CodeMirror-focused {
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow;
}
.CodeMirror-linebackground.line-error {
background-color: @color-error;
opacity: 0.4;
}
.errorGutter {
width: .8em;
}
}
}
@media screen and (max-width: @screen-width-mini) {
.formcontent {
.col-form-label {
flex: auto;
max-width: 100%;
}
.col-6, .col-8, .col-10 {
flex: auto;
max-width: 100%;
}
}
}
/* Some common icons for "iconized inputs" */
.input-group .icon {
text-decoration: none;
padding: .375rem .5rem;
&.input-group-text {
min-width: 2.4rem;
}
&:before {
&:extend(.font-icon-class);
margin: 0 !important;
line-height: 1;
font-size: 1.1em;
}
&.user:before {
content: @fa-var-user;
}
&.pass:before {
content: @fa-var-lock;
}
&.host:before {
content: @fa-var-home;
}
&.cancel:before {
content: @fa-var-times;
}
&.delete:before {
content: @fa-var-trash-alt;
}
&.edit:before {
content: @fa-var-pencil-alt;
}
&.add:before {
content: @fa-var-plus;
}
+ &.add.recipient:before {
+ content: @fa-var-users;
+ }
&.search:before {
content: @fa-var-search;
}
&.filter:before {
content: @fa-var-filter;
}
&.key:before {
content: @fa-var-key;
}
.inner {
display: none;
}
}
+.input-group a {
+ &:focus {
+ background-color: @color-input-border-focus-shadow;
+ outline: 0;
+ }
+}
@proplist-record-height: 2rem;
.proplist {
margin-bottom: 0;
padding: 0;
li {
list-style-type: none;
line-height: @proplist-record-height;
margin-bottom: .25rem;
display: flex;
&:last-child {
margin-bottom: 0;
}
input[type=radio] {
vertical-align: middle;
margin-right: .5em;
& + .input-group {
flex-grow: 1;
}
}
label:not(.input-group-text) {
margin: 0;
line-height: @proplist-record-height;
}
.icon-checkbox {
height: @proplist-record-height;
margin-left: -1.2em;
& + label {
height: @proplist-record-height;
margin-right: .5rem;
padding: 0 !important;
line-height: 1.25;
html.touch &:before {
line-height: 1.1;
}
}
}
select {
width: auto;
display: inline;
}
}
}
.checklist {
.form-check-input + label {
margin-right: .25rem;
line-height: 1;
}
& > label {
display: inline-block !important;
}
}
/*** Forms in popups ***/
.popup form.propform {
padding: .5rem;
overflow-x: hidden;
}
.popupmenu.form {
&.nolist {
padding: 0 .5rem;
}
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
li:not(.separator) {
padding: 0 1rem;
label {
margin: 0;
line-height: @listing-line-height;
input {
margin-right: .5rem;
}
}
}
input {
vertical-align: middle;
}
select {
margin: .5rem 0;
}
.buttons {
text-align: center;
padding: .5rem;
}
}
html.touch .popupmenu.form {
li {
input.icon-checkbox + label:before {
line-height: 1.4;
}
}
}
/*** Smart list (multiple input) field ***/
.multi-input {
& > .content {
max-height: 11.65em;
overflow: hidden;
overflow-y: auto;
border-radius: .25rem;
border: 1px solid @color-input-border;
&.focused {
.style-input-focus;
}
// TODO: style button focus
}
a.icon {
&.reset:before {
&:extend(.font-icon-class);
content: @fa-var-trash-alt;
}
}
input.form-control {
padding-left: .75rem;
}
input,
input:focus,
.input-group-text {
border-radius: 0;
border: 0;
border-bottom: 1px solid @color-input-border;
box-shadow: none;
}
.input-group-text {
border-left: 1px solid @color-input-border;
}
.input-group-append {
margin-left: 0;
}
.input-group {
margin: 0 !important;
flex-wrap: nowrap; // Bootstrap makes them wrappable (imho)
&:last-child * {
border-bottom: 0;
}
}
& + .btn {
margin-top: .5rem;
}
&.is-invalid {
& > .content {
border: 1px solid @color-input-border-invalid;
&.focused {
border-color: @color-input-border-invalid;
box-shadow: 0 0 0 .2rem @color-input-border-invalid-shadow;
}
}
& > .invalid-feedback {
display: inline-block;
}
}
}
/*** Files upload widget with list of files, upload form and drop area ***/
.file-upload {
padding: 1rem 1rem 12rem;
margin: 0 1rem .25rem 1rem;
border-radius: .5rem;
border: .2rem dashed @color-table-border;
fieldset & {
margin: 0;
}
.upload-form {
text-align: center;
padding-bottom: 1em;
a.btn,
button {
margin-bottom: .25rem;
}
}
.hint {
margin-bottom: .5rem;
color: @color-form-hint;
text-align: center;
}
.attachmentslist {
li {
position: relative;
display: flex;
padding-right: 1.5em;
a.filename {
flex: 1;
}
a.delete,
a.cancelupload {
position: absolute;
right: 0;
width: auto; // fix button width if the widget is in a .popupmenu
&:before {
margin: 0;
}
}
.inner {
display: none;
}
}
}
&.droptarget {
padding-bottom: .5rem !important;
&:after {
content: @icon-file-drop;
width: 10rem;
margin: 5rem auto 0 auto;
display: block;
}
&.active {
border-color: darken(@color-toolbar-button-background-hover, 20%);
}
&.hover {
border-color: darken(@color-toolbar-button-background-hover, 20%);
background-color: @color-toolbar-button-background-hover;
}
}
}
/*** Smart recipient input field ***/
@recipient-input-margin-fix: .25rem;
.recipient-input {
display: flex;
flex-wrap: wrap;
padding: 0 .75rem @recipient-input-margin-fix .75rem;
list-style-type: none;
cursor: text;
&.focus {
.style-input-focus;
}
.recipient {
display: flex;
position: relative;
max-width: 50%;
border: 1px solid @color-recipient-input-border;
background-color: @color-recipient-input-background;
border-radius: .25em;
padding: 0 .25em;
margin-top: @recipient-input-margin-fix;
margin-right: .2em;
white-space: nowrap;
cursor: default;
@media screen and (max-width: 450px) {
width: 100%;
max-width: 100%;
}
}
.name {
.overflow-ellipsis;
flex-grow: 1;
display: inline-block;
line-height: 1.1;
padding: .25rem;
vertical-align: middle;
}
.email {
text-indent: -5000rem;
display: inline-block;
width: 0;
}
.quotes {
position: absolute;
width: 0;
opacity: 0;
}
a.button.icon {
font-size: .8em;
cursor: pointer;
padding: 0 0 0 .25em;
&:before {
float: none;
display: inline-block;
width: 1em;
margin: 0;
line-height: 1.5;
}
}
+ li {
+ max-width: 100%;
+ }
+
input {
- width: 15px;
+ width: 40px;
+ max-width: 100%;
background: transparent !important;
border: 0 !important;
margin-top: @recipient-input-margin-fix;
outline: 0;
line-height: 1.5;
}
}
/*** Tagedit widget (from jqueryui plugin) ***/
.tagedit-list {
display: flex;
flex-wrap: wrap;
padding: 0 .75rem @recipient-input-margin-fix .75rem;
margin: 0;
list-style-type: none;
min-height: 2.3rem;
& + .placeholder {
display: none;
}
&[tabindex="-1"] {
.style-input-focus;
}
li.tagedit-listelement-new {
margin-top: @recipient-input-margin-fix;
input {
width: 15px;
background: transparent !important;
border: 0;
outline: 0;
margin: 0;
padding: 0;
line-height: 1.5;
&.tagedit-input-disabled {
visibility: hidden;
}
}
}
li.tagedit-listelement-old {
max-width: 50%;
border: 1px solid @color-recipient-input-border;
background-color: @color-recipient-input-background;
border-radius: .25em;
margin-top: @recipient-input-margin-fix;
margin-right: .2em;
white-space: nowrap;
a /* TODO: .tagedit-close, .tagedit-break, .tagedit-delete, .tagedit-save */ {
font-size: .8em;
cursor: pointer;
display: inline-block;
width: 1.1em;
overflow: hidden;
vertical-align: middle;
margin-right: .2rem;
&:before {
&:extend(.font-icon-class);
content: @fa-var-times;
width: 1em;
line-height: 1.2;
}
}
span {
.overflow-ellipsis;
flex-grow: 1;
display: inline-block;
line-height: 1.4;
padding: 0 .25rem;
vertical-align: middle;
}
}
li.tagedit-listelement-focus {
// TODO
}
}
/*** Skin selection widget ***/
.skinselection {
white-space: nowrap;
display: table-row;
& > span {
display: table-cell;
vertical-align: middle;
padding: .1em .5em;
white-space: normal;
&:last-child {
padding-right: 0;
}
}
.skinitem input {
width: auto;
}
.skinname {
font-weight: bold;
}
.skinlicense,
.skinlicense a {
font-style: italic;
text-decoration: none;
}
.skinlicense a:hover {
text-decoration: underline;
}
.skinlicense,
.skinauthor {
font-size: 90%;
}
.skinthumbnail {
width: 64px;
height: 64px;
border: 1px solid @color-input-border;
background: #fff;
border-radius: 4px;
}
}
/*** Percent input with jQuery-UI slider ***/
// Structure: <input><span.label><div.ui-slider>
.input-percent-slider {
display: flex;
align-items: center;
input {
max-width: 4em;
}
span.label {
line-height: 2.4;
padding: 0 .5rem 0 .25rem;
}
div.ui-slider {
flex: 1;
margin: 0 .5em;
}
}
/*** Image upload widget ***/
.image-upload {
position: relative;
overflow: hidden;
cursor: pointer;
background-color: @color-image-upload-background;
a.button {
display: none;
position: absolute;
left: 0;
top: 0;
background-color: rgba(255, 255, 255, .85);
border-radius: 5px;
width: 2.5em;
padding: .5em;
margin: .5em;
line-height: 1;
}
&.changed a.button {
display: inline;
}
}
/*** Pretty icon checkbox ***/
input.icon-checkbox {
opacity: 0;
margin-left: -1.5em;
cursor: pointer;
.form-check > * > & {
margin-left: -1.5em !important;
}
& + label {
margin: 0;
display: inline;
font-size: 1.2rem;
cursor: pointer;
width: 1.5em;
html.touch & {
font-size: 1.5rem;
}
&:before {
&:extend(.font-icon-class);
content: @fa-var-toggle-off;
margin: 0;
color: @color-checkbox;
height: 2rem;
}
// A link after checkbox in a form, used in e.g. newmail_notifier plugin
& + a {
display: inline-block;
line-height: 2rem;
padding-left: .5rem;
white-space: nowrap;
}
.input-group & {
padding: 0 .25rem 0 0;
&:before {
line-height: 1;
height: auto;
}
}
}
&:disabled + label {
opacity: 0.4;
pointer-events: none;
}
&:checked + label:before {
content: @fa-var-toggle-on;
color: @color-checkbox-checked;
}
&:focus + label:before {
color: @color-checkbox-focus;
}
&:checked:focus + label:before {
color: @color-checkbox-checked-focus;
}
&.form-check-input + label {
font-size: 1.25rem;
padding-top: .25rem;
}
}
.input-group-combo {
select:first-of-type {
&.alone {
border-radius: .25rem !important;
}
&:not(.alone) {
flex: unset;
width: auto;
}
}
.input-group {
padding: 0 !important;
flex: 2;
}
select + select,
.input-group :first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
/*** General browser hacks ***/
// Remove outline on selects in Firefox
@-moz-document url-prefix() {
select:-moz-focusring {
color: transparent !important;
text-shadow: 0 0 0 @color-font !important;
}
}
/*** Bootstrap style overrides ***/
// Fixes height of some text inputs
// FIXME: Really? See managesieve's vacation form for all of these
td.input-group .input-group-append,
td.datetime input,
td.input-group input {
height: ~"calc(2.25rem + 2px)";
}
.form-control {
color: @color-font;
&:focus {
color: @color-font;
border-color: @color-input-border-focus;
box-shadow: 0 0 0 .2rem @color-input-border-focus-shadow;
}
&.is-invalid {
border-color: @color-input-border-invalid;
&:focus {
border-color: @color-input-border-invalid;
box-shadow: 0 0 0 .2rem @color-input-border-invalid-shadow;
}
}
&::placeholder {
color: @color-input-placeholder;
}
}
.invalid-feedback {
color: @color-error;
}
.form-group {
margin-bottom: .5rem;
}
.input-group-text {
color: @color-input;
background-color: @color-input-addon-background;
input:focus {
z-index: 1;
border-color: @color-input-border-focus !important;
}
}
diff --git a/skins/elastic/styles/widgets/mail.less b/skins/elastic/styles/widgets/mail.less
index 13d9e10c4..d5674d721 100644
--- a/skins/elastic/styles/widgets/mail.less
+++ b/skins/elastic/styles/widgets/mail.less
@@ -1,312 +1,314 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Mail message body elements ***/
@mail-header-photo-height: 4rem;
#message-header {
margin-bottom: 1rem;
.subject {
display: flex;
font-size: 1.5rem;
font-weight: bold;
white-space: nowrap;
body.status-flagged &:before {
&:extend(.font-icon-class);
display: inline;
float: none;
content: @fa-var-flag;
font-size: 1em;
color: @color-error;
}
a.extwin {
margin-left: .25rem;
&:before {
&:extend(.font-icon-class);
font-size: 75%;
line-height: 1.5;
margin: 0;
content: @fa-var-external-link-square-alt;
}
}
& > span {
.overflow-ellipsis;
}
span.inner {
display: none;
}
}
.short-header {
display: flex;
img.contactphoto {
margin: 0 1rem 0 0;
border-radius: 50%;
width: @mail-header-photo-height;
height: @mail-header-photo-height;
}
div.header-content {
min-height: @mail-header-photo-height;
flex: 1;
}
div.header-subject {
line-height: @mail-header-photo-height/2;
& > span {
line-height: 1.5;
display: inline-block;
vertical-align: middle;
}
}
div.header-links {
a {
font-size: 90%;
margin-right: .5rem;
text-decoration: none;
white-space: nowrap;
display: inline-block;
&:before {
&:extend(.font-icon-class);
height: 1.5rem;
line-height: 1.3;
}
&.envelope:before {
content: @fa-var-envelope;
}
&.html:before {
content: @fa-var-image;
}
&.plain:before {
content: @fa-var-align-justify;
}
&.zipdownload:before {
content: @fa-var-download;
}
}
}
.message-partheaders {
margin: 0 !important;
padding: .25rem 0 !important;
}
}
a.extwin,
a.headers {
text-decoration: none;
}
}
#message-content {
.attachmentslist:not(:empty) {
margin-bottom: 1rem;
}
}
#message-objects + .ui.alert {
float: left;
}
#messagebody {
- // TODO
-
&.mailvelope {
margin: 0;
}
}
.message-part,
.message-htmlpart {
padding-top: .5rem;
&:not(:first-child) {
border-top: 1px solid lighten(@color-mail-headers, 50%);
margin-top: .5rem;
}
div.rcmBody {
// Remove margins that can be set by the mail message styles
margin: 0 !important;
}
blockquote {
.overflow-ellipsis;
color: @color-blockquote-0;
border-left: 2px solid @color-blockquote-0-border;
border-right: 2px solid @color-blockquote-0-border;
background-color: @color-blockquote-background;
margin: 2px 0;
padding: 0 .4em;
blockquote {
color: @color-blockquote-1;
border-left: 2px solid @color-blockquote-1-border;
border-right: 2px solid @color-blockquote-1-border;
blockquote {
color: @color-blockquote-2;
border-left: 2px solid @color-blockquote-2-border;
border-right: 2px solid @color-blockquote-2-border;
}
}
span.blockquote-link {
top: 0;
cursor: pointer;
right: .5rem;
min-width: 4rem;
padding: .2rem .25rem .2rem .5rem;
font-size: 90%;
text-align: center;
color: @color-black-shade-text;
background: @color-black-shade-bg;
border: 1px solid @color-black-shade-border;
border-radius: .3rem;
line-height: 1;
.font-family; // don't inherit monospace font
&:after {
&:extend(.font-icon-class);
content: @fa-var-angle-down;
display: inline-block;
float: none;
margin: 0;
font-size: 90%;
}
&.collapsed:after {
content: @fa-var-angle-up;
}
}
&.blockquote-header {
text-overflow: ellipsis !important;
padding-right: 5rem !important;
}
}
}
.message-part {
span.sig {
color: @color-mail-signature;
}
div.pre {
font-family: monospace;
}
&.mailvelope iframe {
min-height: 35em;
}
}
.message-partheaders {
padding: .5rem 0;
margin: .5rem 0 0 0;
font-size: 90%;
border-top: 1px solid @color-list-border;
border-bottom: 1px solid @color-list-border;
color: @color-mail-headers;
.header-title {
.overflow-ellipsis;
white-space: nowrap;
max-width: 7em;
font-weight: bold;
padding-right: 1rem;
vertical-align: top;
}
.subject {
font-weight: bold;
}
& + .message-part,
& + .message-htmlpart {
border-top: 0;
margin: 0;
}
}
.image-tools {
position: absolute;
top: 5rem;
left: 0;
height: @layout-header-height;
overflow: hidden;
transform: translateX(-87%);
transition: transform 0.3s ease-in-out;
background-color: @color-image-tools-background;
border-radius: 0 .3rem .3rem 0;
.toolbar {
float: left;
height: @layout-header-height;
a.button:before {
width: auto;
height: 1.75rem;
display: block;
float: none;
}
}
a.button.icon.tools {
padding: 0 .25rem;
display: inline-block;
height: @layout-header-height;
span.inner {
display: none;
}
&:before {
line-height: @layout-header-height;
margin: 0;
}
}
&.open {
transform: translateX(0);
a.button.icon.tools:before {
content: @fa-var-chevron-left;
}
}
a {
color: @color-image-tools;
&:focus,
&:hover {
background-color: @color-image-tools-hover !important;
outline: 0;
}
}
}
+
+#compose-attachments {
+ margin: .5rem .5rem 0 .5rem;
+}
diff --git a/skins/elastic/styles/widgets/toolbar.less b/skins/elastic/styles/widgets/toolbar.less
index 325683cc1..b5b125b4f 100644
--- a/skins/elastic/styles/widgets/toolbar.less
+++ b/skins/elastic/styles/widgets/toolbar.less
@@ -1,889 +1,880 @@
/**
* Roundcube webmail styles for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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 authors in the README.md file.
* See http://creativecommons.org/licenses/by-sa/3.0/ for details.
*/
/*** Toolbar widget ***/
.toolbar {
margin: 0;
font-size: 1rem;
text-align: center;
&.listing a,
a {
text-decoration: none;
cursor: pointer !important; // TODO: re-consider .listing class use on toolbar
color: @color-toolbar-button;
}
a.button {
display: inline-block;
border: 0 !important;
height: @layout-header-height;
min-width: 3.5rem;
max-width: 6rem;
width: auto; // reset width defined for links in .listing
padding: .45rem;
text-align: center;
text-overflow: ellipsis;
overflow-x: hidden;
line-height: 1.5;
&:hover,
&:focus {
outline: 0;
}
&.selected {
color: @color-success;
}
&:before {
&:extend(.font-icon-class);
float: none;
margin: 0;
line-height: 1.75;
}
&.reply:before {
content: @fa-var-reply;
}
&.reply-all:before {
content: @fa-var-reply-all;
}
&.forward:before {
content: @fa-var-share;
}
&.delete:before {
content: @fa-var-trash-alt;
}
&.markmessage:before {
content: @fa-var-tag;
}
&.more:before {
content: @fa-var-ellipsis-h;
}
&.dropdown:before {
content: @fa-var-caret-down;
}
&.settings:before {
content: @fa-var-sliders-h;
}
&.create:before {
content: @fa-var-plus-square;
}
&.move:before {
content: @fa-var-arrows-alt;
}
&.purge:before {
content: @fa-var-eraser;
}
&.print:before {
content: @fa-var-print;
}
&.search:before {
content: @fa-var-search;
}
&.upload:before,
&.import:before {
content: @fa-var-upload;
}
&.download:before,
&.export:before {
content: @fa-var-download;
}
&.compose:before {
content: @fa-var-edit;
}
&.archive:before {
content: @fa-var-archive;
}
&.junk:before {
content: @fa-var-fire;
}
&.enigma:before,
&.encrypt:before {
content: @fa-var-lock;
}
&.prev:before {
content: @fa-var-arrow-left;
}
&.next:before {
content: @fa-var-arrow-right;
}
&.firstpage:before {
content: @fa-var-angle-double-left;
}
&.prevpage:before {
content: @fa-var-angle-left;
}
&.nextpage:before {
content: @fa-var-angle-right;
}
&.lastpage:before {
content: @fa-var-angle-double-right;
}
&.send:before {
content: @fa-var-paper-plane;
}
&.back:before {
content: @fa-var-arrow-left;
}
&.closewin:before {
content: @fa-var-window-close;
}
&.save:before {
.font-icon-regular(@fa-var-save);
}
&.vcard:before,
&.attach:before {
content: @fa-var-paperclip;
}
&.spellcheck:before {
content: @fa-var-magic; // TODO
}
&.signature:before {
content: @fa-var-id-card;
}
&.responses:before {
content: @fa-var-comment;
}
&.select:before {
.font-icon-regular(@fa-var-check-circle);
}
&.threads:before {
content: @fa-var-comments;
}
&.actions:before {
content: @fa-var-cog;
}
&.refresh:before {
content: @fa-var-sync;
}
- &.addto:before {
- content: @fa-var-user-plus;
- }
- &.addcc:before {
- content: @fa-var-user-plus;
- }
- &.addbcc:before {
- content: @fa-var-user-plus;
- }
&.addressbook:before {
content: @fa-var-user;
}
&.expand:before {
content: @fa-var-angle-right;
}
&.collapse:before {
content: @fa-var-angle-down;
}
&.submit:before {
content: @fa-var-check;
}
&.edit:before {
content: @fa-var-pencil-alt;
}
&.qrcode:before {
content: @fa-var-qrcode;
}
&.properties:before {
content: @fa-var-file;
}
&.zoomin:before {
content: @fa-var-search-plus;
}
&.zoomout:before {
content: @fa-var-search-minus;
}
&.rotate:before {
content: @fa-var-redo-alt;
}
}
&:not(.popupmenu) span.inner {
font-size: 90%;
font-weight: normal;
}
.dropbutton {
a.button.dropdown {
font-size: 75%;
overflow: hidden; // for IE11
span.inner {
display: none;
}
}
a.button:first-child {
padding-right: 0;
}
}
& > .spacer {
width: 1em;
}
&.pagenav {
display: flex;
- justify-content: normal !important;
+ justify-content: space-between;
align-items: center;
a.button {
flex-grow: 1;
min-width: 2rem !important;
height: @layout-footer-small-height;
&:before {
line-height: 1.25;
}
}
.pagenav-text {
.overflow-ellipsis;
color: @color-list-pagenav;
flex-grow: 4;
font-size: 80%;
white-space: nowrap;
}
input {
width: 3rem;
max-width: 5rem;
font-size: 90%;
text-align: center;
max-height: 1.6rem;
html.layout-phone & {
display: none;
}
}
span.inner {
display: none;
}
&.pagenav-list {
cursor: pointer;
background-color: @color-searchbar-background;
border-bottom: 1px solid @color-list-border;
a.button {
flex-grow: unset;
}
.pagenav-text {
text-align: left;
font-size: 100%;
}
& + .navlist {
background-color: #fbfbfb;
}
&.expanded + .navlist {
border-bottom: 1px solid @color-layout-border;
}
}
}
&.footer {
a.button:before {
height: 1.75rem;
float: none;
display: block;
width: auto;
}
}
&.footer.small {
a.button:before {
height: auto; // for Chrome
}
}
&.content-frame-navigation.hide-nav-buttons {
a.next,
a.prev {
display: none;
}
}
.listselectors {
max-width: 100%;
display: flex;
justify-content: space-around;
}
}
.header {
a.button {
color: @color-toolbar-button;
}
.buttons {
display: block;
button {
display: block;
float: left;
cursor: pointer;
color: @color-toolbar-button;
background-color: transparent;
border: 0;
padding: 0;
height: @layout-touch-header-height;
line-height: @layout-touch-header-height;
width: 2.5em;
&:before {
font-size: 1.75rem;
}
}
a.button {
display: inline-block;
&:before {
&:extend(.font-icon-class);
float: none;
}
&.reply:before {
content: @fa-var-reply;
}
&.send:before {
content: @fa-var-paper-plane;
}
}
}
}
.popupmenu.toolbar.listing {
a.button {
max-width: 100%;
width: 100%;
text-align: left;
line-height: @layout-touch-menu-record-height;
height: @layout-touch-menu-record-height;
&:before {
display: inline-block;
line-height: inherit;
margin-right: .5rem;
}
}
.dropbutton {
display: flex;
a.button:first-child {
.overflow-ellipsis;
flex: 1;
}
a.button.dropdown {
font-size: 100%;
cursor: pointer;
width: auto;
&:before {
content: @fa-var-angle-right;
margin-left: .5em;
margin-right: 0;
}
span.inner {
display: none;
}
}
}
li.spacer {
display: none;
}
li:last-child {
border: 0;
}
}
.toolbarmenu li {
&.separator {
// TODO: all separator properties
line-height: 1.5rem !important;
font-size: .75rem !important;
padding: 0 .5rem;
color: @color-popover-separator;
background-color: @color-popover-separator-background;
label {
margin: 0; // Unsets Bootstrap label margin, bug?
}
}
&.checkbox > label {
margin: 0;
width: 100%;
input.icon-checkbox {
right: auto;
& + label {
left: 0;
margin: 0 .2em 0 .35em;
font-size: 1.1rem;
}
}
}
a {
opacity: .5;
&.active {
opacity: 1;
}
}
&:last-child {
border-bottom: none;
}
a:before {
&:extend(.font-icon-class);
}
a.print:before {
content: @fa-var-print;
}
a.copy:before {
content: @fa-var-copy;
}
a.move:before {
content: @fa-var-arrows-alt;
}
a.purge:before {
content: @fa-var-eraser;
}
a.source:before {
content: @fa-var-file-code;
}
a.download:before {
content: @fa-var-download;
}
a.extwin:before {
content: @fa-var-external-link-square-alt;
}
a.create:before {
content: @fa-var-plus-square;
}
a.edit:before {
content: @fa-var-edit;
}
a.edit.asnew:before {
content: @fa-var-pencil-alt;
}
a.rename:before {
content: @fa-var-pencil-alt;
}
a.read:before {
.font-icon-regular(@fa-var-circle);
}
a.unread:before {
content: @fa-var-circle;
}
a.flag:before {
content: @fa-var-flag;
}
a.unflag:before {
.font-icon-regular(@fa-var-flag);
}
a.filterlink:before {
content: @fa-var-filter;
}
a.reply.list:before,
a.reply.all:before {
content: @fa-var-reply-all;
}
a.forward:before,
a.forward.bounce:before,
a.forward.attachment:before,
a.forward.inline:before {
content: @fa-var-share;
}
a.download.mbox:before,
a.download.eml:before,
a.download.maildir:before {
content: @fa-var-download;
}
a.export.selection:before,
a.export.all:before {
content: @fa-var-download;
}
a.select:before {
content: @fa-var-check-circle;
}
a.threads:before {
content: @fa-var-comments;
}
a.selection:before {
content: @fa-var-mouse-pointer;
}
a.select.all:before {
.font-icon-regular(@fa-var-check-square);
}
a.select.none:before {
content: @fa-var-times;
}
a.select.page:before {
content: @fa-var-bars;
}
a.select.flagged:before {
content: @fa-var-flag;
}
a.select.unread:before {
content: @fa-var-circle;
}
a.select.invert:before {
.font-icon-regular(@fa-var-square);
}
a.expand.all:before,
a.expand.unread:before,
a.expand.none:before {
content: @fa-var-comments;
}
a.search:before {
content: @fa-var-search;
}
a.delete:before {
content: @fa-var-trash-alt;
}
a.expunge:before {
content: @fa-var-compress;
}
a.import:before {
content: @fa-var-upload;
}
a.settings:before {
content: @fa-var-sliders-h;
}
a.insertresponse:before {
content: @fa-var-comment;
}
a.compose:before {
content: @fa-var-edit;
}
a.addressbook:before {
content: @fa-var-user;
}
a.recipient:before {
.font-icon-regular(@fa-var-envelope);
}
a.status:before {
.font-icon-regular(@fa-var-lightbulb);
}
a.folders:before {
content: @fa-var-folder;
}
a.remove:before {
content: @fa-var-eraser;
}
a.showurl:before {
content: @fa-var-link;
}
a.qrcode:before {
content: @fa-var-qrcode;
}
a.assigngroup:before {
content: @fa-var-user-plus;
}
a.removegroup:before {
content: @fa-var-user-times;
}
a.vcard:before {
content: @fa-var-paperclip;
}
a.encrypt:before {
content: @fa-var-lock;
}
a.encrypt.sign:before {
content: @fa-var-lock; // TODO
}
}
.toolbarmenu.listing li {
&.checkbox > label {
padding: 0 .5rem 0 2.5em;
}
&:hover {
&.checkbox > label,
input.icon-checkbox + label:before,
a.active {
color: @color-toolbarmenu-hover;
background-color: @color-toolbarmenu-hover-background;
}
}
}
html.touch {
.toolbarmenu.listing td,
.toolbarmenu.listing li,
#layout > :not(.content) > .header a.button {
font-size: 1.2rem;
}
.toolbarmenu.listing li {
&.checkbox > label {
padding: 0 .5rem 0 2.75rem;
}
}
.toolbarmenu li {
input.icon-checkbox + label {
font-size: 1.3rem;
}
}
}
html.ie11 .toolbar .dropbutton a.dropdown:before {
font-size: 80%;
}
@media screen and (min-width: (@screen-width-small + 1px)) {
.toolbar {
.header > & {
display: flex; // for Safari on desktop
}
a.button {
&:not(.disabled):focus,
&:not(.disabled):hover {
background-color: @color-toolbar-button-background-hover;
}
}
&.listing li {
display: inline-block;
border: 0;
a.button:before {
height: 1.75rem;
float: none;
display: block;
width: auto;
margin: 0;
}
}
.dropbutton {
height: @layout-header-height;
display: inline-block;
&:hover {
background-color: @color-toolbar-button-background-hover;
}
a.button.dropdown {
min-width: 1.1rem;
padding: 0 .3rem;
&:hover {
background-color: darken(@color-toolbar-button-background-hover, 5%);
}
&:before {
height: @layout-header-height;
line-height: 4.2;
}
}
}
}
.toolbar.content-frame-navigation {
display: none !important;
}
.header a.button.icon {
&:not(.disabled):focus,
&:not(.disabled):hover {
background-color: @color-toolbar-button-background-hover;
outline: 0;
}
&:before {
margin: 0;
}
}
}
@media screen and (max-width: @screen-width-small) {
body > #layout > div > .toolbar.footer {
justify-content: space-around;
& > * {
flex-grow: 1;
}
.buttons {
display: flex;
justify-content: space-around;
}
.listselectors > * {
flex-grow: 1;
}
}
.toolbar.listing a {
color: @color-font;
}
}
a.button.icon.toolbar-button {
order: 8;
@media screen and (min-width: (@screen-width-large + 1px)) {
height: @layout-header-height;
line-height: 1.5;
padding: .45rem;
&:before {
float: none;
height: 1.75rem;
line-height: 1.75;
width: auto;
}
span.inner {
display: inline;
font-weight: normal;
font-size: 90%;
}
}
}
/*** Searchbar and searchoptions widgets ***/
.searchbar {
height: @layout-searchbar-height;
min-height: @layout-searchbar-height; // because of Flexbox
line-height: @layout-searchbar-height;
background-color: @color-searchbar-background;
border-bottom: 1px solid @color-list-border;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
position: relative;
padding-right: .5rem;
form {
flex: 1;
display: flex;
&:before {
&:extend(.font-icon-class);
content: @fa-var-search;
height: @layout-searchbar-height;
color: @color-list-pagenav;
margin: 0 0 0 .75rem;
}
}
input {
width: 100%;
border: 0;
background: transparent;
padding: .5rem;
}
a.button {
height: @layout-searchbar-height;
min-width: auto;
color: @color-toolbar-button;
&:before {
&:extend(.font-icon-class);
}
&.options {
&:before {
content: @fa-var-angle-down;
line-height: 1.25;
width: 1em;
}
}
&.reset {
display: none;
&:before {
content: @fa-var-times;
font-size: 1em;
}
}
&.search {
display: none;
}
}
span.inner {
display: none;
}
&.active {
a.button.reset {
display: inline;
}
}
&.open a.button.options:before {
content: @fa-var-angle-up;
}
}
.searchoptions {
button.search {
width: 100%;
}
ul.proplist {
& + div {
margin-top: 1rem;
}
}
.input-group {
&:not(:last-child) {
margin-bottom: .5rem;
}
.input-group-prepend {
width: 30%;
}
label {
width: 100%;
}
}
.formbuttons {
// this is needed because we hide .formbuttons on small devices
// we don't want it for search options form
display: block !important;
}
}
diff --git a/skins/elastic/templates/compose.html b/skins/elastic/templates/compose.html
index 15f3f2922..6c77265f5 100644
--- a/skins/elastic/templates/compose.html
+++ b/skins/elastic/templates/compose.html
@@ -1,284 +1,281 @@
<roundcube:include file="includes/layout.html" />
<roundcube:include file="includes/menu.html" condition="!env:extwin && !env:framed" />
<roundcube:add_label name="recipientsadded" />
<roundcube:add_label name="nocontactselected" />
<roundcube:add_label name="recipient" />
+<roundcube:add_label name="insert" />
+<roundcube:add_label name="insertcontact" />
<roundcube:add_label name="recipientedit" />
<h1 class="voice"><roundcube:label name="compose" /></h1>
-<!-- inline address book -->
-<div class="sidebar listbox" role="region" aria-labelledby="aria-label-composecontacts">
- <div class="header no-toolbar">
- <a class="button icon back-content-button" href="#content" data-hidden="big"><span class="inner"><roundcube:label name="back" /></span></a>
- <span id="aria-label-composecontacts" class="header-title"><roundcube:label name="contacts" /></span>
- </div>
- <roundcube:object name="searchform" id="searchform" wrapper="searchbar toolbar"
- label="contactsearchform" buttontitle="findcontacts" ariatag="h2" />
- <div class="scroller" tabindex="-1">
- <roundcube:object name="addressbooks" id="directorylist" class="treelist listing iconized"
- summary="ariasummarycomposecontacts" />
- <roundcube:object name="addresslist" id="contacts-table" class="listing iconized contactlist"
- noheader="true" role="listbox" data-list="contact_list" />
- </div>
- <div class="footer toolbar" role="toolbar">
- <roundcube:button command="add-recipient" prop="to" type="link" title="to"
- class="button addto disabled" classAct="button addto" innerClass="inner" content="To+" />
- <roundcube:button command="add-recipient" prop="cc" type="link" title="cc"
- class="button addcc disabled" classAct="button addcc" innerClass="inner" content="Cc+" />
- <roundcube:button command="add-recipient" prop="bcc" type="link" title="bcc"
- class="button addbcc disabled" classAct="button addbcc" innerClass="inner" content="Bcc+" />
- <roundcube:container name="compose-contacts-toolbar" id="compose-contacts-toolbar" />
- </div>
- <roundcube:include file="includes/pagenav.html" />
-</div>
-
<!-- compose options and attachments list -->
-<div class="list listbox">
+<div class="sidebar listbox">
<div class="header">
<a class="button icon back-content-button" href="#content" data-hidden="big"><span class="inner"><roundcube:label name="back" /></span></a>
<span class="header-title all-sizes"><roundcube:label name="optionsandattachments" /></span>
</div>
<div class="scroller">
+ <!-- attachments -->
+ <div id="compose-attachments" class="file-upload" role="region" aria-labelledby="aria-label-compose-attachments">
+ <h2 id="aria-label-compose-attachments" class="voice"><roundcube:label name="attachments" /></h2>
+ <div class="upload-form">
+ <roundcube:object name="composeAttachmentForm" mode="hint" />
+ <button class="btn btn-secondary attach" tabindex="2" href="#" onclick="rcmail.upload_input('uploadform')"><roundcube:label name="addattachment" /></button>
+ </div>
+ <roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" tabindex="2" />
+ <roundcube:object name="fileDropArea" id="compose-attachments" />
+ </div>
<!-- compose options -->
<div id="compose-options" class="formcontent" role="region" aria-labelledby="aria-label-composeoptions">
<h2 id="aria-label-composeoptions" class="voice"><roundcube:label name="arialabelcomposeoptions" /></h2>
+ <roundcube:container name="composeoptions" id="compose-options" />
<roundcube:if condition="!in_array('mdn_default', (array)config:dont_override)" />
<div class="form-group row form-check">
<label for="compose-mdn" class="col-form-label col-6"><roundcube:label name="returnreceipt" /></label>
<div class="col-6 form-check">
<roundcube:object name="mdnCheckBox" id="compose-mdn" noform="true" tabindex="2" class="form-check-input" />
</div>
</div>
<roundcube:endif />
<roundcube:if condition="!in_array('dsn_default', (array)config:dont_override)" />
<div class="form-group row form-check">
<label for="compose-dsn" class="col-form-label col-6"><roundcube:label name="dsn" /></label>
<div class="col-6 form-check">
<roundcube:object name="dsnCheckBox" id="compose-dsn" noform="true" tabindex="2" class="form-check-input" />
</div>
</div>
<roundcube:endif />
<div class="form-group row">
<label for="compose-priority" class="col-form-label col-6"><roundcube:label name="priority" /></label>
<div class="col-6">
<roundcube:object name="prioritySelector" id="compose-priority" noform="true" tabindex="2" />
</div>
</div>
<roundcube:if condition="!config:no_save_sent_messages" />
<div class="form-group row">
<label for="compose-store-target" class="col-form-label col-6"><roundcube:label name="savesentmessagein" /></label>
<div class="col-6">
<roundcube:object name="storetarget" id="compose-store-target" noform="true" tabindex="2" />
</div>
</div>
<roundcube:endif />
- <roundcube:container name="composeoptions" id="compose-options" />
<roundcube:if condition="!in_array('htmleditor', (array)config:dont_override)" />
<div class="form-group row hidden">
<label for="editor-selector" class="col-form-label col-6"><roundcube:label name="editortype" /></label>
<div class="col-6">
<roundcube:object name="editorSelector" id="editor-selector" editorid="composebody" noform="true" tabindex="2" />
</div>
</div>
<roundcube:endif />
</div>
- <div id="compose-attachments" class="file-upload" role="region" aria-labelledby="aria-label-compose-attachments">
- <h2 id="aria-label-compose-attachments" class="voice"><roundcube:label name="attachments" /></h2>
- <div class="upload-form">
- <roundcube:object name="composeAttachmentForm" mode="hint" />
- <button class="btn btn-secondary attach" tabindex="2" href="#" onclick="rcmail.upload_input('uploadform')"><roundcube:label name="addattachment" /></button>
- </div>
- <roundcube:object name="composeAttachmentList" id="attachment-list" class="attachmentslist" tabindex="2" />
- <roundcube:object name="fileDropArea" id="compose-attachments" />
- </div>
</div>
</div>
<div class="content listbox selected" role="main">
<h2 id="aria-label-toolbar" class="voice"><roundcube:label name="arialabeltoolbar" /></h2>
<div class="header">
<a class="button icon menu-button" href="#menu"><span class="inner"><roundcube:label name="menu" /></span></a>
<span class="header-title"><roundcube:label name="compose" /></span>
<!-- toolbar -->
<div id="messagetoolbar" class="toolbar" role="toolbar" aria-labelledby="aria-label-toolbar">
- <a class="button settings" href="#options" onclick="UI.show_list(true)" data-hidden="big">
+ <a class="button settings" href="#options" onclick="UI.show_sidebar()" data-hidden="big">
<span class="inner"><roundcube:label name="optionsandattachments"></span>
</a>
- <a class="button addressbook" href="#contacts" onclick="UI.show_sidebar()" data-hidden="big">
- <span class="inner"><roundcube:label name="contacts"></span>
- </a>
<roundcube:button command="savedraft" type="link" class="button save draft disabled" classAct="button save draft"
label="save" title="savemessage" tabindex="2" innerclass="inner" data-content-button="true" />
<span class="spacer"></span>
<roundcube:button name="addattachment" type="link" class="button attach"
label="attach" title="addattachment" data-hidden="small"
onclick="if (!$(this).is('.disabled')) rcmail.upload_input('uploadform')"
aria-haspopup="true" aria-expanded="false" tabindex="2" innerclass="inner" />
<roundcube:button command="insert-sig" type="link" class="button signature disabled" classAct="button signature"
label="signature" title="insertsignature" tabindex="2" innerclass="inner" />
<a href="#responses" class="button responses" label="responses" title="<roundcube:label name='insertresponse' />" unselectable="on" tabindex="2" data-popup="responses-menu">
<span class="inner"><roundcube:label name="responses" /></span>
</a>
- <a id="composeoptionslink" class="button settings hidden" href="#options" onclick="UI.show_list(); $(this).addClass('hidden'); $('#composecontactslink').removeClass('hidden')" data-hidden="large,small">
- <span class="inner"><roundcube:label name="options"></span>
- </a>
- <a id="composecontactslink" class="button addressbook" href="#contacts" onclick="UI.show_sidebar(true); $(this).addClass('hidden'); $('#composeoptionslink').removeClass('hidden')" data-hidden="large,small">
- <span class="inner"><roundcube:label name="contacts"></span>
- </a>
<roundcube:if condition="config:enable_spellcheck" />
<span class="dropbutton">
<roundcube:button command="spellcheck" type="link" class="button spellcheck disabled"
classAct="button spellcheck" classSel="button spellcheck pressed"
label="spellcheck" title="checkspelling" tabindex="2" innerclass="inner" />
<a href="#languages" class="button dropdown" tabindex="2" data-popup="spell-menu">
<span class="inner"><roundcube:label name="language" /></span>
</a>
</span>
<roundcube:endif />
<span class="dropbutton" style="display:none">
<roundcube:button command="compose-encrypted" type="link" class="button encrypt disabled"
classAct="button encrypt" classSel="button encrypt selected" innerclass="inner"
label="encrypt" title="encryptmessagemailvelope" tabindex="2" />
<a href="#encryption" id="encryption-menu-button" class="button dropdown" tabindex="2" data-popup="encryption-menu">
<span class="inner"><roundcube:label name="encryptmessagemailvelope" /></span>
</a>
</span>
<roundcube:container name="toolbar" id="compose-toolbar" />
</div>
</div>
<div id="compose-content" class="formcontainer content scroller">
<roundcube:object name="composeFormHead" role="main" class="formcontent" />
<!-- message headers -->
<div id="compose-headers" role="region" aria-labelledby="aria-label-composeheaders">
<h2 id="aria-label-composeheaders" class="voice"><roundcube:label name="arialabelmessageheaders" /></h2>
<div class="compose-headers">
<div id="compose_from" class="form-group row">
<label for="_from" class="col-2 col-form-label"><roundcube:label name="from" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="from" id="_from" form="form" tabindex="1" class="form-control" />
<span class="input-group-append">
<a href="#identities" onclick="return rcmail.command('switch-task', 'settings/identities')" class="input-group-text icon edit" title="<roundcube:label name="editidents" />" tabindex="1"><span class="inner"><roundcube:label name="editidents" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_to" class="form-group row">
<label for="_to" class="col-2 col-form-label"><roundcube:label name="to" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="to" id="_to" form="form" tabindex="1" aria-required="true" data-recipient-input="true" />
+ <span class="input-group-append">
+ <a href="#add-contact" onclick="UI.recipient_selector('to')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
+ </span>
<span class="input-group-append">
<a href="#add-header" data-popup="headers-menu" class="input-group-text icon add" title="<roundcube:label name="addheader" />" tabindex="1"><span class="inner"><roundcube:label name="addheader" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_cc" class="hidden form-group row">
<label for="_cc" class="col-2 col-form-label"><roundcube:label name="cc" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="cc" id="_cc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
- <a href="#delete" onclick="UI.header_reset('_cc')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
+ <a href="#add-contact" onclick="UI.recipient_selector('cc')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
+ </span>
+ <span class="input-group-append">
+ <a href="#delete" onclick="UI.header_reset('_cc')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_bcc" class="hidden form-group row">
<label for="_bcc" class="col-2 col-form-label"><roundcube:label name="bcc" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="bcc" id="_bcc" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
- <a href="#delete" onclick="UI.header_reset('_bcc')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
+ <a href="#add-contact" onclick="UI.recipient_selector('bcc')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
+ </span>
+ <span class="input-group-append">
+ <a href="#delete" onclick="UI.header_reset('_bcc')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_replyto" class="hidden form-group row">
<label for="_replyto" class="col-2 col-form-label"><roundcube:label name="replyto" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="replyto" id="_replyto" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
- <a href="#delete" onclick="UI.header_reset('_replyto')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
+ <a href="#add-contact" onclick="UI.recipient_selector('replyto')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
+ </span>
+ <span class="input-group-append">
+ <a href="#delete" onclick="UI.header_reset('_replyto')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_followupto" class="hidden form-group row">
<label for="_followupto" class="col-2 col-form-label"><roundcube:label name="followupto" /></label>
<div class="col-10">
<div class="input-group">
<roundcube:object name="composeHeaders" part="followupto" id="_followupto" form="form" tabindex="1" data-recipient-input="true" />
<span class="input-group-append">
- <a href="#delete" onclick="UI.header_reset('_followupto')" class="input-group-text icon cancel" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
+ <a href="#add-contact" onclick="UI.recipient_selector('followupto')" class="input-group-text icon add recipient" title="<roundcube:label name="addcontact" />" tabindex="1"><span class="inner"><roundcube:label name="addcontact" /></span></a>
+ </span>
+ <span class="input-group-append">
+ <a href="#delete" onclick="UI.header_reset('_followupto')" class="input-group-text icon delete" title="<roundcube:label name='delete' />" tabindex="1"><span class="inner"><roundcube:label name="delete" /></span></a>
</span>
</div>
</div>
</div>
<div id="compose_subject" class="form-group row">
<label for="compose-subject" class="col-2 col-form-label"><roundcube:label name="subject" /></label>
<div class="col-10">
<roundcube:object name="composeSubject" id="compose-subject" form="form" tabindex="1" class="form-control" />
</div>
</div>
</div>
</div>
<!-- message compose body -->
<div id="composebodycontainer">
<label for="composebody" class="voice"><roundcube:label name="arialabelmessagebody" /></label>
<roundcube:object name="composeBody" id="composebody" form="form" cols="70" rows="20" class="form-control" tabindex="1" />
</div>
</form>
<div class="formbuttons">
<roundcube:button command="send" class="btn btn-primary send" label="send" tabindex="1" data-content-button="true" />
</div>
</div>
</div>
<roundcube:object name="composeAttachmentForm" id="uploadform" mode="smart" />
<div id="spell-menu" class="popupmenu" data-popup-init="spellmenu"></div>
<div id="headers-menu" class="popupmenu" data-popup-init="headersmenu">
<h3 id="aria-label-headersmenu" class="voice"><roundcube:label name="arialabelheadersmenu" /></h3>
<ul class="toolbarmenu listing" role="menu" aria-labelledby="aria-label-headersmenu">
<li role="menuitem"><a data-target="cc" href="#" role="button" tabindex="-1" class="recipient"><roundcube:label name="cc" /></a></li>
<li role="menuitem"><a data-target="bcc" href="#" role="button" tabindex="-1" class="recipient"><roundcube:label name="bcc" /></a></li>
<li role="menuitem"><a data-target="replyto" href="#" role="button" tabindex="-1" class="recipient"><roundcube:label name="replyto" /></a></li>
<li role="menuitem"><a data-target="followupto" href="#" role="button" tabindex="-1" class="recipient"><roundcube:label name="followupto" /></a></li>
</ul>
</div>
<div id="responses-menu" class="popupmenu">
<h3 id="aria-label-responsesmenu" class="voice"><roundcube:label name="arialabelresponsesmenu" /></h3>
<ul class="toolbarmenu listing" role="menu" aria-labelledby="aria-label-responsesmenu">
<li role="separator" class="separator"><label><roundcube:label name="insertresponse" /></label></li>
<roundcube:object name="responseslist" id="responseslist" tagname="ul" itemclass="active" />
<li role="separator" class="separator"><label><roundcube:label name="manageresponses" /></label></li>
<roundcube:button command="save-response" type="link-menuitem" label="newresponse" class="create responses" classAct="create responses active" unselectable="on" />
<roundcube:button command="responses" type="link-menuitem" label="editresponses" class="edit responses" classAct="edit responses active" />
</ul>
</div>
<div id="attachmentmenu" class="popupmenu">
<h3 id="aria-label-attachmentmenu" class="voice"><roundcube:label name="arialabelattachmentmenu" /></h3>
<ul class="toolbarmenu listing" role="menu" aria-labelledby="aria-label-attachmentmenu">
<roundcube:button command="open-attachment" id="attachmenuopen" type="link-menuitem" label="open" class="extwin" classAct="extwin active" />
<roundcube:button command="download-attachment" id="attachmenudownload" type="link-menuitem" label="download" class="download" classAct="download active" />
<roundcube:button command="rename-attachment" id="attachmenurename" type="link-menuitem" label="rename" class="rename" classAct="rename active" />
<roundcube:container name="attachmentmenu" id="attachmentoptionsmenu" />
</ul>
</div>
<div id="encryption-menu" class="popupmenu">
<ul class="toolbarmenu listing" role="menu">
<roundcube:button command="compose-encrypted" type="link-menuitem" label="encryptmessage" class="encrypt" classAct="encrypt active" />
<roundcube:button command="compose-encrypted-signed" type="link-menuitem" label="encryptandsign" class="encrypt sign" classAct="encrypt sign active" />
</ul>
</div>
+<div id="recipient-dialog" class="popupmenu" role="region" aria-labelledby="aria-label-composecontacts">
+ <div class="listbox">
+ <roundcube:object name="searchform" id="searchform" wrapper="searchbar toolbar"
+ label="contactsearchform" buttontitle="findcontacts" ariatag="h2" class="no-bs" />
+ <div class="scroller" tabindex="-1">
+ <roundcube:object name="addressbooks" id="directorylist" class="treelist listing iconized"
+ summary="ariasummarycomposecontacts" />
+ <roundcube:object name="addresslist" id="contacts-table" class="listing iconized contactlist"
+ noheader="true" role="listbox" data-list="contact_list" data-list-select-replace="#recipient-dialog .pagenav-text" />
+ </div>
+ <roundcube:include file="includes/pagenav.html" />
+ </div>
+</div>
+
<roundcube:include file="includes/footer.html" />
diff --git a/skins/elastic/ui.js b/skins/elastic/ui.js
index 87d7a1e8c..be53e96b2 100644
--- a/skins/elastic/ui.js
+++ b/skins/elastic/ui.js
@@ -1,3677 +1,3789 @@
/**
* Roundcube webmail functions for the Elastic skin
*
* Copyright (c) 2017-2018, The Roundcube Dev Team
*
* 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.
*
* @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0
*/
"use strict";
function rcube_elastic_ui()
{
var ref = this,
mode = 'normal', // one of: large, normal, small, phone
touch = false,
ios = false,
is_framed = rcmail.is_framed(),
env = {
config: {
standard_windows: rcmail.env.standard_windows,
message_extwin: rcmail.env.message_extwin,
compose_extwin: rcmail.env.compose_extwin,
help_open_extwin: rcmail.env.help_open_extwin
},
small_screen_config: {
standard_windows: true,
message_extwin: false,
compose_extwin: false,
help_open_extwin: false
}
},
menus = {},
content_buttons = [],
frame_buttons = [],
layout = {
menu: $('#layout > .menu'),
sidebar: $('#layout > .sidebar'),
list: $('#layout > .list'),
content: $('#layout > .content'),
},
buttons = {
menu: $('a.menu-button'),
back_sidebar: $('a.back-sidebar-button'),
back_list: $('a.back-list-button'),
back_content: $('a.back-content-button'),
};
// Public methods
this.register_content_buttons = register_content_buttons;
this.menu_hide = menu_hide;
this.menu_toggle = menu_toggle;
this.menu_destroy = menu_destroy;
this.popup_init = popup_init;
this.about_dialog = about_dialog;
this.headers_dialog = headers_dialog;
this.import_dialog = import_dialog;
this.headers_show = headers_show;
this.spellmenu = spellmenu;
this.searchmenu = searchmenu;
this.headersmenu = headersmenu;
this.header_reset = header_reset;
this.attachmentmenu = attachmentmenu;
this.mailtomenu = mailtomenu;
+ this.recipient_selector = recipient_selector;
this.show_list = show_list;
this.show_sidebar = show_sidebar;
this.smart_field_init = smart_field_init;
this.smart_field_reset = smart_field_reset;
this.form_errors = form_errors;
this.switch_nav_list = switch_nav_list;
this.searchbar_init = searchbar_init;
this.pretty_checkbox = pretty_checkbox;
this.pretty_select = pretty_select;
// Detect screen size/mode
screen_mode();
// Initialize layout
layout_init();
// Convert some elements to Bootstrap style
bootstrap_style();
// Initialize responsive toolbars (have to be before popups init)
toolbar_init();
// Initialize content frame and list handlers
content_frame_init();
// Initialize menu dropdowns
dropdowns_init();
// Setup various UI elements
setup();
// Update layout after initialization
resize();
/**
* Setup procedure
*/
function setup()
{
var title, form, content_buttons = [];
// Intercept jQuery-UI dialogs...
$.ui && $.widget('ui.dialog', $.ui.dialog, {
open: function() {
// .. to unify min width for iframe'd dialogs
if ($(this.element).is('.iframe')) {
this.options.width = Math.max(576, this.options.width);
}
this._super();
// ... to re-style them on dialog open
dialog_open(this);
return this;
},
close: function() {
this._super();
// ... to close custom select dropdowns on dialog close
$('.select-menu:visible').remove();
return this;
}
});
// menu/sidebar/list button
buttons.menu.on('click', function() { app_menu(true); return false; });
buttons.back_sidebar.on('click', function() { show_sidebar(); return false; });
buttons.back_list.on('click', function() { show_list(); return false; });
buttons.back_content.on('click', function() { show_content(true); return false; });
// Initialize search forms
$('.searchbar').each(function() { searchbar_init(this); });
// Set content frame title in parent window (exclude ext-windows and dialog frames)
if (is_framed && !rcmail.env.extwin && !parent.$('.ui-dialog:visible').length) {
if (title = $('h1.voice:first').text()) {
parent.$('#layout > .content > .header > .header-title:not(.constant)').text(title);
}
}
else if (!is_framed) {
title = $('.boxtitle:first', layout.content).detach().text();
if (!title) {
title = $('h1.voice:first').text();
}
if (title) {
$('.header > .header-title', layout.content).text(title);
}
}
// Add content frame toolbar in the footer, for content buttons and navigation
if (!is_framed && layout.content.length && !$(layout.content).is('.no-navbar')
&& !$(layout.content).children('.frame-content').length
) {
env.frame_nav = $('<div class="footer toolbar content-frame-navigation hide-nav-buttons">')
.append($('<a class="button prev">')
.append($('<span class="inner"></span>').text(rcmail.gettext('previous'))))
.append($('<span class="buttons">'))
.append($('<a class="button next">')
.append($('<span class="inner"></span>').text(rcmail.gettext('next'))))
.appendTo(layout.content);
}
// Move some buttons to the frame footer toolbar
$('a[data-content-button]').each(function() {
content_buttons.push(create_cloned_button($(this)));
});
// Move form buttons from the content frame into the frame footer (on parent window)
$('.formbuttons').filter(function() { return !$(this).parent('.searchoptions').length; }).children().each(function() {
var target = $(this);
// skip non-content buttons
if (!is_framed && !target.parents('.content').length) {
return;
}
if (target.is('.cancel')) {
target.addClass('hidden');
return;
}
content_buttons.push(create_cloned_button(target));
});
(is_framed ? parent.UI : ref).register_content_buttons(content_buttons);
// Mail compose features
if (form = rcmail.gui_objects.messageform) {
form = $('form[name="' + form + '"]');
// Show input elements with non-empty value
// These event handlers need to be registered before rcmail 'init' event
$('#_cc, #_bcc, #_replyto, #_followupto', $('.compose-headers')).each(function() {
$(this).on('change', function() {
$('#compose' + $(this).attr('id'))[this.value ? 'removeClass' : 'addClass']('hidden');
});
});
// We put compose options outside of the main form
// Because IE/Edge (<16) does not support 'form' attribute we'll copy
// inputs into the main form as hidden fields
// TODO: Consider doing this for IE/Edge only, just set the 'form' attribute on others
$('#compose-options').find('textarea,input,select').each(function() {
var hidden = $('<input>')
.attr({type: 'hidden', name: $(this).attr('name')})
.appendTo(form);
$(this).attr('tabindex', 2)
.on('change', function() {
hidden.val(this.type != 'checkbox' || this.checked ? $(this).val() : '');
})
.change();
});
}
// Use smart recipient inputs
// This have to be after mail compose feature above
$('[data-recipient-input]').each(function() { recipient_input(this); });
// Image upload widget
$('.image-upload').each(function() { image_upload_input(this); });
// Add HTML/Plain tabs (switch) on top of textarea with TinyMCE editor
$('textarea[data-html-editor]').each(function() { html_editor_init(this); });
$('#dragmessage-menu,#dragcontact-menu').each(function() {
rcmail.gui_object('dragmenu', this.id);
});
// Taskmenu items added by plugins do not use elastic classes (e.g help plugin)
// it's for larry skin compat. We'll assign 'button', 'selected' and icon-specific class.
$('#taskmenu > a').each(function() {
if (/button-([a-z]+)/.test(this.className)) {
var data, name = RegExp.$1,
button = find_button(this.id);
if (button && (data = button.data)) {
if (data.sel) {
data.sel += ' button ' + name;
data.sel = data.sel.replace('button-selected', 'selected');
}
if (data.act) {
data.act += ' button ' + name;
}
rcmail.buttons[button.command][button.index] = data;
rcmail.init_button(button.command, data);
}
$(this).addClass('button ' + name);
$('.button-inner', this).addClass('inner');
}
$(this).on('mouseover', function() { rcube_webmail.long_subject_title(this, 0, $('span.inner', this)); });
});
// Some plugins use 'listbubtton' class, we'll replace it with 'button'
$('.listbutton').each(function() {
var button = find_button(this.id);
$(this).addClass('button').removeClass('listbutton');
if (button.data.sel) {
button.data.sel = button.data.sel.replace('listbutton', 'button');
}
if (button.data.act) {
button.data.act = button.data.act.replace('listbutton', 'button');
}
rcmail.buttons[button.command][button.index] = button.data;
rcmail.init_button(button.command, button.data);
});
// buttons that should be hidden on small screen devices
$('[data-hidden]').each(function() {
var m, v = $(this).data('hidden'),
parent = $(this).parent('li'),
re = /(large|big|small|phone|lbs)/g;
while (m = re.exec(v)) {
$(parent.length ? parent : this).addClass('hidden-' + m[1]);
}
});
// Modify normal checkboxes on lists so they are different
// than those used for row selection, i.e. use icons
$('[data-list]').each(function() {
$('input[type=checkbox]', this).each(function() { pretty_checkbox(this); });
});
// Assign .formcontainer class to the iframe body, when it
// contains .formcontent and .formbuttons.
if (is_framed) {
$('.formcontent').each(function() {
if ($(this).next('.formbuttons').length) {
$(this).parent().addClass('formcontainer');
}
});
}
// move "Download all attachments" button into a better location
$('#attachment-list + a.zipdownload').appendTo('.header-links');
if (ios = $('html').is('.ipad,.iphone')) {
$('.iframe-wrapper, .scroller').addClass('ios-scroll');
}
if ($('html').filter('.ipad,.iphone,.webkit.mobile,.webkit.tablet').addClass('webkit-scroller').length) {
$(layout.menu).addClass('webkit-scroller');
}
// Set .notree class on treelist widget update
$('.treelist').each(function() {
var list = this, callback = function() {
$(list)[$('.treetoggle', list).length > 0 ? 'removeClass' : 'addClass']('notree');
};
if (window.MutationObserver) {
(new MutationObserver(callback)).observe(list, {childList: true, subtree: true});
}
callback();
});
// Store default logo path if not already set
if (!$('#logo').data('src-default')) {
$('#logo').data('src-default', $('#logo').attr('src'));
}
};
/**
* Moves form buttons into the content frame actions toolbar (for mobile)
*/
function register_content_buttons(buttons)
{
// we need these buttons really only in phone mode
if (/*mode == 'phone' && */ env.frame_nav && buttons && buttons.length) {
var toolbar = env.frame_nav.children('.buttons');
content_buttons = [];
$.each(buttons, function() {
if (this.data('target')) {
content_buttons.push(this.data('target'));
}
});
toolbar.html('').append(buttons);
}
};
/**
* Registers cloned button
*/
function register_cloned_button(old_id, new_id, active_class)
{
var button = find_button(old_id);
if (button) {
rcmail.register_button(button.command, new_id, button.data.type, active_class, button.data.sel);
}
};
/**
* Create a button clone for use in toolbar
*/
function create_cloned_button(target, menu_button, add_class)
{
var popup, click = true,
button = $('<a>'),
target_id = target.attr('id') || new Date().getTime(),
button_id = target_id + '-clone',
btn_class = target[0].className + (add_class ? ' ' + add_class : '');
if (!menu_button) {
btn_class = $.trim(btn_class.replace('btn-primary', 'primary').replace(/(btn[a-z-]*|button|disabled)/g, ''))
btn_class += ' button disabled';
}
else if (popup = target.data('popup')) {
button.data('popup', popup).data('toggle-button', target.data('toggle-button'));
popup_init(button[0]);
click = false;
}
button.attr({id: button_id, href: '#', 'class': btn_class})
.append($('<span class="inner">').text(target.text()));
if (click) {
button.on('click', function(e) { target.click(); });
}
if (is_framed && !menu_button) {
button.data('target', target);
frame_buttons.push($.extend({button_id: button_id}, find_button(target[0].id)));
}
else {
// Register the button to get active state updates
register_cloned_button(target_id, button_id, btn_class.replace(' disabled', ''));
}
return button;
};
/**
* Finds an rcmail button
*/
function find_button(id)
{
var i, button, command;
for (command in rcmail.buttons) {
for (i = 0; i < rcmail.buttons[command].length; i++) {
button = rcmail.buttons[command][i];
if (button.id == id) {
return {
command: command,
index: i,
data: button
};
}
}
}
};
/**
* Setup environment
*/
function layout_init()
{
// Select current layout element
env.last_selected = $('#layout > div.selected')[0];
if (!env.last_selected && layout.content.length) {
$.each(['sidebar', 'list', 'content'], function() {
if (layout[this].length) {
env.last_selected = layout[this][0];
layout[this].addClass('selected');
return false;
}
});
}
// Register resize handler
$(window).on('resize', function() {
clearTimeout(env.resize_timeout);
env.resize_timeout = setTimeout(function() { resize(); }, 25);
});
// Enable rcmail.open_window intercepting
env.open_window = rcmail.open_window;
rcmail.open_window = window_open;
rcmail
.addEventListener('message', message_displayed)
.addEventListener('menu-open', menu_toggle)
.addEventListener('menu-close', menu_toggle)
.addEventListener('editor-init', tinymce_init)
.addEventListener('autocomplete_create', rcmail_popup_init)
.addEventListener('googiespell_create', rcmail_popup_init)
.addEventListener('setquota', update_quota)
.addEventListener('enable-command', enable_command_handler)
.addEventListener('init', init);
// Add styling for TinyMCE editor popups
// We need to use MutationObserver, as TinyMCE does not provide any events for this
if (window.MutationObserver && window.tinymce) {
var callback = function(list) {
$.each(list, function() {
$.each(this.addedNodes, function() {
tinymce_style(this);
});
});
};
(new MutationObserver(callback)).observe(document.body, {childList: true});
}
};
/**
* rcmail 'init' event handler
*/
function init()
{
// Additional functionality on list widgets
$('table[data-list]').each(function() {
var button,
table = $(this),
list = table.data('list');
if (rcmail[list] && rcmail[list].multiselect) {
- var button, parent = table.parents('.sidebar,.list,.content').last(),
+ var repl, button,
+ parent = table.parents('.sidebar,.list,.content').last(),
header = parent.find('.header'),
toolbar = header.find('ul');
if (!toolbar.length) {
toolbar = header;
}
else if (button = toolbar.find('a.button.select').data('toggle-button')) {
button = $('#' + button);
}
// Enable checkbox selection on list widgets
rcmail[list].enable_checkbox_selection();
// Add Select button to the list navigation bar
if (!button) {
button = $('<a>').attr({'class': 'button select disabled', role: 'button', title: rcmail.gettext('select')})
.on('click', function() { if ($(this).is('.active')) table.toggleClass('withselection'); })
.append($('<span class="inner">').text(rcmail.gettext('select')));
if (toolbar.is('.toolbar') || toolbar.is('.toolbarmenu')) {
button.prependTo(toolbar).wrap('<li role="menuitem">');
// Add a button to the content toolbar menu too
if (layout.content) {
var button2 = create_cloned_button(button, true, 'hidden-big hidden-large');
$('<li role="menuitem">').append(button2).appendTo('#toolbar-menu');
button = button.add(button2);
}
}
else {
- button.appendTo(toolbar).addClass('icon');
- if (!parent.is('.sidebar')) {
- button.addClass('toolbar-button');
+ if (repl = table.data('list-select-replace')) {
+ $(repl).replaceWith(button);
+ }
+ else {
+ button.appendTo(toolbar).addClass('icon');
+ if (!parent.is('.sidebar')) {
+ button.addClass('toolbar-button');
+ }
}
}
}
// Update Select button state on list update
rcmail.addEventListener('listupdate', function(prop) {
if (prop.list && prop.list == rcmail[list]) {
if (prop.rowcount) {
button.addClass('active').removeClass('disabled').attr('tabindex', 0);
}
else {
button.removeClass('active').addClass('disabled').attr('tabindex', -1);
}
}
});
}
// https://github.com/roundcube/elastic/issues/45
// Draggable blocks scrolling on touch devices, we'll disable it there
if (touch && rcmail[list]) {
if (typeof rcmail[list].draggable == 'function') {
rcmail[list].draggable('destroy');
}
else if (typeof rcmail[list].draggable == 'boolean') {
rcmail[list].draggable = false;
}
}
});
// Display "List is empty..." on the list
if (window.MutationObserver) {
$('[data-label-msg]').filter('ul,table').each(function() {
var fn, observer, callback,
info = $('<div class="listing-info hidden">').insertAfter(this),
table = $(this),
fn = function() {
var ext, command,
msg = table.data('label-msg'),
list = table.is('ul') ? table : table.children('tbody');
if (!rcmail.env.search_request && !rcmail.env.qsearch
&& msg && !list.children(':visible').length
) {
ext = table.data('label-ext');
command = table.data('create-command');
if (ext && (!command || rcmail.commands[command])) {
msg += ' ' + ext;
}
info.text(msg).removeClass('hidden');
return;
}
info.addClass('hidden');
};
callback = function() {
// wait until the UI stops loading and the list is visible
if (rcmail.busy || !table.is(':visible')) {
return setTimeout(callback, 250);
}
clearTimeout(env.list_timer);
env.list_timer = setTimeout(fn, 50);
};
// show/hide the message when something changes on the list
observer = new MutationObserver(callback);
observer.observe(table[0], {childList: true, subtree: true, attributes: true, attributeFilter: ['style']});
// initialize the message
callback();
});
}
// Create floating action button(s)
if ((layout.list.length || layout.content.length) && is_mobile()) {
var fabuttons = [];
$('[data-fab]').each(function() {
var button = $(this),
task = button.data('fab-task') || '*',
action = button.data('fab-action') || '*';
if ((task == '*' || task == rcmail.task)
&& (action == '*' || action == rcmail.env.action || (action == 'none' && !rcmail.env.action))
) {
fabuttons.push(create_cloned_button(button));
}
});
if (fabuttons.length) {
$('<div class="floating-action-buttons">').append(fabuttons)
.appendTo(layout.list.length ? layout.list : layout.content);
}
}
// Add menu link for each attachment
if (rcmail.env.action != 'print') {
$('#attachment-list > li').each(function() {
attachmentmenu_append(this);
});
}
var phone_confirmation = function(label) {
if (mode == 'phone') {
rcmail.display_message(rcmail.gettext(label), 'confirmation');
}
};
rcmail.addEventListener('fileappended', function(e) {
if (e.attachment.complete) {
attachmentmenu_append(e.item);
if (e.attachment.mimetype == 'text/vcard' && rcmail.commands['attach-vcard']) {
phone_confirmation('vcard_attachments.vcardattached');
}
}
})
.addEventListener('managesieve.insertrow', function(o) { bootstrap_style(o.obj); })
.addEventListener('add-recipient', function() { phone_confirmation('recipientsadded'); });
rcmail.init_pagejumper('.pagenav > input');
if (rcmail.task == 'mail') {
if (rcmail.env.action == 'compose') {
// In compose window we do not provide "Back' button, instead
// we modify the Mail button in the task menu to act like it (i.e. calls 'list' command)
if (!rcmail.env.extwin) {
$('a.button.mail', layout.menu).attr('onclick', "return rcmail.command('list','',this,event)");
}
rcmail.addEventListener('compose-encrypted', function(e) {
$("a.mode-html, button.attach").prop('disabled', e.active);
$('a.button.attach, a.button.responses')[e.active ? 'addClass' : 'removeClass']('disabled');
});
$('.sidebar > .footer:not(.pagenav) > a.button').click(function() {
if ($(this).is('.disabled')) {
rcmail.display_message(rcmail.gettext('nocontactselected'), 'warning');
}
});
}
// Append contact menu to all mailto: links
if (rcmail.env.action == 'preview' || rcmail.env.action == 'show') {
$('a').filter('[href^="mailto:"]').each(function() {
mailtomenu_append(this);
});
}
}
else if (rcmail.task == 'settings') {
rcmail.addEventListener('identity-encryption-show', function(p) {
bootstrap_style(p.container);
});
rcmail.addEventListener('identity-encryption-update', function(p) {
bootstrap_style(p.container);
});
}
rcmail.env.thread_padding = '1.5rem';
// Update layout after initialization (again)
// In devel mode we have to wait until all styles are applied by less
if (rcmail.env.devel_mode && window.less) {
less.pageLoadFinished.then(function() {
resize();
});
}
else {
resize();
}
// Add date format placeholder to datepicker inputs
var func, format;
if (format = rcmail.env.date_format_localized) {
func = function(input) { $(input).filter('.datepicker').attr('placeholder', format); };
$('input.datepicker').each(function() { func(this); });
rcmail.addEventListener('insert-edit-field', func);
}
};
/**
* Apply bootstrap classes to html elements
*/
function bootstrap_style(context)
{
if (!context) {
context = document;
}
// Buttons
$('input.button,button', context).not('.btn').addClass('btn').not('.btn-primary,.primary,.mainaction').addClass('btn-secondary');
$('input.button.mainaction,button.primary,button.mainaction', context).addClass('btn-primary');
$('button.btn.delete,button.btn.discard', context).addClass('btn-danger');
$.each(['warning', 'error', 'information', 'confirmation'], function() {
var type = this;
$('.box' + type + ':not(.ui.alert)', context).each(function() {
alert_style(this, type, true);
});
});
// Convert structure of single dialogs (one input or just an image),
// e.g. group create, attachment rename where we use <label>Label<input></label>
if (context != document && $('.popup', context).children().length == 1) {
var content = $('.popup', context).children().first();
if (content.is('img')) {
$('.popup', context).addClass('justified');
}
else if (content.is('label')) {
var input = content.find('input').detach(),
label = content.detach(),
id = input.attr('id');
if (!id) {
input.attr('id', id = 'dialog-input-elastic');
}
$('.popup', context).addClass('formcontent').append(
$('<div class="form-group row">')
.append(label.attr('for', id).addClass('col-sm-2 col-form-label'))
.append($('<div class="col-sm-10">').append(input))
);
input.focus();
}
}
// Forms
- var supported_controls = 'input:not(.button,[type=button],[type=file],[type=radio],[type=checkbox]),textarea';
+ var supported_controls = 'input:not(.button,.no-bs,[type=button],[type=file],[type=radio],[type=checkbox]),textarea';
$(supported_controls, $('.propform', context)).addClass('form-control');
$('[type=checkbox]', $('.propform', context)).addClass('form-check-input');
// Note: On selects we add form-control to get consistent focus
// and to not have to create separate rules for selects and inputs
$('select', context).addClass('form-control custom-select');
if (context != document) {
$(supported_controls, context).addClass('form-control');
}
$('table.propform', context).each(function() {
var text_rows = 0, form_rows = 0;
$(this).find('> tbody > tr').each(function() {
var first, last, row = $(this),
row_classes = ['form-group', 'row'],
cells = row.children('td');
if (cells.length == 2) {
first = cells.first();
last = cells.last();
$('label', first).addClass('col-form-label');
first.addClass('col-sm-4');
last.addClass('col-sm-8');
if (last.find('[type=checkbox]').length == 1 && !last.find('.proplist').length) {
row_classes.push('form-check');
if (last.find('a').length) {
row_classes.push('with-link');
}
form_rows++;
}
else if (!last.find('input:not([type=hidden]),textarea,radio,select').length) {
last.addClass('form-control-plaintext');
text_rows++;
}
else {
form_rows++;
}
// style some multi-input fields
if (last.children('.datepicker') && last.children('input').length == 2) {
last.addClass('datetime');
}
}
else if (cells.length == 1) {
cells.css('width', '100%');
}
row.addClass(row_classes.join(' '));
});
if (text_rows > form_rows) {
$(this).addClass('text-only');
}
});
// Special input + anything entry
$('td.input-group', context).each(function() {
$(this).children(':not(:first)').addClass('input-group-append');
});
// Other forms, e.g. Contact advanced search
$('fieldset.propform:not(.groupped) div.row', context).each(function() {
var has_input = $('input:not([type=hidden]),select,textarea', this).length > 0;
if (has_input) {
$(supported_controls, this).addClass('form-control');
}
$(this).children().last().addClass('col-sm-8' + (!has_input ? ' form-control-plaintext' : ''));
$(this).children().first().addClass('col-sm-4 col-form-label');
$(this).addClass('form-group');
});
// Contact info/edit form
$('fieldset.propform.groupped fieldset', context).each(function() {
$('.row', this).each(function() {
var label, first,
has_input = $('input,select,textarea', this).length > 0,
items = $(this).children();
if (has_input) {
$(supported_controls, this).addClass('form-control');
}
if (items.length < 2) {
return;
}
first = items.first();
if (first.is('select')) {
first.addClass('input-group-prepend');
}
else {
first.wrap('<span class="input-group-prepend">').addClass('input-group-text');
}
if (!has_input) {
items.last().addClass('form-control-plaintext');
}
$('.content', this).addClass('input-group-prepend input-group-append input-group-text');
$('a.deletebutton', this).addClass('input-group-text icon delete').wrap('<span class="input-group-append">');
$(this).addClass('input-group');
});
});
// Other forms, e.g. Insert response
$('.propform > .prop.block:not(.row)', context).each(function() {
$(this).addClass('form-group row').each(function() {
$('label', this).addClass('col-form-label').wrap($('<div class="col-sm-4">'));
$('input,select,textarea', this).wrap($('<div class="col-sm-8">'));
$(supported_controls, this).addClass('form-control');
});
});
$('td.rowbuttons > a', context).addClass('btn');
// Testing Bootstrap Tabs on contact info/edit page
// Tabs do not scale nicely on very small screen, so can be used
// only with small number of tabs with short text labels
$('form.tabbed,div.tabbed', context).each(function(idx, item) {
var tabs = [], nav = $('<ul>').attr({'class': 'nav nav-tabs', role: 'tablist'});
$(this).addClass('tab-content').children('fieldset').each(function(i, fieldset) {
var tab, id = fieldset.id || ('tab' + idx + '-' + i),
tab_class = $(fieldset).data('navlink-class');
$(fieldset).addClass('tab-pane').attr({id: id, role: 'tabpanel'});
tab = $('<li>').addClass('nav-item').append(
$('<a>').addClass('nav-link' + (tab_class ? ' ' + tab_class : ''))
.attr({role: 'tab', 'href': '#' + id})
.text($('legend:first', fieldset).text())
.click(function() {
$(this).tab('show');
// Returning false here prevents from strange scrolling issue
// when the form is in an iframe, e.g. contact edit form
return false;
})
);
$('legend:first', fieldset).hide();
tabs.push(tab);
});
// create the navigation bar
nav.append(tabs).insertBefore(item);
// activate the first tab
$('a.nav-link:first', nav).click();
});
// Make tables pretier
$('table:not(.table,.propform,.listing,.ui-datepicker-calendar)', context)
.filter(function() {
// exclude direct propform children and external content
return !$(this).parent().is('.propform')
&& !$(this).parents('.message-htmlpart,.message-partheaders,.boxinformation,.raw-tables').length;
})
.each(function() {
// TODO: Consider implementing automatic setting of table-responsive on window resize
var table = $(this).addClass('table');
table.parent().addClass('table-responsive-sm');
table.find('thead').addClass('thead-default');
});
// The same for some other checkboxes
// We do this here, not in setup() because we want to cover dialogs
$('input.pretty-checkbox, .propform input[type=checkbox], .form-check > input, .popupmenu.form input[type=checkbox], .toolbarmenu input[type=checkbox]', context)
.each(function() { pretty_checkbox(this); });
// Also when we add action-row of the form, e.g. Managesieve plugin adds them after the page is ready
if ($(context).is('.actionrow')) {
$('input[type=checkbox]', context).each(function() { pretty_checkbox(this); });
}
// Input-group combo is an element with a select field on the left
// and input(s) on right, and where the whole right side can be hidden
// depending on the select position. This code fixes border radius on select
$('.input-group-combo > select:first', context).on('change', function() {
var select = $(this),
fn = function() {
select[select.next().is(':visible') ? 'removeClass' : 'addClass']('alone');
};
setTimeout(fn, 50);
setTimeout(fn, 2000); // for devel mode
}).trigger('change');
// Make message-objects alerts pretty (the same as UI alerts)
$('#message-objects', context).children(':not(.ui.alert)').each(function() {
// message objects with notice class are really warnings
var cl = $(this).removeClass('notice').attr('class').split(/\s/)[0] || 'warning';
alert_style(this, cl);
$(this).addClass('box' + cl);
$('a', this).addClass('btn btn-primary');
});
// Style calendar widget (we use setTimeout() because there's no widget event we could bind to)
$('input.datepicker', context).focus(function() {
setTimeout(function() { bootstrap_style($('.ui-datepicker')); }, 5);
});
// Form validation errors (managesieve plugin)
$('.error', context).addClass('is-invalid');
// Make logon form prettier
if (rcmail.env.task == 'login' && context == document) {
$('#login-form table tr').each(function() {
var input = $('input,select', this),
label = $('label', this),
icon_name = input.data('icon'),
icon = $('<i>').attr('class', 'input-group-text icon ' + input.attr('name').replace('_', ''));
if (icon_name) {
icon.addClass(icon_name);
}
$(this).addClass('form-group row');
label.parent().css('display', 'none');
input.addClass(input.is('select') ? 'custom-select' : 'form-control')
.attr('placeholder', label.text())
.before($('<span class="input-group-prepend">').append(icon))
.parent().addClass('input-group');
});
}
$('select:not([multiple])', context).each(function() { pretty_select(this); });
};
/**
* Detects if the element is TinyMCE dialog/menu
* and adds Elastic styling to it
*/
function tinymce_style(elem)
{
// TinyMCE dialog widnows
if ($(elem).is('.mce-window')) {
var body = $(elem).find('.mce-window-body'),
foot = $(elem).find('.mce-foot > .mce-container-body');
// Apply basic forms style
if (body.length) {
bootstrap_style(body[0]);
}
body.find('button').filter(function() { return $(this).parent('.mce-btn').length > 0; }).removeClass('btn btn-secondary');
// Fix icons in Find and Replace dialog footer
if (foot.children('.mce-widget').length === 5) {
foot.addClass('mce-search-foot');
}
// Apply some form structure fixes and helper classes
$(elem).find('.mce-charmap').parent().parent().addClass('mce-charmap-dialog');
$(elem).find('.mce-combobox').each(function() {
if (!$(this).children('.mce-btn').length) {
$(this).addClass('mce-combobox-fake');
}
});
$(elem).find('.mce-form > .mce-container-body').each(function() {
if ($(this).children('.mce-formitem').length > 4) {
$(this).addClass('mce-form-split');
}
});
$(elem).find('.mce-form').next(':not(.mce-formitem)').addClass('mce-form');
// Fix dialog height (e.g. Table properties dialog)
if (!is_mobile()) {
var offset, max_height = 0, height = body.height();
$(elem).find('.mce-form').each(function() {
max_height = Math.max(max_height, $(this).height());
});
if (height < max_height) {
max_height += (body.find('.mce-tabs').height() || 0) + 25;
body.height(max_height);
$(elem).height($(elem).height() + (max_height - height));
$(elem).css('top', ($(window).height() - $(elem).height())/2 + 'px');
}
}
}
// TinyMCE menus on mobile
else if ($(elem).is('.mce-menu')) {
$(elem).prepend(
$('<h3 class="popover-header">').append(
$('<a class="button icon "' + 'cancel' + '">')
.text(rcmail.gettext('close'))
.on('click', function() { $(document.body).click(); })));
if (window.MutationObserver) {
var callback = function() {
if (mode != 'phone') {
return;
}
if (!$('.mce-menu:visible').length) {
$('div.mce-overlay').click();
}
else if (!$('div.mce-overlay').length) {
$('<div>').attr('class', 'popover-overlay mce-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
};
(new MutationObserver(callback)).observe(elem, {attributes: true});
}
}
};
/**
* Initializes popup menus
*/
function dropdowns_init()
{
$('[data-popup]').each(function() { popup_init(this); });
$(document).on('click', popups_close);
rcube_webmail.set_iframe_events({mousedown: popups_close, touchstart: popups_close});
};
/**
* Init content frame
*/
function content_frame_init()
{
var last_selected = env.last_selected,
title_reset = function(title) {
if (typeof title !== 'string' || !title.length) {
title = $('h1.voice').text() || $('title').text() || '';
}
$('.header > .header-title', layout.content).text(title);
};
// display or reset the content frame
var common_content_handler = function(e, href, show, title)
{
if (is_mobile()) {
content_frame_navigation(href, e);
}
if (show && !layout.content.is(':visible')) {
env.last_selected = layout.content[0];
screen_resize();
if (title) {
title_reset(title);
}
}
else if (!show) {
if (env.last_selected != last_selected && !env.content_lock) {
env.last_selected = last_selected;
screen_resize();
}
title_reset();
}
env.content_lock = false;
};
var common_list_handler = function(e) {
if (mode != 'large' && !env.content_lock && e.force) {
show_list();
}
+ env.content_lock = false;
+
// display current folder name in list header
if (e.title) {
$('.header > .header-title', layout.list).text(e.title);
}
};
var list_handler = function(e) {
var args = {};
+ if (rcmail.env.task == 'addressbook' || rcmail.env.task == 'mail') {
+ args.force = true;
+ }
+
// display current folder name in list header
if (rcmail.env.task == 'mail' && !rcmail.env.action) {
var name = $.type(e) == 'string' ? e : rcmail.env.mailbox,
folder = rcmail.env.mailboxes[name];
args.title = folder ? folder.name : '';
}
common_list_handler(args);
};
// when loading content-frame in small-screen mode display it
layout.content.find('iframe').on('load', function(e) {
var href = '', show = true;
// Reset the scroll position of the iframe-wrapper
$(this).parent('.iframe-wrapper').scrollTop(0);
try {
href = e.target.contentWindow.location.href;
show = !href.endsWith(rcmail.env.blankpage);
// Reset title back to the default
$(e.target.contentWindow).on('unload', title_reset);
}
catch(e) { /* ignore */ }
common_content_handler(e, href, show);
});
rcmail
.addEventListener('afterlist', list_handler)
.addEventListener('afterlistgroup', list_handler)
.addEventListener('afterlistsearch', list_handler)
// plugins
.addEventListener('show-list', function(e) {
+ e.force = true;
common_list_handler(e);
})
.addEventListener('show-content', function(e) {
if (!$(e.obj).is('iframe')) {
$(e.scrollElement || e.obj).scrollTop(0);
if (is_mobile()) {
iframe_loader(e.obj);
}
}
common_content_handler(e.event || new Event, '_action=' + (e.mode || 'edit'), true, e.title);
});
};
/**
* Content frame navigation
*/
function content_frame_navigation(href, event)
{
// Don't display navigation for create/add action frames
if (href.match(/_action=(create|add)/) || href.match(/_nav=hide/)) {
$(env.frame_nav).addClass('hide-nav-buttons');
return;
}
var node, uid, list, _list = $('[data-list]', layout.list).data('list');
if (!_list || !(list = rcmail[_list])) {
// hide navbar if there are no visible buttons, e.g. Help plugin UI
if ($(env.frame_nav).is('.hide-nav-buttons') && !$('.buttons', env.frame_nav).children().length) {
$(env.frame_nav).addClass('hidden');
}
return;
}
$(env.frame_nav).removeClass('hide-nav-buttons hidden');
// expand collapsed row so we do not skip the whole thread
// TODO: Unified interface for list and treelist widgets
if (uid = list.get_single_selection()) {
if (list.rows && list.rows[uid] && !list.rows[uid].expanded) {
list.expand_row(event, uid);
}
else if (list.get_node && (node = list.get_node(uid)) && node.collapsed) {
list.expand(uid);
}
}
var prev, next,
frame = $('#' + rcmail.env.contentframe),
next_button = $('a.button.next', env.frame_nav).off('click').addClass('disabled'),
prev_button = $('a.button.prev', env.frame_nav).off('click').addClass('disabled');
if ((next = list.get_next()) || rcmail.env.current_page < rcmail.env.pagecount) {
next_button.removeClass('disabled').on('click', function() {
env.content_lock = true;
iframe_loader(frame);
if (next) {
list.select(next);
}
else {
rcmail.env.list_uid = 'FIRST';
rcmail.command('nextpage');
}
});
}
if (((prev = list.get_prev()) && (prev != '*' || _list != 'subscription_list')) || rcmail.env.current_page > 1) {
prev_button.removeClass('disabled').on('click', function() {
env.content_lock = true;
iframe_loader(frame);
if (prev) {
list.select(prev);
}
else {
rcmail.env.list_uid = 'LAST';
rcmail.command('previouspage');
}
});
}
};
/**
* Handler for editor-init event
*/
function tinymce_init(o)
{
// Enable autoresize plugin
o.config.plugins += ' autoresize';
if (is_touch()) {
// Make the toolbar icons bigger
o.config.toolbar_items_size = null;
// Use minimalistic toolbar
o.config.toolbar = 'undo redo | insert | styleselect';
if (o.config.plugins.match(/emoticons/)) {
o.config.toolbar += ' emoticons';
}
}
+
+ if (rcmail.task == 'mail' && rcmail.env.action == 'compose') {
+ var form = $('#compose-content > form'),
+ keypress = function(e) {
+ if (e.key == 'Tab' && e.shiftKey) {
+ $('#compose-content > form').scrollTop(0);
+ }
+ };
+
+ // Shift+Tab on mail compose editor scrolls the page to the top
+ o.config.setup_callback = function(ed) {
+ ed.on('keypress', keypress);
+ };
+
+ $('#composebody').on('keypress', keypress);
+
+ // Keep the editor toolbar on top of the screen on scroll
+ form.on('scroll', function() {
+ var container = $('.mce-container-body', form),
+ toolbar = $('.mce-top-part', container),
+ editor_offset = container.offset(),
+ header_top = form.offset().top;
+
+ if (editor_offset && (editor_offset.top - header_top < 0)) {
+ toolbar.css({position: 'fixed', top: header_top + 'px', width: container.width() + 'px'});
+ }
+ else {
+ toolbar.css({position: 'relative', top: 0, width: 'auto'})
+ }
+ });
+
+ $(window).resize(function() { form.trigger('scroll'); });
+ }
};
/**
* Handler for some Roundcube core popups
*/
function rcmail_popup_init(o)
{
// Add some common styling to the autocomplete/googiespell popups
$('table,ul', o.obj).addClass('toolbarmenu listing iconized');
$(o.obj).addClass('popupmenu popover');
bootstrap_style(o.obj);
// for googiespell list
$('input', o.obj).addClass('form-control');
// Modify the googiespell menu on mobile
if (is_mobile() && $(o.obj).is('.googie_window')) {
// Set popup Close title
var title = rcmail.gettext('close'),
class_name = 'button icon cancel',
close_link = $('<a>').attr('class', class_name).text(title)
.click(function(e) {
e.stopPropagation();
$('.popover-overlay').remove();
$(o.obj).hide();
});
$('<h3 class="popover-header">').append(close_link).prependTo(o.obj);
// add overlay element for phone layout
if (!$('.popover-overlay').length) {
$('<div>').attr('class', 'popover-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
$('table,button', o.obj).click(function(e) {
if (!$(e.target).is('input')) {
$('.popover-overlay').remove();
}
});
}
};
/**
* Handler for 'enable-command' event
*/
function enable_command_handler(args)
{
if (is_framed) {
$.each(frame_buttons, function(i, button) {
if (args.command == button.command) {
parent.$('#' + button.button_id)[args.status ? 'removeClass' : 'addClass']('disabled');
}
});
}
if (rcmail.task == 'mail') {
switch (args.command) {
case 'reply-list':
if (rcmail.env.reply_all_mode == 1) {
var label = rcmail.gettext(args.status ? 'replylist' : 'replyall');
$('a.button.reply-all').attr('title', label).find('.inner').text(label);
}
break;
case 'compose-encrypted':
// show the toolbar button for Mailvelope
if (args.status) {
$('a.button.encrypt').parent().show();
}
break;
case 'compose-encrypted-signed':
// enable selector for encrypt and sign
$('#encryption-menu-button').show();
break;
case 'mark':
// show the toolbar button for Mailvelope
$('a.button.markmessage')[args.status ? 'removeClass' : 'addClass']('disabled');
break;
}
}
};
/**
* screen mode
*/
function screen_mode()
{
var size, width = $(window).width();
if (width <= 480)
size = 'phone';
else if (width > 1200)
size = 'large';
else if (width > 768)
size = 'normal';
else
size = 'small';
touch = width <= 1024;
mode = size;
};
/**
* Window resize handler
* Does layout reflows e.g. on screen orientation change
*/
function resize()
{
var mobile;
screen_mode();
screen_resize();
screen_resize_html();
// disable ext-windows and other features
if (mobile = is_mobile()) {
rcmail.set_env(env.small_screen_config);
rcmail.enable_command('extwin', false);
}
else {
rcmail.set_env(env.config);
rcmail.enable_command('extwin', true);
}
// Hide content frame buttons on small devices (with frame toolbar in parent window)
$.each(content_buttons, function() { $(this)[mobile ? 'hide' : 'show'](); });
};
function screen_resize()
{
if (is_framed && !layout.sidebar.length && !layout.list.length) {
return;
}
switch (mode) {
case 'phone': screen_resize_phone(); break;
case 'small': screen_resize_small(); break;
case 'normal': screen_resize_normal(); break;
case 'large': screen_resize_large(); break;
}
screen_resize_logo(mode);
screen_resize_headers();
// On iOS and Android the content frame height is never correct, fix it
if (bw.webkit) {
$('.iframe-wrapper').each(function() {
var h = $(this).height();
if (h) {
$(this).children('iframe').height(h);
}
});
}
};
/**
* Assigns layout-* and touch-mode class to the 'html' element
*
* If we're inside an iframe that is small we have to
* check if the parent window is also small (mobile).
* We use that e.g. to still display desktop-like popovers in dialogs
*/
function screen_resize_html()
{
var meta = layout_metadata(),
html = $(document.documentElement);
if (html[0].className.match(/layout-([a-z]+)/)) {
if (RegExp.$1 != meta.mode) {
html.removeClass('layout-' + RegExp.$1)
.addClass('layout-' + meta.mode);
}
}
else {
html.addClass('layout-' + meta.mode);
}
if (meta.touch && !html.is('.touch')) {
html.addClass('touch');
}
else if (!meta.touch && html.is('.touch')) {
html.removeClass('touch');
}
};
function screen_resize_logo(mode)
{
if (mode == 'phone' && $('#logo').data('src-small')) {
$('#logo').attr('src', $('#logo').data('src-small'));
}
else {
$('#logo').attr('src', $('#logo').data('src-default'));
}
}
/**
* Sets left and right margin to the header title element to make it
* properly centered depending on the number of buttons on both sides
*/
function screen_resize_headers()
{
$('#layout > div > .header').each(function() {
var title, right = 0, left = 0, padding = 0,
sizes = {left: 0, right: 0};
$(this).children(':visible').each(function() {
if (!title && $(this).is('.header-title')) {
title = $(this);
return;
}
sizes[title ? 'right' : 'left'] += this.offsetWidth;
});
if (padding + sizes.right >= sizes.left) {
right = 0;
left = sizes.right + padding - sizes.left;
}
else {
left = 0;
right = sizes.left - (padding + sizes.right);
}
$(title).css({
'margin-right': right + 'px',
'margin-left': left + 'px',
'padding-right': padding + 'px'
});
});
};
function screen_resize_phone()
{
screen_resize_small_all();
app_menu(false);
};
function screen_resize_small()
{
screen_resize_small_all();
app_menu(true);
};
function screen_resize_normal()
{
var show;
if (layout.list.length) {
show = layout.list.is(env.last_selected) || (!layout.sidebar.is(env.last_selected) && !layout.sidebar.is('.layout-sticky'));
layout.list[show ? 'removeClass' : 'addClass']('hidden');
}
if (layout.sidebar.length) {
show = !layout.list.length || layout.sidebar.is(env.last_selected) || layout.sidebar.is('.layout-sticky');
layout.sidebar[show ? 'removeClass' : 'addClass']('hidden');
}
layout.content.removeClass('hidden');
app_menu(true);
screen_resize_small_none();
if (layout.list) {
$('.header > ul.toolbar', layout.list).addClass('popupmenu toolbarmenu').removeClass('toolbar');
}
};
function screen_resize_large()
{
$.each(layout, function(name, item) { item.removeClass('hidden'); });
screen_resize_small_none();
if (layout.list) {
$('.header > ul.toolbarmenu.popupmenu', layout.list).removeClass('popupmenu toolbarmenu').addClass('toolbar');
}
};
function screen_resize_small_all()
{
var show, got_content = false;
if (layout.content.length) {
show = got_content = layout.content.is(env.last_selected);
layout.content[show ? 'removeClass' : 'addClass']('hidden');
$('.header > ul.toolbar', layout.content).addClass('popupmenu');
}
if (layout.list.length) {
show = !got_content && layout.list.is(env.last_selected);
layout.list[show ? 'removeClass' : 'addClass']('hidden');
$('.header > ul.toolbar', layout.list).addClass('popupmenu');
}
if (layout.sidebar.length) {
show = !got_content && (layout.sidebar.is(env.last_selected) || !layout.list.length);
layout.sidebar[show ? 'removeClass' : 'addClass']('hidden');
}
if (got_content) {
buttons.back_list.show();
}
};
function screen_resize_small_none()
{
buttons.back_list.filter(function() { return $(this).parents('.sidebar').length == 0; }).hide();
$('ul.toolbar.popupmenu').removeClass('popupmenu');
};
function show_content(unsticky)
{
// show sidebar and hide list
layout.list.addClass('hidden');
layout.sidebar.addClass('hidden');
layout.content.removeClass('hidden');
if (unsticky) {
layout.sidebar.removeClass('layout-sticky');
}
screen_resize_headers();
env.last_selected = layout.content[0];
};
function show_sidebar(sticky)
{
// show sidebar and hide list
layout.list.addClass('hidden');
layout.sidebar.removeClass('hidden');
if (sticky) {
layout.sidebar.addClass('layout-sticky');
}
if (mode == 'small' || mode == 'phone') {
layout.content.addClass('hidden');
}
screen_resize_headers();
env.last_selected = layout.sidebar[0];
};
function show_list(scroll)
{
if (!layout.list.length && !layout.sidebar.length) {
history.back();
}
else {
// show list and hide sidebar and content
layout.sidebar.addClass('hidden').removeClass('layout-sticky');
layout.list.removeClass('hidden');
if (mode == 'small' || mode == 'phone') {
hide_content();
}
if (scroll) {
layout.list.children('.scroller').scrollTop(0);
}
env.last_selected = layout.list[0];
}
screen_resize_headers();
};
function hide_content()
{
// show sidebar or list, hide content frame
env.last_selected = layout.list[0] || layout.sidebar[0];
screen_resize();
// reset content frame, so we can load it again
rcmail.show_contentframe(false);
// now we have to unselect selected row on the list
$('[data-list]', layout.list).each(function() {
var list = $(this).data('list');
if (rcmail[list]) {
if (rcmail[list].clear_selection) {
rcmail[list].clear_selection(); // list widget
}
else if (rcmail[list].select) {
rcmail[list].select(); // treelist widget
}
}
});
};
// show menu widget
function app_menu(show)
{
if (show) {
if (mode == 'phone') {
$('<div id="menu-overlay" class="popover-overlay">')
.on('click', function() { app_menu(false); })
.appendTo('body');
if (!env.menu_initialized) {
env.menu_initialized = true;
$('a', layout.menu).on('click', function(e) { if (mode == 'phone') app_menu(); });
}
layout.menu.addClass('popover');
}
layout.menu.removeClass('hidden');
}
else {
$('#menu-overlay').remove();
layout.menu.addClass('hidden').removeClass('popover');
}
};
/**
* Triggered when a UI message is displayed
*/
function message_displayed(p)
{
if (p.type == 'loading' && $('.iframe-loader:visible').length) {
// hide original message object, we don't need two "loaders"
rcmail.hide_message(p.object);
return;
}
alert_style(p.object, p.type, true);
$(p.object).attr('role', 'alert');
// $('a', p.object).addClass('alert-link');
};
/**
* Applies some styling and icon to an alert object
*/
function alert_style(object, type, wrap)
{
var tmp, classes = 'ui alert',
addicon = !$(object).is('.noicon'),
map = {
information: 'alert-info',
notice: 'alert-info',
confirmation: 'alert-success',
warning: 'alert-warning',
error: 'alert-danger',
loading: 'alert-info loading',
uploading: 'alert-info loading',
vcardattachment: 'alert-info' /* vcard_attachments plugin */
};
// Type can be e.g. 'notice chat'
type = type.split(' ')[0];
if (wrap && addicon && !$(object).is('.aligned-buttons')) {
// we need the content to be non-text node for best alignment
tmp = $(object).html();
$(object).html($('<span>').html(tmp));
}
if (tmp = map[type]) {
classes += ' ' + tmp;
if (addicon) {
$('<i>').attr('class', 'icon').prependTo(object);
}
}
$(object).addClass(classes);
};
/**
* Set UI dialogs size/style depending on screen size
*/
function dialog_open(dialog)
{
var me = $(dialog.uiDialog),
width = me.width(),
height = me.height(),
maxWidth = $(window).width(),
maxHeight = $(window).height();
if (maxWidth <= 480) {
me.css({width: '100%', height: '100%'});
}
else {
if (height > maxHeight) {
me.css('height', '100%');
}
if (width > maxWidth) {
me.css('width', '100%');
}
}
// Close all popovers
$(document).click();
// Display loader when the dialog has an iframe
iframe_loader($('div.popup > iframe', me));
// TODO: style buttons/forms
bootstrap_style(dialog.uiDialog);
};
/**
* Initializes searchbar widget
*/
function searchbar_init(bar)
{
var options_button = $('a.button.options', bar),
input = $('input:not([type=hidden])', bar),
placeholder = input.attr('placeholder'),
form = $('form', bar),
is_search_pending = function() {
if (input.val()) {
return true;
}
if (rcmail.task == 'mail' && $('#s_interval').val()) {
return true;
}
if (rcmail.gui_objects.search_filter && $(rcmail.gui_objects.search_filter).val() != 'ALL') {
return true;
}
if (rcmail.gui_objects.foldersfilter && $(rcmail.gui_objects.foldersfilter).val() != '---') {
return true;
}
},
update_func = function() {
$(bar)[is_search_pending() ? 'addClass' : 'removeClass']('active');
};
options_button.on('click', function(e) {
var id = $(this).data('target'),
options = $('#' + id),
open = options.is(':visible');
if (options.length) {
if (!open) {
if (ref[id]) {
ref[id](options.get(0), this, e);
}
else if (typeof window[id] == 'function') {
window[id](options.get(0), this, e);
}
}
options.next()[open ? 'show' : 'hide']();
options.toggleClass('hidden');
$('.floating-action-buttons').toggleClass('hidden');
$(bar).toggleClass('open');
$('button.search', options).off('click.search').on('click.search', function() {
options_button.trigger('click');
update_func();
});
}
});
input.on('input change', update_func)
.on('focus blur', function(e) { input.attr('placeholder', e.type == 'blur' ? placeholder : ''); });
// Search reset action
$('a.reset', bar).on('click', function(e) {
// for treelist widget's search setting val and keyup.treelist is needed
// in normal search form reset-search command will do the trick
input.val('').change().trigger('keyup.treelist', {keyCode: 27});
if ($(bar).is('.open')) {
options_button.click();
}
// Reset filter
if (rcmail.gui_objects.search_filter) {
$(rcmail.gui_objects.search_filter).val('ALL');
}
if (rcmail.gui_objects.foldersfilter) {
$(rcmail.gui_objects.foldersfilter).val('---').change();
rcmail.folder_filter('---');
}
update_func();
});
rcmail.addEventListener('init', function() { update_func(); })
.addEventListener('beforelist', function() {
if ($(bar).is('.open')) {
options_button.click(); // close options form on 'list' request
}
})
.addEventListener('responsebeforesearch', function() {
update_func();
});
};
/**
* Converts toolbar menu into popup-menu for small screens
*/
function toolbar_init()
{
if (env.got_smart_toolbar) {
return;
}
env.got_smart_toolbar = true;
var list_mark, items = [],
list_items = [],
meta = layout_metadata(),
button_func = function(button, items, cloned) {
var item = $('<li role="menuitem">'),
button = cloned ? create_cloned_button($(button), true, 'hidden-big hidden-large') : $(button).detach();
// Remove empty text nodes that break alignment of text of the menu item
button.contents().filter(function() { if (this.nodeType == 3 && !$.trim(this.nodeValue).length) $(this).remove(); });
if (button.is('.spacer')) {
item.addClass('spacer');
}
else {
item.append(button);
}
items.push(item);
};
// convert content toolbar to a popup list
if (layout.content) {
$('.header > .toolbar', layout.content).each(function() {
var toolbar = $(this);
toolbar.children().each(function() { button_func(this, items); });
toolbar.remove();
});
}
// convert list toolbar to a popup list
if (layout.list) {
$('.header > .toolbar', layout.list).each(function() {
var toolbar = $(this);
list_mark = toolbar.next();
toolbar.children().each(function() {
if (meta.mode != 'large') {
// TODO: Would be better to set this automatically on submenu display
// i.e. in show/shown event (see popup_init()), if possible
$(this).data('popup-pos', 'right');
}
// add items to the content menu too
button_func(this, items, true);
button_func(this, list_items);
});
toolbar.remove();
});
}
// special elements to clone and add to the toolbar (mobile only)
$('ul[data-menu="toolbar-small"] > li > a').each(function() {
var button = $(this).clone();
button.attr('id', this.id + '_clone');
// TODO: rcmail.register_button()
items.push($('<li role="menuitem">').addClass('hidden-big').append(button));
});
// append the new list toolbar and menu button
if (list_items.length) {
var container = layout.list.children('.header'),
menu_attrs = {'class': 'toolbar popupmenu listing iconized', id: 'toolbar-list-menu'},
menu_button = $('<a class="button icon toolbar-list-button" href="#list-menu">')
.attr({'data-popup': 'toolbar-list-menu'}),
// TODO: copy original toolbar attributes (class, role, aria-*)
toolbar = $('<ul>').attr(menu_attrs).data('popup-parent', container).append(list_items);
if (list_mark.length) {
toolbar.insertBefore(list_mark);
}
else {
container.append(toolbar);
}
container.append(menu_button);
}
// append the new toolbar and menu button
if (items.length) {
var container = layout.content.children('.header'),
menu_attrs = {'class': 'toolbar popupmenu listing iconized', id: 'toolbar-menu'},
menu_button = $('<a class="button icon toolbar-menu-button" href="#menu">')
.attr({'data-popup': 'toolbar-menu'});
container
// TODO: copy original toolbar attributes (class, role, aria-*)
.append($('<ul>').attr(menu_attrs).data('popup-parent', container).append(items))
.append(menu_button);
if (layout.list.length) {
// bind toolbar menu with the menu button in the list header
$('a.toolbar-menu-button', layout.list).click(function(e) {
e.stopPropagation();
menu_button.click();
});
}
}
};
/**
* Initialize a popup for specified button element
*/
function popup_init(item, win)
{
// On mobile we display the menu from the frame in the parent window
if (is_framed && is_mobile()) {
return parent.UI.popup_init(item, win || window);
}
if (!win) win = window;
var level,
popup_id = $(item).data('popup'),
popup = $(win.$('#' + popup_id).get(0)), // a "hack" to support elements in frames
popup_orig = popup,
title = $(item).attr('title'),
content_element = function() {
// On mobile we display a menu from the frame in the parent window
// To make menu actions working we have to clone the menu
// and pass click events to it...
if (win != window) {
popup = popup_orig.clone(true, true);
popup.attr('id', popup_id + '-clone')
.appendTo(document.body)
.find('li > a, li.checkbox > label').attr('onclick', '').off('click').on('click', function(e) {
if (!$(this).is('.disabled')) {
$(item).popover('hide');
win.$('#' + $(this).attr('id')).click();
}
return false;
});
}
return popup.get(0);
};
$(item).attr({
'aria-haspopup': 'true',
'aria-expanded': 'false',
'aria-owns': popup_id,
})
.popover({
content: content_element,
trigger: $(item).data('popup-trigger') || 'click',
placement: $(item).data('popup-pos') || 'bottom',
animation: true,
boundary: 'window', // fix for https://github.com/twbs/bootstrap/issues/25428
html: true
})
.on('show.bs.popover', function(event) {
var init_func = popup.data('popup-init');
if (popup_id && menus[popup_id]) {
menus[popup_id].transitioning = true;
}
if (init_func && ref[init_func]) {
ref[init_func](popup.get(0), item, event);
}
else if (init_func && win[init_func]) {
win[init_func](popup.get(0), item, event);
}
level = $('div.popover:visible').length + 1;
popup.removeClass('hidden').attr('aria-hidden', false)
// Stop propagation on menu items that have popups
// to make a click on them not hide their parent menu(s)
.find('[aria-haspopup="true"]')
.data('level', level + 1)
.off('click.popup')
.on('click.popup', function(e) { e.stopPropagation(); });
if (!is_mobile()) {
// Set popup height so it is less than the window height
popup.css('max-height', Math.min(36 * 15 - 1, $(window).height() - 30));
}
})
.on('shown.bs.popover', function(event) {
var mobile = is_mobile(),
popover = $('#' + $(item).attr('aria-describedby'));
level = $(item).data('level') || 1;
// Set popup Back/Close title
if (mobile) {
var label = level > 1 ? 'back' : 'close',
title = rcmail.gettext(label),
class_name = 'button icon ' + (label == 'back' ? 'back' : 'cancel');
$('.popover-header', popover).empty()
.append($('<a>').attr('class', class_name).text(title)
.on('click', function(e) {
$(item).popover('hide');
if (level > 1) {
e.stopPropagation();
}
})
.on('mousedown', function(e) {
// stop propagation to i.e. do not close jQuery-UI dialogs below
e.stopPropagation();
})
);
}
// Hide other menus on the same level
$.each(menus, function(id, prop) {
if ($(prop.target).data('level') == level && id != popup_id) {
menu_hide(id);
}
});
// On keyboard event focus the first (active) entry and enable keyboard navigation
if ($(item).data('event') == 'key') {
popover.off('keydown.popup').on('keydown.popup', 'a.active', function(e) {
var entry, node, mode = 'next';
switch (e.which) {
case 27: // ESC
case 9: // TAB
$(item).popover('toggle').focus();
return false;
case 13: // ENTER
case 32: // SPACE
$(this).trigger('click').data('event', 'key');
return false; // for IE
case 38: // ARROW-UP
case 63232:
mode = 'previous';
case 40: // ARROW-DOWN
case 63233:
entry = e.target.parentNode;
while (entry = entry[mode + 'Sibling']) {
if (node = $(entry).children('.active')[0]) {
node.focus();
break;
}
}
return false; // prevents from scrolling the whole page
}
});
popover.find('a.active:first').focus();
}
if (popup_id && menus[popup_id]) {
menus[popup_id].transitioning = false;
}
// add overlay element for phone layout
if (mobile && !$('.popover-overlay').length) {
$('<div>').attr('class', 'popover-overlay')
.appendTo('body')
.click(function() { $(this).remove(); });
}
$('.popover-body', popover).addClass('webkit-scroller');
})
.on('hide.bs.popover', function() {
if (level == 1) {
$('.popover-overlay').remove();
}
if (popup_id && menus[popup_id] && popup.is(':visible')) {
menus[popup_id].transitioning = true;
}
})
.on('hidden.bs.popover', function() {
if (/-clone$/.test(popup.attr('id'))) {
popup.remove();
}
else {
popup.attr('aria-hidden', true)
// Some menus aren't being hidden, force that
.addClass('hidden')
// Bootstrap will detach the popup element from
// the DOM (https://github.com/twbs/bootstrap/issues/20219)
// making our menus to not update buttons state.
// Work around this by attaching it back to the DOM tree.
popup.appendTo(popup.data('popup-parent') || document.body);
}
// close orphaned popovers, for some reason there are sometimes such dummy elements left
$('.popover-body:empty').each(function() { $(this).parent().remove(); });
if (popup_id && menus[popup_id]) {
delete menus[popup_id];
}
})
// Because Bootstrap does not provide originalEvent in show/shown events
// we have to handle that by our own using click and keydown handlers
.on('click', function() {
$(this).data('event', 'mouse');
})
.on('keydown', function(e) {
switch (e.originalEvent.which) {
case 13:
case 32:
// Open the popup on ENTER or SPACE
e.preventDefault();
$(this).data('event', 'key').popover('toggle');
break;
case 27:
// Close the popup on ESC key
$(this).popover('hide');
break;
}
});
// re-add title attribute removed by bootstrap popover
if (title) {
$(item).attr('title', title);
}
popup.attr('aria-hidden', 'true').data('button', item);
// stop propagation to e.g. do not hide the popup when
// clicking inside on form elements
if (popup.data('editable')) {
popup.on('click mousedown', function(e) { e.stopPropagation(); });
}
};
/**
* Closes all popups (for use as event handler)
*/
function popups_close(e)
{
$('.popover.show').each(function() {
var popup = $('.popover-body', this),
button = popup.children().first().data('button');
if (button && e.target != button && !$(button).find(e.target).length && typeof button !== 'string') {
$(button).popover('hide');
}
if (!button) {
$(this).remove();
}
});
};
/**
* Handler for menu-open and menu-close events
*/
function menu_toggle(p)
{
if (!p || !p.name || (p.props && p.props.skinable === false)) {
return;
}
if (is_framed && is_mobile()) {
if (!p.win) {
p.win = window;
}
return parent.UI.menu_toggle(p);
}
if (p.name == 'messagelistmenu') {
menu_messagelist(p);
}
else if (p.event == 'menu-open') {
var fn, pos,
content = $('ul:first', p.obj),
target = p.props && p.props.link ? p.props.link : p.originalEvent.target;
if ($(target).is('span')) {
target = $(target).parents('a,li')[0];
}
if (p.name.match(/^drag/)) {
// create a fake element to position drag menu on the cursor position
pos = rcube_event.get_mouse_pos(p.originalEvent);
target = $('<a>').css({
position: 'absolute',
left: pos.x,
top: pos.y,
height: '1px',
width: '1px',
visibility: 'hidden'
})
.appendTo(document.body).get(0);
}
pos = $(target).data('popup-pos') || 'right';
if (p.name == 'folder-selector') {
content.addClass('listing folderlist');
}
else if (p.name == 'addressbook-selector' || p.name == 'contactgroup-selector') {
content.addClass('listing contactlist');
}
else if (content.hasClass('toolbarmenu')) {
content.addClass('listing');
}
if (p.name == 'pagejump-selector') {
content.addClass('simplelist');
p.obj.addClass('simplelist');
pos = 'top';
}
// There can be only one menu of the same type
if (menus[p.name]) {
menu_hide(p.name, p.originalEvent);
}
// Popover menus use animation. Sometimes the same menu is
// immediately hidden and shown (e.g. folder-selector for copy and move action)
// we have to wait until the previous menu hides before we can open it again
fn = function() {
if (menus[p.name] && menus[p.name].transitioning) {
return setTimeout(fn, 50);
}
if (!$(target).data('popup')) {
$(target).data({
popup: p.name,
'popup-pos': pos,
'popup-trigger': 'manual'
});
popup_init(target, p.win);
}
menus[p.name] = {target: target};
$(target).popover('show');
}
fn();
}
else {
menu_hide(p.name, p.originalEvent);
}
// Stop propagation so multi-level menus work properly
p.originalEvent.stopPropagation();
};
/**
* Close menu by name
*/
function menu_hide(name, event)
{
var target = menu_target(name);
if (name.match(/^drag/)) {
$(target).popover('dispose').remove();
}
else {
$(target).popover('hide');
// In phone mode close all menus when forwardmenu is requested to be closed
// FIXME: This is a hack, we need some generic solution.
if (name == 'forwardmenu') {
popups_close(event);
}
}
};
/**
* Destroys menu by name
*
* This is required when you replace the menu content element
*/
function menu_destroy(name)
{
$('[aria-owns=' + name + ']').popover('dispose').data('popup', null);
};
/**
* Get menu target by name
*/
function menu_target(name)
{
var target;
if (menus[name]) {
target = menus[name].target;
}
else {
target = $('#' + name).data('button');
if (!target) {
// catch cases as 'forwardmenu' where menu suffix has no hyphen
// or try with -menu suffix if it's not in the menu name already
if (name.match(/(?!-)menu$/)) {
name = name.substr(0, name.length - 4);
}
target = $('#' + name + '-menu').data('button');
}
}
return target;
};
/**
* Messages list options dialog
*/
function menu_messagelist(p)
{
var content = $('#listoptions-menu'),
width = content.width() + 25,
dialog = content.clone(true);
// set form values
$('select[name="sort_col"]', dialog).val(rcmail.env.sort_col || '');
$('select[name="sort_ord"]', dialog).val(rcmail.env.sort_order || 'ASC');
$('select[name="mode"]', dialog).val(rcmail.env.threading ? 'threads' : 'list');
// Fix id/for attributes
$('select', dialog).each(function() { this.id = this.id + '-clone'; });
$('label', dialog).each(function() { $(this).attr('for', $(this).attr('for') + '-clone'); });
var save_func = function(e) {
if (rcube_event.is_keyboard(e.originalEvent)) {
$('#listmenulink').focus();
}
var col = $('select[name="sort_col"]', dialog).val(),
ord = $('select[name="sort_ord"]', dialog).val(),
mode = $('select[name="mode"]', dialog).val();
rcmail.set_list_options([], col, ord, mode == 'threads' ? 1 : 0);
return true;
};
dialog = rcmail.simple_dialog(dialog, rcmail.gettext('listoptionstitle'), save_func, {
closeOnEscape: true,
minWidth: 400
});
};
/**
* About dialog
*/
function about_dialog(elem)
{
var support_url, support_func, support_button = false,
dialog = $('<iframe>').attr({id: 'aboutframe', src: rcmail.url('settings/about', {_framed: 1})}),
support_link = $('#supportlink');
if (support_link.length && (support_url = support_link.attr('href'))) {
support_button = support_link.text();
support_func = function(e) { support_url.indexOf('mailto:') < 0 ? window.open(support_url) : location.href = support_url; };
}
rcmail.simple_dialog(dialog, $(elem).text(), support_func, {
button: support_button,
button_class: 'help',
cancel_button: 'close',
height: 400
});
};
/**
* Show/hide more mail headers (envelope)
*/
function headers_show(button)
{
var headers = $(button).parent().prev();
headers[headers.is('.hidden') ? 'removeClass' : 'addClass']('hidden');
};
/**
* Mail headers dialog
*/
function headers_dialog()
{
var props = {_uid: rcmail.env.uid, _mbox: rcmail.env.mailbox, _framed: 1},
dialog = $('<iframe>').attr({id: 'headersframe', src: rcmail.url('headers', props)});
rcmail.simple_dialog(dialog, rcmail.gettext('arialabelmessageheaders'), null, {
cancel_button: 'close',
height: 400
});
};
/**
* Mail import dialog
*/
function import_dialog()
{
var content = $('#uploadform'),
dialog = content.clone();
var save_func = function(e) {
return rcmail.command('import-messages', $(dialog.find('form')[0]));
};
rcmail.simple_dialog(dialog, rcmail.gettext('importmessages'), save_func, {
button: 'import',
closeOnEscape: true,
minWidth: 400
});
};
/**
* Search options menu popup
*/
function searchmenu(obj)
{
var n, all,
list = $('input[name="s_mods[]"]', obj),
scope_select = $('#s_scope', obj),
mbox = rcmail.env.mailbox,
mods = rcmail.env.search_mods,
scope = rcmail.env.search_scope || 'base';
if (!$(obj).data('initialized')) {
$(obj).data('initialized', true);
if (list.length) {
list.on('change', function() { set_searchmod(obj, this); });
rcmail.addEventListener('beforesearch', function() { set_searchmod(obj); });
}
}
if (rcmail.env.search_mods) {
if (rcmail.env.task == 'mail') {
if (scope == 'all') {
mbox = '*';
}
mods = mods[mbox] ? mods[mbox] : mods['*'];
all = 'text';
scope_select.val(scope);
}
else {
all = '*';
}
if (mods[all]) {
list.map(function() {
this.checked = true;
this.disabled = this.value != all;
});
}
else {
list.prop('disabled', false).prop('checked', false);
for (n in mods) {
list.filter('[value="' + n + '"]').prop('checked', true);
}
}
}
};
function set_searchmod(menu, elem)
{
var all, m, task = rcmail.env.task,
mods = rcmail.env.search_mods,
mbox = rcmail.env.mailbox,
scope = $('#s_scope', menu).val(),
interval = $('#s_interval', menu).val();
if (scope == 'all') {
mbox = '*';
}
if (!mods) {
mods = {};
}
if (task == 'mail') {
if (!mods[mbox]) {
mods[mbox] = rcube_clone_object(mods['*']);
}
m = mods[mbox];
all = 'text';
rcmail.env.search_scope = scope;
rcmail.env.search_interval = interval;
}
else { //addressbook
m = mods;
all = '*';
}
if (!elem) {
return;
}
if (!elem.checked) {
delete(m[elem.value]);
}
else {
m[elem.value] = 1;
}
// mark all fields
if (elem.value == all) {
$('input[name="s_mods[]"]', menu).map(function() {
if (this == elem) {
return;
}
this.checked = true;
if (elem.checked) {
this.disabled = true;
delete m[this.value];
}
else {
this.disabled = false;
m[this.value] = 1;
}
});
}
rcmail.set_searchmods(m);
};
/**
* Spellcheck languages list
*/
function spellmenu(obj)
{
var i, link, li, list = [],
lang = rcmail.spellcheck_lang(),
ul = $('ul', obj);
if (!ul.length) {
ul = $('<ul class="toolbarmenu selectable listing iconized" role="menu">');
for (i in rcmail.env.spell_langs) {
li = $('<li role="menuitem">');
link = $('<a href="#'+ i +'" tabindex="0"></a>')
.text(rcmail.env.spell_langs[i])
.addClass('active').data('lang', i)
.on('click keypress', function(e) {
if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) {
rcmail.spellcheck_lang_set($(this).data('lang'));
rcmail.hide_menu('spell-menu', e);
return false;
}
});
link.appendTo(li);
list.push(li);
}
ul.append(list).appendTo(obj);
}
// select current language
$('li', ul).each(function() {
var el = $('a', this);
if (el.data('lang') == lang) {
el.addClass('selected').attr('aria-selected', 'true');
}
else if (el.hasClass('selected')) {
el.removeClass('selected').removeAttr('aria-selected');
}
});
};
/**
* Attachment menu
*/
function attachmentmenu(obj, button, event)
{
var id = $(button).parent().attr('id').replace(/^attach/, '');
$.each(['open', 'download', 'rename'], function() {
var action = this;
$('#attachmenu' + action, obj).off('click').attr('onclick', '').click(function(e) {
rcmail.command(action + '-attachment', id, this, e.originalEvent);
});
});
// call menu-open so core can set state of menu commands
rcmail.command('menu-open', {menu: 'attachmentmenu', id: id}, obj, event);
};
/**
* Appends drop-icon to attachments list item (to invoke attachment menu)
*/
function attachmentmenu_append(item)
{
item = $(item);
if (!item.is('.no-menu') && !item.children('.drop').length) {
var label = rcmail.gettext('options');
var button = $('<a>')
.attr({
href: '#',
tabindex: 0,
title: label,
'class': 'button icon dropdown skip-content'
})
.on('click', function(e) {
attachmentmenu($('#attachmentmenu'), button, e);
})
.append($('<span>').attr('class', 'inner').text(label))
.appendTo(item);
}
};
/**
* Mailto menu
*/
function mailtomenu(obj, button, event)
{
var mailto = $(button).attr('href').replace(/^mailto:/, '');
if (mailto.indexOf('@') < 0) {
return true; // let the browser handle this
}
if (rcmail.env.has_writeable_addressbook) {
$('.addressbook', obj).addClass('active')
.off('click').on('click', function(e) {
var i, contact = mailto,
txt = $(button).filter('.rcmContactAddress').text();
contact = contact.split('?')[0].split(',')[0].replace(/(^<|>$)/g, '');
if (txt) {
txt = txt.replace('<' + contact + '>', '');
contact = '"' + $.trim(txt) + '" <' + contact + '>';
}
rcmail.command('add-contact', contact, this, e.originalEvent);
});
}
$('.compose', obj).off('click').on('click', function(e) {
rcmail.command('compose', mailto, this, e.originalEvent);
});
return rcmail.command('menu-open', {menu: 'mailto-menu', link: button}, button, event);
};
/**
* Appends popup menu to mailto links
*/
function mailtomenu_append(item)
{
$(item).attr('onclick', '').on('click', function(e) {
return mailtomenu($('#mailto-menu'), item, e);
});
};
/**
* Headers menu in mail compose
*/
function headersmenu(obj, button, event)
{
$('li > a', obj).each(function() {
var target = '#compose_' + $(this).data('target');
$(this)[$(target).is(':visible') ? 'removeClass' : 'addClass']('active')
.off().on('click', function() {
$(target).removeClass('hidden').find('.recipient-input input').focus();
});
});
};
/**
* Reset/hide compose message recipient input
*/
function header_reset(id)
{
$('#' + id).val('').change()
// jump to the next input
.closest('.form-group').nextAll(':not(.hidden)').first().find('input').focus();
};
+ /**
+ * Recipient (contact) selector
+ */
+ function recipient_selector(field, opts)
+ {
+ if (!opts) opts = {};
+
+ var title = rcmail.gettext(opts.title || 'insertcontact'),
+ dialog = $('#recipient-dialog'),
+ parent = dialog.parent(),
+ close_func = function() {
+ if (dialog.is(':visible')) {
+ rcmail.env.recipient_dialog.dialog('close');
+ }
+ },
+ insert_func = function() {
+ if (opts.action) {
+ opts.action();
+ close_func();
+ return;
+ }
+
+ rcmail.command('add-recipient');
+ };
+
+ if (!rcmail.env.recipient_selector_initialized) {
+ rcmail.addEventListener('add-recipient', close_func);
+ rcmail.env.recipient_selector_initialized = true;
+ }
+
+ if (field) {
+ rcmail.env.focused_field = '#_' + field;
+ }
+
+ rcmail.contact_list.clear_selection();
+ rcmail.contact_list.multiselect = 'multiselect' in opts ? opts.multiselect : true;
+
+ rcmail.env.recipient_dialog = rcmail.simple_dialog(dialog, title, insert_func, {
+ button: rcmail.gettext(opts.button || 'insert'),
+ button_class: opts.button_class || 'insert recipient',
+ height: 600,
+ classes: {
+ 'ui-dialog-content': 'p-0' // remove padding on dialog content
+ },
+ open: function() {
+ // Don't want focus in the search field, we focus first contacts source record instead
+ $('#directorylist a:first').focus();
+ },
+ close: function() {
+ dialog.appendTo(parent);
+ $(this).remove();
+ $(opts.focus || rcmail.env.focused_field).focus();
+ }
+ });
+ };
+
/**
* Create/Update quota widget (setquota event handler)
*/
function update_quota(p)
{
var element = $('#quotadisplay'),
bar = element.find('.bar'),
value = p.total ? p.percent : 0;
if (!bar.length) {
bar = $('<span class="bar"><span class="value"></span></span>').appendTo(element);
}
if (value > 0 && value < 10) {
value = 10; // smaller values look not so nice
}
bar.find('.value').css('width', value + '%')[value >= 90 ? 'addClass' : 'removeClass']('warning');
element.attr('title', element.find('.count').attr('title'));
if (p.table) {
element.css('cursor', 'pointer').data('popup-pos', 'top')
.off('click').on('click', function(e) {
rcmail.simple_dialog(p.table, 'quota', null, {cancel_button: 'close'});
});
}
else {
element.tooltip('dispose').tooltip({trigger: is_mobile() ? 'click' : 'hover'});
}
};
/**
* Replaces recipient input with content-editable element that uses "recipient boxes"
*/
function recipient_input(obj)
{
var list, input, ac_props, update_lock,
input_len_update = function() {
- input.css('width', input.val().length * 10 + 15);
+ input.css('width', Math.max(40, input.val().length * 15 + 25));
},
apply_func = function() {
// update the original input
$(obj).val(list.text() + input.val());
},
focus_func = function() {
list.addClass('focus');
// move cursor to the end of input text, use setTimeout for Firefox
setTimeout(function() { rcmail.set_caret_pos(input.get(0), input.val().length); }, 1);
},
insert_recipient = function(name, email, replace) {
var recipient = $('<li class="recipient">'),
name_element = $('<span class="name">').html(recipient_input_name(name || email))
.on('dblclick', function(e) { recipient_input_edit_dialog(e, insert_recipient); }),
email_element = $('<span class="email">'),
// TODO: should the 'close' link have tabindex?
link = $('<a>').attr({'class': 'button icon remove'})
.click(function() {
recipient.remove();
apply_func();
input.focus();
return false;
});
if (name) {
email = ' <' + email + '>';
}
email_element.text((name ? email : '') + ',');
recipient.attr('title', name ? (name + email) : null)
.append([name_element, email_element, link])
if (replace)
replace.replaceWith(recipient);
else
recipient.insertBefore(input.parent());
},
update_func = function(text) {
var result;
if (update_lock) {
return;
}
update_lock = true;
text = (text || input.val()).replace(/[,;\s]+$/, '');
result = recipient_input_parser(text);
$.each(result.recipients, function() {
insert_recipient(this.name, this.email);
});
// setTimeout() here is needed for proper input reset on paste event
// This is also the reason why we need parse_lock
setTimeout(function() {
input.val(result.text);
apply_func();
input_len_update();
update_lock = false;
}, 1);
return result.recipients.length > 0;
},
parse_func = function(e) {
+ // FIXME: This is a workaround for a bug where on a touch device
+ // selecting a recipient from autocomplete list do not work because
+ // of some events race condition (?)
+ if (this.value.indexOf('@') < 0) {
+ return;
+ }
+
if (e.type == 'blur') {
list.removeClass('focus');
}
// On paste the text is not yet in the input we have to use clipboard.
// Also because on paste new-line characters are replaced by spaces (#6460)
update_func(e.type == 'paste' ? (e.originalEvent.clipboardData || window.clipboardData).getData('text') : null);
},
keydown_func = function(e) {
// On Backspace remove the last recipient
if (e.keyCode == 8 && !input.val().length) {
list.children('li.recipient:last').remove();
apply_func();
return false;
}
// Here we add a recipient box when the separator (,;) or Enter was pressed
- else if (e.key == ',' || e.key == ';' || e.key == 'Enter') {
+ else if (e.key == ',' || e.key == ';' || (e.key == 'Enter' && !rcmail.ksearch_visible())) {
if (update_func()) {
return false;
}
}
input_len_update();
};
// Create the input element and "editable" area
input = $('<input>').attr({type: 'text', tabindex: $(obj).attr('tabindex')})
.on('paste change blur', parse_func)
.on('keydown', keydown_func)
.on('focus mousedown', focus_func);
list = $('<ul>').addClass('form-control recipient-input')
.append($('<li>').append(input))
.on('click', function() { input.focus(); });
- // "Replace" the original input/textarea with the content-editable div
+ // Hide the original input/textarea
// Note: we do not remove the original element, and we do not use
// display: none, because we want to handle onfocus event
// Note: tabindex:-1 to make Shift+TAB working on these widgets
$(obj).css({position: 'absolute', opacity: 0, left: '-5000px', width: '10px'})
.attr('tabindex', -1)
.after(list)
// some core code sometimes focuses or changes the original node
// in such cases we wan't to parse it's value and apply changes
// to the widget element
.on('focus', function(e) { input.focus(); })
.on('change', function(e) {
$('li.recipient', list).remove();
input.val(this.value).change();
})
// copy and parse the value already set
.change();
// this one line is here to fix border of Bootstrap's input-group,
// input-group should not contain any hidden elements
$(obj).detach().insertBefore(list.parent());
if (rcmail.env.autocomplete_threads > 0) {
ac_props = {
threads: rcmail.env.autocomplete_threads,
sources: rcmail.env.autocomplete_sources
};
}
// Init autocompletion
rcmail.init_address_input_events(input, ac_props);
};
/**
* Parses recipient address input and extracts recipients from it
*/
function recipient_input_parser(text)
{
// support new-line as a separator, for paste action (#6460)
text = $.trim(text.replace(/[,;\s]*[\r\n]+/g, ','));
var recipients = [],
address_rx_part = '(\\S+|("[^"]+"))@\\S+',
recipient_rx1 = new RegExp('(<' + address_rx_part + '>)'),
recipient_rx2 = new RegExp('(' + address_rx_part + ')'),
global_rx = /(?=\S)[^",;]*(?:"[^\\"]*(?:\\[,;\S][^\\"]*)*"[^",;]*)*/g,
matches = text.match(global_rx);
$.each(matches || [], function() {
if (this.length && (recipient_rx1.test(this) || recipient_rx2.test(this))) {
var email = RegExp.$1,
name = $.trim(this.replace(email, ''));
recipients.push({
name: name,
email: email.replace(/(^<|>$)/g, ''),
text: this
});
text = text.replace(this, '');
}
});
- text = text.replace(/[,;]+/, ',').replace(/^[,;]/, '');
+ text = text.replace(/[,;]+/, ',').replace(/^[,;\s]+/, '');
return {recipients: recipients, text: text};
};
/**
* Generates HTML for a text adding <span class="hidden">
* for quote/backslash characters, so they are hidden from the user,
* but still in place to make copying simpler
*
* Note: Selection works in Chrome, but not in Firefox?
*/
function recipient_input_name(text)
{
var i, char, result = '', len = text.length;
if (text.charAt(0) != '"' && text.indexOf('"') > -1) {
text = '"' + text.replace('\\', '\\\\').replace('"', '\\"') + '"';
}
for (i=0; i<len; i++) {
char = text.charAt(i);
switch (char) {
case '"':
if (i > 0 && i < len - 1) {
result += '"';
break;
}
result += '<span class="quotes">' + char + '</span>';
break;
case '\\':
result += '<span class="quotes">' + char + '</span>';
if (text.charAt(i+1) == '\\') {
result += char;
i++;
}
break;
case '<':
result += '&lt;';
break;
case '>':
result += '&gt;';
break;
default:
result += char;
}
}
return result;
};
/**
* Displays dialog to edit a recipient entry
*/
function recipient_input_edit_dialog(e, callback)
{
var element = $(e.target).parents('.recipient'),
recipient = element.text().replace(/,+$/, ''),
input = $('<input>').attr({type: 'text', size: 50}).val(recipient),
content = $('<label>').text(rcmail.gettext('recipient')).append(input);
rcmail.simple_dialog(content, 'recipientedit', function() {
var result, value = input.val();
if (value) {
if (value != recipient) {
result = recipient_input_parser(value);
if (result.recipients.length != 1) {
return false;
}
callback(result.recipients[0].name, result.recipients[0].email, element);
}
return true;
}
});
};
/**
* Adds logic to the contact photo widget
*/
function image_upload_input(obj)
{
var reset_button = $('<a>')
.attr({'class': 'icon button delete', href: '#', })
.click(function(e) { rcmail.command('delete-photo', '', this, e); return false; });
$(obj).append(reset_button).click(function() { rcmail.upload_input('upload-form'); });
$('img', obj).on('load', function() {
// FIXME: will that work in IE?
var state = (this.currentSrc || this.src).indexOf(rcmail.env.photo_placeholder) != -1;
$(obj)[state ? 'removeClass' : 'addClass']('changed');
});
};
/**
* Displays loading... overlay for iframes
*/
function iframe_loader(frame)
{
frame = $(frame);
if (frame.length) {
var loader = $('<div>').attr('class', 'iframe-loader')
.append($('<div>').attr('class', 'spinner').text(rcmail.gettext('loading')));
// custom 'loaded' event is expected to be triggered by plugins
// when using the loader not on an iframe
frame.on('load error loaded', function() {
// wait some time to make sure the iframe stopped loading
setTimeout(function() { loader.remove(); }, 500);
})
.parent().append(loader);
// fix scrolling in iOS
if (ios) {
frame.parent().addClass('ios-scroll');
}
}
};
/**
* Checkbox wrapper
*/
function pretty_checkbox(checkbox)
{
var checkbox = $(checkbox),
id = checkbox.attr('id');
if (checkbox.is('.icon-checkbox')) {
return;
}
if (!id) {
if (!env.icon_checkbox) env.icon_checkbox = 0;
id = 'icochk' + (++env.icon_checkbox);
checkbox.attr('id', id);
}
checkbox.addClass('icon-checkbox form-check-input').after(
$('<label>').attr({'for': id, title: checkbox.attr('title') || ''})
.on('click', function(e) { e.stopPropagation(); })
);
};
/**
* Make select dropdowns pretty
* TODO: searching, optgroup, [multiple], iPhone/iPad
*/
function pretty_select(select)
{
// iPhone is not supported yet (problem with browser dropdown on focus)
if (bw.iphone || bw.ipad) {
return;
}
select = $(select);
if (select.is('.pretty-select')) {
return;
}
var select_ident = 'select' + select.attr('id') + select.attr('name');
var is_menu_open = function() {
// Use proper window in cases when the select element intialized
// inside an iframe is then used in a dialog inside a parent's window
// For some reason we can't access data-button property in cross-window
// case, we use data-ident attribute instead
var win = select[0].ownerDocument.defaultView;
if (win.$('.select-menu .listing').data('ident') == select_ident) {
return true;
}
};
var close_func = function() {
var open = is_menu_open();
select.popover('dispose').focus();
return !open;
};
var open_func = function(e) {
var items = [],
dialog = select.closest('.ui-dialog')[0],
min_width = select.outerWidth(),
max_height = $(document.body).height() - 75,
max_width = $(document.body).width() - 20,
value = select.val();
if (!is_mobile()) {
max_height *= 0.5;
}
$('option', select).each(function() {
var label = $(this).text(),
link = $('<a href="#">')
.data('value', this.value)
.addClass(this.disabled ? 'disabled' : 'active' + (this.value == value ? ' selected' : ''));
if (label.length) {
link.text(label);
}
else {
link.html('&nbsp;'); // link can't be empty
}
items.push($('<li>').append(link));
});
var list = $('<ul class="toolbarmenu listing selectable iconized">')
.attr('data-ident', select_ident)
.append(items)
.on('click', 'a.active', function() {
// first close the list, then update the select, the order is important
//for cases when the select might be removed in change event (datepicker)
var val = $(this).data('value'), ret = close_func();
select.val(val).change();
return ret;
})
.on('keydown', 'a.active', function(e) {
var item, node, mode = 'next';
switch (e.which) {
case 27: // ESC
case 9: // TAB
return close_func();
case 13: // ENTER
case 32: // SPACE
$(this).click();
return false; // for IE
case 38: // ARROW-UP
case 63232:
mode = 'previous';
case 40: // ARROW-DOWN
case 63233:
item = e.target.parentNode;
while (item = item[mode + 'Sibling']) {
if (node = $(item).children('.active')[0]) {
node.focus();
break;
}
}
return false; // prevents from scrolling the whole page
}
});
select.popover('dispose')
.popover({
// because of focus issues we can't always use body,
// if select is in a dialog, popover has to be a child of this dialog
container: dialog || 'body',
content: list[0],
placement: 'bottom',
trigger: 'manual',
boundary: 'viewport',
html: true,
offset: '0,2',
template: '<div class="popover select-menu" style="min-width: ' + min_width + 'px; max-width: ' + max_width + 'px">'
+ '<div class="popover-header"></div>'
+ '<div class="popover-body" style="max-height: ' + max_height + 'px"></div></div>'
})
.on('shown.bs.popover', function() {
// Set popup Close title
list.parent().prev()
.empty()
.append($('<a class="button icon cancel">').text(rcmail.gettext('close'))
.on('click', function(e) {
e.stopPropagation();
return close_func();
})
);
// focus first active element on the list
if (rcube_event.is_keyboard(e)) {
list.find('a.active:first').focus();
}
})
.popover('show');
};
select.addClass('pretty-select custom-select form-control')
.on('mousedown keydown', function(e) {
select = $(e.target); // so it works after clone
// Do nothing on disabled select or on TAB key
if (select.prop('disabled')) {
return;
}
if (e.which == 9) {
close_func();
return true;
}
// Close popup on ESC key or on click if already open
if (e.which == 27 || (e.type == 'mousedown' && is_menu_open())) {
return close_func();
}
select.focus();
// prevent displaying browser-default select dropdown
select.prop('disabled', true);
setTimeout(function() { select.prop('disabled', false); }, 0);
e.stopPropagation();
// display options in our way (on SPACE, ENTER, ARROW-DOWN or mousedown)
if (e.type == 'mousedown' || e.which == 13 || e.which == 32 || e.which == 40 || e.which == 63233) {
open_func(e);
return false;
}
})
.on('click', function(e) {
// Stop propagation of click event to prevent from
// disposing the menu by general popover closing handler (popups_close())
e.stopPropagation();
return false;
});
};
/**
* HTML editor textarea wrapper with nice looking tabs-like switch
*/
function html_editor_init(obj)
{
// Here we support two structures
// 1. <div><textarea></textarea><select name="editorSelector"></div>
// 2. <tr><td><td><td><textarea></textarea></td></tr>
// <tr><td><td><td><input type="checkbox"></td></tr>
var sw, is_table = false,
editor = $(obj),
parent = editor.parent(),
+ tabindex = editor.attr('tabindex'),
mode = function() {
if (is_table) {
return sw.is(':checked') ? 'html' : 'plain';
}
return sw.val();
},
tabs = $('<ul class="nav nav-tabs">')
.append($('<li class="nav-item">')
.append($('<a class="nav-link mode-html" href="#">')
.text(rcmail.gettext('htmltoggle'))))
.append($('<li class="nav-item">')
.append($('<a class="nav-link mode-plain" href="#">')
.text(rcmail.gettext('plaintoggle'))));
if (parent.is('td')) {
sw = $('input[type="checkbox"]', parent.parent().next());
is_table = true;
}
else {
sw = $('[name="editorSelector"]', obj.form);
}
// sanity check
if (sw.length != 1) {
return;
}
parent.addClass('html-editor');
editor.before(tabs);
- $('a', tabs).attr('tabindex', editor.attr('tabindex'))
+ $('a', tabs).attr('tabindex', tabindex)
.on('click', function(e) {
var id = editor.attr('id'), is_html = $(this).is('.mode-html');
e.preventDefault();
if (rcmail.command('toggle-editor', {id: id, html: is_html}, '', e.originalEvent)) {
- $(this).tab('show');
+ $(this).tab('show').prop('tabindex', -1);
+ $('.mode-' + (is_html ? 'plain' : 'html'), tabs).prop('tabindex', tabindex);
if (is_table) {
sw.prop('checked', is_html);
}
}
})
- .filter('.mode-' + mode()).tab('show');
+ .filter('.mode-' + mode()).tab('show').prop('tabindex', -1);
if (is_table) {
// Hide unwanted table cells
sw.parent().parent().hide();
parent.prev().hide();
// Modify the textarea cell to use 100% width
parent.addClass('col-sm-12');
}
// make the textarea autoresizeable
textarea_autoresize_init(editor);
};
/**
* Make the textarea autoresizeable depending on it's content length.
* The way there's no vertical scrollbar.
*/
function textarea_autoresize_init(textarea)
{
var resize = function(e) {
clearTimeout(env.textarea_timer);
env.textarea_timer = setTimeout(function() {
var area = $(e.target),
initial_height = area.data('initial-height'),
scroll_height = area[0].scrollHeight;
// do nothing when the area is hidden
if (!scroll_height) {
return;
}
if (!initial_height) {
area.data('initial-height', initial_height = scroll_height);
}
// strange effect in Chrome/Firefox when you delete a line in the textarea
// the scrollHeight is not decreased by the line height, but by 2px
// so jumps up many times in small steps, we'd rather use one big step
if (area.outerHeight() - scroll_height == 2) {
scroll_height -= 19; // 21px is the assumed line height
}
area.outerHeight(Math.max(initial_height, scroll_height));
}, 10);
};
$(textarea).css('overflow-y', 'hidden').on('input', resize).trigger('input');
// Make sure the height is up-to-date also in time intervals
setInterval(function() { $(textarea).trigger('input'); }, 1000);
};
// Inititalizes smart list input
function smart_field_init(field)
{
var tip, id = field.id + '_list',
area = $('<div class="multi-input"><div class="content"></div><div class="invalid-feedback"></div></div>'),
list = field.value ? field.value.split("\n") : [''];
if ($('#' + id).length) {
return;
}
// add input rows
$.each(list, function(i, v) {
smart_field_row_add($('.content', area), v, field.name, i, $(field).data('size'));
});
area.attr('id', id);
field = $(field);
if (field.attr('disabled')) {
area.hide();
}
// disable the original field anyway, we don't want it in POST
else {
field.prop('disabled', true);
}
if (field.data('hidden')) {
area.hide();
}
field.after(area);
if (field.hasClass('is-invalid')) {
area.addClass('is-invalid');
$('.invalid-feedback', area).text(field.data('error-msg'));
}
};
function smart_field_row_add(area, value, name, idx, size, after)
{
// build row element content
var input, elem = $('<div class="input-group">'
+ '<input type="text" class="form-control">'
+ '<span class="input-group-append"><a class="icon reset input-group-text" href="#"></a></span>'
+ '</div>'),
attrs = {value: value, name: name + '[]'};
if (size) {
attrs.size = size;
}
input = $('input', elem).attr(attrs)
.keydown(function(e) {
var input = $(this);
// element creation event (on Enter)
if (e.which == 13) {
var name = input.attr('name').replace(/\[\]$/, ''),
dt = (new Date()).getTime(),
elem = smart_field_row_add(area, '', name, dt, size, input.parent());
$('input', elem).focus();
}
// backspace or delete: remove input, focus previous one
else if ((e.which == 8 || e.which == 46) && input.val() == '') {
var parent = input.parent(),
siblings = area.children();
if (siblings.length > 1) {
if (parent.prev().length) {
parent.prev().children('input').focus();
}
else {
parent.next().children('input').focus();
}
parent.remove();
return false;
}
}
});
// element deletion event
$('a.reset', elem).click(function() {
var record = $(this.parentNode.parentNode);
if (area.children().length > 1) {
$('input', record.next().length ? record.next() : record.prev()).focus();
record.remove();
}
else {
$('input', record).val('').focus();
}
});
$(elem).find('input,a')
.on('focus', function() { area.addClass('focused'); })
.on('blur', function() { area.removeClass('focused'); });
if (after) {
after.after(elem);
}
else {
elem.appendTo(area);
}
return elem;
};
// Reset and fill the smart list input with new data
function smart_field_reset(field, data)
{
var id = field.id + '_list',
list = data.length ? data : [''],
area = $('#' + id).children('.content');
area.empty();
// add input rows
$.each(list, function(i, v) {
smart_field_row_add(area, v, field.name, i, $(field).data('size'));
});
};
/**
* Register form errors, mark fields as invalid, dsplay the error below the input
*/
function form_errors(tips)
{
$.each(tips, function() {
var input = $('#' + this[0]).addClass('is-invalid');
if (input.data('type') == 'list') {
input.data('error-msg', this[2]);
return;
}
input.after($('<span class="invalid-feedback">').text(this[2]));
});
};
/**
* Show/hide the navigation list
*/
function switch_nav_list(obj)
{
var records, height, speed = 250,
button = $('a.button', obj),
navlist = $(obj).next();
if (!navlist.height()) {
records = $('tr,li', navlist).filter(function() { return this.style.display != 'none'; });
height = $(records[0]).height() || 50;
navlist.animate({height: (Math.min(5, records.length) * height + 1) + 'px'}, speed);
button.addClass('collapse').removeClass('expand');
$(obj).addClass('expanded');
}
else {
navlist.animate({height: '0'}, speed);
button.addClass('expand').removeClass('collapse');
$(obj).removeClass('expanded');
}
};
/**
* Wrapper for rcmail.open_window to intercept window opening
* and display a dialog with an iframe instead of a real window.
*/
function window_open(url)
{
// Use 4th argument to bypass the dialog-mode e.g. for external windows
if (!is_mobile() || arguments[3] === true) {
return env.open_window.apply(rcmail, arguments);
}
// _extwin=1, _framed=1 are required to display attachment preview
// layout properly and make mobile menus working
url = rcmail.add_url(url, '_framed', 1);
url = rcmail.add_url(url, '_extwin', 1);
var label, title = '',
props = {cancel_button: 'close', width: 768, height: 768},
frame = $('<iframe>').attr({id: 'windowframe', src: url});
if (/_action=([a-z_]+)/.test(url) && (label = rcmail.labels[RegExp.$1])) {
title = label;
}
if (/_frame=1/.test(url)) {
props.dialogClass = 'no-titlebar';
}
rcmail.simple_dialog(frame, title, null, props);
return true;
};
/**
* Get layout modes. In frame mode returns the parent layout modes.
*/
function layout_metadata()
{
if (is_framed) {
var doc = $(parent.document.documentElement);
return {
mode: doc[0].className.match(/layout-([a-z]+)/) ? RegExp.$1 : mode,
touch: doc.is('.touch'),
};
}
return {mode: mode, touch: touch};
};
/**
* Returns true if the layout is in 'small' or 'phone' mode
*/
function is_mobile()
{
var meta = layout_metadata();
return meta.mode == 'phone' || meta.mode == 'small';
};
/**
* Returns true if the layout is in 'touch' mode
*/
function is_touch()
{
var meta = layout_metadata();
return meta.touch;
};
}
if (window.rcmail) {
/**
* Elastic version of show_menu as we don't need e.g. menu positioning from core
* TODO: keyboard navigation in menus
*/
rcmail.show_menu = function(prop, show, event)
{
var name = typeof prop == 'object' ? prop.menu : prop,
obj = $('#' + name);
if (typeof prop == 'string') {
prop = {menu: name};
}
// just delegate the action to rcube_elastic_ui
return rcmail.triggerEvent(show === false ? 'menu-close' : 'menu-open', {name: name, obj: obj, props: prop, originalEvent: event});
}
/**
* Elastic version of hide_menu as we don't need e.g. menus stack handling
*/
rcmail.hide_menu = function(name, event)
{
// delegate to rcube_elastic_ui
return rcmail.triggerEvent('menu-close', {name: name, props: {menu: name}, originalEvent: event});
}
}
else {
// rcmail does not exists e.g. on the error template inside a frame
// we fake the engine a little
var rcmail = parent.rcmail;
var rcube_webmail = parent.rcube_webmail;
var bw = {};
}
var UI = new rcube_elastic_ui();

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 19, 6:08 PM (1 d, 15 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
458703
Default Alt Text
(538 KB)

Event Timeline