9273 lines
268 KiB
C
9273 lines
268 KiB
C
/*
|
|
* e-html-editor-view.c
|
|
*
|
|
* Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with the program; if not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include "e-html-editor-view.h"
|
|
#include "e-html-editor.h"
|
|
#include "e-emoticon-chooser.h"
|
|
|
|
#include <e-util/e-util.h>
|
|
#include <e-util/e-marshal.h>
|
|
#include <glib/gi18n-lib.h>
|
|
#include <gdk/gdkkeysyms.h>
|
|
|
|
#define E_HTML_EDITOR_VIEW_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE \
|
|
((obj), E_TYPE_HTML_EDITOR_VIEW, EHTMLEditorViewPrivate))
|
|
|
|
#define UNICODE_ZERO_WIDTH_SPACE "\xe2\x80\x8b"
|
|
#define UNICODE_NBSP "\xc2\xa0"
|
|
|
|
#define URL_PATTERN \
|
|
"((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[\\-;:&=\\+\\$,\\w]+@)?" \
|
|
"[A-Za-z0-9\\.\\-]+|(?:www\\.|[\\-;:&=\\+\\$,\\w]+@)" \
|
|
"[A-Za-z0-9\\.\\-]+)((?:\\/[\\+~%\\/\\.\\w\\-]*)?\\?" \
|
|
"?(?:[\\-\\+=&;%@\\.\\w]*)#?(?:[\\.\\!\\/\\\\w]*))?)"
|
|
|
|
#define URL_PATTERN_SPACE URL_PATTERN "\\s"
|
|
|
|
/* http://www.w3.org/TR/html5/forms.html#valid-e-mail-address */
|
|
#define E_MAIL_PATTERN \
|
|
"[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}"\
|
|
"[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*"
|
|
|
|
#define E_MAIL_PATTERN_SPACE E_MAIL_PATTERN "\\s"
|
|
|
|
#define QUOTE_SYMBOL ">"
|
|
|
|
/* Keep synchronized with the same value in EHTMLEditorSelection */
|
|
#define SPACES_PER_LIST_LEVEL 8
|
|
#define TAB_LENGTH 8
|
|
|
|
#define HTML_KEY_CODE_BACKSPACE 8
|
|
#define HTML_KEY_CODE_RETURN 13
|
|
#define HTML_KEY_CODE_CONTROL 17
|
|
#define HTML_KEY_CODE_SPACE 32
|
|
#define HTML_KEY_CODE_DELETE 46
|
|
|
|
#define TRY_TO_PRESERVE_BLOCKS 0
|
|
|
|
/**
|
|
* EHTMLEditorView:
|
|
*
|
|
* The #EHTMLEditorView is a WebKit-based rich text editor. The view itself
|
|
* only provides means to configure global behavior of the editor. To work
|
|
* with the actual content, current cursor position or current selection,
|
|
* use #EHTMLEditorSelection object.
|
|
*/
|
|
|
|
struct _EHTMLEditorViewPrivate {
|
|
gint changed : 1;
|
|
gint inline_spelling : 1;
|
|
gint magic_links : 1;
|
|
gint magic_smileys : 1;
|
|
gint unicode_smileys : 1;
|
|
gint can_copy : 1;
|
|
gint can_cut : 1;
|
|
gint can_paste : 1;
|
|
gint can_redo : 1;
|
|
gint can_undo : 1;
|
|
gint reload_in_progress : 1;
|
|
gint html_mode : 1;
|
|
|
|
EHTMLEditorSelection *selection;
|
|
|
|
GHashTable *inline_images;
|
|
|
|
GSettings *mail_settings;
|
|
GSettings *font_settings;
|
|
GSettings *aliasing_settings;
|
|
|
|
gboolean convert_in_situ;
|
|
gboolean body_input_event_removed;
|
|
gboolean is_message_from_draft;
|
|
gboolean is_message_from_edit_as_new;
|
|
gboolean is_message_from_selection;
|
|
gboolean remove_initial_input_line;
|
|
gboolean return_key_pressed;
|
|
gboolean space_key_pressed;
|
|
gboolean smiley_written;
|
|
|
|
GHashTable *old_settings;
|
|
|
|
GQueue *post_reload_operations;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_CAN_COPY,
|
|
PROP_CAN_CUT,
|
|
PROP_CAN_PASTE,
|
|
PROP_CAN_REDO,
|
|
PROP_CAN_UNDO,
|
|
PROP_CHANGED,
|
|
PROP_HTML_MODE,
|
|
PROP_INLINE_SPELLING,
|
|
PROP_MAGIC_LINKS,
|
|
PROP_MAGIC_SMILEYS,
|
|
PROP_UNICODE_SMILEYS,
|
|
PROP_SPELL_CHECKER
|
|
};
|
|
|
|
enum {
|
|
POPUP_EVENT,
|
|
PASTE_PRIMARY_CLIPBOARD,
|
|
LAST_SIGNAL
|
|
};
|
|
|
|
static guint signals[LAST_SIGNAL] = { 0 };
|
|
|
|
static CamelDataCache *emd_global_http_cache = NULL;
|
|
|
|
typedef void (*PostReloadOperationFunc) (EHTMLEditorView *view, gpointer data);
|
|
|
|
typedef struct {
|
|
PostReloadOperationFunc func;
|
|
gpointer data;
|
|
GDestroyNotify data_free_func;
|
|
} PostReloadOperation;
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (
|
|
EHTMLEditorView,
|
|
e_html_editor_view,
|
|
WEBKIT_TYPE_WEB_VIEW,
|
|
G_IMPLEMENT_INTERFACE (
|
|
E_TYPE_EXTENSIBLE, NULL))
|
|
|
|
static void
|
|
html_editor_view_queue_post_reload_operation (EHTMLEditorView *view,
|
|
PostReloadOperationFunc func,
|
|
gpointer data,
|
|
GDestroyNotify data_free_func)
|
|
{
|
|
PostReloadOperation *op;
|
|
|
|
g_return_if_fail (func != NULL);
|
|
|
|
if (view->priv->post_reload_operations == NULL)
|
|
view->priv->post_reload_operations = g_queue_new ();
|
|
|
|
op = g_new0 (PostReloadOperation, 1);
|
|
op->func = func;
|
|
op->data = data;
|
|
op->data_free_func = data_free_func;
|
|
|
|
g_queue_push_head (view->priv->post_reload_operations, op);
|
|
}
|
|
|
|
static WebKitDOMRange *
|
|
html_editor_view_get_dom_range (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMDOMWindow *window;
|
|
WebKitDOMDOMSelection *selection;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
window = webkit_dom_document_get_default_view (document);
|
|
selection = webkit_dom_dom_window_get_selection (window);
|
|
|
|
if (webkit_dom_dom_selection_get_range_count (selection) < 1) {
|
|
return NULL;
|
|
}
|
|
|
|
return webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_user_changed_contents_cb (EHTMLEditorView *view,
|
|
gpointer user_data)
|
|
{
|
|
WebKitWebView *web_view;
|
|
gboolean can_redo, can_undo;
|
|
|
|
web_view = WEBKIT_WEB_VIEW (view);
|
|
|
|
e_html_editor_view_set_changed (view, TRUE);
|
|
|
|
can_redo = webkit_web_view_can_redo (web_view);
|
|
if (view->priv->can_redo != can_redo) {
|
|
view->priv->can_redo = can_redo;
|
|
g_object_notify (G_OBJECT (view), "can-redo");
|
|
}
|
|
|
|
can_undo = webkit_web_view_can_undo (web_view);
|
|
if (view->priv->can_undo != can_undo) {
|
|
view->priv->can_undo = can_undo;
|
|
g_object_notify (G_OBJECT (view), "can-undo");
|
|
}
|
|
}
|
|
|
|
static void
|
|
html_editor_view_selection_changed_cb (EHTMLEditorView *view,
|
|
gpointer user_data)
|
|
{
|
|
WebKitWebView *web_view;
|
|
gboolean can_copy, can_cut, can_paste;
|
|
|
|
web_view = WEBKIT_WEB_VIEW (view);
|
|
|
|
/* When the webview is being (re)loaded, the document is in an
|
|
* inconsistant state and there is no selection, so don't propagate
|
|
* the signal further to EHTMLEditorSelection and others and wait until
|
|
* the load is finished. */
|
|
if (view->priv->reload_in_progress) {
|
|
g_signal_stop_emission_by_name (view, "selection-changed");
|
|
return;
|
|
}
|
|
|
|
can_copy = webkit_web_view_can_copy_clipboard (web_view);
|
|
if (view->priv->can_copy != can_copy) {
|
|
view->priv->can_copy = can_copy;
|
|
g_object_notify (G_OBJECT (view), "can-copy");
|
|
}
|
|
|
|
can_cut = webkit_web_view_can_cut_clipboard (web_view);
|
|
if (view->priv->can_cut != can_cut) {
|
|
view->priv->can_cut = can_cut;
|
|
g_object_notify (G_OBJECT (view), "can-cut");
|
|
}
|
|
|
|
can_paste = webkit_web_view_can_paste_clipboard (web_view);
|
|
if (view->priv->can_paste != can_paste) {
|
|
view->priv->can_paste = can_paste;
|
|
g_object_notify (G_OBJECT (view), "can-paste");
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
html_editor_view_should_show_delete_interface_for_element (EHTMLEditorView *view,
|
|
WebKitDOMHTMLElement *element)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
WebKitDOMElement *
|
|
get_parent_block_element (WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);
|
|
|
|
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))
|
|
return WEBKIT_DOM_ELEMENT (node);
|
|
|
|
while (parent &&
|
|
!WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_HEADING_ELEMENT (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent) &&
|
|
!element_has_tag (parent, "address")) {
|
|
parent = webkit_dom_node_get_parent_element (
|
|
WEBKIT_DOM_NODE (parent));
|
|
}
|
|
|
|
return parent;
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_force_spell_check_for_current_paragraph (EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMDOMSelection *dom_selection;
|
|
WebKitDOMDOMWindow *window;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker;
|
|
WebKitDOMElement *parent, *element;
|
|
WebKitDOMRange *end_range, *actual;
|
|
WebKitDOMText *text;
|
|
|
|
if (!view->priv->inline_spelling)
|
|
return;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
window = webkit_dom_document_get_default_view (document);
|
|
dom_selection = webkit_dom_dom_window_get_selection (window);
|
|
|
|
element = webkit_dom_document_query_selector (
|
|
document, "body[spellcheck=true]", NULL);
|
|
|
|
if (!element)
|
|
return;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
e_html_editor_selection_save (selection);
|
|
|
|
selection_start_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-start-marker", NULL);
|
|
selection_end_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-end-marker", NULL);
|
|
|
|
if (!selection_start_marker || !selection_end_marker)
|
|
return;
|
|
|
|
/* Block callbacks of selection-changed signal as we don't want to
|
|
* recount all the block format things in EHTMLEditorSelection and here as well
|
|
* when we are moving with caret */
|
|
g_signal_handlers_block_by_func (
|
|
view, html_editor_view_selection_changed_cb, NULL);
|
|
e_html_editor_selection_block_selection_changed (selection);
|
|
|
|
parent = get_parent_block_element (WEBKIT_DOM_NODE (selection_end_marker));
|
|
|
|
/* Append some text on the end of the element */
|
|
text = webkit_dom_document_create_text_node (document, "-x-evo-end");
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (parent),
|
|
WEBKIT_DOM_NODE (text),
|
|
NULL);
|
|
|
|
parent = get_parent_block_element (WEBKIT_DOM_NODE (selection_start_marker));
|
|
|
|
/* Create range that's pointing on the end of this text */
|
|
end_range = webkit_dom_document_create_range (document);
|
|
webkit_dom_range_select_node_contents (
|
|
end_range, WEBKIT_DOM_NODE (text), NULL);
|
|
webkit_dom_range_collapse (end_range, FALSE, NULL);
|
|
|
|
/* Move on the beginning of the paragraph */
|
|
actual = webkit_dom_document_create_range (document);
|
|
webkit_dom_range_select_node_contents (
|
|
actual, WEBKIT_DOM_NODE (parent), NULL);
|
|
webkit_dom_range_collapse (actual, TRUE, NULL);
|
|
webkit_dom_dom_selection_remove_all_ranges (dom_selection);
|
|
webkit_dom_dom_selection_add_range (dom_selection, actual);
|
|
|
|
/* Go through all words to spellcheck them. To avoid this we have to wait for
|
|
* http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */
|
|
actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
|
|
/* We are moving forward word by word until we hit the text on the end of
|
|
* the paragraph that we previously inserted there */
|
|
while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) {
|
|
webkit_dom_dom_selection_modify (
|
|
dom_selection, "move", "forward", "word");
|
|
actual = webkit_dom_dom_selection_get_range_at (
|
|
dom_selection, 0, NULL);
|
|
}
|
|
|
|
/* Remove the text that we inserted on the end of the paragraph */
|
|
remove_node (WEBKIT_DOM_NODE (text));
|
|
|
|
/* Unblock the callbacks */
|
|
g_signal_handlers_unblock_by_func (
|
|
view, html_editor_view_selection_changed_cb, NULL);
|
|
e_html_editor_selection_unblock_selection_changed (selection);
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
}
|
|
|
|
static WebKitDOMElement *
|
|
create_selection_marker (WebKitDOMDocument *document,
|
|
gboolean start)
|
|
{
|
|
WebKitDOMElement *element;
|
|
|
|
element = webkit_dom_document_create_element (
|
|
document, "SPAN", NULL);
|
|
webkit_dom_element_set_id (
|
|
element,
|
|
start ? "-x-evo-selection-start-marker" :
|
|
"-x-evo-selection-end-marker");
|
|
|
|
return element;
|
|
}
|
|
|
|
static void
|
|
remove_selection_markers (WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMElement *marker;
|
|
|
|
marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
if (marker)
|
|
remove_node (WEBKIT_DOM_NODE (marker));
|
|
marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-end-marker");
|
|
if (marker)
|
|
remove_node (WEBKIT_DOM_NODE (marker));
|
|
}
|
|
|
|
static void
|
|
add_selection_markers_into_element_start (WebKitDOMDocument *document,
|
|
WebKitDOMElement *element,
|
|
WebKitDOMElement **selection_start_marker,
|
|
WebKitDOMElement **selection_end_marker)
|
|
{
|
|
WebKitDOMElement *marker;
|
|
|
|
remove_selection_markers (document);
|
|
marker = create_selection_marker (document, FALSE);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (element),
|
|
WEBKIT_DOM_NODE (marker),
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
|
|
NULL);
|
|
if (selection_end_marker)
|
|
*selection_end_marker = marker;
|
|
|
|
marker = create_selection_marker (document, TRUE);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (element),
|
|
WEBKIT_DOM_NODE (marker),
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
|
|
NULL);
|
|
if (selection_start_marker)
|
|
*selection_start_marker = marker;
|
|
}
|
|
|
|
static void
|
|
add_selection_markers_into_element_end (WebKitDOMDocument *document,
|
|
WebKitDOMElement *element,
|
|
WebKitDOMElement **selection_start_marker,
|
|
WebKitDOMElement **selection_end_marker)
|
|
{
|
|
WebKitDOMElement *marker;
|
|
|
|
remove_selection_markers (document);
|
|
marker = create_selection_marker (document, TRUE);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (marker), NULL);
|
|
if (selection_start_marker)
|
|
*selection_start_marker = marker;
|
|
|
|
marker = create_selection_marker (document, FALSE);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (marker), NULL);
|
|
if (selection_end_marker)
|
|
*selection_end_marker = marker;
|
|
}
|
|
|
|
static void
|
|
refresh_spell_check (EHTMLEditorView *view,
|
|
gboolean enable_spell_check)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMDOMSelection *dom_selection;
|
|
WebKitDOMDOMWindow *window;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker;
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMRange *end_range, *actual;
|
|
WebKitDOMText *text;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
window = webkit_dom_document_get_default_view (document);
|
|
dom_selection = webkit_dom_dom_window_get_selection (window);
|
|
|
|
/* Enable/Disable spellcheck in composer */
|
|
body = webkit_dom_document_get_body (document);
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (body),
|
|
"spellcheck",
|
|
enable_spell_check ? "true" : "false",
|
|
NULL);
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
e_html_editor_selection_save (selection);
|
|
|
|
selection_start_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-start-marker", NULL);
|
|
selection_end_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-end-marker", NULL);
|
|
|
|
/* Sometimes the web view is not focused, so we have to save the selection
|
|
* manually into the body */
|
|
if (!selection_start_marker || !selection_end_marker) {
|
|
WebKitDOMNode *child;
|
|
|
|
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
|
|
if (!child)
|
|
return;
|
|
|
|
add_selection_markers_into_element_start (
|
|
document,
|
|
WEBKIT_DOM_ELEMENT (child),
|
|
&selection_start_marker,
|
|
&selection_end_marker);
|
|
}
|
|
|
|
/* Block callbacks of selection-changed signal as we don't want to
|
|
* recount all the block format things in EHTMLEditorSelection and here as well
|
|
* when we are moving with caret */
|
|
g_signal_handlers_block_by_func (
|
|
view, html_editor_view_selection_changed_cb, NULL);
|
|
e_html_editor_selection_block_selection_changed (selection);
|
|
|
|
/* Append some text on the end of the body */
|
|
text = webkit_dom_document_create_text_node (document, "-x-evo-end");
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (text), NULL);
|
|
|
|
/* Create range that's pointing on the end of this text */
|
|
end_range = webkit_dom_document_create_range (document);
|
|
webkit_dom_range_select_node_contents (
|
|
end_range, WEBKIT_DOM_NODE (text), NULL);
|
|
webkit_dom_range_collapse (end_range, FALSE, NULL);
|
|
|
|
/* Move on the beginning of the document */
|
|
webkit_dom_dom_selection_modify (
|
|
dom_selection, "move", "backward", "documentboundary");
|
|
|
|
/* Go through all words to spellcheck them. To avoid this we have to wait for
|
|
* http://www.w3.org/html/wg/drafts/html/master/editing.html#dom-forcespellcheck */
|
|
actual = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
|
|
/* We are moving forward word by word until we hit the text on the end of
|
|
* the body that we previously inserted there */
|
|
while (actual && webkit_dom_range_compare_boundary_points (end_range, 2, actual, NULL) != 0) {
|
|
webkit_dom_dom_selection_modify (
|
|
dom_selection, "move", "forward", "word");
|
|
actual = webkit_dom_dom_selection_get_range_at (
|
|
dom_selection, 0, NULL);
|
|
}
|
|
|
|
/* Remove the text that we inserted on the end of the body */
|
|
remove_node (WEBKIT_DOM_NODE (text));
|
|
|
|
/* Unblock the callbacks */
|
|
g_signal_handlers_unblock_by_func (
|
|
view, html_editor_view_selection_changed_cb, NULL);
|
|
e_html_editor_selection_unblock_selection_changed (selection);
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_turn_spell_check_off (EHTMLEditorView *view)
|
|
{
|
|
refresh_spell_check (view, FALSE);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_force_spell_check (EHTMLEditorView *view)
|
|
{
|
|
if (view->priv->inline_spelling)
|
|
refresh_spell_check (view, TRUE);
|
|
}
|
|
|
|
static gint
|
|
get_citation_level (WebKitDOMNode *node,
|
|
gboolean set_plaintext_quoted)
|
|
{
|
|
WebKitDOMNode *parent = node;
|
|
gint level = 0;
|
|
|
|
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
|
|
if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
|
|
webkit_dom_element_has_attribute (WEBKIT_DOM_ELEMENT (parent), "type")) {
|
|
level++;
|
|
|
|
if (set_plaintext_quoted) {
|
|
element_add_class (
|
|
WEBKIT_DOM_ELEMENT (parent),
|
|
"-x-evo-plaintext-quoted");
|
|
}
|
|
}
|
|
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
}
|
|
|
|
return level;
|
|
}
|
|
|
|
static gchar *
|
|
get_quotation_for_level (gint quote_level)
|
|
{
|
|
gint ii;
|
|
GString *output = g_string_new ("");
|
|
|
|
for (ii = 0; ii < quote_level; ii++) {
|
|
g_string_append (output, "<span class=\"-x-evo-quote-character\">");
|
|
g_string_append (output, QUOTE_SYMBOL);
|
|
g_string_append (output, " ");
|
|
g_string_append (output, "</span>");
|
|
}
|
|
|
|
return g_string_free (output, FALSE);
|
|
}
|
|
|
|
static void
|
|
quote_plain_text_element_after_wrapping (WebKitDOMDocument *document,
|
|
WebKitDOMElement *element,
|
|
gint quote_level)
|
|
{
|
|
WebKitDOMNodeList *list;
|
|
WebKitDOMNode *quoted_node;
|
|
gint length, ii;
|
|
gchar *quotation;
|
|
|
|
quoted_node = WEBKIT_DOM_NODE (
|
|
webkit_dom_document_create_element (document, "SPAN", NULL));
|
|
webkit_dom_element_set_class_name (
|
|
WEBKIT_DOM_ELEMENT (quoted_node), "-x-evo-quoted");
|
|
quotation = get_quotation_for_level (quote_level);
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (quoted_node), quotation, NULL);
|
|
|
|
list = webkit_dom_element_query_selector_all (
|
|
element, "br.-x-evo-wrap-br", NULL);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (element),
|
|
quoted_node,
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)),
|
|
NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *br = webkit_dom_node_list_item (list, ii);
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (br),
|
|
webkit_dom_node_clone_node (quoted_node, TRUE),
|
|
webkit_dom_node_get_next_sibling (br),
|
|
NULL);
|
|
g_object_unref (br);
|
|
}
|
|
|
|
g_object_unref (list);
|
|
g_free (quotation);
|
|
}
|
|
|
|
static gboolean
|
|
is_citation_node (WebKitDOMNode *node)
|
|
{
|
|
char *value;
|
|
|
|
if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))
|
|
return FALSE;
|
|
|
|
value = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "type");
|
|
|
|
/* citation == <blockquote type='cite'> */
|
|
if (g_strcmp0 (value, "cite") == 0) {
|
|
g_free (value);
|
|
return TRUE;
|
|
} else {
|
|
g_free (value);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
return_pressed_in_empty_line (EHTMLEditorSelection *selection,
|
|
WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMDOMSelection *dom_selection;
|
|
WebKitDOMDOMWindow *dom_window;
|
|
WebKitDOMNode *node;
|
|
WebKitDOMRange *range;
|
|
|
|
dom_window = webkit_dom_document_get_default_view (document);
|
|
dom_selection = webkit_dom_dom_window_get_selection (dom_window);
|
|
|
|
range = webkit_dom_dom_selection_get_range_at (dom_selection, 0, NULL);
|
|
if (!range)
|
|
return FALSE;
|
|
|
|
node = webkit_dom_range_get_start_container (range, NULL);
|
|
if (!WEBKIT_DOM_IS_TEXT (node)) {
|
|
WebKitDOMNode *first_child;
|
|
|
|
first_child = webkit_dom_node_get_first_child (node);
|
|
if (first_child && WEBKIT_DOM_IS_ELEMENT (first_child) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (first_child), "-x-evo-quoted")) {
|
|
WebKitDOMNode *prev_sibling;
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (node);
|
|
if (!prev_sibling)
|
|
return webkit_dom_range_get_collapsed (range, NULL);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static WebKitDOMNode *
|
|
get_parent_block_node_from_child (WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
|
|
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-temp-text-wrapper") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quote-character") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-signature") ||
|
|
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "b") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "i") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (parent), "u"))
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quoted"))
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
return parent;
|
|
}
|
|
|
|
static WebKitDOMElement *
|
|
prepare_paragraph (EHTMLEditorSelection *selection,
|
|
WebKitDOMDocument *document,
|
|
gboolean with_selection)
|
|
{
|
|
WebKitDOMElement *element, *paragraph;
|
|
|
|
paragraph = e_html_editor_selection_get_paragraph_element (
|
|
selection, document, -1, 0);
|
|
|
|
if (with_selection)
|
|
add_selection_markers_into_element_start (
|
|
document, paragraph, NULL, NULL);
|
|
|
|
element = webkit_dom_document_create_element (document, "BR", NULL);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (paragraph), WEBKIT_DOM_NODE (element), NULL);
|
|
|
|
return paragraph;
|
|
}
|
|
|
|
static WebKitDOMElement *
|
|
insert_new_line_into_citation (EHTMLEditorView *view,
|
|
const gchar *html_to_insert)
|
|
{
|
|
gboolean html_mode, ret_val, avoid_editor_call;
|
|
EHTMLEditorSelection *selection;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *element, *paragraph = NULL;
|
|
|
|
html_mode = e_html_editor_view_get_html_mode (view);
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
avoid_editor_call =
|
|
return_pressed_in_empty_line (selection, document);
|
|
|
|
if (avoid_editor_call) {
|
|
WebKitDOMElement *selection_start_marker;
|
|
WebKitDOMNode *current_block, *parent, *parent_block, *block_clone;
|
|
|
|
e_html_editor_selection_save (selection);
|
|
|
|
selection_start_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
|
|
current_block = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
|
|
block_clone = webkit_dom_node_clone_node (current_block, TRUE);
|
|
/* Find selection start marker and restore it after the new line
|
|
* is inserted */
|
|
selection_start_marker = webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (block_clone), "#-x-evo-selection-start-marker", NULL);
|
|
|
|
/* Find parent node that is immediate child of the BODY */
|
|
/* Build the same structure of parent nodes of the current block */
|
|
parent_block = current_block;
|
|
parent = webkit_dom_node_get_parent_node (parent_block);
|
|
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
|
|
WebKitDOMNode *node;
|
|
|
|
parent_block = parent;
|
|
node = webkit_dom_node_clone_node (parent_block, FALSE);
|
|
webkit_dom_node_append_child (node, block_clone, NULL);
|
|
block_clone = node;
|
|
parent = webkit_dom_node_get_parent_node (parent_block);
|
|
}
|
|
|
|
paragraph = e_html_editor_selection_get_paragraph_element (
|
|
selection, document, -1, 0);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
WEBKIT_DOM_NODE (
|
|
webkit_dom_document_create_element (document, "BR", NULL)),
|
|
NULL);
|
|
|
|
/* Insert the selection markers to right place */
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_start_marker)),
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)),
|
|
NULL);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (paragraph)),
|
|
NULL);
|
|
|
|
/* Insert the cloned nodes before the BODY parent node */
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent_block),
|
|
block_clone,
|
|
parent_block,
|
|
NULL);
|
|
|
|
/* Insert the new empty paragraph before the BODY parent node */
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent_block),
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
parent_block,
|
|
NULL);
|
|
|
|
/* Remove the old block (its copy was moved to the right place) */
|
|
remove_node (current_block);
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
|
|
return NULL;
|
|
} else {
|
|
ret_val = e_html_editor_view_exec_command (
|
|
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, NULL);
|
|
|
|
if (!ret_val)
|
|
return NULL;
|
|
|
|
element = webkit_dom_document_query_selector (
|
|
document, "body>br", NULL);
|
|
|
|
if (!element)
|
|
return NULL;
|
|
}
|
|
|
|
if (!html_mode) {
|
|
WebKitDOMNode *next_sibling;
|
|
|
|
next_sibling = webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (element));
|
|
|
|
if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_sibling)) {
|
|
gint citation_level, length;
|
|
gint word_wrap_length =
|
|
e_html_editor_selection_get_word_wrap_length (selection);
|
|
WebKitDOMNode *node;
|
|
|
|
node = webkit_dom_node_get_first_child (next_sibling);
|
|
while (node && is_citation_node (node))
|
|
node = webkit_dom_node_get_first_child (node);
|
|
|
|
citation_level = get_citation_level (node, FALSE);
|
|
length = word_wrap_length - 2 * citation_level;
|
|
|
|
/* Rewrap and requote first block after the newly inserted line */
|
|
if (node && WEBKIT_DOM_IS_ELEMENT (node)) {
|
|
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (node));
|
|
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (node));
|
|
node = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
|
|
selection, WEBKIT_DOM_ELEMENT (node), length));
|
|
quote_plain_text_element_after_wrapping (
|
|
document, WEBKIT_DOM_ELEMENT (node), citation_level);
|
|
}
|
|
|
|
e_html_editor_view_force_spell_check (view);
|
|
}
|
|
}
|
|
|
|
if (html_to_insert && *html_to_insert) {
|
|
paragraph = prepare_paragraph (selection, document, FALSE);
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (paragraph),
|
|
html_to_insert,
|
|
NULL);
|
|
add_selection_markers_into_element_end (
|
|
document, paragraph, NULL, NULL);
|
|
} else
|
|
paragraph = prepare_paragraph (selection, document, TRUE);
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
WEBKIT_DOM_NODE (element),
|
|
NULL);
|
|
|
|
remove_node (WEBKIT_DOM_NODE (element));
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
|
|
return paragraph;
|
|
}
|
|
|
|
static void
|
|
set_base64_to_element_attribute (EHTMLEditorView *view,
|
|
WebKitDOMElement *element,
|
|
const gchar *attribute)
|
|
{
|
|
gchar *attribute_value;
|
|
const gchar *base64_src;
|
|
|
|
attribute_value = webkit_dom_element_get_attribute (element, attribute);
|
|
|
|
if ((base64_src = g_hash_table_lookup (view->priv->inline_images, attribute_value)) != NULL) {
|
|
const gchar *base64_data = strstr (base64_src, ";") + 1;
|
|
gchar *name;
|
|
glong name_length;
|
|
|
|
name_length =
|
|
g_utf8_strlen (base64_src, -1) -
|
|
g_utf8_strlen (base64_data, -1) - 1;
|
|
name = g_strndup (base64_src, name_length);
|
|
|
|
webkit_dom_element_set_attribute (element, "data-inline", "", NULL);
|
|
webkit_dom_element_set_attribute (element, "data-name", name, NULL);
|
|
webkit_dom_element_set_attribute (element, attribute, base64_data, NULL);
|
|
|
|
g_free (name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
change_cid_images_src_to_base64 (EHTMLEditorView *view)
|
|
{
|
|
gint ii, length;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *document_element;
|
|
WebKitDOMNamedNodeMap *attributes;
|
|
WebKitDOMNodeList *list;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
document_element = webkit_dom_document_get_document_element (document);
|
|
|
|
list = webkit_dom_document_query_selector_all (document, "img[src^=\"cid:\"]", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
|
|
set_base64_to_element_attribute (view, WEBKIT_DOM_ELEMENT (node), "src");
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
/* Namespaces */
|
|
attributes = webkit_dom_element_get_attributes (document_element);
|
|
length = webkit_dom_named_node_map_get_length (attributes);
|
|
for (ii = 0; ii < length; ii++) {
|
|
gchar *name;
|
|
WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
|
|
|
|
name = webkit_dom_node_get_local_name (node);
|
|
|
|
if (g_str_has_prefix (name, "xmlns:")) {
|
|
const gchar *ns = name + 6;
|
|
gchar *attribute_ns = g_strconcat (ns, ":src", NULL);
|
|
gchar *selector = g_strconcat ("img[", ns, "\\:src^=\"cid:\"]", NULL);
|
|
gint ns_length, jj;
|
|
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, selector, NULL);
|
|
ns_length = webkit_dom_node_list_get_length (list);
|
|
for (jj = 0; jj < ns_length; jj++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, jj);
|
|
|
|
set_base64_to_element_attribute (
|
|
view, WEBKIT_DOM_ELEMENT (node), attribute_ns);
|
|
g_object_unref (node);
|
|
}
|
|
|
|
g_object_unref (list);
|
|
g_free (attribute_ns);
|
|
g_free (selector);
|
|
}
|
|
g_object_unref (node);
|
|
g_free (name);
|
|
}
|
|
g_object_unref (attributes);
|
|
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "[background^=\"cid:\"]", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
|
|
set_base64_to_element_attribute (
|
|
view, WEBKIT_DOM_ELEMENT (node), "background");
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
g_hash_table_remove_all (view->priv->inline_images);
|
|
}
|
|
|
|
/* For purpose of this function see e-mail-formatter-quote.c */
|
|
static void
|
|
put_body_in_citation (WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMElement *cite_body = webkit_dom_document_query_selector (
|
|
document, "span.-x-evo-cite-body", NULL);
|
|
|
|
if (cite_body) {
|
|
WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
|
|
WebKitDOMNode *citation;
|
|
WebKitDOMNode *sibling;
|
|
|
|
citation = WEBKIT_DOM_NODE (
|
|
webkit_dom_document_create_element (document, "blockquote", NULL));
|
|
webkit_dom_element_set_id (WEBKIT_DOM_ELEMENT (citation), "-x-evo-main-cite");
|
|
webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (citation), "type", "cite", NULL);
|
|
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (body),
|
|
citation,
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
|
|
NULL);
|
|
|
|
while ((sibling = webkit_dom_node_get_next_sibling (citation)))
|
|
webkit_dom_node_append_child (citation, sibling, NULL);
|
|
|
|
remove_node (WEBKIT_DOM_NODE (cite_body));
|
|
}
|
|
}
|
|
|
|
/* For purpose of this function see e-mail-formatter-quote.c */
|
|
static void
|
|
move_elements_to_body (WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
|
|
WebKitDOMNodeList *list;
|
|
gint ii;
|
|
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "span.-x-evo-to-body[data-headers]", NULL);
|
|
for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) {
|
|
WebKitDOMNode *child;
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
|
|
while ((child = webkit_dom_node_get_first_child (node))) {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (body),
|
|
child,
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (body)),
|
|
NULL);
|
|
}
|
|
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "span.-x-evo-to-body[data-credits]", NULL);
|
|
for (ii = webkit_dom_node_list_get_length (list) - 1; ii >= 0; ii--) {
|
|
char *credits;
|
|
WebKitDOMElement *pre_element;
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
|
|
pre_element = webkit_dom_document_create_element (document, "pre", NULL);
|
|
credits = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "data-credits");
|
|
webkit_dom_html_element_set_inner_text (WEBKIT_DOM_HTML_ELEMENT (pre_element), credits, NULL);
|
|
g_free (credits);
|
|
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (body),
|
|
WEBKIT_DOM_NODE (pre_element),
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (body)),
|
|
NULL);
|
|
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
}
|
|
|
|
static void
|
|
repair_gmail_blockquotes (WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMNodeList *list;
|
|
gint ii, length;
|
|
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "blockquote.gmail_quote", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
|
|
webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "class");
|
|
webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (node), "style");
|
|
webkit_dom_element_set_attribute (WEBKIT_DOM_ELEMENT (node), "type", "cite", NULL);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
}
|
|
|
|
/* Based on original use_pictograms() from GtkHTML */
|
|
static const gchar *emoticons_chars =
|
|
/* 0 */ "DO)(|/PQ*!"
|
|
/* 10 */ "S\0:-\0:\0:-\0"
|
|
/* 20 */ ":\0:;=-\"\0:;"
|
|
/* 30 */ "B\"|\0:-'\0:X"
|
|
/* 40 */ "\0:\0:-\0:\0:-"
|
|
/* 50 */ "\0:\0:-\0:\0:-"
|
|
/* 60 */ "\0:\0:\0:-\0:\0"
|
|
/* 70 */ ":-\0:\0:-\0:\0";
|
|
static gint emoticons_states[] = {
|
|
/* 0 */ 12, 17, 22, 34, 43, 48, 53, 58, 65, 70,
|
|
/* 10 */ 75, 0, -15, 15, 0, -15, 0, -17, 20, 0,
|
|
/* 20 */ -17, 0, -14, -20, -14, 28, 63, 0, -14, -20,
|
|
/* 30 */ -3, 63, -18, 0, -12, 38, 41, 0, -12, -2,
|
|
/* 40 */ 0, -4, 0, -10, 46, 0, -10, 0, -19, 51,
|
|
/* 50 */ 0, -19, 0, -11, 56, 0, -11, 0, -13, 61,
|
|
/* 60 */ 0, -13, 0, -6, 0, 68, -7, 0, -7, 0,
|
|
/* 70 */ -16, 73, 0, -16, 0, -21, 78, 0, -21, 0 };
|
|
static const gchar *emoticons_icon_names[] = {
|
|
"face-angel",
|
|
"face-angry",
|
|
"face-cool",
|
|
"face-crying",
|
|
"face-devilish",
|
|
"face-embarrassed",
|
|
"face-kiss",
|
|
"face-laugh", /* not used */
|
|
"face-monkey", /* not used */
|
|
"face-plain",
|
|
"face-raspberry",
|
|
"face-sad",
|
|
"face-sick",
|
|
"face-smile",
|
|
"face-smile-big",
|
|
"face-smirk",
|
|
"face-surprise",
|
|
"face-tired",
|
|
"face-uncertain",
|
|
"face-wink",
|
|
"face-worried"
|
|
};
|
|
|
|
static gboolean
|
|
is_return_key (GdkEventKey *event)
|
|
{
|
|
return (
|
|
(event->keyval == GDK_KEY_Return) ||
|
|
(event->keyval == GDK_KEY_Linefeed) ||
|
|
(event->keyval == GDK_KEY_KP_Enter));
|
|
}
|
|
|
|
static void
|
|
html_editor_view_check_magic_links (EHTMLEditorView *view,
|
|
WebKitDOMRange *range,
|
|
gboolean include_space_by_user)
|
|
{
|
|
gchar *node_text;
|
|
gchar **urls;
|
|
GRegex *regex = NULL;
|
|
GMatchInfo *match_info;
|
|
gint start_pos_url, end_pos_url;
|
|
WebKitDOMNode *node;
|
|
gboolean include_space = FALSE;
|
|
gboolean is_email_address = FALSE;
|
|
|
|
if (include_space_by_user == TRUE)
|
|
include_space = TRUE;
|
|
else
|
|
include_space = view->priv->space_key_pressed;
|
|
|
|
node = webkit_dom_range_get_end_container (range, NULL);
|
|
|
|
if (view->priv->return_key_pressed)
|
|
node = webkit_dom_node_get_previous_sibling (node);
|
|
|
|
if (!node)
|
|
return;
|
|
|
|
if (!WEBKIT_DOM_IS_TEXT (node)) {
|
|
if (webkit_dom_node_has_child_nodes (node))
|
|
node = webkit_dom_node_get_first_child (node);
|
|
if (!WEBKIT_DOM_IS_TEXT (node))
|
|
return;
|
|
}
|
|
|
|
node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
|
|
if (!node_text || !(*node_text) || !g_utf8_validate (node_text, -1, NULL))
|
|
return;
|
|
|
|
if (strstr (node_text, "@") && !strstr (node_text, "://")) {
|
|
is_email_address = TRUE;
|
|
regex = g_regex_new (include_space ? E_MAIL_PATTERN_SPACE : E_MAIL_PATTERN, 0, 0, NULL);
|
|
} else
|
|
regex = g_regex_new (include_space ? URL_PATTERN_SPACE : URL_PATTERN, 0, 0, NULL);
|
|
|
|
if (!regex) {
|
|
g_free (node_text);
|
|
return;
|
|
}
|
|
|
|
g_regex_match_all (regex, node_text, G_REGEX_MATCH_NOTEMPTY, &match_info);
|
|
urls = g_match_info_fetch_all (match_info);
|
|
|
|
if (urls) {
|
|
gchar *final_url, *url_end_raw;
|
|
glong url_start, url_end, url_length;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNode *url_text_node_clone;
|
|
WebKitDOMText *url_text_node;
|
|
WebKitDOMElement *anchor;
|
|
const gchar* url_text;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
if (!view->priv->return_key_pressed)
|
|
e_html_editor_selection_save_caret_position (
|
|
e_html_editor_view_get_selection (view));
|
|
|
|
g_match_info_fetch_pos (match_info, 0, &start_pos_url, &end_pos_url);
|
|
|
|
/* Get start and end position of url in node's text because positions
|
|
* that we get from g_match_info_fetch_pos are not UTF-8 aware */
|
|
url_end_raw = g_strndup(node_text, end_pos_url);
|
|
url_end = g_utf8_strlen (url_end_raw, -1);
|
|
|
|
url_length = g_utf8_strlen (urls[0], -1);
|
|
url_start = url_end - url_length;
|
|
|
|
webkit_dom_text_split_text (
|
|
WEBKIT_DOM_TEXT (node),
|
|
include_space ? url_end - 1 : url_end,
|
|
NULL);
|
|
|
|
url_text_node = webkit_dom_text_split_text (
|
|
WEBKIT_DOM_TEXT (node), url_start, NULL);
|
|
url_text_node_clone = webkit_dom_node_clone_node (
|
|
WEBKIT_DOM_NODE (url_text_node), TRUE);
|
|
url_text = webkit_dom_text_get_whole_text (
|
|
WEBKIT_DOM_TEXT (url_text_node_clone));
|
|
|
|
if (g_str_has_prefix (url_text, "www."))
|
|
final_url = g_strconcat ("http://" , url_text, NULL);
|
|
else if (is_email_address)
|
|
final_url = g_strconcat ("mailto:" , url_text, NULL);
|
|
else
|
|
final_url = g_strdup (url_text);
|
|
|
|
/* Create and prepare new anchor element */
|
|
anchor = webkit_dom_document_create_element (document, "A", NULL);
|
|
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (anchor),
|
|
url_text,
|
|
NULL);
|
|
|
|
webkit_dom_html_anchor_element_set_href (
|
|
WEBKIT_DOM_HTML_ANCHOR_ELEMENT (anchor),
|
|
final_url);
|
|
|
|
/* Insert new anchor element into document */
|
|
webkit_dom_node_replace_child (
|
|
webkit_dom_node_get_parent_node (node),
|
|
WEBKIT_DOM_NODE (anchor),
|
|
WEBKIT_DOM_NODE (url_text_node),
|
|
NULL);
|
|
|
|
if (!view->priv->return_key_pressed)
|
|
e_html_editor_selection_restore_caret_position (
|
|
e_html_editor_view_get_selection (view));
|
|
|
|
g_free (url_end_raw);
|
|
g_free (final_url);
|
|
} else {
|
|
WebKitDOMElement *parent;
|
|
WebKitDOMNode *prev_sibling;
|
|
gchar *href, *text, *url;
|
|
gint diff;
|
|
const char* text_to_append;
|
|
gboolean appending_to_link = FALSE;
|
|
|
|
parent = webkit_dom_node_get_parent_element (node);
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (node);
|
|
|
|
/* If previous sibling is ANCHOR and actual text node is not beginning with
|
|
* space => we're appending to link */
|
|
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
|
|
text_to_append = webkit_dom_node_get_text_content (node);
|
|
if (g_strcmp0 (text_to_append, "") != 0 &&
|
|
!g_unichar_isspace (g_utf8_get_char (text_to_append))) {
|
|
|
|
appending_to_link = TRUE;
|
|
parent = WEBKIT_DOM_ELEMENT (prev_sibling);
|
|
}
|
|
}
|
|
|
|
/* If parent is ANCHOR => we're editing the link */
|
|
if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (parent) && !appending_to_link) {
|
|
g_match_info_free (match_info);
|
|
g_regex_unref (regex);
|
|
g_free (node_text);
|
|
return;
|
|
}
|
|
|
|
/* edit only if href and description are the same */
|
|
href = webkit_dom_html_anchor_element_get_href (
|
|
WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent));
|
|
|
|
if (appending_to_link) {
|
|
gchar *inner_text;
|
|
|
|
inner_text =
|
|
webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (parent)),
|
|
|
|
text = g_strconcat (inner_text, text_to_append, NULL);
|
|
g_free (inner_text);
|
|
} else
|
|
text = webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (parent));
|
|
|
|
if (strstr (href, "://") && !strstr (text, "://")) {
|
|
url = strstr (href, "://") + 3;
|
|
diff = strlen (text) - strlen (url);
|
|
|
|
if (text [strlen (text) - 1] != '/')
|
|
diff++;
|
|
|
|
if ((g_strcmp0 (url, text) != 0 && ABS (diff) == 1) || appending_to_link) {
|
|
gchar *inner_html, *protocol, *new_href;
|
|
|
|
protocol = g_strndup (href, strstr (href, "://") - href + 3);
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (parent));
|
|
new_href = g_strconcat (
|
|
protocol, inner_html, appending_to_link ? text_to_append : "", NULL);
|
|
|
|
webkit_dom_html_anchor_element_set_href (
|
|
WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
|
|
new_href);
|
|
|
|
if (appending_to_link) {
|
|
webkit_dom_html_element_insert_adjacent_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (parent),
|
|
"beforeend",
|
|
text_to_append,
|
|
NULL);
|
|
|
|
remove_node (node);
|
|
}
|
|
|
|
g_free (new_href);
|
|
g_free (protocol);
|
|
g_free (inner_html);
|
|
}
|
|
} else {
|
|
diff = strlen (text) - strlen (href);
|
|
if (text [strlen (text) - 1] != '/')
|
|
diff++;
|
|
|
|
if ((g_strcmp0 (href, text) != 0 && ABS (diff) == 1) || appending_to_link) {
|
|
gchar *inner_html;
|
|
gchar *new_href;
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (parent));
|
|
new_href = g_strconcat (
|
|
inner_html,
|
|
appending_to_link ? text_to_append : "",
|
|
NULL);
|
|
|
|
webkit_dom_html_anchor_element_set_href (
|
|
WEBKIT_DOM_HTML_ANCHOR_ELEMENT (parent),
|
|
new_href);
|
|
|
|
if (appending_to_link) {
|
|
webkit_dom_html_element_insert_adjacent_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (parent),
|
|
"beforeend",
|
|
text_to_append,
|
|
NULL);
|
|
|
|
remove_node (node);
|
|
}
|
|
|
|
g_free (new_href);
|
|
g_free (inner_html);
|
|
}
|
|
|
|
}
|
|
g_free (text);
|
|
g_free (href);
|
|
}
|
|
|
|
g_match_info_free (match_info);
|
|
g_regex_unref (regex);
|
|
g_free (node_text);
|
|
}
|
|
|
|
typedef struct _LoadContext LoadContext;
|
|
|
|
struct _LoadContext {
|
|
EHTMLEditorView *view;
|
|
gchar *content_type;
|
|
gchar *name;
|
|
EEmoticon *emoticon;
|
|
};
|
|
|
|
static LoadContext *
|
|
emoticon_load_context_new (EHTMLEditorView *view,
|
|
EEmoticon *emoticon)
|
|
{
|
|
LoadContext *load_context;
|
|
|
|
load_context = g_slice_new0 (LoadContext);
|
|
load_context->view = view;
|
|
load_context->emoticon = emoticon;
|
|
|
|
return load_context;
|
|
}
|
|
|
|
static void
|
|
emoticon_load_context_free (LoadContext *load_context)
|
|
{
|
|
g_free (load_context->content_type);
|
|
g_free (load_context->name);
|
|
g_slice_free (LoadContext, load_context);
|
|
}
|
|
|
|
static void
|
|
emoticon_insert_span (EHTMLEditorView *view,
|
|
EEmoticon *emoticon,
|
|
WebKitDOMElement *span)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gboolean misplaced_selection = FALSE, empty = FALSE;
|
|
gchar *node_text = NULL, *content;
|
|
const gchar *emoticon_start;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker;
|
|
WebKitDOMNode *node, *insert_before, *prev_sibling, *next_sibling;
|
|
WebKitDOMNode *selection_end_marker_parent;
|
|
WebKitDOMRange *range;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
if (!e_html_editor_selection_is_collapsed (selection))
|
|
e_html_editor_view_exec_command (
|
|
view, E_HTML_EDITOR_VIEW_COMMAND_DELETE, NULL);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
e_html_editor_selection_save (selection);
|
|
selection_start_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
selection_end_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-end-marker");
|
|
|
|
/* If the selection was not saved, move it into the first child of body */
|
|
if (!selection_start_marker || !selection_end_marker) {
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMNode *child;
|
|
|
|
body = webkit_dom_document_get_body (document);
|
|
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
|
|
|
|
add_selection_markers_into_element_start (
|
|
document,
|
|
WEBKIT_DOM_ELEMENT (child),
|
|
&selection_start_marker,
|
|
&selection_end_marker);
|
|
}
|
|
|
|
/* Sometimes selection end marker is in body. Move it into next sibling */
|
|
selection_end_marker_parent = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (selection_end_marker_parent)) {
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (selection_start_marker)),
|
|
WEBKIT_DOM_NODE (selection_end_marker),
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
NULL);
|
|
}
|
|
selection_end_marker_parent = webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
|
|
/* Determine before what node we have to insert the smiley */
|
|
insert_before = WEBKIT_DOM_NODE (selection_start_marker);
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
if (prev_sibling) {
|
|
if (webkit_dom_node_is_same_node (
|
|
prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) {
|
|
insert_before = WEBKIT_DOM_NODE (selection_end_marker);
|
|
} else {
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
|
|
if (prev_sibling &&
|
|
webkit_dom_node_is_same_node (
|
|
prev_sibling, WEBKIT_DOM_NODE (selection_end_marker))) {
|
|
insert_before = WEBKIT_DOM_NODE (selection_end_marker);
|
|
}
|
|
}
|
|
} else
|
|
insert_before = WEBKIT_DOM_NODE (selection_start_marker);
|
|
|
|
/* Look if selection is misplaced - that means that the selection was
|
|
* restored before the previously inserted smiley in situations when we
|
|
* are writing more smileys in a row */
|
|
next_sibling = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker));
|
|
if (next_sibling && WEBKIT_DOM_IS_ELEMENT (next_sibling))
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (next_sibling), "-x-evo-smiley-wrapper"))
|
|
misplaced_selection = TRUE;
|
|
|
|
range = html_editor_view_get_dom_range (view);
|
|
node = webkit_dom_range_get_end_container (range, NULL);
|
|
if (WEBKIT_DOM_IS_TEXT (node))
|
|
node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
|
|
|
|
content = webkit_dom_node_get_text_content (selection_end_marker_parent);
|
|
empty = !*content || (g_strcmp0 (content, UNICODE_ZERO_WIDTH_SPACE) == 0);
|
|
g_free (content);
|
|
|
|
if (misplaced_selection) {
|
|
/* Insert smiley and selection markers after it */
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (insert_before),
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
webkit_dom_node_get_next_sibling (next_sibling),
|
|
NULL);
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (insert_before),
|
|
WEBKIT_DOM_NODE (selection_end_marker),
|
|
webkit_dom_node_get_next_sibling (next_sibling),
|
|
NULL);
|
|
span = WEBKIT_DOM_ELEMENT (
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (insert_before),
|
|
WEBKIT_DOM_NODE (span),
|
|
webkit_dom_node_get_next_sibling (next_sibling),
|
|
NULL));
|
|
} else {
|
|
span = WEBKIT_DOM_ELEMENT (
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (insert_before),
|
|
WEBKIT_DOM_NODE (span),
|
|
insert_before,
|
|
NULL));
|
|
}
|
|
|
|
/* ​ == UNICODE_ZERO_WIDTH_SPACE */
|
|
if (empty || !view->priv->smiley_written)
|
|
webkit_dom_html_element_insert_adjacent_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (span), "afterend", "​", NULL);
|
|
|
|
/* Remove the text that represents the text version of smiley that was
|
|
* written into the composer. */
|
|
if (node_text && view->priv->smiley_written) {
|
|
emoticon_start = g_utf8_strrchr (
|
|
node_text, -1, g_utf8_get_char (emoticon->text_face));
|
|
/* Check if the written smiley is really the one that we inserted. */
|
|
if (emoticon_start && g_str_has_prefix (emoticon_start, emoticon->text_face)) {
|
|
webkit_dom_character_data_delete_data (
|
|
WEBKIT_DOM_CHARACTER_DATA (node),
|
|
g_utf8_strlen (node_text, -1) - strlen (emoticon_start),
|
|
strlen (emoticon->text_face),
|
|
NULL);
|
|
}
|
|
view->priv->smiley_written = FALSE;
|
|
}
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
|
|
e_html_editor_view_set_changed (view, TRUE);
|
|
|
|
g_free (node_text);
|
|
}
|
|
|
|
static void
|
|
emoticon_read_async_cb (GFile *file,
|
|
GAsyncResult *result,
|
|
LoadContext *load_context)
|
|
{
|
|
EHTMLEditorView *view = load_context->view;
|
|
EEmoticon *emoticon = load_context->emoticon;
|
|
GError *error = NULL;
|
|
gboolean html_mode;
|
|
gchar *mime_type;
|
|
gchar *base64_encoded, *output, *data;
|
|
GFileInputStream *input_stream;
|
|
GOutputStream *output_stream;
|
|
gssize size;
|
|
WebKitDOMElement *wrapper, *image, *smiley_text;
|
|
WebKitDOMDocument *document;
|
|
|
|
input_stream = g_file_read_finish (file, result, &error);
|
|
g_return_if_fail (!error && input_stream);
|
|
|
|
output_stream = g_memory_output_stream_new (NULL, 0, g_realloc, g_free);
|
|
|
|
size = g_output_stream_splice (
|
|
output_stream, G_INPUT_STREAM (input_stream),
|
|
G_OUTPUT_STREAM_SPLICE_NONE, NULL, &error);
|
|
|
|
if (error || (size == -1))
|
|
goto out;
|
|
|
|
mime_type = g_content_type_get_mime_type (load_context->content_type);
|
|
|
|
data = g_memory_output_stream_get_data (G_MEMORY_OUTPUT_STREAM (output_stream));
|
|
base64_encoded = g_base64_encode ((const guchar *) data, size);
|
|
output = g_strconcat ("data:", mime_type, ";base64,", base64_encoded, NULL);
|
|
|
|
html_mode = e_html_editor_view_get_html_mode (view);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
/* Insert span with image representation and another one with text
|
|
* represetation and hide/show them dependant on active composer mode */
|
|
wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
|
|
if (html_mode)
|
|
webkit_dom_element_set_attribute (
|
|
wrapper, "class", "-x-evo-smiley-wrapper -x-evo-resizable-wrapper", NULL);
|
|
else
|
|
webkit_dom_element_set_attribute (
|
|
wrapper, "class", "-x-evo-smiley-wrapper", NULL);
|
|
|
|
image = webkit_dom_document_create_element (document, "IMG", NULL);
|
|
webkit_dom_element_set_attribute (image, "src", output, NULL);
|
|
webkit_dom_element_set_attribute (image, "alt", emoticon->text_face, NULL);
|
|
webkit_dom_element_set_attribute (image, "-x-evo-smiley", emoticon->icon_name, NULL);
|
|
webkit_dom_element_set_attribute (image, "class", "-x-evo-smiley-img", NULL);
|
|
if (!html_mode)
|
|
webkit_dom_element_set_attribute (image, "style", "display: none;", NULL);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (image), NULL);
|
|
|
|
smiley_text = webkit_dom_document_create_element (document, "SPAN", NULL);
|
|
webkit_dom_element_set_attribute (smiley_text, "class", "-x-evo-smiley-text", NULL);
|
|
if (html_mode)
|
|
webkit_dom_element_set_attribute (smiley_text, "style", "display: none;", NULL);
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (smiley_text), emoticon->text_face, NULL);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (smiley_text), NULL);
|
|
|
|
emoticon_insert_span (view, emoticon, wrapper);
|
|
|
|
g_free (base64_encoded);
|
|
g_free (output);
|
|
g_free (mime_type);
|
|
g_object_unref (output_stream);
|
|
out:
|
|
emoticon_load_context_free (load_context);
|
|
}
|
|
|
|
static void
|
|
emoticon_query_info_async_cb (GFile *file,
|
|
GAsyncResult *result,
|
|
LoadContext *load_context)
|
|
{
|
|
GError *error = NULL;
|
|
GFileInfo *info;
|
|
|
|
info = g_file_query_info_finish (file, result, &error);
|
|
g_return_if_fail (!error && info);
|
|
|
|
load_context->content_type = g_strdup (g_file_info_get_content_type (info));
|
|
load_context->name = g_strdup (g_file_info_get_name (info));
|
|
|
|
g_file_read_async (
|
|
file, G_PRIORITY_DEFAULT, NULL,
|
|
(GAsyncReadyCallback) emoticon_read_async_cb, load_context);
|
|
|
|
g_object_unref (info);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_insert_smiley (EHTMLEditorView *view,
|
|
EEmoticon *emoticon)
|
|
{
|
|
GFile *file;
|
|
gchar *filename_uri;
|
|
LoadContext *load_context;
|
|
|
|
if (e_html_editor_view_get_unicode_smileys (view)) {
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *wrapper;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (wrapper), emoticon->unicode_character, NULL);
|
|
|
|
emoticon_insert_span (view, emoticon, wrapper);
|
|
} else {
|
|
filename_uri = e_emoticon_get_uri (emoticon);
|
|
g_return_if_fail (filename_uri != NULL);
|
|
|
|
load_context = emoticon_load_context_new (view, emoticon);
|
|
|
|
file = g_file_new_for_uri (filename_uri);
|
|
g_file_query_info_async (
|
|
file, "standard::*", G_FILE_QUERY_INFO_NONE,
|
|
G_PRIORITY_DEFAULT, NULL,
|
|
(GAsyncReadyCallback) emoticon_query_info_async_cb, load_context);
|
|
|
|
g_free (filename_uri);
|
|
g_object_unref (file);
|
|
}
|
|
}
|
|
|
|
static void
|
|
html_editor_view_check_magic_smileys (EHTMLEditorView *view,
|
|
WebKitDOMRange *range)
|
|
{
|
|
gint pos;
|
|
gint state;
|
|
gint relative;
|
|
gint start;
|
|
gchar *node_text;
|
|
gunichar uc;
|
|
WebKitDOMNode *node;
|
|
|
|
node = webkit_dom_range_get_end_container (range, NULL);
|
|
if (!WEBKIT_DOM_IS_TEXT (node))
|
|
return;
|
|
|
|
node_text = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (node));
|
|
if (node_text == NULL)
|
|
return;
|
|
|
|
start = webkit_dom_range_get_end_offset (range, NULL) - 1;
|
|
pos = start;
|
|
state = 0;
|
|
while (pos >= 0) {
|
|
uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos));
|
|
relative = 0;
|
|
while (emoticons_chars[state + relative]) {
|
|
if (emoticons_chars[state + relative] == uc)
|
|
break;
|
|
relative++;
|
|
}
|
|
state = emoticons_states[state + relative];
|
|
/* 0 .. not found, -n .. found n-th */
|
|
if (state <= 0)
|
|
break;
|
|
pos--;
|
|
}
|
|
|
|
/* Special case needed to recognize angel and devilish. */
|
|
if (pos > 0 && state == -14) {
|
|
uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
|
|
if (uc == 'O') {
|
|
state = -1;
|
|
pos--;
|
|
} else if (uc == '>') {
|
|
state = -5;
|
|
pos--;
|
|
}
|
|
}
|
|
|
|
if (state < 0) {
|
|
const EEmoticon *emoticon;
|
|
|
|
if (pos > 0) {
|
|
uc = g_utf8_get_char (g_utf8_offset_to_pointer (node_text, pos - 1));
|
|
if (!g_unichar_isspace (uc)) {
|
|
g_free (node_text);
|
|
return;
|
|
}
|
|
}
|
|
|
|
emoticon = (e_emoticon_chooser_lookup_emoticon (
|
|
emoticons_icon_names[-state - 1]));
|
|
view->priv->smiley_written = TRUE;
|
|
e_html_editor_view_insert_smiley (view, (EEmoticon *) emoticon);
|
|
}
|
|
|
|
g_free (node_text);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_set_links_active (EHTMLEditorView *view,
|
|
gboolean active)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *style;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
if (active) {
|
|
style = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-style-a");
|
|
if (style)
|
|
remove_node (WEBKIT_DOM_NODE (style));
|
|
} else {
|
|
WebKitDOMHTMLHeadElement *head;
|
|
head = webkit_dom_document_get_head (document);
|
|
|
|
style = webkit_dom_document_create_element (document, "STYLE", NULL);
|
|
webkit_dom_element_set_id (style, "-x-evo-style-a");
|
|
webkit_dom_element_set_attribute (style, "type", "text/css", NULL);
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (style), "a { cursor: text; }", NULL);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style), NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
fix_paragraph_structure_after_pressing_enter_after_smiley (EHTMLEditorSelection *selection,
|
|
WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMElement *element;
|
|
|
|
element = webkit_dom_document_query_selector (
|
|
document, "span.-x-evo-smiley-wrapper > br", NULL);
|
|
|
|
if (element) {
|
|
WebKitDOMNode *parent;
|
|
|
|
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element));
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (
|
|
webkit_dom_node_get_parent_node (parent)),
|
|
UNICODE_ZERO_WIDTH_SPACE,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
mark_node_as_paragraph_after_ending_list (EHTMLEditorSelection *selection,
|
|
WebKitDOMDocument *document)
|
|
{
|
|
gint ii, length;
|
|
WebKitDOMNodeList *list;
|
|
|
|
/* When pressing Enter on empty line in the list WebKit will end that
|
|
* list and inserts <div><br></div> so mark it for wrapping */
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "body > div:not(.-x-evo-paragraph) > br", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_get_parent_node (
|
|
webkit_dom_node_list_item (list, ii));
|
|
|
|
e_html_editor_selection_set_paragraph_style (
|
|
selection, WEBKIT_DOM_ELEMENT (node), -1, 0, "");
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
}
|
|
|
|
static gboolean
|
|
surround_text_with_paragraph_if_needed (EHTMLEditorSelection *selection,
|
|
WebKitDOMDocument *document,
|
|
WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
|
|
WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (node);
|
|
WebKitDOMElement *element;
|
|
|
|
/* All text in composer has to be written in div elements, so if
|
|
* we are writing something straight to the body, surround it with
|
|
* paragraph */
|
|
if (WEBKIT_DOM_IS_TEXT (node) &&
|
|
WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node))) {
|
|
element = e_html_editor_selection_put_node_into_paragraph (
|
|
selection,
|
|
document,
|
|
node,
|
|
e_html_editor_selection_get_caret_position_node (document));
|
|
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
|
|
remove_node (next_sibling);
|
|
|
|
/* Tab character */
|
|
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "Apple-tab-span")) {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (element),
|
|
prev_sibling,
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (element)),
|
|
NULL);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
body_keydown_event_cb (WebKitDOMElement *element,
|
|
WebKitDOMUIEvent *event,
|
|
EHTMLEditorView *view)
|
|
{
|
|
glong key_code;
|
|
|
|
key_code = webkit_dom_ui_event_get_key_code (event);
|
|
if (key_code == HTML_KEY_CODE_CONTROL)
|
|
html_editor_view_set_links_active (view, TRUE);
|
|
}
|
|
|
|
static void
|
|
body_keypress_event_cb (WebKitDOMElement *element,
|
|
WebKitDOMUIEvent *event,
|
|
EHTMLEditorView *view)
|
|
{
|
|
glong key_code;
|
|
|
|
view->priv->return_key_pressed = FALSE;
|
|
view->priv->space_key_pressed = FALSE;
|
|
|
|
key_code = webkit_dom_ui_event_get_key_code (event);
|
|
if (key_code == HTML_KEY_CODE_RETURN)
|
|
view->priv->return_key_pressed = TRUE;
|
|
else if (key_code == HTML_KEY_CODE_SPACE)
|
|
view->priv->space_key_pressed = TRUE;
|
|
}
|
|
|
|
static void
|
|
body_input_event_cb (WebKitDOMElement *element,
|
|
WebKitDOMEvent *event,
|
|
EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
WebKitDOMNode *node;
|
|
WebKitDOMRange *range = html_editor_view_get_dom_range (view);
|
|
WebKitDOMDocument *document;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
e_html_editor_view_set_changed (view, TRUE);
|
|
|
|
if (view->priv->magic_smileys)
|
|
html_editor_view_check_magic_smileys (view, range);
|
|
|
|
if (view->priv->return_key_pressed || view->priv->space_key_pressed) {
|
|
html_editor_view_check_magic_links (view, range, FALSE);
|
|
mark_node_as_paragraph_after_ending_list (selection, document);
|
|
if (view->priv->html_mode)
|
|
fix_paragraph_structure_after_pressing_enter_after_smiley (
|
|
selection, document);
|
|
} else {
|
|
WebKitDOMNode *node;
|
|
|
|
node = webkit_dom_range_get_end_container (range, NULL);
|
|
|
|
if (surround_text_with_paragraph_if_needed (selection, document, node)) {
|
|
e_html_editor_selection_restore_caret_position (selection);
|
|
node = webkit_dom_range_get_end_container (range, NULL);
|
|
range = html_editor_view_get_dom_range (view);
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_TEXT (node)) {
|
|
gchar *text;
|
|
|
|
text = webkit_dom_node_get_text_content (node);
|
|
|
|
if (g_strcmp0 (text, "") != 0 && !g_unichar_isspace (g_utf8_get_char (text))) {
|
|
WebKitDOMNode *prev_sibling;
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (node);
|
|
|
|
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling))
|
|
html_editor_view_check_magic_links (view, range, FALSE);
|
|
}
|
|
g_free (text);
|
|
}
|
|
}
|
|
|
|
node = webkit_dom_range_get_end_container (range, NULL);
|
|
|
|
/* After toggling monospaced format, we are using UNICODE_ZERO_WIDTH_SPACE
|
|
* to move caret into right space. When this callback is called it is not
|
|
* necassary anymore so remove it */
|
|
if (view->priv->html_mode) {
|
|
WebKitDOMElement *parent = webkit_dom_node_get_parent_element (node);
|
|
|
|
if (parent) {
|
|
WebKitDOMNode *prev_sibling;
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (
|
|
WEBKIT_DOM_NODE (parent));
|
|
|
|
if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
|
|
gchar *text = webkit_dom_node_get_text_content (
|
|
prev_sibling);
|
|
|
|
if (g_strcmp0 (text, UNICODE_ZERO_WIDTH_SPACE) == 0)
|
|
remove_node (prev_sibling);
|
|
|
|
g_free (text);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/* If text before caret includes UNICODE_ZERO_WIDTH_SPACE character, remove it */
|
|
if (WEBKIT_DOM_IS_TEXT (node)) {
|
|
gchar *text = webkit_dom_character_data_get_data (WEBKIT_DOM_CHARACTER_DATA (node));
|
|
glong length = webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node));
|
|
WebKitDOMNode *parent;
|
|
|
|
/* We have to preserve empty paragraphs with just UNICODE_ZERO_WIDTH_SPACE
|
|
* character as when we will remove it it will collapse */
|
|
if (length > 1) {
|
|
if (g_str_has_prefix (text, UNICODE_ZERO_WIDTH_SPACE))
|
|
webkit_dom_character_data_replace_data (
|
|
WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
|
|
else if (g_str_has_suffix (text, UNICODE_ZERO_WIDTH_SPACE))
|
|
webkit_dom_character_data_replace_data (
|
|
WEBKIT_DOM_CHARACTER_DATA (node), length - 1, 1, "", NULL);
|
|
}
|
|
g_free (text);
|
|
|
|
parent = webkit_dom_node_get_parent_node (node);
|
|
if ((WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (parent) ||
|
|
WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent)) &&
|
|
!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph")) {
|
|
if (e_html_editor_view_get_html_mode (view)) {
|
|
element_add_class (
|
|
WEBKIT_DOM_ELEMENT (parent), "-x-evo-paragraph");
|
|
} else {
|
|
e_html_editor_selection_set_paragraph_style (
|
|
selection,
|
|
WEBKIT_DOM_ELEMENT (parent),
|
|
-1, 0, "");
|
|
}
|
|
}
|
|
|
|
/* When new smiley is added we have to use UNICODE_HIDDEN_SPACE to set the
|
|
* caret position to right place. It is removed when user starts typing. But
|
|
* when the user will press left arrow he will move the caret into
|
|
* smiley wrapper. If he will start to write there we have to move the written
|
|
* text out of the wrapper and move caret to right place */
|
|
if (WEBKIT_DOM_IS_ELEMENT (parent) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-smiley-wrapper")) {
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
e_html_editor_selection_get_caret_position_node (
|
|
document),
|
|
webkit_dom_node_get_next_sibling (parent),
|
|
NULL);
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
node,
|
|
webkit_dom_node_get_next_sibling (parent),
|
|
NULL);
|
|
e_html_editor_selection_restore_caret_position (selection);
|
|
}
|
|
}
|
|
|
|
/* Writing into quoted content */
|
|
if (!view->priv->html_mode) {
|
|
gint citation_level;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker;
|
|
WebKitDOMNode *node, *parent;
|
|
WebKitDOMRange *range;
|
|
|
|
range = html_editor_view_get_dom_range (view);
|
|
node = webkit_dom_range_get_end_container (range, NULL);
|
|
|
|
citation_level = get_citation_level (node, FALSE);
|
|
if (citation_level == 0)
|
|
return;
|
|
|
|
selection_start_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-start-marker", NULL);
|
|
if (selection_start_marker)
|
|
return;
|
|
|
|
e_html_editor_selection_save (selection);
|
|
|
|
selection_start_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-start-marker", NULL);
|
|
selection_end_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-end-marker", NULL);
|
|
/* If the selection was not saved, move it into the first child of body */
|
|
if (!selection_start_marker || !selection_end_marker) {
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMNode *child;
|
|
|
|
body = webkit_dom_document_get_body (document);
|
|
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
|
|
|
|
add_selection_markers_into_element_start (
|
|
document,
|
|
WEBKIT_DOM_ELEMENT (child),
|
|
&selection_start_marker,
|
|
&selection_end_marker);
|
|
}
|
|
|
|
/* We have to process elements only inside normal block */
|
|
parent = WEBKIT_DOM_NODE (get_parent_block_element (
|
|
WEBKIT_DOM_NODE (selection_start_marker)));
|
|
if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent)) {
|
|
e_html_editor_selection_restore (selection);
|
|
return;
|
|
}
|
|
|
|
if (selection_start_marker) {
|
|
gchar *content;
|
|
gint text_length, word_wrap_length, length;
|
|
WebKitDOMElement *block;
|
|
gboolean remove_quoting = FALSE;
|
|
|
|
word_wrap_length =
|
|
e_html_editor_selection_get_word_wrap_length (selection);
|
|
length = word_wrap_length - 2 * citation_level;
|
|
|
|
block = WEBKIT_DOM_ELEMENT (parent);
|
|
if (webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) {
|
|
WebKitDOMNode *prev_sibling;
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
|
|
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
|
|
remove_quoting = element_has_class (
|
|
WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
|
|
}
|
|
|
|
content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (block));
|
|
text_length = g_utf8_strlen (content, -1);
|
|
g_free (content);
|
|
|
|
/* Wrap and quote the line */
|
|
if (!remove_quoting && text_length >= word_wrap_length) {
|
|
remove_quoting_from_element (block);
|
|
|
|
block = e_html_editor_selection_wrap_paragraph_length (
|
|
selection, block, length);
|
|
webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
|
|
quote_plain_text_element_after_wrapping (
|
|
document, WEBKIT_DOM_ELEMENT (block), citation_level);
|
|
selection_start_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-start-marker", NULL);
|
|
if (!selection_start_marker)
|
|
add_selection_markers_into_element_end (
|
|
document,
|
|
WEBKIT_DOM_ELEMENT (block),
|
|
NULL,
|
|
NULL);
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
e_html_editor_view_force_spell_check_for_current_paragraph (view);
|
|
|
|
return;
|
|
}
|
|
}
|
|
e_html_editor_selection_restore (selection);
|
|
}
|
|
}
|
|
|
|
static void
|
|
remove_input_event_listener_from_body (EHTMLEditorView *view)
|
|
{
|
|
if (!view->priv->body_input_event_removed) {
|
|
WebKitDOMDocument *document;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
webkit_dom_event_target_remove_event_listener (
|
|
WEBKIT_DOM_EVENT_TARGET (
|
|
webkit_dom_document_get_body (document)),
|
|
"input",
|
|
G_CALLBACK (body_input_event_cb),
|
|
FALSE);
|
|
|
|
view->priv->body_input_event_removed = TRUE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
register_input_event_listener_on_body (EHTMLEditorView *view)
|
|
{
|
|
if (view->priv->body_input_event_removed) {
|
|
WebKitDOMDocument *document;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
webkit_dom_event_target_add_event_listener (
|
|
WEBKIT_DOM_EVENT_TARGET (
|
|
webkit_dom_document_get_body (document)),
|
|
"input",
|
|
G_CALLBACK (body_input_event_cb),
|
|
FALSE,
|
|
view);
|
|
|
|
view->priv->body_input_event_removed = FALSE;
|
|
}
|
|
}
|
|
|
|
static void
|
|
remove_empty_blocks (WebKitDOMDocument *document)
|
|
{
|
|
gint ii, length;
|
|
WebKitDOMNodeList *list;
|
|
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "blockquote[type=cite] > :empty", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
|
|
g_object_unref (list);
|
|
}
|
|
|
|
/* Following two functions are used when deleting the selection inside
|
|
* the quoted content. The thing is that normally the quote marks are not
|
|
* selectable by user. But this caused a lof of problems for WebKit when removing
|
|
* the selection. This will avoid it as when the delete or backspace key is pressed
|
|
* we will make the quote marks user selectable so they will act as any other text.
|
|
* On HTML keyup event callback we will make them again non-selectable. */
|
|
static void
|
|
disable_quote_marks_select (WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMHTMLHeadElement *head;
|
|
WebKitDOMElement *style_element;
|
|
|
|
head = webkit_dom_document_get_head (document);
|
|
|
|
if (!webkit_dom_document_get_element_by_id (document, "-x-evo-quote-style")) {
|
|
style_element = webkit_dom_document_create_element (document, "style", NULL);
|
|
webkit_dom_element_set_id (style_element, "-x-evo-quote-style");
|
|
webkit_dom_element_set_attribute (style_element, "type", "text/css", NULL);
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (style_element),
|
|
".-x-evo-quoted { -webkit-user-select: none; }",
|
|
NULL);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style_element), NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
enable_quote_marks_select (WebKitDOMDocument *document)
|
|
{
|
|
WebKitDOMElement *style_element;
|
|
|
|
if ((style_element = webkit_dom_document_get_element_by_id (document, "-x-evo-quote-style")))
|
|
remove_node (WEBKIT_DOM_NODE (style_element));
|
|
}
|
|
|
|
static void
|
|
body_keyup_event_cb (WebKitDOMElement *element,
|
|
WebKitDOMUIEvent *event,
|
|
EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
glong key_code;
|
|
|
|
register_input_event_listener_on_body (view);
|
|
selection = e_html_editor_view_get_selection (view);
|
|
if (!e_html_editor_selection_is_collapsed (selection))
|
|
return;
|
|
|
|
key_code = webkit_dom_ui_event_get_key_code (event);
|
|
if (key_code == HTML_KEY_CODE_BACKSPACE || key_code == HTML_KEY_CODE_DELETE) {
|
|
/* This will fix the structure after the situations where some text
|
|
* inside the quoted content is selected and afterwards deleted with
|
|
* BackSpace or Delete. */
|
|
gint level;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker;
|
|
WebKitDOMElement *tmp_element;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNode *parent;
|
|
|
|
if (e_html_editor_view_get_html_mode (view))
|
|
return;
|
|
|
|
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
|
|
|
|
disable_quote_marks_select (document);
|
|
/* Remove empty blocks if presented. */
|
|
remove_empty_blocks (document);
|
|
|
|
e_html_editor_selection_save (selection);
|
|
selection_start_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
selection_end_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-end-marker");
|
|
|
|
/* If we deleted a selection the caret will be inside the quote marks, fix it. */
|
|
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start_marker));
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-quote-character")) {
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (
|
|
webkit_dom_node_get_parent_node (parent)),
|
|
WEBKIT_DOM_NODE (selection_end_marker),
|
|
webkit_dom_node_get_next_sibling (
|
|
webkit_dom_node_get_parent_node (parent)),
|
|
NULL);
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (
|
|
webkit_dom_node_get_parent_node (parent)),
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
webkit_dom_node_get_next_sibling (
|
|
webkit_dom_node_get_parent_node (parent)),
|
|
NULL);
|
|
}
|
|
|
|
/* Under some circumstances we will end with block inside the citation
|
|
* that has the quote marks removed and we have to reinsert them back. */
|
|
level = get_citation_level (WEBKIT_DOM_NODE (selection_start_marker), FALSE);
|
|
if (level > 0) {
|
|
WebKitDOMNode *prev_sibling;
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
if (!prev_sibling ||
|
|
(WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling) &&
|
|
!webkit_dom_node_get_previous_sibling (prev_sibling))) {
|
|
WebKitDOMElement *block;
|
|
|
|
block = WEBKIT_DOM_ELEMENT (get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker)));
|
|
if (element_has_class (block, "-x-evo-paragraph")) {
|
|
gint length, word_wrap_length;
|
|
|
|
word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);
|
|
length = word_wrap_length - 2 * (level - 1);
|
|
block = e_html_editor_selection_wrap_paragraph_length (
|
|
selection, block, length);
|
|
webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
|
|
}
|
|
quote_plain_text_element_after_wrapping (
|
|
document, block, level);
|
|
}
|
|
}
|
|
|
|
/* Situation where the start of the selection was in the beginning
|
|
* of the block in quoted content and the end in the beginning of
|
|
* content that is after the citation or the selection end was in
|
|
* the end of the quoted content (showed by ^). The correct structure
|
|
* in these cases is to have empty block after the citation.
|
|
*
|
|
* > |xxx
|
|
* > xxx^
|
|
* |xxx
|
|
* */
|
|
tmp_element = webkit_dom_document_get_element_by_id (document, "-x-evo-tmp-block");
|
|
if (tmp_element) {
|
|
remove_wrapping_from_element (tmp_element);
|
|
remove_quoting_from_element (tmp_element);
|
|
webkit_dom_element_remove_attribute (tmp_element, "id");
|
|
|
|
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (tmp_element));
|
|
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (parent)))
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
WEBKIT_DOM_NODE (tmp_element),
|
|
webkit_dom_node_get_next_sibling (parent),
|
|
NULL);
|
|
}
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
} else if (key_code == HTML_KEY_CODE_CONTROL)
|
|
html_editor_view_set_links_active (view, FALSE);
|
|
}
|
|
|
|
static void
|
|
clipboard_text_received_for_paste_as_text (GtkClipboard *clipboard,
|
|
const gchar *text,
|
|
EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
|
|
if (!text || !*text)
|
|
return;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
e_html_editor_selection_insert_as_text (selection, text);
|
|
}
|
|
|
|
static void
|
|
clipboard_text_received (GtkClipboard *clipboard,
|
|
const gchar *text,
|
|
EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gchar *escaped_text;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *blockquote, *element, *selection_start;
|
|
WebKitDOMNode *sibling;
|
|
|
|
if (!text || !*text)
|
|
return;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
/* This is a trick to escape any HTML characters (like <, > or &).
|
|
* <textarea> automatically replaces all these unsafe characters
|
|
* by <, > etc. */
|
|
element = webkit_dom_document_create_element (document, "textarea", NULL);
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (element), text, NULL);
|
|
escaped_text = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (element));
|
|
|
|
element = webkit_dom_document_create_element (document, "pre", NULL);
|
|
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (element), escaped_text, NULL);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (element),
|
|
e_html_editor_selection_get_caret_position_node (document),
|
|
NULL);
|
|
|
|
blockquote = webkit_dom_document_create_element (document, "blockquote", NULL);
|
|
webkit_dom_element_set_attribute (blockquote, "type", "cite", NULL);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (blockquote), WEBKIT_DOM_NODE (element), NULL);
|
|
|
|
if (!e_html_editor_view_get_html_mode (view))
|
|
e_html_editor_view_quote_plain_text_element (view, element);
|
|
|
|
element = webkit_dom_document_create_element (document, "pre", NULL);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (element), WEBKIT_DOM_NODE (blockquote), NULL);
|
|
|
|
e_html_editor_selection_save (selection);
|
|
|
|
selection_start = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
sibling = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start));
|
|
/* Check if block is empty. If so, replace it otherwise insert the quoted
|
|
* content after current block. */
|
|
if (!sibling || WEBKIT_DOM_IS_HTMLBR_ELEMENT (sibling)) {
|
|
sibling = webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (selection_start));
|
|
sibling = webkit_dom_node_get_next_sibling (sibling);
|
|
if (!sibling || WEBKIT_DOM_IS_HTMLBR_ELEMENT (sibling)) {
|
|
webkit_dom_node_replace_child (
|
|
webkit_dom_node_get_parent_node (
|
|
webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (selection_start))),
|
|
WEBKIT_DOM_NODE (element),
|
|
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (selection_start)),
|
|
NULL);
|
|
}
|
|
} else {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (webkit_dom_document_get_body (document)),
|
|
WEBKIT_DOM_NODE (element),
|
|
webkit_dom_node_get_next_sibling (
|
|
webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (selection_start))),
|
|
NULL);
|
|
}
|
|
|
|
e_html_editor_selection_restore_caret_position (selection);
|
|
|
|
e_html_editor_view_force_spell_check_for_current_paragraph (view);
|
|
|
|
g_free (escaped_text);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id) {
|
|
case PROP_CHANGED:
|
|
e_html_editor_view_set_changed (
|
|
E_HTML_EDITOR_VIEW (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_HTML_MODE:
|
|
e_html_editor_view_set_html_mode (
|
|
E_HTML_EDITOR_VIEW (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_INLINE_SPELLING:
|
|
e_html_editor_view_set_inline_spelling (
|
|
E_HTML_EDITOR_VIEW (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_MAGIC_LINKS:
|
|
e_html_editor_view_set_magic_links (
|
|
E_HTML_EDITOR_VIEW (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_MAGIC_SMILEYS:
|
|
e_html_editor_view_set_magic_smileys (
|
|
E_HTML_EDITOR_VIEW (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
|
|
case PROP_UNICODE_SMILEYS:
|
|
e_html_editor_view_set_unicode_smileys (
|
|
E_HTML_EDITOR_VIEW (object),
|
|
g_value_get_boolean (value));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id) {
|
|
case PROP_CAN_COPY:
|
|
g_value_set_boolean (
|
|
value, webkit_web_view_can_copy_clipboard (
|
|
WEBKIT_WEB_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_CAN_CUT:
|
|
g_value_set_boolean (
|
|
value, webkit_web_view_can_cut_clipboard (
|
|
WEBKIT_WEB_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_CAN_PASTE:
|
|
g_value_set_boolean (
|
|
value, webkit_web_view_can_paste_clipboard (
|
|
WEBKIT_WEB_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_CAN_REDO:
|
|
g_value_set_boolean (
|
|
value, webkit_web_view_can_redo (
|
|
WEBKIT_WEB_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_CAN_UNDO:
|
|
g_value_set_boolean (
|
|
value, webkit_web_view_can_undo (
|
|
WEBKIT_WEB_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_CHANGED:
|
|
g_value_set_boolean (
|
|
value, e_html_editor_view_get_changed (
|
|
E_HTML_EDITOR_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_HTML_MODE:
|
|
g_value_set_boolean (
|
|
value, e_html_editor_view_get_html_mode (
|
|
E_HTML_EDITOR_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_INLINE_SPELLING:
|
|
g_value_set_boolean (
|
|
value, e_html_editor_view_get_inline_spelling (
|
|
E_HTML_EDITOR_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_MAGIC_LINKS:
|
|
g_value_set_boolean (
|
|
value, e_html_editor_view_get_magic_links (
|
|
E_HTML_EDITOR_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_MAGIC_SMILEYS:
|
|
g_value_set_boolean (
|
|
value, e_html_editor_view_get_magic_smileys (
|
|
E_HTML_EDITOR_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_UNICODE_SMILEYS:
|
|
g_value_set_boolean (
|
|
value, e_html_editor_view_get_unicode_smileys (
|
|
E_HTML_EDITOR_VIEW (object)));
|
|
return;
|
|
|
|
case PROP_SPELL_CHECKER:
|
|
g_value_set_object (
|
|
value, e_html_editor_view_get_spell_checker (
|
|
E_HTML_EDITOR_VIEW (object)));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_dispose (GObject *object)
|
|
{
|
|
EHTMLEditorViewPrivate *priv;
|
|
|
|
priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);
|
|
|
|
g_clear_object (&priv->selection);
|
|
|
|
if (priv->aliasing_settings != NULL) {
|
|
g_signal_handlers_disconnect_by_data (priv->aliasing_settings, object);
|
|
g_object_unref (priv->aliasing_settings);
|
|
priv->aliasing_settings = NULL;
|
|
}
|
|
|
|
if (priv->font_settings != NULL) {
|
|
g_signal_handlers_disconnect_by_data (priv->font_settings, object);
|
|
g_object_unref (priv->font_settings);
|
|
priv->font_settings = NULL;
|
|
}
|
|
|
|
if (priv->mail_settings != NULL) {
|
|
g_signal_handlers_disconnect_by_data (priv->mail_settings, object);
|
|
g_object_unref (priv->mail_settings);
|
|
priv->mail_settings = NULL;
|
|
}
|
|
|
|
g_hash_table_remove_all (priv->inline_images);
|
|
|
|
/* Chain up to parent's dispose() method. */
|
|
G_OBJECT_CLASS (e_html_editor_view_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_finalize (GObject *object)
|
|
{
|
|
EHTMLEditorViewPrivate *priv;
|
|
|
|
priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (object);
|
|
|
|
g_hash_table_destroy (priv->inline_images);
|
|
|
|
if (priv->old_settings) {
|
|
g_hash_table_destroy (priv->old_settings);
|
|
priv->old_settings = NULL;
|
|
}
|
|
|
|
if (priv->post_reload_operations) {
|
|
g_warn_if_fail (g_queue_is_empty (priv->post_reload_operations));
|
|
|
|
g_queue_free (priv->post_reload_operations);
|
|
priv->post_reload_operations = NULL;
|
|
}
|
|
|
|
/* Chain up to parent's finalize() method. */
|
|
G_OBJECT_CLASS (e_html_editor_view_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_constructed (GObject *object)
|
|
{
|
|
e_extensible_load_extensions (E_EXTENSIBLE (object));
|
|
|
|
/* Chain up to parent's constructed() method. */
|
|
G_OBJECT_CLASS (e_html_editor_view_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_move_selection_on_point (GtkWidget *widget)
|
|
{
|
|
gint x, y;
|
|
GdkDeviceManager *device_manager;
|
|
GdkDevice *pointer;
|
|
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (widget));
|
|
|
|
device_manager = gdk_display_get_device_manager (
|
|
gtk_widget_get_display (GTK_WIDGET (widget)));
|
|
pointer = gdk_device_manager_get_client_pointer (device_manager);
|
|
gdk_window_get_device_position (
|
|
gtk_widget_get_window (GTK_WIDGET (widget)), pointer, &x, &y, NULL);
|
|
|
|
e_html_editor_selection_set_on_point (
|
|
e_html_editor_view_get_selection (E_HTML_EDITOR_VIEW (widget)), x, y);
|
|
}
|
|
|
|
static gboolean
|
|
html_editor_view_button_press_event (GtkWidget *widget,
|
|
GdkEventButton *event)
|
|
{
|
|
gboolean event_handled, collapsed;
|
|
EHTMLEditorSelection *selection;
|
|
|
|
selection = e_html_editor_view_get_selection (E_HTML_EDITOR_VIEW (widget));
|
|
collapsed = e_html_editor_selection_is_collapsed (selection);
|
|
|
|
if (event->button == 2) {
|
|
/* Middle click paste */
|
|
if (collapsed)
|
|
html_editor_view_move_selection_on_point (widget);
|
|
g_signal_emit (widget, signals[PASTE_PRIMARY_CLIPBOARD], 0);
|
|
event_handled = TRUE;
|
|
} else if (event->button == 3) {
|
|
if (collapsed)
|
|
html_editor_view_move_selection_on_point (widget);
|
|
g_signal_emit (
|
|
widget, signals[POPUP_EVENT],
|
|
0, event, &event_handled);
|
|
} else {
|
|
event_handled = FALSE;
|
|
}
|
|
|
|
if (event_handled)
|
|
return TRUE;
|
|
|
|
/* Chain up to parent's button_press_event() method. */
|
|
return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
|
|
button_press_event (widget, event);
|
|
}
|
|
|
|
static gboolean
|
|
html_editor_view_button_release_event (GtkWidget *widget,
|
|
GdkEventButton *event)
|
|
{
|
|
WebKitWebView *webview;
|
|
WebKitHitTestResult *hit_test;
|
|
WebKitHitTestResultContext context;
|
|
gchar *uri;
|
|
|
|
webview = WEBKIT_WEB_VIEW (widget);
|
|
hit_test = webkit_web_view_get_hit_test_result (webview, event);
|
|
|
|
g_object_get (
|
|
hit_test,
|
|
"context", &context,
|
|
"link-uri", &uri,
|
|
NULL);
|
|
|
|
g_object_unref (hit_test);
|
|
|
|
/* Left click on a link */
|
|
if ((context & WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK) &&
|
|
(event->button == 1)) {
|
|
|
|
/* Ctrl + Left Click on link opens it, otherwise ignore the
|
|
* click completely */
|
|
if (event->state & GDK_CONTROL_MASK) {
|
|
GtkWidget *toplevel;
|
|
GdkScreen *screen;
|
|
|
|
toplevel = gtk_widget_get_toplevel (widget);
|
|
screen = gtk_window_get_screen (GTK_WINDOW (toplevel));
|
|
gtk_show_uri (screen, uri, event->time, NULL);
|
|
g_free (uri);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
g_free (uri);
|
|
|
|
/* Chain up to parent's button_release_event() method. */
|
|
return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
|
|
button_release_event (widget, event);
|
|
}
|
|
|
|
static gboolean
|
|
prevent_from_deleting_last_element_in_body (EHTMLEditorView *view)
|
|
{
|
|
gboolean ret_val = FALSE;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMNode *node;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = webkit_dom_document_get_body (document);
|
|
|
|
node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
|
|
if (!node || (
|
|
webkit_dom_node_get_next_sibling (node) &&
|
|
!webkit_dom_node_get_next_sibling (webkit_dom_node_get_next_sibling (node)))) {
|
|
gchar *content;
|
|
|
|
content = webkit_dom_node_get_text_content (WEBKIT_DOM_NODE (body));
|
|
|
|
if (!*content)
|
|
ret_val = TRUE;
|
|
|
|
g_free (content);
|
|
|
|
if (webkit_dom_element_query_selector (WEBKIT_DOM_ELEMENT (body), "img", NULL))
|
|
ret_val = FALSE;
|
|
}
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
static gboolean
|
|
change_quoted_block_to_normal (EHTMLEditorView *view)
|
|
{
|
|
gint citation_level, success = FALSE;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker, *block;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
selection_start_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-start-marker", NULL);
|
|
selection_end_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-end-marker", NULL);
|
|
|
|
if (!selection_start_marker || !selection_end_marker)
|
|
return FALSE;
|
|
|
|
block = WEBKIT_DOM_ELEMENT (get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker)));
|
|
|
|
citation_level = get_citation_level (
|
|
WEBKIT_DOM_NODE (selection_start_marker), FALSE);
|
|
|
|
if (selection_start_marker && citation_level > 0) {
|
|
if (webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (block), ".-x-evo-quoted", NULL)) {
|
|
|
|
WebKitDOMNode *prev_sibling;
|
|
|
|
webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
|
|
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling))
|
|
success = element_has_class (
|
|
WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-quoted");
|
|
/* We really have to be in the beginning of paragraph and
|
|
* not on the beginning of some line in the paragraph */
|
|
if (success && webkit_dom_node_get_previous_sibling (prev_sibling))
|
|
success = FALSE;
|
|
}
|
|
|
|
if (view->priv->html_mode)
|
|
success = WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (
|
|
webkit_dom_node_get_parent_element (
|
|
WEBKIT_DOM_NODE (block)));
|
|
}
|
|
|
|
if (success && citation_level == 1) {
|
|
gchar *inner_html;
|
|
WebKitDOMElement *paragraph;
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (block));
|
|
webkit_dom_element_set_id (
|
|
WEBKIT_DOM_ELEMENT (block), "-x-evo-to-remove");
|
|
|
|
paragraph = insert_new_line_into_citation (view, inner_html);
|
|
g_free (inner_html);
|
|
|
|
if (paragraph) {
|
|
if (view->priv->html_mode) {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (paragraph)),
|
|
NULL);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
WEBKIT_DOM_NODE (selection_end_marker),
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (paragraph)),
|
|
NULL);
|
|
|
|
}
|
|
|
|
remove_quoting_from_element (paragraph);
|
|
remove_wrapping_from_element (paragraph);
|
|
}
|
|
|
|
if (block)
|
|
remove_node (WEBKIT_DOM_NODE (block));
|
|
block = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-to-remove");
|
|
if (block)
|
|
remove_node (WEBKIT_DOM_NODE (block));
|
|
|
|
if (paragraph)
|
|
remove_node_if_empty (
|
|
webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (paragraph)));
|
|
}
|
|
|
|
if (success && citation_level > 1) {
|
|
gint length, word_wrap_length;
|
|
EHTMLEditorSelection *selection;
|
|
WebKitDOMNode *parent;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);
|
|
length = word_wrap_length - 2 * (citation_level - 1);
|
|
|
|
if (view->priv->html_mode) {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (block),
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (block)),
|
|
NULL);
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (block),
|
|
WEBKIT_DOM_NODE (selection_end_marker),
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (block)),
|
|
NULL);
|
|
|
|
}
|
|
|
|
remove_quoting_from_element (block);
|
|
remove_wrapping_from_element (block);
|
|
|
|
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (block));
|
|
|
|
if (!webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (block))) {
|
|
/* Currect block is in the beginning of citation, just move it
|
|
* before the citation where already is */
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
WEBKIT_DOM_NODE (block),
|
|
parent,
|
|
NULL);
|
|
} else if (!webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (block))) {
|
|
/* Currect block is at the end of the citation, just move it
|
|
* after the citation where already is */
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
WEBKIT_DOM_NODE (block),
|
|
webkit_dom_node_get_next_sibling (parent),
|
|
NULL);
|
|
} else {
|
|
/* Current block is somewhere in the middle of the citation
|
|
* so we need to split the citation and insert the block into
|
|
* the citation that is one level lower */
|
|
WebKitDOMNode *clone, *child;
|
|
|
|
clone = webkit_dom_node_clone_node (parent, FALSE);
|
|
|
|
/* Move nodes that are after the currect block into the
|
|
* new blockquote */
|
|
child = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (block));
|
|
while (child) {
|
|
WebKitDOMNode *next = webkit_dom_node_get_next_sibling (child);
|
|
webkit_dom_node_append_child (clone, child, NULL);
|
|
child = next;
|
|
}
|
|
|
|
clone = webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
clone,
|
|
webkit_dom_node_get_next_sibling (parent),
|
|
NULL);
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
WEBKIT_DOM_NODE (block),
|
|
clone,
|
|
NULL);
|
|
}
|
|
|
|
block = e_html_editor_selection_wrap_paragraph_length (
|
|
selection, block, length);
|
|
webkit_dom_node_normalize (WEBKIT_DOM_NODE (block));
|
|
quote_plain_text_element_after_wrapping (
|
|
document, block, citation_level - 1);
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static gboolean
|
|
fix_structure_after_delete_before_quoted_content (EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gboolean collapsed = FALSE;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker;
|
|
WebKitDOMNode *block, *node;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
collapsed = e_html_editor_selection_is_collapsed (selection);
|
|
|
|
e_html_editor_selection_save (selection);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
selection_start_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-start-marker", NULL);
|
|
selection_end_marker = webkit_dom_document_query_selector (
|
|
document, "span#-x-evo-selection-end-marker", NULL);
|
|
|
|
if (!selection_start_marker || !selection_end_marker)
|
|
return FALSE;
|
|
|
|
if (collapsed) {
|
|
WebKitDOMNode *next_sibling;
|
|
|
|
block = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
|
|
next_sibling = webkit_dom_node_get_next_sibling (block);
|
|
|
|
/* Next block is quoted content */
|
|
if (!WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (next_sibling))
|
|
goto restore;
|
|
|
|
/* Delete was pressed in block without any content */
|
|
if (webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (selection_start_marker)))
|
|
goto restore;
|
|
|
|
/* If there is just BR element go ahead */
|
|
node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (selection_end_marker));
|
|
if (node && !WEBKIT_DOM_IS_HTMLBR_ELEMENT (node))
|
|
goto restore;
|
|
else {
|
|
/* Remove the empty block and move caret into the beginning of the citation */
|
|
remove_node (block);
|
|
|
|
e_html_editor_selection_move_caret_into_element (
|
|
document, WEBKIT_DOM_ELEMENT (next_sibling), TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
} else {
|
|
WebKitDOMNode *end_block;
|
|
|
|
/* Let the quote marks be selectable to nearly correctly remove the
|
|
* selection. Corrections after are done in body_keyup_event_cb. */
|
|
enable_quote_marks_select (document);
|
|
|
|
node = webkit_dom_node_get_previous_sibling (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
|
|
if (!node || !WEBKIT_DOM_IS_ELEMENT (node))
|
|
goto restore;
|
|
|
|
if (!element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-quoted"))
|
|
goto restore;
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (node)),
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
WEBKIT_DOM_NODE (node),
|
|
NULL);
|
|
|
|
block = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
end_block = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
|
|
/* Situation where the start of the selection is in the beginning
|
|
* of the block in quoted content and the end in the beginning of
|
|
* content that is after the citation or the selection end is in
|
|
* the end of the quoted content (showed by ^). We have to
|
|
* mark the start block to correctly restore the structure
|
|
* afterwards.
|
|
*
|
|
* > |xxx
|
|
* > xxx^
|
|
* |xxx
|
|
* */
|
|
if (get_citation_level (end_block, FALSE) > 0) {
|
|
WebKitDOMNode *parent;
|
|
|
|
if (webkit_dom_node_get_next_sibling (end_block))
|
|
goto restore;
|
|
|
|
parent = webkit_dom_node_get_parent_node (end_block);
|
|
while (parent && WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent)) {
|
|
WebKitDOMNode *next_parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
if (webkit_dom_node_get_next_sibling (parent) &&
|
|
!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (next_parent))
|
|
goto restore;
|
|
|
|
parent = next_parent;
|
|
}
|
|
}
|
|
node = webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
if (!node || WEBKIT_DOM_IS_HTMLBR_ELEMENT (node)) {
|
|
webkit_dom_element_set_id (
|
|
WEBKIT_DOM_ELEMENT (block), "-x-evo-tmp-block");
|
|
}
|
|
}
|
|
restore:
|
|
e_html_editor_selection_restore (selection);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
selection_is_in_table (WebKitDOMDocument *document,
|
|
gboolean *first_cell,
|
|
WebKitDOMNode **table_node)
|
|
{
|
|
WebKitDOMDOMWindow *window;
|
|
WebKitDOMDOMSelection *selection;
|
|
WebKitDOMNode *node, *parent;
|
|
WebKitDOMRange *range;
|
|
|
|
window = webkit_dom_document_get_default_view (document);
|
|
selection = webkit_dom_dom_window_get_selection (window);
|
|
|
|
if (first_cell != NULL)
|
|
*first_cell = FALSE;
|
|
|
|
if (table_node != NULL)
|
|
*table_node = NULL;
|
|
|
|
if (webkit_dom_dom_selection_get_range_count (selection) < 1)
|
|
return FALSE;
|
|
|
|
range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
|
|
node = webkit_dom_range_get_start_container (range, NULL);
|
|
|
|
parent = node;
|
|
while (parent && !WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
|
|
if (WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (parent)) {
|
|
if (first_cell != NULL) {
|
|
if (!webkit_dom_node_get_previous_sibling (parent)) {
|
|
gboolean on_start = TRUE;
|
|
WebKitDOMNode *tmp;
|
|
|
|
tmp = webkit_dom_node_get_previous_sibling (node);
|
|
if (!tmp && WEBKIT_DOM_IS_TEXT (node))
|
|
on_start = webkit_dom_range_get_start_offset (range, NULL) == 0;
|
|
else if (tmp)
|
|
on_start = FALSE;
|
|
|
|
if (on_start) {
|
|
node = webkit_dom_node_get_parent_node (parent);
|
|
if (node && WEBKIT_DOM_HTML_TABLE_ROW_ELEMENT (node))
|
|
if (!webkit_dom_node_get_previous_sibling (node))
|
|
*first_cell = TRUE;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (WEBKIT_DOM_IS_HTML_TABLE_ELEMENT (parent)) {
|
|
if (table_node != NULL)
|
|
*table_node = parent;
|
|
}
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
}
|
|
|
|
if (table_node == NULL)
|
|
return FALSE;
|
|
|
|
return *table_node != NULL;
|
|
}
|
|
|
|
static gboolean
|
|
jump_to_next_table_cell (EHTMLEditorView *view,
|
|
gboolean jump_back)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMDOMWindow *window;
|
|
WebKitDOMDOMSelection *selection;
|
|
WebKitDOMNode *node, *cell;
|
|
WebKitDOMRange *range;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
window = webkit_dom_document_get_default_view (document);
|
|
selection = webkit_dom_dom_window_get_selection (window);
|
|
|
|
if (webkit_dom_dom_selection_get_range_count (selection) < 1)
|
|
return FALSE;
|
|
|
|
range = webkit_dom_dom_selection_get_range_at (selection, 0, NULL);
|
|
node = webkit_dom_range_get_start_container (range, NULL);
|
|
|
|
cell = node;
|
|
while (cell && !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (cell)) {
|
|
cell = webkit_dom_node_get_parent_node (cell);
|
|
}
|
|
|
|
if (!WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (cell))
|
|
return FALSE;
|
|
|
|
if (jump_back) {
|
|
/* Get previous cell */
|
|
node = webkit_dom_node_get_previous_sibling (cell);
|
|
if (!node || !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) {
|
|
/* No cell, go one row up. */
|
|
node = webkit_dom_node_get_parent_node (cell);
|
|
node = webkit_dom_node_get_previous_sibling (node);
|
|
if (node && WEBKIT_DOM_IS_HTML_TABLE_ROW_ELEMENT (node)) {
|
|
node = webkit_dom_node_get_last_child (node);
|
|
} else {
|
|
/* No row above, move to the block before table. */
|
|
node = webkit_dom_node_get_parent_node (cell);
|
|
while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node)))
|
|
node = webkit_dom_node_get_parent_node (node);
|
|
|
|
node = webkit_dom_node_get_previous_sibling (node);
|
|
}
|
|
}
|
|
} else {
|
|
/* Get next cell */
|
|
node = webkit_dom_node_get_next_sibling (cell);
|
|
if (!node || !WEBKIT_DOM_IS_HTML_TABLE_CELL_ELEMENT (node)) {
|
|
/* No cell, go one row below. */
|
|
node = webkit_dom_node_get_parent_node (cell);
|
|
node = webkit_dom_node_get_next_sibling (node);
|
|
if (node && WEBKIT_DOM_IS_HTML_TABLE_ROW_ELEMENT (node)) {
|
|
node = webkit_dom_node_get_first_child (node);
|
|
} else {
|
|
/* No row below, move to the block after table. */
|
|
node = webkit_dom_node_get_parent_node (cell);
|
|
while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (webkit_dom_node_get_parent_node (node)))
|
|
node = webkit_dom_node_get_parent_node (node);
|
|
|
|
node = webkit_dom_node_get_next_sibling (node);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!node)
|
|
return FALSE;
|
|
|
|
webkit_dom_range_select_node_contents (range, node, NULL);
|
|
webkit_dom_range_collapse (range, TRUE, NULL);
|
|
webkit_dom_dom_selection_remove_all_ranges (selection);
|
|
webkit_dom_dom_selection_add_range (selection, range);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
delete_character_from_quoted_line_start (EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *element;
|
|
WebKitDOMNode *node, *beginning;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
/* We have to be in quoted content. */
|
|
if (!e_html_editor_selection_is_citation (selection))
|
|
return FALSE;
|
|
|
|
/* Selection is just caret. */
|
|
if (!e_html_editor_selection_is_collapsed (selection))
|
|
return FALSE;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
e_html_editor_selection_save (selection);
|
|
|
|
element = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
|
|
/* selection end marker */
|
|
node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element));
|
|
|
|
/* We have to be on the end of line. */
|
|
if (webkit_dom_node_get_next_sibling (node))
|
|
return FALSE;
|
|
|
|
/* Before the caret is just text. */
|
|
node = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (element));
|
|
if (!(node && WEBKIT_DOM_IS_TEXT (node)))
|
|
return FALSE;
|
|
|
|
/* There is just one character. */
|
|
if (webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node)) != 1)
|
|
return FALSE;
|
|
|
|
beginning = webkit_dom_node_get_previous_sibling (WEBKIT_DOM_NODE (node));
|
|
if (!(beginning && WEBKIT_DOM_IS_ELEMENT (beginning)))
|
|
return FALSE;
|
|
|
|
/* Before the text is the beginning of line. */
|
|
if (!(element_has_class (WEBKIT_DOM_ELEMENT (beginning), "-x-evo-quoted")))
|
|
return FALSE;
|
|
|
|
remove_node (beginning);
|
|
remove_node (node);
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
html_editor_view_key_press_event (GtkWidget *widget,
|
|
GdkEventKey *event)
|
|
{
|
|
EHTMLEditorView *view = E_HTML_EDITOR_VIEW (widget);
|
|
|
|
if (event->keyval == GDK_KEY_Menu) {
|
|
gboolean event_handled, collapsed;
|
|
EHTMLEditorSelection *selection;
|
|
|
|
selection = e_html_editor_view_get_selection (E_HTML_EDITOR_VIEW (widget));
|
|
collapsed = e_html_editor_selection_is_collapsed (selection);
|
|
|
|
if (collapsed)
|
|
html_editor_view_move_selection_on_point (widget);
|
|
|
|
g_signal_emit (
|
|
widget, signals[POPUP_EVENT],
|
|
0, event, &event_handled);
|
|
|
|
return event_handled;
|
|
}
|
|
|
|
if (event->keyval == GDK_KEY_Tab || event->keyval == GDK_KEY_ISO_Left_Tab) {
|
|
WebKitDOMDocument *document;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
if (selection_is_in_table (document, NULL, NULL))
|
|
if (jump_to_next_table_cell (view, event->keyval == GDK_KEY_ISO_Left_Tab))
|
|
return TRUE;
|
|
|
|
if (event->keyval == GDK_KEY_Tab)
|
|
return e_html_editor_view_exec_command (
|
|
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "\t");
|
|
else
|
|
return TRUE;
|
|
}
|
|
|
|
if (is_return_key (event)) {
|
|
EHTMLEditorSelection *selection;
|
|
EHTMLEditorSelectionBlockFormat format;
|
|
gboolean first_cell = FALSE;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNode *table = NULL;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
/* Return pressed in the the begining of the first cell will insert
|
|
* new block before the table (and move the caret there) if none
|
|
* is already there, otherwise it will act as normal return. */
|
|
if (selection_is_in_table (document, &first_cell, &table) && first_cell) {
|
|
WebKitDOMNode *node;
|
|
|
|
node = webkit_dom_node_get_previous_sibling (table);
|
|
if (!node) {
|
|
node = webkit_dom_node_get_next_sibling (table);
|
|
node = webkit_dom_node_clone_node (node, FALSE);
|
|
webkit_dom_node_append_child (
|
|
node,
|
|
WEBKIT_DOM_NODE (webkit_dom_document_create_element (
|
|
document, "br", NULL)),
|
|
NULL);
|
|
add_selection_markers_into_element_start (
|
|
document, WEBKIT_DOM_ELEMENT (node), NULL, NULL);
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (table),
|
|
node,
|
|
table,
|
|
NULL);
|
|
e_html_editor_selection_restore (selection);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
/* When user presses ENTER in a citation block, WebKit does
|
|
* not break the citation automatically, so we need to use
|
|
* the special command to do it. */
|
|
if (e_html_editor_selection_is_citation (selection)) {
|
|
remove_input_event_listener_from_body (view);
|
|
return (insert_new_line_into_citation (view, "")) ? TRUE : FALSE;
|
|
}
|
|
|
|
/* When the return is pressed in a H1-6 element, WebKit doesn't
|
|
* continue with the same element, but creates normal paragraph,
|
|
* so we have to unset the bold font. */
|
|
format = e_html_editor_selection_get_block_format (selection);
|
|
if (format >= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H1 &&
|
|
format <= E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_H6)
|
|
e_html_editor_selection_set_bold (selection, FALSE);
|
|
}
|
|
|
|
if (event->keyval == GDK_KEY_BackSpace) {
|
|
EHTMLEditorSelection *selection;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
/* BackSpace pressed in the beginning of quoted content changes
|
|
* format to normal and inserts text into body */
|
|
if (e_html_editor_selection_is_collapsed (selection)) {
|
|
e_html_editor_selection_save (selection);
|
|
if (change_quoted_block_to_normal (view)) {
|
|
e_html_editor_selection_restore (selection);
|
|
e_html_editor_view_force_spell_check_for_current_paragraph (view);
|
|
return TRUE;
|
|
}
|
|
e_html_editor_selection_restore (selection);
|
|
} else
|
|
remove_input_event_listener_from_body (view);
|
|
|
|
/* BackSpace in indented block decrease indent level by one */
|
|
if (e_html_editor_selection_is_indented (selection)) {
|
|
WebKitDOMElement *caret;
|
|
WebKitDOMNode *prev_sibling;
|
|
|
|
caret = e_html_editor_selection_save_caret_position (selection);
|
|
|
|
/* Empty text node before caret */
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (
|
|
WEBKIT_DOM_NODE (caret));
|
|
if (prev_sibling && WEBKIT_DOM_IS_TEXT (prev_sibling)) {
|
|
gchar *content;
|
|
|
|
content = webkit_dom_node_get_text_content (prev_sibling);
|
|
if (g_strcmp0 (content, "") == 0)
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (prev_sibling);
|
|
g_free (content);
|
|
}
|
|
|
|
if (!prev_sibling) {
|
|
e_html_editor_selection_clear_caret_position_marker (selection);
|
|
e_html_editor_selection_unindent (selection);
|
|
return TRUE;
|
|
} else
|
|
e_html_editor_selection_clear_caret_position_marker (selection);
|
|
}
|
|
|
|
if (prevent_from_deleting_last_element_in_body (view))
|
|
return TRUE;
|
|
}
|
|
|
|
if (event->keyval == GDK_KEY_Delete || event->keyval == GDK_KEY_BackSpace) {
|
|
if (event->keyval == GDK_KEY_BackSpace && !view->priv->html_mode) {
|
|
if (delete_character_from_quoted_line_start (view))
|
|
return TRUE;
|
|
}
|
|
if (fix_structure_after_delete_before_quoted_content (view))
|
|
return TRUE;
|
|
}
|
|
|
|
/* Chain up to parent's key_press_event() method. */
|
|
return GTK_WIDGET_CLASS (e_html_editor_view_parent_class)->
|
|
key_press_event (widget, event);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_paste_as_text (EHTMLEditorView *view)
|
|
{
|
|
GtkClipboard *clipboard;
|
|
|
|
clipboard = gtk_clipboard_get_for_display (
|
|
gdk_display_get_default (),
|
|
GDK_SELECTION_CLIPBOARD);
|
|
|
|
gtk_clipboard_request_text (
|
|
clipboard,
|
|
(GtkClipboardTextReceivedFunc) clipboard_text_received_for_paste_as_text,
|
|
view);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
|
|
{
|
|
GtkClipboard *clipboard;
|
|
|
|
clipboard = gtk_clipboard_get_for_display (
|
|
gdk_display_get_default (),
|
|
GDK_SELECTION_CLIPBOARD);
|
|
|
|
gtk_clipboard_request_text (
|
|
clipboard,
|
|
(GtkClipboardTextReceivedFunc) clipboard_text_received,
|
|
view);
|
|
}
|
|
|
|
static gboolean
|
|
html_editor_view_image_exists_in_cache (const gchar *image_uri)
|
|
{
|
|
gchar *filename;
|
|
gchar *hash;
|
|
gboolean exists = FALSE;
|
|
|
|
g_return_val_if_fail (emd_global_http_cache != NULL, FALSE);
|
|
|
|
hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, image_uri, -1);
|
|
filename = camel_data_cache_get_filename (
|
|
emd_global_http_cache, "http", hash);
|
|
|
|
if (filename != NULL) {
|
|
exists = g_file_test (filename, G_FILE_TEST_EXISTS);
|
|
g_free (filename);
|
|
}
|
|
|
|
g_free (hash);
|
|
|
|
return exists;
|
|
}
|
|
|
|
static gchar *
|
|
html_editor_view_redirect_uri (EHTMLEditorView *view,
|
|
const gchar *uri)
|
|
{
|
|
EImageLoadingPolicy image_policy;
|
|
GSettings *settings;
|
|
gboolean uri_is_http;
|
|
|
|
uri_is_http =
|
|
g_str_has_prefix (uri, "http:") ||
|
|
g_str_has_prefix (uri, "https:") ||
|
|
g_str_has_prefix (uri, "evo-http:") ||
|
|
g_str_has_prefix (uri, "evo-https:");
|
|
|
|
/* Redirect http(s) request to evo-http(s) protocol.
|
|
* See EMailRequest for further details about this. */
|
|
if (uri_is_http) {
|
|
gchar *new_uri;
|
|
SoupURI *soup_uri;
|
|
gboolean image_exists;
|
|
|
|
/* Check Evolution's cache */
|
|
image_exists = html_editor_view_image_exists_in_cache (uri);
|
|
|
|
settings = e_util_ref_settings ("org.gnome.evolution.mail");
|
|
image_policy = g_settings_get_enum (settings, "image-loading-policy");
|
|
g_object_unref (settings);
|
|
/* If the URI is not cached and we are not allowed to load it
|
|
* then redirect to invalid URI, so that webkit would display
|
|
* a native placeholder for it. */
|
|
if (!image_exists && (image_policy == E_IMAGE_LOADING_POLICY_NEVER)) {
|
|
return g_strdup ("about:blank");
|
|
}
|
|
|
|
new_uri = g_strconcat ("evo-", uri, NULL);
|
|
soup_uri = soup_uri_new (new_uri);
|
|
g_free (new_uri);
|
|
|
|
new_uri = soup_uri_to_string (soup_uri, FALSE);
|
|
|
|
soup_uri_free (soup_uri);
|
|
|
|
return new_uri;
|
|
}
|
|
|
|
return g_strdup (uri);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_resource_requested (WebKitWebView *web_view,
|
|
WebKitWebFrame *frame,
|
|
WebKitWebResource *resource,
|
|
WebKitNetworkRequest *request,
|
|
WebKitNetworkResponse *response,
|
|
gpointer user_data)
|
|
{
|
|
const gchar *original_uri;
|
|
|
|
original_uri = webkit_network_request_get_uri (request);
|
|
|
|
if (original_uri != NULL) {
|
|
gchar *redirected_uri;
|
|
|
|
redirected_uri = html_editor_view_redirect_uri (
|
|
E_HTML_EDITOR_VIEW (web_view), original_uri);
|
|
|
|
webkit_network_request_set_uri (request, redirected_uri);
|
|
|
|
g_free (redirected_uri);
|
|
}
|
|
}
|
|
|
|
static void
|
|
e_html_editor_view_class_init (EHTMLEditorViewClass *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
GtkWidgetClass *widget_class;
|
|
|
|
g_type_class_add_private (class, sizeof (EHTMLEditorViewPrivate));
|
|
|
|
object_class = G_OBJECT_CLASS (class);
|
|
object_class->get_property = html_editor_view_get_property;
|
|
object_class->set_property = html_editor_view_set_property;
|
|
object_class->dispose = html_editor_view_dispose;
|
|
object_class->finalize = html_editor_view_finalize;
|
|
object_class->constructed = html_editor_view_constructed;
|
|
|
|
widget_class = GTK_WIDGET_CLASS (class);
|
|
widget_class->button_press_event = html_editor_view_button_press_event;
|
|
widget_class->button_release_event = html_editor_view_button_release_event;
|
|
widget_class->key_press_event = html_editor_view_key_press_event;
|
|
|
|
class->paste_clipboard_quoted = html_editor_view_paste_clipboard_quoted;
|
|
|
|
/**
|
|
* EHTMLEditorView:can-copy
|
|
*
|
|
* Determines whether it's possible to copy to clipboard. The action
|
|
* is usually disabled when there is no selection to copy.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CAN_COPY,
|
|
g_param_spec_boolean (
|
|
"can-copy",
|
|
"Can Copy",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:can-cut
|
|
*
|
|
* Determines whether it's possible to cut to clipboard. The action
|
|
* is usually disabled when there is no selection to cut.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CAN_CUT,
|
|
g_param_spec_boolean (
|
|
"can-cut",
|
|
"Can Cut",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:can-paste
|
|
*
|
|
* Determines whether it's possible to paste from clipboard. The action
|
|
* is usually disabled when there is no valid content in clipboard to
|
|
* paste.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CAN_PASTE,
|
|
g_param_spec_boolean (
|
|
"can-paste",
|
|
"Can Paste",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:can-redo
|
|
*
|
|
* Determines whether it's possible to redo previous action. The action
|
|
* is usually disabled when there is no action to redo.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CAN_REDO,
|
|
g_param_spec_boolean (
|
|
"can-redo",
|
|
"Can Redo",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:can-undo
|
|
*
|
|
* Determines whether it's possible to undo last action. The action
|
|
* is usually disabled when there is no previous action to undo.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CAN_UNDO,
|
|
g_param_spec_boolean (
|
|
"can-undo",
|
|
"Can Undo",
|
|
NULL,
|
|
FALSE,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:changed
|
|
*
|
|
* Determines whether document has been modified
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_CHANGED,
|
|
g_param_spec_boolean (
|
|
"changed",
|
|
_("Changed property"),
|
|
_("Whether editor changed"),
|
|
FALSE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:html-mode
|
|
*
|
|
* Determines whether HTML or plain text mode is enabled.
|
|
**/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_HTML_MODE,
|
|
g_param_spec_boolean (
|
|
"html-mode",
|
|
"HTML Mode",
|
|
"Edit HTML or plain text",
|
|
TRUE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView::inline-spelling
|
|
*
|
|
* Determines whether automatic spellchecking is enabled.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_INLINE_SPELLING,
|
|
g_param_spec_boolean (
|
|
"inline-spelling",
|
|
"Inline Spelling",
|
|
"Check your spelling as you type",
|
|
TRUE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:magic-links
|
|
*
|
|
* Determines whether automatic conversion of text links into
|
|
* HTML links is enabled.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_MAGIC_LINKS,
|
|
g_param_spec_boolean (
|
|
"magic-links",
|
|
"Magic Links",
|
|
"Make URIs clickable as you type",
|
|
TRUE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:magic-smileys
|
|
*
|
|
* Determines whether automatic conversion of text smileys into
|
|
* images or Unicode characters is enabled.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_MAGIC_SMILEYS,
|
|
g_param_spec_boolean (
|
|
"magic-smileys",
|
|
"Magic Smileys",
|
|
"Convert emoticons to images or Unicode characters as you type",
|
|
TRUE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:unicode-smileys
|
|
*
|
|
* Determines whether Unicode characters should be used for smileys.
|
|
*/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_UNICODE_SMILEYS,
|
|
g_param_spec_boolean (
|
|
"unicode-smileys",
|
|
"Unicode Smileys",
|
|
"Use Unicode characters for smileys",
|
|
TRUE,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:spell-checker:
|
|
*
|
|
* The #ESpellChecker used for spell checking.
|
|
**/
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_SPELL_CHECKER,
|
|
g_param_spec_object (
|
|
"spell-checker",
|
|
"Spell Checker",
|
|
"The spell checker",
|
|
E_TYPE_SPELL_CHECKER,
|
|
G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS));
|
|
|
|
/**
|
|
* EHTMLEditorView:popup-event
|
|
*
|
|
* Emitted whenever a context menu is requested.
|
|
*/
|
|
signals[POPUP_EVENT] = g_signal_new (
|
|
"popup-event",
|
|
G_TYPE_FROM_CLASS (class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (EHTMLEditorViewClass, popup_event),
|
|
g_signal_accumulator_true_handled, NULL,
|
|
e_marshal_BOOLEAN__BOXED,
|
|
G_TYPE_BOOLEAN, 1,
|
|
GDK_TYPE_EVENT | G_SIGNAL_TYPE_STATIC_SCOPE);
|
|
/**
|
|
* EHTMLEditorView:paste-primary-clipboad
|
|
*
|
|
* Emitted when user presses middle button on EHTMLEditorView
|
|
*/
|
|
signals[PASTE_PRIMARY_CLIPBOARD] = g_signal_new (
|
|
"paste-primary-clipboard",
|
|
G_TYPE_FROM_CLASS (class),
|
|
G_SIGNAL_RUN_LAST,
|
|
G_STRUCT_OFFSET (EHTMLEditorViewClass, paste_primary_clipboard),
|
|
NULL, NULL,
|
|
g_cclosure_marshal_VOID__VOID,
|
|
G_TYPE_NONE, 0);
|
|
}
|
|
|
|
static void
|
|
insert_quote_symbols (WebKitDOMHTMLElement *element,
|
|
gint quote_level,
|
|
gboolean skip_first,
|
|
gboolean insert_newline)
|
|
{
|
|
gchar *text;
|
|
gint ii;
|
|
GString *output;
|
|
gchar *quotation;
|
|
|
|
if (!WEBKIT_DOM_IS_HTML_ELEMENT (element))
|
|
return;
|
|
|
|
text = webkit_dom_html_element_get_inner_html (element);
|
|
output = g_string_new ("");
|
|
quotation = get_quotation_for_level (quote_level);
|
|
|
|
if (g_strcmp0 (text, "\n") == 0) {
|
|
g_string_append (output, "<span class=\"-x-evo-quoted\">");
|
|
g_string_append (output, quotation);
|
|
g_string_append (output, "</span>");
|
|
g_string_append (output, "\n");
|
|
} else {
|
|
gchar **lines;
|
|
|
|
lines = g_strsplit (text, "\n", 0);
|
|
|
|
for (ii = 0; lines[ii]; ii++) {
|
|
if (ii == 0 && skip_first) {
|
|
if (g_strv_length (lines) == 1) {
|
|
g_strfreev (lines);
|
|
goto exit;
|
|
}
|
|
g_string_append (output, lines[ii]);
|
|
g_string_append (output, "\n");
|
|
}
|
|
|
|
g_string_append (output, "<span class=\"-x-evo-quoted\">");
|
|
g_string_append (output, quotation);
|
|
g_string_append (output, "</span>");
|
|
|
|
/* Insert line of text */
|
|
g_string_append (output, lines[ii]);
|
|
if ((ii == g_strv_length (lines) - 1) &&
|
|
!g_str_has_suffix (text, "\n") && !insert_newline) {
|
|
/* If we are on last line and node's text doesn't
|
|
* end with \n, don't insert it */
|
|
break;
|
|
}
|
|
g_string_append (output, "\n");
|
|
}
|
|
|
|
g_strfreev (lines);
|
|
}
|
|
|
|
webkit_dom_html_element_set_inner_html (element, output->str, NULL);
|
|
exit:
|
|
g_free (quotation);
|
|
g_free (text);
|
|
g_string_free (output, TRUE);
|
|
}
|
|
|
|
static void
|
|
quote_node (WebKitDOMDocument *document,
|
|
WebKitDOMNode *node,
|
|
gint quote_level)
|
|
{
|
|
gboolean skip_first = FALSE;
|
|
gboolean insert_newline = FALSE;
|
|
gboolean is_html_node = FALSE;
|
|
WebKitDOMElement *wrapper;
|
|
WebKitDOMNode *node_clone, *prev_sibling, *next_sibling;
|
|
|
|
/* Don't quote when we are not in citation */
|
|
if (quote_level == 0)
|
|
return;
|
|
|
|
if (WEBKIT_DOM_IS_COMMENT (node))
|
|
return;
|
|
|
|
if (WEBKIT_DOM_IS_HTML_ELEMENT (node)) {
|
|
insert_quote_symbols (
|
|
WEBKIT_DOM_HTML_ELEMENT (node), quote_level, FALSE, FALSE);
|
|
return;
|
|
}
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (node);
|
|
next_sibling = webkit_dom_node_get_next_sibling (node);
|
|
|
|
is_html_node =
|
|
!WEBKIT_DOM_IS_TEXT (prev_sibling) &&
|
|
!WEBKIT_DOM_IS_COMMENT (prev_sibling) && (
|
|
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling) ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "b") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "i") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (prev_sibling), "u"));
|
|
|
|
if (prev_sibling && is_html_node)
|
|
skip_first = TRUE;
|
|
|
|
/* Skip the BR between first blockquote and pre */
|
|
if (quote_level == 1 && next_sibling && WEBKIT_DOM_IS_HTML_PRE_ELEMENT (next_sibling))
|
|
return;
|
|
|
|
/* Do temporary wrapper */
|
|
wrapper = webkit_dom_document_create_element (document, "SPAN", NULL);
|
|
webkit_dom_element_set_class_name (wrapper, "-x-evo-temp-text-wrapper");
|
|
|
|
node_clone = webkit_dom_node_clone_node (node, TRUE);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
node_clone,
|
|
NULL);
|
|
|
|
insert_quote_symbols (
|
|
WEBKIT_DOM_HTML_ELEMENT (wrapper),
|
|
quote_level,
|
|
skip_first,
|
|
insert_newline);
|
|
|
|
webkit_dom_node_replace_child (
|
|
webkit_dom_node_get_parent_node (node),
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
node,
|
|
NULL);
|
|
}
|
|
|
|
static void
|
|
insert_quote_symbols_before_node (WebKitDOMDocument *document,
|
|
WebKitDOMNode *node,
|
|
gint quote_level,
|
|
gboolean is_html_node)
|
|
{
|
|
gboolean skip, wrap_br;
|
|
gchar *quotation;
|
|
WebKitDOMElement *element;
|
|
|
|
quotation = get_quotation_for_level (quote_level);
|
|
element = webkit_dom_document_create_element (document, "SPAN", NULL);
|
|
element_add_class (element, "-x-evo-quoted");
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (element), quotation, NULL);
|
|
|
|
/* Don't insert temporary BR before BR that is used for wrapping */
|
|
skip = WEBKIT_DOM_IS_HTMLBR_ELEMENT (node);
|
|
wrap_br = element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br");
|
|
skip = skip && wrap_br;
|
|
|
|
if (is_html_node && !skip) {
|
|
WebKitDOMElement *new_br;
|
|
|
|
new_br = webkit_dom_document_create_element (document, "br", NULL);
|
|
element_add_class (new_br, "-x-evo-temp-br");
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (node),
|
|
WEBKIT_DOM_NODE (new_br),
|
|
node,
|
|
NULL);
|
|
}
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (node),
|
|
WEBKIT_DOM_NODE (element),
|
|
node,
|
|
NULL);
|
|
|
|
if (is_html_node && !wrap_br)
|
|
remove_node (node);
|
|
|
|
g_free (quotation);
|
|
}
|
|
|
|
static gboolean
|
|
element_is_selection_marker (WebKitDOMElement *element)
|
|
{
|
|
gboolean is_marker = FALSE;
|
|
|
|
is_marker =
|
|
element_has_id (element, "-x-evo-selection-start-marker") ||
|
|
element_has_id (element, "-x-evo-selection-end-marker");
|
|
|
|
return is_marker;
|
|
}
|
|
|
|
static gboolean
|
|
check_if_suppress_next_node (WebKitDOMNode *node)
|
|
{
|
|
if (!node)
|
|
return FALSE;
|
|
|
|
if (node && WEBKIT_DOM_IS_ELEMENT (node))
|
|
if (element_is_selection_marker (WEBKIT_DOM_ELEMENT (node)))
|
|
if (!webkit_dom_node_get_previous_sibling (node))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
quote_br_node (WebKitDOMNode *node,
|
|
gint quote_level)
|
|
{
|
|
gchar *quotation, *content;
|
|
|
|
quotation = get_quotation_for_level (quote_level);
|
|
|
|
content = g_strconcat (
|
|
"<span class=\"-x-evo-quoted\">",
|
|
quotation,
|
|
"</span><br class=\"-x-evo-temp-br\">",
|
|
NULL);
|
|
|
|
webkit_dom_html_element_set_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (node),
|
|
content,
|
|
NULL);
|
|
|
|
g_free (content);
|
|
g_free (quotation);
|
|
}
|
|
|
|
static void
|
|
quote_plain_text_recursive (WebKitDOMDocument *document,
|
|
WebKitDOMNode *node,
|
|
WebKitDOMNode *start_node,
|
|
gint quote_level)
|
|
{
|
|
gboolean skip_node = FALSE;
|
|
gboolean move_next = FALSE;
|
|
gboolean suppress_next = FALSE;
|
|
gboolean is_html_node = FALSE;
|
|
gboolean next = FALSE;
|
|
WebKitDOMNode *next_sibling, *prev_sibling;
|
|
|
|
node = webkit_dom_node_get_first_child (node);
|
|
|
|
while (node) {
|
|
gchar *text_content;
|
|
|
|
skip_node = FALSE;
|
|
move_next = FALSE;
|
|
is_html_node = FALSE;
|
|
|
|
if (WEBKIT_DOM_IS_COMMENT (node) ||
|
|
WEBKIT_DOM_IS_HTML_META_ELEMENT (node) ||
|
|
WEBKIT_DOM_IS_HTML_STYLE_ELEMENT (node) ||
|
|
WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (node)) {
|
|
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
}
|
|
|
|
prev_sibling = webkit_dom_node_get_previous_sibling (node);
|
|
next_sibling = webkit_dom_node_get_next_sibling (node);
|
|
|
|
if (WEBKIT_DOM_IS_TEXT (node)) {
|
|
/* Start quoting after we are in blockquote */
|
|
if (quote_level > 0 && !suppress_next) {
|
|
/* When quoting text node, we are wrappering it and
|
|
* afterwards replacing it with that wrapper, thus asking
|
|
* for next_sibling after quoting will return NULL bacause
|
|
* that node don't exist anymore */
|
|
quote_node (document, node, quote_level);
|
|
node = next_sibling;
|
|
skip_node = TRUE;
|
|
}
|
|
|
|
goto next_node;
|
|
}
|
|
|
|
if (!(WEBKIT_DOM_IS_ELEMENT (node) || WEBKIT_DOM_IS_HTML_ELEMENT (node)))
|
|
goto next_node;
|
|
|
|
if (element_has_id (WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-position")) {
|
|
if (quote_level > 0)
|
|
element_add_class (
|
|
WEBKIT_DOM_ELEMENT (node), "-x-evo-caret-quoting");
|
|
|
|
move_next = TRUE;
|
|
suppress_next = TRUE;
|
|
next = FALSE;
|
|
goto next_node;
|
|
}
|
|
|
|
if (element_is_selection_marker (WEBKIT_DOM_ELEMENT (node))) {
|
|
/* If there is collapsed selection in the beginning of line
|
|
* we cannot suppress first text that is after the end of
|
|
* selection */
|
|
suppress_next = check_if_suppress_next_node (prev_sibling);
|
|
if (suppress_next)
|
|
next = FALSE;
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
}
|
|
|
|
if (!WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) &&
|
|
webkit_dom_element_get_child_element_count (WEBKIT_DOM_ELEMENT (node)) != 0)
|
|
goto with_children;
|
|
|
|
/* Even in plain text mode we can have some basic html element
|
|
* like anchor and others. When Forwaring e-mail as Quoted EMFormat
|
|
* generates header that contatains <b> tags (bold font).
|
|
* We have to treat these elements separately to avoid
|
|
* modifications of theirs inner texts */
|
|
is_html_node =
|
|
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (node) ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (node), "b") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (node), "i") ||
|
|
element_has_tag (WEBKIT_DOM_ELEMENT (node), "u");
|
|
|
|
if (is_html_node) {
|
|
gboolean wrap_br;
|
|
|
|
wrap_br =
|
|
prev_sibling &&
|
|
WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling) &&
|
|
element_has_class (
|
|
WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-wrap-br");
|
|
|
|
if (!prev_sibling || wrap_br)
|
|
insert_quote_symbols_before_node (
|
|
document, node, quote_level, FALSE);
|
|
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling) && !wrap_br)
|
|
insert_quote_symbols_before_node (
|
|
document, prev_sibling, quote_level, TRUE);
|
|
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
}
|
|
|
|
/* If element doesn't have children, we can quote it */
|
|
if (is_citation_node (node)) {
|
|
/* Citation with just text inside */
|
|
quote_node (document, node, quote_level + 1);
|
|
/* Set citation as quoted */
|
|
element_add_class (
|
|
WEBKIT_DOM_ELEMENT (node),
|
|
"-x-evo-plaintext-quoted");
|
|
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
}
|
|
|
|
if (!WEBKIT_DOM_IS_HTMLBR_ELEMENT (node)) {
|
|
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (prev_sibling)) {
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
}
|
|
goto not_br;
|
|
} else if (element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-first-br") ||
|
|
element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-last-br")) {
|
|
quote_br_node (node, quote_level);
|
|
node = next_sibling;
|
|
skip_node = TRUE;
|
|
goto next_node;
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
|
|
WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (next_sibling) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
|
|
/* Situation when anchors are alone on line */
|
|
text_content = webkit_dom_node_get_text_content (prev_sibling);
|
|
|
|
if (g_str_has_suffix (text_content, "\n")) {
|
|
insert_quote_symbols_before_node (
|
|
document, node, quote_level, FALSE);
|
|
remove_node (node);
|
|
g_free (text_content);
|
|
node = next_sibling;
|
|
skip_node = TRUE;
|
|
goto next_node;
|
|
}
|
|
g_free (text_content);
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling)) {
|
|
quote_br_node (prev_sibling, quote_level);
|
|
node = next_sibling;
|
|
skip_node = TRUE;
|
|
goto next_node;
|
|
}
|
|
|
|
if (!prev_sibling && !next_sibling) {
|
|
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
|
|
|
|
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (parent) ||
|
|
WEBKIT_DOM_IS_HTML_PRE_ELEMENT (parent) ||
|
|
(WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (parent) &&
|
|
!is_citation_node (parent))) {
|
|
insert_quote_symbols_before_node (
|
|
document, node, quote_level, FALSE);
|
|
|
|
goto next_node;
|
|
}
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_ELEMENT (prev_sibling) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (prev_sibling), "-x-evo-temp-text-wrapper")) {
|
|
text_content = webkit_dom_node_get_text_content (prev_sibling);
|
|
if (text_content && !*text_content) {
|
|
insert_quote_symbols_before_node (
|
|
document, node, quote_level, FALSE);
|
|
|
|
g_free (text_content);
|
|
goto next_node;
|
|
|
|
}
|
|
|
|
g_free (text_content);
|
|
}
|
|
|
|
if (is_citation_node (prev_sibling)) {
|
|
insert_quote_symbols_before_node (
|
|
document, node, quote_level, FALSE);
|
|
goto next_node;
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node) &&
|
|
!next_sibling &&
|
|
element_is_selection_marker (WEBKIT_DOM_ELEMENT (prev_sibling))) {
|
|
insert_quote_symbols_before_node (
|
|
document, node, quote_level, FALSE);
|
|
goto next_node;
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node)) {
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
}
|
|
|
|
not_br:
|
|
text_content = webkit_dom_node_get_text_content (node);
|
|
if (text_content && !*text_content) {
|
|
g_free (text_content);
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
}
|
|
g_free (text_content);
|
|
|
|
quote_node (document, node, quote_level);
|
|
|
|
move_next = TRUE;
|
|
goto next_node;
|
|
|
|
with_children:
|
|
if (is_citation_node (node)) {
|
|
/* Go deeper and increase level */
|
|
quote_plain_text_recursive (
|
|
document, node, start_node, quote_level + 1);
|
|
/* set citation as quoted */
|
|
element_add_class (
|
|
WEBKIT_DOM_ELEMENT (node),
|
|
"-x-evo-plaintext-quoted");
|
|
move_next = TRUE;
|
|
} else {
|
|
quote_plain_text_recursive (
|
|
document, node, start_node, quote_level);
|
|
move_next = TRUE;
|
|
}
|
|
next_node:
|
|
if (next) {
|
|
suppress_next = FALSE;
|
|
next = FALSE;
|
|
}
|
|
|
|
if (suppress_next)
|
|
next = TRUE;
|
|
|
|
if (!skip_node) {
|
|
/* Move to next node */
|
|
if (!move_next && webkit_dom_node_has_child_nodes (node)) {
|
|
node = webkit_dom_node_get_first_child (node);
|
|
} else if (webkit_dom_node_get_next_sibling (node)) {
|
|
node = webkit_dom_node_get_next_sibling (node);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
WebKitDOMElement *
|
|
e_html_editor_view_quote_plain_text_element (EHTMLEditorView *view,
|
|
WebKitDOMElement *element)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNode *element_clone;
|
|
WebKitDOMNodeList *list;
|
|
gint ii, length, level;
|
|
|
|
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
|
|
|
|
element_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (element), TRUE);
|
|
level = get_citation_level (WEBKIT_DOM_NODE (element), TRUE);
|
|
|
|
/* Remove old quote characters if the exists */
|
|
list = webkit_dom_element_query_selector_all (
|
|
WEBKIT_DOM_ELEMENT (element_clone), "span.-x-evo-quoted", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
webkit_dom_node_normalize (element_clone);
|
|
quote_plain_text_recursive (
|
|
document, element_clone, element_clone, level);
|
|
|
|
/* Set citation as quoted */
|
|
if (is_citation_node (element_clone))
|
|
element_add_class (
|
|
WEBKIT_DOM_ELEMENT (element_clone),
|
|
"-x-evo-plaintext-quoted");
|
|
|
|
/* Replace old element with one, that is quoted */
|
|
webkit_dom_node_replace_child (
|
|
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (element)),
|
|
element_clone,
|
|
WEBKIT_DOM_NODE (element),
|
|
NULL);
|
|
|
|
return WEBKIT_DOM_ELEMENT (element_clone);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_quote_plain_text:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Quote text inside citation blockquotes in plain text mode.
|
|
*
|
|
* As this function is cloning and replacing all citation blockquotes keep on
|
|
* mind that any pointers to nodes inside these blockquotes will be invalidated.
|
|
*/
|
|
WebKitDOMElement *
|
|
e_html_editor_view_quote_plain_text (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMNode *body_clone;
|
|
WebKitDOMNamedNodeMap *attributes;
|
|
WebKitDOMNodeList *list;
|
|
WebKitDOMElement *element;
|
|
gint ii, length;
|
|
gulong attributes_length;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
/* Check if the document is already quoted */
|
|
element = webkit_dom_document_query_selector (
|
|
document, ".-x-evo-plaintext-quoted", NULL);
|
|
if (element)
|
|
return NULL;
|
|
|
|
body = webkit_dom_document_get_body (document);
|
|
body_clone = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);
|
|
|
|
/* Clean unwanted spaces before and after blockquotes */
|
|
list = webkit_dom_element_query_selector_all (
|
|
WEBKIT_DOM_ELEMENT (body_clone), "blockquote[type|=cite]", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *blockquote = webkit_dom_node_list_item (list, ii);
|
|
WebKitDOMNode *prev_sibling = webkit_dom_node_get_previous_sibling (blockquote);
|
|
WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (blockquote);
|
|
|
|
if (prev_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (prev_sibling))
|
|
remove_node (prev_sibling);
|
|
|
|
if (next_sibling && WEBKIT_DOM_IS_HTMLBR_ELEMENT (next_sibling))
|
|
remove_node (next_sibling);
|
|
|
|
if (webkit_dom_node_has_child_nodes (blockquote)) {
|
|
WebKitDOMNode *child = webkit_dom_node_get_first_child (blockquote);
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
|
|
remove_node (child);
|
|
}
|
|
g_object_unref (blockquote);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
webkit_dom_node_normalize (body_clone);
|
|
quote_plain_text_recursive (document, body_clone, body_clone, 0);
|
|
|
|
/* Copy attributes */
|
|
attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
|
|
attributes_length = webkit_dom_named_node_map_get_length (attributes);
|
|
for (ii = 0; ii < attributes_length; ii++) {
|
|
gchar *name, *value;
|
|
WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
|
|
|
|
name = webkit_dom_node_get_local_name (node);
|
|
value = webkit_dom_node_get_node_value (node);
|
|
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (body_clone), name, value, NULL);
|
|
|
|
g_object_unref (node);
|
|
g_free (name);
|
|
g_free (value);
|
|
}
|
|
g_object_unref (attributes);
|
|
|
|
/* Replace old BODY with one, that is quoted */
|
|
webkit_dom_node_replace_child (
|
|
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)),
|
|
body_clone,
|
|
WEBKIT_DOM_NODE (body),
|
|
NULL);
|
|
|
|
return WEBKIT_DOM_ELEMENT (body_clone);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_dequote_plain_text:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Dequote already quoted plain text in editor.
|
|
* Editor have to be quoted with e_html_editor_view_quote_plain_text otherwise
|
|
* it's not working.
|
|
*/
|
|
void
|
|
e_html_editor_view_dequote_plain_text (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNodeList *paragraphs;
|
|
gint length, ii;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
paragraphs = webkit_dom_document_query_selector_all (
|
|
document, "blockquote.-x-evo-plaintext-quoted", NULL);
|
|
length = webkit_dom_node_list_get_length (paragraphs);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMElement *element;
|
|
|
|
element = WEBKIT_DOM_ELEMENT (webkit_dom_node_list_item (paragraphs, ii));
|
|
|
|
if (is_citation_node (WEBKIT_DOM_NODE (element))) {
|
|
element_remove_class (element, "-x-evo-plaintext-quoted");
|
|
remove_quoting_from_element (element);
|
|
}
|
|
g_object_unref (element);
|
|
}
|
|
g_object_unref (paragraphs);
|
|
}
|
|
|
|
static gboolean
|
|
create_anchor_for_link (const GMatchInfo *info,
|
|
GString *res,
|
|
gpointer data)
|
|
{
|
|
gint offset = 0;
|
|
gchar *match;
|
|
gboolean address_surrounded;
|
|
|
|
match = g_match_info_fetch (info, 0);
|
|
|
|
address_surrounded =
|
|
strstr (match, "@") &&
|
|
!strstr (match, "://") &&
|
|
g_str_has_prefix (match, "<") &&
|
|
g_str_has_suffix (match, ">");
|
|
|
|
if (address_surrounded)
|
|
offset += 4;
|
|
|
|
if (g_str_has_prefix (match, " "))
|
|
offset += 6;
|
|
|
|
if (address_surrounded)
|
|
g_string_append (res, "<");
|
|
|
|
g_string_append (res, "<a href=\"");
|
|
if (strstr (match, "@") && !strstr (match, "://")) {
|
|
g_string_append (res, "mailto:");
|
|
g_string_append (res, match + offset);
|
|
if (address_surrounded)
|
|
g_string_truncate (res, res->len - 4);
|
|
|
|
g_string_append (res, "\">");
|
|
g_string_append (res, match + offset);
|
|
if (address_surrounded)
|
|
g_string_truncate (res, res->len - 4);
|
|
} else {
|
|
g_string_append (res, match + offset);
|
|
g_string_append (res, "\">");
|
|
g_string_append (res, match + offset);
|
|
}
|
|
g_string_append (res, "</a>");
|
|
|
|
if (address_surrounded)
|
|
g_string_append (res, ">");
|
|
|
|
g_free (match);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
replace_to_nbsp (const GMatchInfo *info,
|
|
GString *res)
|
|
{
|
|
gchar *match;
|
|
gint ii = 0;
|
|
|
|
match = g_match_info_fetch (info, 0);
|
|
|
|
while (match[ii] != '\0') {
|
|
if (match[ii] == ' ') {
|
|
/* Alone spaces or spaces before/after tabulator. */
|
|
g_string_append (res, " ");
|
|
} else if (match[ii] == '\t') {
|
|
/* Replace tabs with their WebKit HTML representation. */
|
|
g_string_append (res, "<span class=\"Apple-tab-span\" style=\"white-space:pre\">\t</span>");
|
|
}
|
|
|
|
ii++;
|
|
}
|
|
|
|
g_free (match);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
surround_links_with_anchor (const gchar *text)
|
|
{
|
|
return (strstr (text, "http") || strstr (text, "ftp") ||
|
|
strstr (text, "www") || strstr (text, "@"));
|
|
}
|
|
|
|
static void
|
|
append_new_paragraph (WebKitDOMElement *parent,
|
|
WebKitDOMElement **paragraph)
|
|
{
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (parent),
|
|
WEBKIT_DOM_NODE (*paragraph),
|
|
NULL);
|
|
|
|
*paragraph = NULL;
|
|
}
|
|
|
|
static WebKitDOMElement *
|
|
create_and_append_new_paragraph (EHTMLEditorSelection *selection,
|
|
WebKitDOMDocument *document,
|
|
WebKitDOMElement *parent,
|
|
WebKitDOMNode *block,
|
|
const gchar *content)
|
|
{
|
|
WebKitDOMElement *paragraph;
|
|
|
|
if (!block || WEBKIT_DOM_IS_HTML_DIV_ELEMENT (block))
|
|
paragraph = e_html_editor_selection_get_paragraph_element (
|
|
selection, document, -1, 0);
|
|
else
|
|
paragraph = WEBKIT_DOM_ELEMENT (webkit_dom_node_clone_node (block, FALSE));
|
|
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (paragraph),
|
|
content,
|
|
NULL);
|
|
|
|
append_new_paragraph (parent, ¶graph);
|
|
|
|
return paragraph;
|
|
}
|
|
|
|
static void
|
|
append_citation_mark (WebKitDOMDocument *document,
|
|
WebKitDOMElement *parent,
|
|
const gchar *citation_mark_text)
|
|
{
|
|
WebKitDOMText *text;
|
|
|
|
text = webkit_dom_document_create_text_node (document, citation_mark_text);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (parent),
|
|
WEBKIT_DOM_NODE (text),
|
|
NULL);
|
|
}
|
|
|
|
static glong
|
|
get_decoded_line_length (WebKitDOMDocument *document,
|
|
const gchar *line_text)
|
|
{
|
|
glong total_length = 0, length = 0;
|
|
WebKitDOMElement *decode;
|
|
WebKitDOMNode *node;
|
|
|
|
decode = webkit_dom_document_create_element (document, "DIV", NULL);
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (decode), line_text, NULL);
|
|
|
|
node = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (decode));
|
|
while (node) {
|
|
if (WEBKIT_DOM_IS_TEXT (node)) {
|
|
gulong text_length = 0;
|
|
|
|
text_length = webkit_dom_character_data_get_length (WEBKIT_DOM_CHARACTER_DATA (node));
|
|
total_length += text_length;
|
|
length += text_length;
|
|
} if (WEBKIT_DOM_IS_ELEMENT (node)) {
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (node), "Apple-tab-span")) {
|
|
total_length += TAB_LENGTH - length % TAB_LENGTH;
|
|
length = 0;
|
|
}
|
|
}
|
|
node = webkit_dom_node_get_next_sibling (node);
|
|
}
|
|
|
|
g_object_unref (decode);
|
|
|
|
return total_length;
|
|
}
|
|
|
|
static gboolean
|
|
check_if_end_paragraph (const gchar *input,
|
|
glong length,
|
|
gboolean preserve_next_line)
|
|
{
|
|
const gchar *next_space;
|
|
|
|
next_space = strstr (input, " ");
|
|
if (next_space) {
|
|
const gchar *next_br;
|
|
glong length_next_word =
|
|
next_space - input - 4;
|
|
|
|
if (g_str_has_prefix (input + 4, "<br>"))
|
|
length_next_word = 0;
|
|
|
|
if (length_next_word > 0)
|
|
next_br = strstr (input + 4, "<br>");
|
|
|
|
if (length_next_word > 0 && next_br < next_space)
|
|
length_next_word = 0;
|
|
|
|
if (length_next_word + length < 72)
|
|
return TRUE;
|
|
} else {
|
|
/* If the current text to insert doesn't contain space we
|
|
* have to look on the previous line if we were preserving
|
|
* the block or not */
|
|
return !preserve_next_line;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* This parses the HTML code (that contains just text, and BR elements)
|
|
* into paragraphs.
|
|
* HTML code in that format we can get by taking innerText from some element,
|
|
* setting it to another one and finally getting innerHTML from it */
|
|
static void
|
|
parse_html_into_paragraphs (EHTMLEditorView *view,
|
|
WebKitDOMDocument *document,
|
|
WebKitDOMElement *blockquote,
|
|
WebKitDOMNode *block,
|
|
const gchar *html)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gboolean ignore_next_br = FALSE;
|
|
gboolean first_element = TRUE;
|
|
gboolean citation_was_first_element = FALSE;
|
|
const gchar *prev_br, *next_br;
|
|
GRegex *regex_nbsp = NULL, *regex_link = NULL, *regex_email = NULL;
|
|
WebKitDOMElement *paragraph = NULL;
|
|
gboolean preserve_next_line = FALSE;
|
|
gboolean has_citation = FALSE;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (blockquote), "", NULL);
|
|
|
|
prev_br = html;
|
|
next_br = strstr (prev_br, "<br>");
|
|
|
|
/* Replace single spaces on the beginning of line, 2+ spaces and
|
|
* tabulators with non breaking spaces */
|
|
regex_nbsp = g_regex_new ("^\\s{1}|\\s{2,}|\x9", 0, 0, NULL);
|
|
|
|
while (next_br) {
|
|
gboolean local_ignore_next_br = ignore_next_br;
|
|
gboolean local_preserve_next_line = preserve_next_line;
|
|
gboolean preserve_block = TRY_TO_PRESERVE_BLOCKS;
|
|
const gchar *citation = NULL, *citation_end = NULL;
|
|
const gchar *rest = NULL, *with_br = NULL;
|
|
gchar *to_insert = NULL;
|
|
|
|
ignore_next_br = FALSE;
|
|
preserve_next_line = TRUE;
|
|
|
|
to_insert = g_utf8_substring (
|
|
prev_br, 0, g_utf8_pointer_to_offset (prev_br, next_br));
|
|
|
|
with_br = strstr (to_insert, "<br>");
|
|
citation = strstr (to_insert, "##CITATION_");
|
|
if (citation) {
|
|
gchar *citation_mark;
|
|
|
|
has_citation = TRUE;
|
|
if (strstr (citation, "END##")) {
|
|
ignore_next_br = TRUE;
|
|
if (paragraph)
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
}
|
|
|
|
citation_end = strstr (citation + 2, "##");
|
|
if (citation_end)
|
|
rest = citation_end + 2;
|
|
|
|
if (first_element)
|
|
citation_was_first_element = TRUE;
|
|
|
|
if (paragraph)
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
|
|
citation_mark = g_utf8_substring (
|
|
citation, 0, g_utf8_pointer_to_offset (citation, rest));
|
|
|
|
append_citation_mark (document, blockquote, citation_mark);
|
|
|
|
g_free (citation_mark);
|
|
} else
|
|
rest = with_br ?
|
|
to_insert + 4 + (with_br - to_insert) : to_insert;
|
|
|
|
if (!rest) {
|
|
preserve_next_line = FALSE;
|
|
goto next;
|
|
}
|
|
|
|
if (*rest) {
|
|
gboolean empty = FALSE;
|
|
gchar *truncated = g_strdup (rest);
|
|
gchar *rest_to_insert;
|
|
|
|
empty = !*truncated && strlen (rest) > 0;
|
|
|
|
if (strchr (" +-@*=\t;#", *rest))
|
|
preserve_block = FALSE;
|
|
|
|
rest_to_insert = g_regex_replace_eval (
|
|
regex_nbsp,
|
|
empty ? rest : truncated,
|
|
-1,
|
|
0,
|
|
0,
|
|
(GRegexEvalCallback) replace_to_nbsp,
|
|
NULL,
|
|
NULL);
|
|
g_free (truncated);
|
|
|
|
if (surround_links_with_anchor (rest_to_insert)) {
|
|
gboolean is_email_address =
|
|
strstr (rest_to_insert, "@") &&
|
|
!strstr (rest_to_insert, "://");
|
|
|
|
if (is_email_address && !regex_email)
|
|
regex_email = g_regex_new (E_MAIL_PATTERN, 0, 0, NULL);
|
|
if (!is_email_address && !regex_link)
|
|
regex_link = g_regex_new (URL_PATTERN, 0, 0, NULL);
|
|
|
|
truncated = g_regex_replace_eval (
|
|
is_email_address ? regex_email : regex_link,
|
|
rest_to_insert,
|
|
-1,
|
|
0,
|
|
0,
|
|
create_anchor_for_link,
|
|
NULL,
|
|
NULL);
|
|
|
|
g_free (rest_to_insert);
|
|
rest_to_insert = truncated;
|
|
}
|
|
|
|
if (g_strcmp0 (rest_to_insert, UNICODE_ZERO_WIDTH_SPACE) == 0) {
|
|
if (paragraph)
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
|
|
paragraph = create_and_append_new_paragraph (
|
|
selection, document, blockquote, block, "<br>");
|
|
} else if (preserve_block) {
|
|
gchar *html;
|
|
gchar *content_to_append;
|
|
|
|
if (!paragraph) {
|
|
if (!block || WEBKIT_DOM_IS_HTML_DIV_ELEMENT (block))
|
|
paragraph = e_html_editor_selection_get_paragraph_element (
|
|
selection, document, -1, 0);
|
|
else
|
|
paragraph = WEBKIT_DOM_ELEMENT (webkit_dom_node_clone_node (block, FALSE));
|
|
}
|
|
|
|
html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (paragraph));
|
|
|
|
content_to_append = g_strconcat (
|
|
html && *html ? " " : "",
|
|
rest_to_insert ? rest_to_insert : "<br>",
|
|
NULL),
|
|
|
|
webkit_dom_html_element_insert_adjacent_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (paragraph),
|
|
"beforeend",
|
|
content_to_append,
|
|
NULL);
|
|
|
|
g_free (html);
|
|
g_free (content_to_append);
|
|
} else {
|
|
if (paragraph)
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
|
|
paragraph = create_and_append_new_paragraph (
|
|
selection, document, blockquote, block, rest_to_insert);
|
|
}
|
|
|
|
if (rest_to_insert && *rest_to_insert && preserve_block && paragraph) {
|
|
glong length = 0;
|
|
|
|
/* If the line contains some encoded chracters (i.e. >)
|
|
* we can't use the strlen functions. */
|
|
if (strstr (rest_to_insert, "&"))
|
|
length = get_decoded_line_length (document, rest_to_insert);
|
|
else
|
|
length = g_utf8_strlen (rest_to_insert, -1);
|
|
|
|
/* End the block if there is line with less that 62 characters. */
|
|
/* The shorter line can also mean that there is a long word on next
|
|
* line (and the line was wrapped). So look at it and decide what to do. */
|
|
if (length < 62 && check_if_end_paragraph (next_br, length, local_preserve_next_line)) {
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
preserve_next_line = FALSE;
|
|
}
|
|
|
|
if (length > 72) {
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
preserve_next_line = FALSE;
|
|
}
|
|
}
|
|
|
|
citation_was_first_element = FALSE;
|
|
|
|
g_free (rest_to_insert);
|
|
} else if (with_br) {
|
|
if (!citation && (!local_ignore_next_br || citation_was_first_element)) {
|
|
if (paragraph)
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
|
|
paragraph = create_and_append_new_paragraph (
|
|
selection, document, blockquote, block, "<br>");
|
|
|
|
citation_was_first_element = FALSE;
|
|
} else if (first_element && !citation_was_first_element) {
|
|
paragraph = create_and_append_new_paragraph (
|
|
selection,
|
|
document,
|
|
blockquote,
|
|
block,
|
|
"<br class=\"-x-evo-first-br\">");
|
|
} else
|
|
preserve_next_line = FALSE;
|
|
} else
|
|
preserve_next_line = FALSE;
|
|
next:
|
|
first_element = FALSE;
|
|
prev_br = next_br;
|
|
next_br = strstr (prev_br + 4, "<br>");
|
|
g_free (to_insert);
|
|
}
|
|
|
|
if (paragraph)
|
|
append_new_paragraph (blockquote, ¶graph);
|
|
|
|
if (g_utf8_strlen (prev_br, -1) > 0) {
|
|
gchar *rest_to_insert;
|
|
gchar *truncated = g_strdup (
|
|
g_str_has_prefix (prev_br, "<br>") ? prev_br + 4 : prev_br);
|
|
|
|
/* On the end on the HTML there is always an extra BR element,
|
|
* so skip it and if there was another BR element before it mark it. */
|
|
if (truncated && !*truncated) {
|
|
WebKitDOMNode *child;
|
|
|
|
child = webkit_dom_node_get_last_child (
|
|
WEBKIT_DOM_NODE (blockquote));
|
|
if (child) {
|
|
child = webkit_dom_node_get_first_child (child);
|
|
if (child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)) {
|
|
element_add_class (
|
|
WEBKIT_DOM_ELEMENT (child),
|
|
"-x-evo-last-br");
|
|
}
|
|
}
|
|
g_free (truncated);
|
|
goto end;
|
|
}
|
|
|
|
if (g_ascii_strncasecmp (truncated, "##CITATION_END##", 16) == 0) {
|
|
append_citation_mark (document, blockquote, truncated);
|
|
g_free (truncated);
|
|
goto end;
|
|
}
|
|
|
|
rest_to_insert = g_regex_replace_eval (
|
|
regex_nbsp,
|
|
truncated,
|
|
-1,
|
|
0,
|
|
0,
|
|
(GRegexEvalCallback) replace_to_nbsp,
|
|
NULL,
|
|
NULL);
|
|
g_free (truncated);
|
|
|
|
if (surround_links_with_anchor (rest_to_insert)) {
|
|
gboolean is_email_address =
|
|
strstr (rest_to_insert, "@") &&
|
|
!strstr (rest_to_insert, "://");
|
|
|
|
if (is_email_address && !regex_email)
|
|
regex_email = g_regex_new (E_MAIL_PATTERN, 0, 0, NULL);
|
|
if (!is_email_address && !regex_link)
|
|
regex_link = g_regex_new (URL_PATTERN, 0, 0, NULL);
|
|
|
|
truncated = g_regex_replace_eval (
|
|
is_email_address ? regex_email : regex_link,
|
|
rest_to_insert,
|
|
-1,
|
|
0,
|
|
0,
|
|
create_anchor_for_link,
|
|
NULL,
|
|
NULL);
|
|
|
|
g_free (rest_to_insert);
|
|
rest_to_insert = truncated;
|
|
}
|
|
|
|
if (g_strcmp0 (rest_to_insert, UNICODE_ZERO_WIDTH_SPACE) == 0)
|
|
create_and_append_new_paragraph (
|
|
selection, document, blockquote, block, "<br>");
|
|
else
|
|
create_and_append_new_paragraph (
|
|
selection, document, blockquote, block, rest_to_insert);
|
|
|
|
g_free (rest_to_insert);
|
|
}
|
|
|
|
end:
|
|
if (has_citation) {
|
|
gchar *inner_html;
|
|
GString *start, *end;
|
|
|
|
/* Replace text markers with actual HTML blockquotes */
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (blockquote));
|
|
start = e_str_replace_string (
|
|
inner_html, "##CITATION_START##","<blockquote type=\"cite\">");
|
|
end = e_str_replace_string (
|
|
start->str, "##CITATION_END##", "</blockquote>");
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (blockquote), end->str, NULL);
|
|
|
|
g_free (inner_html);
|
|
g_string_free (start, TRUE);
|
|
g_string_free (end, TRUE);
|
|
}
|
|
|
|
if (regex_email != NULL)
|
|
g_regex_unref (regex_email);
|
|
if (regex_link != NULL)
|
|
g_regex_unref (regex_link);
|
|
g_regex_unref (regex_nbsp);
|
|
}
|
|
|
|
static void
|
|
mark_citation (WebKitDOMElement *citation)
|
|
{
|
|
gchar *inner_html, *surrounded;
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (citation));
|
|
|
|
surrounded = g_strconcat (
|
|
"<span>##CITATION_START##</span>", inner_html,
|
|
"<span>##CITATION_END##</span>", NULL);
|
|
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (citation), surrounded, NULL);
|
|
|
|
element_add_class (citation, "marked");
|
|
|
|
g_free (inner_html);
|
|
g_free (surrounded);
|
|
}
|
|
|
|
static gint
|
|
create_text_markers_for_citations_in_document (WebKitDOMDocument *document)
|
|
{
|
|
gint count = 0;
|
|
WebKitDOMElement *citation;
|
|
|
|
citation = webkit_dom_document_query_selector (
|
|
document, "blockquote[type=cite]:not(.marked)", NULL);
|
|
|
|
while (citation) {
|
|
mark_citation (citation);
|
|
count ++;
|
|
|
|
citation = webkit_dom_document_query_selector (
|
|
document, "blockquote[type=cite]:not(.marked)", NULL);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static gint
|
|
create_text_markers_for_citations_in_element (WebKitDOMElement *element)
|
|
{
|
|
gint count = 0;
|
|
WebKitDOMElement *citation;
|
|
|
|
citation = webkit_dom_element_query_selector (
|
|
element, "blockquote[type=cite]:not(.marked)", NULL);
|
|
|
|
while (citation) {
|
|
mark_citation (citation);
|
|
count ++;
|
|
|
|
citation = webkit_dom_element_query_selector (
|
|
element, "blockquote[type=cite]:not(.marked)", NULL);
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static void
|
|
quote_plain_text_elements_after_wrapping_in_document (WebKitDOMDocument *document)
|
|
{
|
|
gint length, ii;
|
|
WebKitDOMNodeList *list;
|
|
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "blockquote[type=cite] > div.-x-evo-paragraph", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
gint citation_level;
|
|
WebKitDOMNode *child;
|
|
|
|
child = webkit_dom_node_list_item (list, ii);
|
|
citation_level = get_citation_level (child, TRUE);
|
|
quote_plain_text_element_after_wrapping (
|
|
document, WEBKIT_DOM_ELEMENT (child), citation_level);
|
|
g_object_unref (child);
|
|
}
|
|
g_object_unref (list);
|
|
}
|
|
|
|
static void
|
|
clear_attributes (WebKitDOMDocument *document)
|
|
{
|
|
gint length, ii;
|
|
WebKitDOMNamedNodeMap *attributes;
|
|
WebKitDOMHTMLElement *body = webkit_dom_document_get_body (document);
|
|
WebKitDOMHTMLHeadElement *head = webkit_dom_document_get_head (document);
|
|
WebKitDOMElement *document_element =
|
|
webkit_dom_document_get_document_element (document);
|
|
|
|
/* Remove all attributes from HTML element */
|
|
attributes = webkit_dom_element_get_attributes (document_element);
|
|
length = webkit_dom_named_node_map_get_length (attributes);
|
|
for (ii = length - 1; ii >= 0; ii--) {
|
|
WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
|
|
|
|
webkit_dom_element_remove_attribute_node (
|
|
document_element, WEBKIT_DOM_ATTR (node), NULL);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (attributes);
|
|
|
|
/* Remove everything from HEAD element */
|
|
while (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)))
|
|
remove_node (webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (head)));
|
|
|
|
/* Make the quote marks non-selectable. */
|
|
disable_quote_marks_select (document);
|
|
|
|
/* Remove non Evolution attributes from BODY element */
|
|
attributes = webkit_dom_element_get_attributes (WEBKIT_DOM_ELEMENT (body));
|
|
length = webkit_dom_named_node_map_get_length (attributes);
|
|
for (ii = length - 1; ii >= 0; ii--) {
|
|
gchar *name;
|
|
WebKitDOMNode *node = webkit_dom_named_node_map_item (attributes, ii);
|
|
|
|
name = webkit_dom_node_get_local_name (node);
|
|
|
|
if (!g_str_has_prefix (name, "data-") && (g_strcmp0 (name, "spellcheck") != 0))
|
|
webkit_dom_element_remove_attribute_node (
|
|
WEBKIT_DOM_ELEMENT (body),
|
|
WEBKIT_DOM_ATTR (node),
|
|
NULL);
|
|
|
|
g_free (name);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (attributes);
|
|
}
|
|
|
|
static void
|
|
register_html_events_handlers (EHTMLEditorView *view,
|
|
WebKitDOMHTMLElement *body)
|
|
{
|
|
webkit_dom_event_target_add_event_listener (
|
|
WEBKIT_DOM_EVENT_TARGET (body),
|
|
"keydown",
|
|
G_CALLBACK (body_keydown_event_cb),
|
|
FALSE,
|
|
view);
|
|
|
|
webkit_dom_event_target_add_event_listener (
|
|
WEBKIT_DOM_EVENT_TARGET (body),
|
|
"keypress",
|
|
G_CALLBACK (body_keypress_event_cb),
|
|
FALSE,
|
|
view);
|
|
|
|
webkit_dom_event_target_add_event_listener (
|
|
WEBKIT_DOM_EVENT_TARGET (body),
|
|
"keyup",
|
|
G_CALLBACK (body_keyup_event_cb),
|
|
FALSE,
|
|
view);
|
|
}
|
|
|
|
static void
|
|
html_editor_convert_view_content (EHTMLEditorView *view,
|
|
const gchar *preferred_text)
|
|
{
|
|
EHTMLEditorSelection *selection = e_html_editor_view_get_selection (view);
|
|
gboolean start_bottom, empty = FALSE;
|
|
gchar *inner_html;
|
|
gint ii, length;
|
|
GSettings *settings;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *paragraph, *content_wrapper, *top_signature;
|
|
WebKitDOMElement *cite_body, *signature, *wrapper;
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMNodeList *list;
|
|
WebKitDOMNode *node;
|
|
|
|
settings = e_util_ref_settings ("org.gnome.evolution.mail");
|
|
start_bottom = g_settings_get_boolean (settings, "composer-reply-start-bottom");
|
|
g_object_unref (settings);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = webkit_dom_document_get_body (document);
|
|
/* Wrapper that will represent the new body. */
|
|
wrapper = webkit_dom_document_create_element (document, "div", NULL);
|
|
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);
|
|
|
|
cite_body = webkit_dom_document_query_selector (
|
|
document, "span.-x-evo-cite-body", NULL);
|
|
|
|
/* content_wrapper when the processed text will be placed. */
|
|
content_wrapper = webkit_dom_document_create_element (
|
|
document, cite_body ? "blockquote" : "div", NULL);
|
|
if (cite_body) {
|
|
webkit_dom_element_set_attribute (content_wrapper, "type", "cite", NULL);
|
|
webkit_dom_element_set_attribute (content_wrapper, "id", "-x-evo-main-cite", NULL);
|
|
}
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (wrapper), WEBKIT_DOM_NODE (content_wrapper), NULL);
|
|
|
|
/* Remove all previously inserted paragraphs. */
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, ".-x-evo-paragraph", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
/* Insert the paragraph where the caret will be. */
|
|
paragraph = prepare_paragraph (selection, document, TRUE);
|
|
webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
WEBKIT_DOM_NODE (paragraph),
|
|
start_bottom ?
|
|
webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (content_wrapper)) :
|
|
WEBKIT_DOM_NODE (content_wrapper),
|
|
NULL);
|
|
|
|
/* Insert signature (if presented) to the right position. */
|
|
top_signature = webkit_dom_document_query_selector (
|
|
document, ".-x-evo-top-signature", NULL);
|
|
signature = webkit_dom_document_query_selector (
|
|
document, ".-x-evo-signature-content_wrapper", NULL);
|
|
if (signature) {
|
|
if (top_signature) {
|
|
WebKitDOMElement *spacer;
|
|
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
WEBKIT_DOM_NODE (signature),
|
|
start_bottom ?
|
|
WEBKIT_DOM_NODE (content_wrapper) :
|
|
webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (paragraph)),
|
|
NULL);
|
|
/* Insert NL after the signature */
|
|
spacer = prepare_paragraph (selection, document, FALSE);
|
|
element_add_class (spacer, "-x-evo-top-signature-spacer");
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
WEBKIT_DOM_NODE (spacer),
|
|
webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (signature)),
|
|
NULL);
|
|
} else {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
WEBKIT_DOM_NODE (signature),
|
|
webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (
|
|
start_bottom ? paragraph : content_wrapper)),
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
/* Move credits to the body */
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "span.-x-evo-to-body[data-credits]", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
char *credits;
|
|
WebKitDOMElement *pre_element;
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
|
|
pre_element = webkit_dom_document_create_element (document, "pre", NULL);
|
|
credits = webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (node), "data-credits");
|
|
webkit_dom_html_element_set_inner_text (WEBKIT_DOM_HTML_ELEMENT (pre_element), credits, NULL);
|
|
g_free (credits);
|
|
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
WEBKIT_DOM_NODE (pre_element),
|
|
WEBKIT_DOM_NODE (content_wrapper),
|
|
NULL);
|
|
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
/* Move headers to body */
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "span.-x-evo-to-body[data-headers]", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node, *child;
|
|
|
|
node = webkit_dom_node_list_item (list, ii);
|
|
while ((child = webkit_dom_node_get_first_child (node))) {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (wrapper),
|
|
child,
|
|
WEBKIT_DOM_NODE (content_wrapper),
|
|
NULL);
|
|
}
|
|
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
repair_gmail_blockquotes (document);
|
|
create_text_markers_for_citations_in_document (document);
|
|
|
|
if (preferred_text && *preferred_text)
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (content_wrapper), preferred_text, NULL);
|
|
else {
|
|
gchar *inner_text;
|
|
|
|
inner_text = webkit_dom_html_element_get_inner_text (body);
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (content_wrapper), inner_text, NULL);
|
|
|
|
g_free (inner_text);
|
|
}
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (content_wrapper));
|
|
|
|
/* Replace the old body with the new one. */
|
|
node = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), FALSE);
|
|
webkit_dom_node_replace_child (
|
|
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (body)),
|
|
node,
|
|
WEBKIT_DOM_NODE (body),
|
|
NULL);
|
|
body = WEBKIT_DOM_HTML_ELEMENT (node);
|
|
|
|
/* Copy all to nodes to the new body. */
|
|
while ((node = webkit_dom_node_get_last_child (WEBKIT_DOM_NODE (wrapper)))) {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (body),
|
|
WEBKIT_DOM_NODE (node),
|
|
webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body)),
|
|
NULL);
|
|
}
|
|
remove_node (WEBKIT_DOM_NODE (wrapper));
|
|
|
|
if (inner_html && !*inner_html)
|
|
empty = TRUE;
|
|
|
|
length = webkit_dom_element_get_child_element_count (WEBKIT_DOM_ELEMENT (body));
|
|
if (length <= 1) {
|
|
empty = TRUE;
|
|
if (length == 1) {
|
|
WebKitDOMNode *child;
|
|
|
|
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
|
|
empty = child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child);
|
|
}
|
|
}
|
|
|
|
if (preferred_text && *preferred_text)
|
|
empty = FALSE;
|
|
|
|
if (!empty)
|
|
parse_html_into_paragraphs (view, document, content_wrapper, NULL, inner_html);
|
|
else
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (content_wrapper),
|
|
WEBKIT_DOM_NODE (prepare_paragraph (selection, document, FALSE)),
|
|
NULL);
|
|
|
|
if (!cite_body) {
|
|
if (!empty) {
|
|
WebKitDOMNode *child;
|
|
|
|
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (content_wrapper)))) {
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (content_wrapper)),
|
|
child,
|
|
WEBKIT_DOM_NODE (content_wrapper),
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
remove_node (WEBKIT_DOM_NODE (content_wrapper));
|
|
}
|
|
|
|
if (view->priv->is_message_from_edit_as_new || view->priv->remove_initial_input_line || !start_bottom) {
|
|
WebKitDOMNode *child;
|
|
|
|
remove_node (WEBKIT_DOM_NODE (paragraph));
|
|
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body));
|
|
if (child)
|
|
add_selection_markers_into_element_start (
|
|
document, WEBKIT_DOM_ELEMENT (child), NULL, NULL);
|
|
}
|
|
|
|
paragraph = webkit_dom_document_query_selector (document, "br.-x-evo-last-br", NULL);
|
|
if (paragraph)
|
|
webkit_dom_element_remove_attribute (paragraph, "class");
|
|
paragraph = webkit_dom_document_query_selector (document, "br.-x-evo-first-br", NULL);
|
|
if (paragraph)
|
|
webkit_dom_element_remove_attribute (paragraph, "class");
|
|
|
|
if (!e_html_editor_view_get_html_mode (view)) {
|
|
e_html_editor_selection_wrap_paragraphs_in_document (
|
|
selection, document);
|
|
|
|
quote_plain_text_elements_after_wrapping_in_document (document);
|
|
}
|
|
|
|
clear_attributes (document);
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
e_html_editor_view_force_spell_check (view);
|
|
|
|
/* Register on input event that is called when the content (body) is modified */
|
|
webkit_dom_event_target_add_event_listener (
|
|
WEBKIT_DOM_EVENT_TARGET (body),
|
|
"input",
|
|
G_CALLBACK (body_input_event_cb),
|
|
FALSE,
|
|
view);
|
|
|
|
register_html_events_handlers (view, body);
|
|
|
|
g_free (inner_html);
|
|
}
|
|
|
|
static void
|
|
fix_structure_after_pasting_multiline_content (WebKitDOMNode *node)
|
|
{
|
|
WebKitDOMNode *first_child, *parent;
|
|
|
|
/* When pasting content that does not contain just the
|
|
* one line text WebKit inserts all the content after the
|
|
* first line into one element. So we have to take it out
|
|
* of this element and insert it after that element. */
|
|
parent = webkit_dom_node_get_parent_node (node);
|
|
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent))
|
|
return;
|
|
first_child = webkit_dom_node_get_first_child (parent);
|
|
while (first_child) {
|
|
WebKitDOMNode *next_child =
|
|
webkit_dom_node_get_next_sibling (first_child);
|
|
if (webkit_dom_node_has_child_nodes (first_child))
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
first_child,
|
|
parent,
|
|
NULL);
|
|
first_child = next_child;
|
|
}
|
|
remove_node (parent);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_insert_converted_html_into_selection (EHTMLEditorView *view,
|
|
gboolean is_html,
|
|
const gchar *html)
|
|
{
|
|
EHTMLEditorSelection *selection = e_html_editor_view_get_selection (view);
|
|
gboolean has_selection;
|
|
gchar *inner_html;
|
|
gint citation_level;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *selection_start_marker, *selection_end_marker, *element;
|
|
WebKitDOMNode *node;
|
|
WebKitDOMNode *current_block;
|
|
|
|
remove_input_event_listener_from_body (view);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
e_html_editor_selection_save (selection);
|
|
selection_start_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
selection_end_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-end-marker");
|
|
current_block = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (current_block))
|
|
current_block = NULL;
|
|
|
|
element = webkit_dom_document_create_element (document, "div", NULL);
|
|
if (is_html) {
|
|
gchar *inner_text;
|
|
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (element), html, NULL);
|
|
inner_text = webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (element));
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (element), inner_text, NULL);
|
|
|
|
g_free (inner_text);
|
|
} else
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (element), html, NULL);
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (element));
|
|
parse_html_into_paragraphs (view, document, element, current_block, inner_html);
|
|
|
|
g_free (inner_html);
|
|
|
|
has_selection = !e_html_editor_selection_is_collapsed (selection);
|
|
|
|
citation_level = get_citation_level (WEBKIT_DOM_NODE (selection_end_marker), FALSE);
|
|
/* Pasting into the citation */
|
|
if (citation_level > 0) {
|
|
gint length;
|
|
gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (selection);
|
|
WebKitDOMElement *br;
|
|
WebKitDOMNode *first_paragraph, *last_paragraph;
|
|
WebKitDOMNode *child, *parent;
|
|
|
|
first_paragraph = webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (element));
|
|
last_paragraph = webkit_dom_node_get_last_child (
|
|
WEBKIT_DOM_NODE (element));
|
|
|
|
length = word_wrap_length - 2 * citation_level;
|
|
|
|
/* Pasting text that was parsed just into one paragraph */
|
|
if (webkit_dom_node_is_same_node (first_paragraph, last_paragraph)) {
|
|
WebKitDOMNode *child, *parent;
|
|
|
|
parent = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
|
|
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
|
|
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
|
|
|
|
while ((child = webkit_dom_node_get_first_child (first_paragraph)))
|
|
webkit_dom_node_insert_before (
|
|
parent,
|
|
child,
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
NULL);
|
|
|
|
parent = WEBKIT_DOM_NODE (
|
|
e_html_editor_selection_wrap_paragraph_length (
|
|
selection, WEBKIT_DOM_ELEMENT (parent), length));
|
|
webkit_dom_node_normalize (parent);
|
|
quote_plain_text_element_after_wrapping (
|
|
document, WEBKIT_DOM_ELEMENT (parent), citation_level);
|
|
|
|
goto delete;
|
|
}
|
|
|
|
/* Pasting content parsed into the multiple paragraphs */
|
|
parent = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
|
|
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
|
|
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
|
|
|
|
/* Move the elements from the first paragraph before the selection start element */
|
|
while ((child = webkit_dom_node_get_first_child (first_paragraph)))
|
|
webkit_dom_node_insert_before (
|
|
parent,
|
|
child,
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
NULL);
|
|
|
|
remove_node (first_paragraph);
|
|
|
|
/* If the BR element is on the last position, remove it as we don't need it */
|
|
child = webkit_dom_node_get_last_child (parent);
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child))
|
|
remove_node (child);
|
|
|
|
parent = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
|
|
child = webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
/* Move the elements that are in the same paragraph as the selection end
|
|
* on the end of pasted text, but avoid BR on the end of paragraph */
|
|
while (child) {
|
|
WebKitDOMNode *next_child =
|
|
webkit_dom_node_get_next_sibling (child);
|
|
if (!(!next_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)))
|
|
webkit_dom_node_append_child (last_paragraph, child, NULL);
|
|
child = next_child;
|
|
}
|
|
|
|
/* Caret will be restored on the end of pasted text */
|
|
webkit_dom_node_append_child (
|
|
last_paragraph,
|
|
WEBKIT_DOM_NODE (create_selection_marker (document, TRUE)),
|
|
NULL);
|
|
|
|
webkit_dom_node_append_child (
|
|
last_paragraph,
|
|
WEBKIT_DOM_NODE (create_selection_marker (document, FALSE)),
|
|
NULL);
|
|
|
|
/* Insert the paragraph with the end of the pasted text after
|
|
* the paragraph that contains the selection end */
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (parent),
|
|
last_paragraph,
|
|
webkit_dom_node_get_next_sibling (parent),
|
|
NULL);
|
|
|
|
/* Wrap, quote and move all paragraphs from pasted text into the body */
|
|
while ((child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element)))) {
|
|
child = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
|
|
selection, WEBKIT_DOM_ELEMENT (child), length));
|
|
quote_plain_text_element_after_wrapping (
|
|
document, WEBKIT_DOM_ELEMENT (child), citation_level);
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (last_paragraph),
|
|
child,
|
|
last_paragraph,
|
|
NULL);
|
|
}
|
|
|
|
webkit_dom_node_normalize (last_paragraph);
|
|
|
|
last_paragraph = WEBKIT_DOM_NODE (
|
|
e_html_editor_selection_wrap_paragraph_length (
|
|
selection, WEBKIT_DOM_ELEMENT (last_paragraph), length));
|
|
quote_plain_text_element_after_wrapping (
|
|
document, WEBKIT_DOM_ELEMENT (last_paragraph), citation_level);
|
|
|
|
remove_quoting_from_element (WEBKIT_DOM_ELEMENT (parent));
|
|
remove_wrapping_from_element (WEBKIT_DOM_ELEMENT (parent));
|
|
|
|
parent = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
parent = WEBKIT_DOM_NODE (e_html_editor_selection_wrap_paragraph_length (
|
|
selection, WEBKIT_DOM_ELEMENT (parent), length));
|
|
quote_plain_text_element_after_wrapping (
|
|
document, WEBKIT_DOM_ELEMENT (parent), citation_level);
|
|
|
|
/* If the pasted text begun or ended with a new line we have to
|
|
* quote these paragraphs as well */
|
|
br = webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (last_paragraph), "br.-x-evo-last-br", NULL);
|
|
if (br) {
|
|
WebKitDOMNode *parent;
|
|
|
|
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br));
|
|
quote_plain_text_recursive (document, parent, parent, citation_level);
|
|
webkit_dom_element_remove_attribute (br, "class");
|
|
}
|
|
|
|
br = webkit_dom_document_query_selector (
|
|
document, "* > br.-x-evo-first-br", NULL);
|
|
if (br) {
|
|
WebKitDOMNode *parent;
|
|
|
|
parent = webkit_dom_node_get_parent_node (WEBKIT_DOM_NODE (br));
|
|
quote_plain_text_recursive (document, parent, parent, citation_level);
|
|
webkit_dom_element_remove_attribute (br, "class");
|
|
}
|
|
delete:
|
|
e_html_editor_selection_restore (selection);
|
|
/* Remove the text that was meant to be replaced by the pasted text */
|
|
if (has_selection)
|
|
e_html_editor_view_exec_command (
|
|
view, E_HTML_EDITOR_VIEW_COMMAND_DELETE, NULL);
|
|
|
|
g_object_unref (element);
|
|
goto out;
|
|
}
|
|
|
|
remove_node (WEBKIT_DOM_NODE (selection_start_marker));
|
|
remove_node (WEBKIT_DOM_NODE (selection_end_marker));
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (element));
|
|
e_html_editor_view_exec_command (
|
|
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, inner_html);
|
|
g_free (inner_html);
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (element));
|
|
if (g_str_has_suffix (inner_html, " ")) {
|
|
e_html_editor_view_exec_command (
|
|
view, E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, " ");
|
|
}
|
|
g_free (inner_html);
|
|
|
|
g_object_unref (element);
|
|
e_html_editor_selection_save (selection);
|
|
|
|
element = webkit_dom_document_query_selector (
|
|
document, "* > br.-x-evo-first-br", NULL);
|
|
if (element) {
|
|
WebKitDOMNode *next_sibling;
|
|
WebKitDOMNode *parent;
|
|
|
|
parent = webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (element));
|
|
|
|
next_sibling = webkit_dom_node_get_next_sibling (parent);
|
|
if (next_sibling)
|
|
remove_node (WEBKIT_DOM_NODE (parent));
|
|
else
|
|
webkit_dom_element_remove_attribute (element, "class");
|
|
}
|
|
|
|
element = webkit_dom_document_query_selector (
|
|
document, "* > br.-x-evo-last-br", NULL);
|
|
if (element) {
|
|
WebKitDOMNode *parent;
|
|
WebKitDOMNode *child;
|
|
|
|
parent = webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (element));
|
|
|
|
node = webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent));
|
|
if (node) {
|
|
node = webkit_dom_node_get_first_child (node);
|
|
if (node) {
|
|
inner_html = webkit_dom_node_get_text_content (node);
|
|
if (g_str_has_prefix (inner_html, UNICODE_NBSP))
|
|
webkit_dom_character_data_replace_data (
|
|
WEBKIT_DOM_CHARACTER_DATA (node), 0, 1, "", NULL);
|
|
g_free (inner_html);
|
|
}
|
|
}
|
|
|
|
selection_end_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-end-marker");
|
|
|
|
if (has_selection) {
|
|
/* Everything after the selection end marker have to be in separate
|
|
* paragraph */
|
|
child = webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (selection_end_marker));
|
|
/* Move the elements that are in the same paragraph as the selection end
|
|
* on the end of pasted text, but avoid BR on the end of paragraph */
|
|
while (child) {
|
|
WebKitDOMNode *next_child =
|
|
webkit_dom_node_get_next_sibling (child);
|
|
if (!(!next_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)))
|
|
webkit_dom_node_append_child (parent, child, NULL);
|
|
child = next_child;
|
|
}
|
|
|
|
remove_node (WEBKIT_DOM_NODE (element));
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (
|
|
webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (selection_end_marker))),
|
|
parent,
|
|
webkit_dom_node_get_next_sibling (
|
|
webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (selection_end_marker))),
|
|
NULL);
|
|
node = parent;
|
|
} else {
|
|
node = webkit_dom_node_get_next_sibling (parent);
|
|
if (!node)
|
|
fix_structure_after_pasting_multiline_content (parent);
|
|
}
|
|
|
|
if (node) {
|
|
/* Restore caret on the end of pasted text */
|
|
webkit_dom_node_insert_before (
|
|
node,
|
|
WEBKIT_DOM_NODE (selection_end_marker),
|
|
webkit_dom_node_get_first_child (node),
|
|
NULL);
|
|
|
|
selection_start_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
webkit_dom_node_insert_before (
|
|
node,
|
|
WEBKIT_DOM_NODE (selection_start_marker),
|
|
webkit_dom_node_get_first_child (node),
|
|
NULL);
|
|
}
|
|
|
|
if (element)
|
|
webkit_dom_element_remove_attribute (element, "class");
|
|
|
|
if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (parent)) && !has_selection)
|
|
remove_node (parent);
|
|
} else {
|
|
/* When pasting the content that was copied from the composer, WebKit
|
|
* restores the selection wrongly, thus is saved wrongly and we have
|
|
* to fix it */
|
|
WebKitDOMNode *paragraph, *parent, *clone1, *clone2;
|
|
|
|
selection_start_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-start-marker");
|
|
selection_end_marker = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-selection-end-marker");
|
|
|
|
paragraph = get_parent_block_node_from_child (
|
|
WEBKIT_DOM_NODE (selection_start_marker));
|
|
parent = webkit_dom_node_get_parent_node (paragraph);
|
|
webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (parent), "id");
|
|
|
|
/* Check if WebKit created wrong structure */
|
|
clone1 = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (paragraph), FALSE);
|
|
clone2 = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (parent), FALSE);
|
|
if (webkit_dom_node_is_equal_node (clone1, clone2))
|
|
fix_structure_after_pasting_multiline_content (paragraph);
|
|
|
|
g_object_unref (clone1);
|
|
g_object_unref (clone2);
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (selection_start_marker)),
|
|
WEBKIT_DOM_NODE (selection_end_marker),
|
|
webkit_dom_node_get_next_sibling (
|
|
WEBKIT_DOM_NODE (selection_start_marker)),
|
|
NULL);
|
|
}
|
|
|
|
e_html_editor_selection_restore (selection);
|
|
out:
|
|
e_html_editor_view_force_spell_check (view);
|
|
e_html_editor_selection_scroll_to_caret (selection);
|
|
|
|
register_input_event_listener_on_body (view);
|
|
}
|
|
|
|
static void
|
|
e_html_editor_settings_changed_cb (GSettings *settings,
|
|
const gchar *key,
|
|
EHTMLEditorView *view)
|
|
{
|
|
GVariant *new_value, *old_value;
|
|
|
|
new_value = g_settings_get_value (settings, key);
|
|
old_value = g_hash_table_lookup (view->priv->old_settings, key);
|
|
|
|
if (!new_value || !old_value || !g_variant_equal (new_value, old_value)) {
|
|
if (new_value)
|
|
g_hash_table_insert (view->priv->old_settings, g_strdup (key), new_value);
|
|
else
|
|
g_hash_table_remove (view->priv->old_settings, key);
|
|
|
|
e_html_editor_view_update_fonts (view);
|
|
} else if (new_value) {
|
|
g_variant_unref (new_value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_new:
|
|
*
|
|
* Returns a new instance of the editor.
|
|
*
|
|
* Returns: A newly created #EHTMLEditorView. [transfer-full]
|
|
*/
|
|
EHTMLEditorView *
|
|
e_html_editor_view_new (void)
|
|
{
|
|
return g_object_new (E_TYPE_HTML_EDITOR_VIEW, NULL);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_selection:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Returns an #EHTMLEditorSelection object which represents current selection or
|
|
* cursor position within the editor document. The #EHTMLEditorSelection allows
|
|
* programmer to manipulate with formatting, selection, styles etc.
|
|
*
|
|
* Returns: An always valid #EHTMLEditorSelection object. The object is owned by
|
|
* the @view and should never be free'd.
|
|
*/
|
|
EHTMLEditorSelection *
|
|
e_html_editor_view_get_selection (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);
|
|
|
|
return view->priv->selection;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_exec_command:
|
|
* @view: an #EHTMLEditorView
|
|
* @command: an #EHTMLEditorViewCommand to execute
|
|
* @value: value of the command (or @NULL if the command does not require value)
|
|
*
|
|
* The function will fail when @value is @NULL or empty but the current @command
|
|
* requires a value to be passed. The @value is ignored when the @command does
|
|
* not expect any value.
|
|
*
|
|
* Returns: @TRUE when the command was succesfully executed, @FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
e_html_editor_view_exec_command (EHTMLEditorView *view,
|
|
EHTMLEditorViewCommand command,
|
|
const gchar *value)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
const gchar *cmd_str = 0;
|
|
gboolean has_value;
|
|
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
#define CHECK_COMMAND(cmd,str,val) case cmd:\
|
|
if (val) {\
|
|
g_return_val_if_fail (value && *value, FALSE);\
|
|
}\
|
|
has_value = val; \
|
|
cmd_str = str;\
|
|
break;
|
|
|
|
switch (command) {
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BACKGROUND_COLOR, "BackColor", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_BOLD, "Bold", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_COPY, "Copy", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CREATE_LINK, "CreateLink", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_CUT, "Cut", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DEFAULT_PARAGRAPH_SEPARATOR, "DefaultParagraphSeparator", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_DELETE, "Delete", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FIND_STRING, "FindString", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_NAME, "FontName", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE, "FontSize", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FONT_SIZE_DELTA, "FontSizeDelta", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORE_COLOR, "ForeColor", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORMAT_BLOCK, "FormatBlock", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_FORWARD_DELETE, "ForwardDelete", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_HILITE_COLOR, "HiliteColor", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INDENT, "Indent", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HORIZONTAL_RULE, "InsertHorizontalRule", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_HTML, "InsertHTML", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_IMAGE, "InsertImage", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_LINE_BREAK, "InsertLineBreak", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_NEW_LINE_IN_QUOTED_CONTENT, "InsertNewlineInQuotedContent", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_ORDERED_LIST, "InsertOrderedList", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_PARAGRAPH, "InsertParagraph", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_TEXT, "InsertText", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_INSERT_UNORDERED_LIST, "InsertUnorderedList", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_ITALIC, "Italic", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_CENTER, "JustifyCenter", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_FULL, "JustifyFull", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_LEFT, "JustifyLeft", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_NONE, "JustifyNone", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_JUSTIFY_RIGHT, "JustifyRight", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_OUTDENT, "Outdent", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE, "Paste", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AND_MATCH_STYLE, "PasteAndMatchStyle", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PASTE_AS_PLAIN_TEXT, "PasteAsPlainText", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_PRINT, "Print", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REDO, "Redo", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_REMOVE_FORMAT, "RemoveFormat", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SELECT_ALL, "SelectAll", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STRIKETHROUGH, "Strikethrough", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "StyleWithCSS", TRUE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUBSCRIPT, "Subscript", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_SUPERSCRIPT, "Superscript", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_TRANSPOSE, "Transpose", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDERLINE, "Underline", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNDO, "Undo", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNLINK, "Unlink", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_UNSELECT, "Unselect", FALSE)
|
|
CHECK_COMMAND (E_HTML_EDITOR_VIEW_COMMAND_USE_CSS, "UseCSS", TRUE)
|
|
}
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
return webkit_dom_document_exec_command (
|
|
document, cmd_str, FALSE, has_value ? value : "" );
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_changed:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Whether content of the editor has been changed.
|
|
*
|
|
* Returns: @TRUE when document was changed, @FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
e_html_editor_view_get_changed (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->changed;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_changed:
|
|
* @view: an #EHTMLEditorView
|
|
* @changed: whether document has been changed or not
|
|
*
|
|
* Sets whether document has been changed or not. The editor is tracking changes
|
|
* automatically, but sometimes it's necessary to change the dirty flag to force
|
|
* "Save changes" dialog for example.
|
|
*/
|
|
void
|
|
e_html_editor_view_set_changed (EHTMLEditorView *view,
|
|
gboolean changed)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
if (view->priv->changed == changed)
|
|
return;
|
|
|
|
view->priv->changed = changed;
|
|
|
|
g_object_notify (G_OBJECT (view), "changed");
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_html_mode:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Whether the editor is in HTML mode or plain text mode. In HTML mode,
|
|
* more formatting options are avilable an the email is sent as
|
|
* multipart/alternative.
|
|
*
|
|
* Returns: @TRUE when HTML mode is enabled, @FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
e_html_editor_view_get_html_mode (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->html_mode;
|
|
}
|
|
|
|
static gint
|
|
get_indentation_level (WebKitDOMElement *element)
|
|
{
|
|
WebKitDOMElement *parent;
|
|
gint level = 1;
|
|
|
|
parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (element));
|
|
/* Count level of indentation */
|
|
while (!WEBKIT_DOM_IS_HTML_BODY_ELEMENT (parent)) {
|
|
if (element_has_class (parent, "-x-evo-indented"))
|
|
level++;
|
|
|
|
parent = webkit_dom_node_get_parent_element (WEBKIT_DOM_NODE (parent));
|
|
}
|
|
|
|
return level;
|
|
}
|
|
|
|
static void
|
|
process_blockquote (WebKitDOMElement *blockquote)
|
|
{
|
|
WebKitDOMNodeList *list;
|
|
int jj, length;
|
|
|
|
/* First replace wrappers */
|
|
list = webkit_dom_element_query_selector_all (
|
|
blockquote, "span.-x-evo-temp-text-wrapper", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (jj = 0; jj < length; jj++) {
|
|
WebKitDOMNode *quoted_node;
|
|
gchar *text_content;
|
|
|
|
quoted_node = webkit_dom_node_list_item (list, jj);
|
|
text_content = webkit_dom_node_get_text_content (quoted_node);
|
|
webkit_dom_html_element_set_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);
|
|
|
|
g_free (text_content);
|
|
g_object_unref (quoted_node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
/* Afterwards replace quote nodes with symbols */
|
|
list = webkit_dom_element_query_selector_all (
|
|
blockquote, "span.-x-evo-quoted", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (jj = 0; jj < length; jj++) {
|
|
WebKitDOMNode *quoted_node;
|
|
gchar *text_content;
|
|
|
|
quoted_node = webkit_dom_node_list_item (list, jj);
|
|
text_content = webkit_dom_node_get_text_content (quoted_node);
|
|
webkit_dom_html_element_set_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (quoted_node), text_content, NULL);
|
|
|
|
g_free (text_content);
|
|
g_object_unref (quoted_node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
if (element_has_class (blockquote, "-x-evo-indented")) {
|
|
WebKitDOMNode *child;
|
|
gchar *spaces;
|
|
|
|
spaces = g_strnfill (4 * get_indentation_level (blockquote), ' ');
|
|
|
|
child = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (blockquote));
|
|
while (child) {
|
|
/* If next sibling is indented blockqoute skip it,
|
|
* it will be processed afterwards */
|
|
if (WEBKIT_DOM_IS_ELEMENT (child) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
|
|
child = webkit_dom_node_get_next_sibling (child);
|
|
|
|
if (WEBKIT_DOM_IS_TEXT (child)) {
|
|
gchar *text_content;
|
|
gchar *indented_text;
|
|
|
|
text_content = webkit_dom_text_get_whole_text (WEBKIT_DOM_TEXT (child));
|
|
indented_text = g_strconcat (spaces, text_content, NULL);
|
|
|
|
webkit_dom_text_replace_whole_text (
|
|
WEBKIT_DOM_TEXT (child),
|
|
indented_text,
|
|
NULL);
|
|
|
|
g_free (text_content);
|
|
g_free (indented_text);
|
|
}
|
|
|
|
if (!child)
|
|
break;
|
|
|
|
/* Move to next node */
|
|
if (webkit_dom_node_has_child_nodes (child))
|
|
child = webkit_dom_node_get_first_child (child);
|
|
else if (webkit_dom_node_get_next_sibling (child))
|
|
child = webkit_dom_node_get_next_sibling (child);
|
|
else {
|
|
if (webkit_dom_node_is_equal_node (WEBKIT_DOM_NODE (blockquote), child))
|
|
break;
|
|
|
|
child = webkit_dom_node_get_parent_node (child);
|
|
if (child)
|
|
child = webkit_dom_node_get_next_sibling (child);
|
|
}
|
|
}
|
|
g_free (spaces);
|
|
|
|
webkit_dom_element_remove_attribute (blockquote, "style");
|
|
}
|
|
}
|
|
|
|
/* Taken from GtkHTML */
|
|
static gchar *
|
|
get_alpha_value (gint value,
|
|
gboolean lower)
|
|
{
|
|
GString *str;
|
|
gchar *rv;
|
|
gint add = lower ? 'a' : 'A';
|
|
|
|
str = g_string_new (". ");
|
|
|
|
do {
|
|
g_string_prepend_c (str, ((value - 1) % 26) + add);
|
|
value = (value - 1) / 26;
|
|
} while (value);
|
|
|
|
rv = str->str;
|
|
g_string_free (str, FALSE);
|
|
|
|
return rv;
|
|
}
|
|
|
|
/* Taken from GtkHTML */
|
|
static gchar *
|
|
get_roman_value (gint value,
|
|
gboolean lower)
|
|
{
|
|
GString *str;
|
|
const gchar *base = "IVXLCDM";
|
|
gchar *rv;
|
|
gint b, r, add = lower ? 'a' - 'A' : 0;
|
|
|
|
if (value > 3999)
|
|
return g_strdup ("?. ");
|
|
|
|
str = g_string_new (". ");
|
|
|
|
for (b = 0; value > 0 && b < 7 - 1; b += 2, value /= 10) {
|
|
r = value % 10;
|
|
if (r != 0) {
|
|
if (r < 4) {
|
|
for (; r; r--)
|
|
g_string_prepend_c (str, base[b] + add);
|
|
} else if (r == 4) {
|
|
g_string_prepend_c (str, base[b + 1] + add);
|
|
g_string_prepend_c (str, base[b] + add);
|
|
} else if (r == 5) {
|
|
g_string_prepend_c (str, base[b + 1] + add);
|
|
} else if (r < 9) {
|
|
for (; r > 5; r--)
|
|
g_string_prepend_c (str, base[b] + add);
|
|
g_string_prepend_c (str, base[b + 1] + add);
|
|
} else if (r == 9) {
|
|
g_string_prepend_c (str, base[b + 2] + add);
|
|
g_string_prepend_c (str, base[b] + add);
|
|
}
|
|
}
|
|
}
|
|
|
|
rv = str->str;
|
|
g_string_free (str, FALSE);
|
|
|
|
return rv;
|
|
}
|
|
|
|
static void
|
|
process_list_to_plain_text (EHTMLEditorView *view,
|
|
WebKitDOMElement *element,
|
|
gint level,
|
|
GString *output)
|
|
{
|
|
EHTMLEditorSelectionBlockFormat format;
|
|
EHTMLEditorSelectionAlignment alignment;
|
|
gint counter = 1;
|
|
gchar *indent_per_level = g_strnfill (SPACES_PER_LIST_LEVEL, ' ');
|
|
WebKitDOMNode *item;
|
|
gint word_wrap_length = e_html_editor_selection_get_word_wrap_length (
|
|
e_html_editor_view_get_selection (view));
|
|
|
|
format = e_html_editor_selection_get_list_format_from_node (
|
|
WEBKIT_DOM_NODE (element));
|
|
|
|
/* Process list items to plain text */
|
|
item = webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (element));
|
|
while (item) {
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (item))
|
|
g_string_append (output, "\n");
|
|
|
|
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
|
|
gchar *space, *item_str = NULL;
|
|
gint ii = 0;
|
|
WebKitDOMElement *wrapped;
|
|
GString *item_value = g_string_new ("");
|
|
|
|
alignment = e_html_editor_selection_get_list_alignment_from_node (
|
|
WEBKIT_DOM_NODE (item));
|
|
|
|
wrapped = webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (item), ".-x-evo-wrap-br", NULL);
|
|
/* Wrapped text */
|
|
if (wrapped) {
|
|
WebKitDOMNode *node = webkit_dom_node_get_first_child (item);
|
|
GString *line = g_string_new ("");
|
|
while (node) {
|
|
if (WEBKIT_DOM_IS_TEXT (node)) {
|
|
/* append text from line */
|
|
gchar *text_content;
|
|
text_content = webkit_dom_node_get_text_content (node);
|
|
g_string_append (line, text_content);
|
|
g_free (text_content);
|
|
}
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (node) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (node), "-x-evo-wrap-br")) {
|
|
g_string_append (line, "\n");
|
|
/* put spaces before line characters -> wordwraplength - indentation */
|
|
for (ii = 0; ii < level; ii++)
|
|
g_string_append (line, indent_per_level);
|
|
g_string_append (item_value, line->str);
|
|
g_string_erase (line, 0, -1);
|
|
}
|
|
node = webkit_dom_node_get_next_sibling (node);
|
|
}
|
|
|
|
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT)
|
|
g_string_append (item_value, line->str);
|
|
|
|
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
|
|
gchar *fill = NULL;
|
|
gint fill_length;
|
|
|
|
fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
|
|
fill_length -= ii * SPACES_PER_LIST_LEVEL;
|
|
fill_length /= 2;
|
|
|
|
if (fill_length < 0)
|
|
fill_length = 0;
|
|
|
|
fill = g_strnfill (fill_length, ' ');
|
|
|
|
g_string_append (item_value, fill);
|
|
g_string_append (item_value, line->str);
|
|
g_free (fill);
|
|
}
|
|
|
|
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
|
|
gchar *fill = NULL;
|
|
gint fill_length;
|
|
|
|
fill_length = word_wrap_length - g_utf8_strlen (line->str, -1);
|
|
fill_length -= ii * SPACES_PER_LIST_LEVEL;
|
|
|
|
if (fill_length < 0)
|
|
fill_length = 0;
|
|
|
|
fill = g_strnfill (fill_length, ' ');
|
|
|
|
g_string_append (item_value, fill);
|
|
g_string_append (item_value, line->str);
|
|
g_free (fill);
|
|
}
|
|
g_string_free (line, TRUE);
|
|
/* that same here */
|
|
} else {
|
|
gchar *text_content =
|
|
webkit_dom_node_get_text_content (item);
|
|
g_string_append (item_value, text_content);
|
|
g_free (text_content);
|
|
}
|
|
|
|
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_UNORDERED_LIST) {
|
|
space = g_strnfill (SPACES_PER_LIST_LEVEL - 2, ' ');
|
|
item_str = g_strdup_printf (
|
|
"%s* %s", space, item_value->str);
|
|
g_free (space);
|
|
}
|
|
|
|
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
|
|
gint length = 1, tmp = counter;
|
|
|
|
while ((tmp = tmp / 10) > 1)
|
|
length++;
|
|
|
|
if (tmp == 1)
|
|
length++;
|
|
|
|
space = g_strnfill (SPACES_PER_LIST_LEVEL - 2 - length, ' ');
|
|
item_str = g_strdup_printf (
|
|
"%s%d. %s", space, counter, item_value->str);
|
|
g_free (space);
|
|
}
|
|
|
|
if (format > E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST) {
|
|
gchar *value;
|
|
|
|
if (format == E_HTML_EDITOR_SELECTION_BLOCK_FORMAT_ORDERED_LIST_ALPHA)
|
|
value = get_alpha_value (counter, FALSE);
|
|
else
|
|
value = get_roman_value (counter, FALSE);
|
|
|
|
/* Value already containes dot and space */
|
|
space = g_strnfill (SPACES_PER_LIST_LEVEL - strlen (value), ' ');
|
|
item_str = g_strdup_printf (
|
|
"%s%s%s", space, value, item_value->str);
|
|
g_free (space);
|
|
g_free (value);
|
|
}
|
|
|
|
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_LEFT) {
|
|
for (ii = 0; ii < level - 1; ii++) {
|
|
g_string_append (output, indent_per_level);
|
|
}
|
|
g_string_append (output, item_str);
|
|
}
|
|
|
|
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_RIGHT) {
|
|
if (!wrapped) {
|
|
gchar *fill = NULL;
|
|
gint fill_length;
|
|
|
|
fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
|
|
fill_length -= ii * SPACES_PER_LIST_LEVEL;
|
|
|
|
if (fill_length < 0)
|
|
fill_length = 0;
|
|
|
|
if (g_str_has_suffix (item_str, " "))
|
|
fill_length++;
|
|
|
|
fill = g_strnfill (fill_length, ' ');
|
|
|
|
g_string_append (output, fill);
|
|
g_free (fill);
|
|
}
|
|
if (g_str_has_suffix (item_str, " "))
|
|
g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
|
|
else
|
|
g_string_append (output, item_str);
|
|
}
|
|
|
|
if (alignment == E_HTML_EDITOR_SELECTION_ALIGNMENT_CENTER) {
|
|
if (!wrapped) {
|
|
gchar *fill = NULL;
|
|
gint fill_length = 0;
|
|
|
|
for (ii = 0; ii < level - 1; ii++)
|
|
g_string_append (output, indent_per_level);
|
|
|
|
fill_length = word_wrap_length - g_utf8_strlen (item_str, -1);
|
|
fill_length -= ii * SPACES_PER_LIST_LEVEL;
|
|
fill_length /= 2;
|
|
|
|
if (fill_length < 0)
|
|
fill_length = 0;
|
|
|
|
if (g_str_has_suffix (item_str, " "))
|
|
fill_length++;
|
|
|
|
fill = g_strnfill (fill_length, ' ');
|
|
|
|
g_string_append (output, fill);
|
|
g_free (fill);
|
|
}
|
|
if (g_str_has_suffix (item_str, " "))
|
|
g_string_append_len (output, item_str, g_utf8_strlen (item_str, -1) - 1);
|
|
else
|
|
g_string_append (output, item_str);
|
|
}
|
|
|
|
counter++;
|
|
item = webkit_dom_node_get_next_sibling (item);
|
|
if (item)
|
|
g_string_append (output, "\n");
|
|
|
|
g_free (item_str);
|
|
g_string_free (item_value, TRUE);
|
|
} else if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (item) ||
|
|
WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (item)) {
|
|
process_list_to_plain_text (
|
|
view, WEBKIT_DOM_ELEMENT (item), level + 1, output);
|
|
item = webkit_dom_node_get_next_sibling (item);
|
|
} else {
|
|
item = webkit_dom_node_get_next_sibling (item);
|
|
}
|
|
}
|
|
|
|
if (webkit_dom_node_get_next_sibling (WEBKIT_DOM_NODE (element)))
|
|
g_string_append (output, "\n");
|
|
|
|
g_free (indent_per_level);
|
|
}
|
|
|
|
static void
|
|
remove_base_attributes (WebKitDOMElement *element)
|
|
{
|
|
webkit_dom_element_remove_attribute (element, "class");
|
|
webkit_dom_element_remove_attribute (element, "id");
|
|
webkit_dom_element_remove_attribute (element, "name");
|
|
}
|
|
|
|
static void
|
|
remove_evolution_attributes (WebKitDOMElement *element)
|
|
{
|
|
webkit_dom_element_remove_attribute (element, "x-evo-smiley");
|
|
webkit_dom_element_remove_attribute (element, "data-converted");
|
|
webkit_dom_element_remove_attribute (element, "data-edit-as-new");
|
|
webkit_dom_element_remove_attribute (element, "data-evo-draft");
|
|
webkit_dom_element_remove_attribute (element, "data-inline");
|
|
webkit_dom_element_remove_attribute (element, "data-uri");
|
|
webkit_dom_element_remove_attribute (element, "data-message");
|
|
webkit_dom_element_remove_attribute (element, "data-name");
|
|
webkit_dom_element_remove_attribute (element, "data-new-message");
|
|
webkit_dom_element_remove_attribute (element, "spellcheck");
|
|
}
|
|
/*
|
|
static void
|
|
remove_style_attributes (WebKitDOMElement *element)
|
|
{
|
|
webkit_dom_element_remove_attribute (element, "bgcolor");
|
|
webkit_dom_element_remove_attribute (element, "background");
|
|
webkit_dom_element_remove_attribute (element, "style");
|
|
}
|
|
*/
|
|
static gboolean
|
|
replace_to_whitespaces (const GMatchInfo *info,
|
|
GString *res,
|
|
gpointer data)
|
|
{
|
|
gint ii, length = 0;
|
|
gint chars_count = GPOINTER_TO_INT (data);
|
|
|
|
length = TAB_LENGTH - (chars_count % TAB_LENGTH);
|
|
|
|
for (ii = 0; ii < length; ii++)
|
|
g_string_append (res, " ");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
process_elements (EHTMLEditorView *view,
|
|
WebKitDOMNode *node,
|
|
gboolean to_html,
|
|
gboolean changing_mode,
|
|
gboolean to_plain_text,
|
|
GString *buffer)
|
|
{
|
|
WebKitDOMNodeList *nodes;
|
|
gulong ii, length;
|
|
gchar *content;
|
|
gboolean skip_nl = FALSE;
|
|
|
|
if (to_plain_text && !buffer)
|
|
return;
|
|
|
|
if (WEBKIT_DOM_IS_HTML_BODY_ELEMENT (node)) {
|
|
if (changing_mode && to_plain_text) {
|
|
WebKitDOMNamedNodeMap *attributes;
|
|
gulong attributes_length;
|
|
|
|
/* Copy attributes */
|
|
g_string_append (buffer, "<html><head></head><body ");
|
|
attributes = webkit_dom_element_get_attributes (
|
|
WEBKIT_DOM_ELEMENT (node));
|
|
attributes_length =
|
|
webkit_dom_named_node_map_get_length (attributes);
|
|
|
|
for (ii = 0; ii < attributes_length; ii++) {
|
|
gchar *name, *value;
|
|
WebKitDOMNode *node =
|
|
webkit_dom_named_node_map_item (
|
|
attributes, ii);
|
|
|
|
name = webkit_dom_node_get_local_name (node);
|
|
value = webkit_dom_node_get_node_value (node);
|
|
|
|
g_string_append (buffer, name);
|
|
g_string_append (buffer, "=\"");
|
|
g_string_append (buffer, value);
|
|
g_string_append (buffer, "\" ");
|
|
|
|
g_free (name);
|
|
g_free (value);
|
|
g_object_unref (node);
|
|
}
|
|
g_string_append (buffer, ">");
|
|
g_object_unref (attributes);
|
|
}
|
|
if (to_html)
|
|
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (node));
|
|
}
|
|
|
|
nodes = webkit_dom_node_get_child_nodes (node);
|
|
length = webkit_dom_node_list_get_length (nodes);
|
|
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *child;
|
|
gboolean skip_node = FALSE;
|
|
|
|
child = webkit_dom_node_list_item (nodes, ii);
|
|
|
|
if (WEBKIT_DOM_IS_TEXT (child)) {
|
|
gchar *content, *tmp;
|
|
GRegex *regex;
|
|
gint char_count = 0;
|
|
|
|
content = webkit_dom_node_get_text_content (child);
|
|
if (!changing_mode && to_plain_text) {
|
|
/* Replace tabs with 8 whitespaces, otherwise they got
|
|
* replaced by single whitespace */
|
|
if (strstr (content, "\x9")) {
|
|
if (buffer->str && *buffer->str) {
|
|
gchar *start_of_line = g_strrstr_len (
|
|
buffer->str, -1, "\n") + 1;
|
|
|
|
if (start_of_line && *start_of_line)
|
|
char_count = strlen (start_of_line);
|
|
} else
|
|
char_count = 0;
|
|
|
|
regex = g_regex_new ("\x9", 0, 0, NULL);
|
|
tmp = g_regex_replace_eval (
|
|
regex,
|
|
content,
|
|
-1,
|
|
0,
|
|
0,
|
|
(GRegexEvalCallback) replace_to_whitespaces,
|
|
GINT_TO_POINTER (char_count),
|
|
NULL);
|
|
|
|
g_string_append (buffer, tmp);
|
|
g_free (tmp);
|
|
g_free (content);
|
|
content = webkit_dom_node_get_text_content (child);
|
|
g_regex_unref (regex);
|
|
}
|
|
}
|
|
|
|
if (strstr (content, UNICODE_ZERO_WIDTH_SPACE)) {
|
|
regex = g_regex_new (UNICODE_ZERO_WIDTH_SPACE, 0, 0, NULL);
|
|
tmp = g_regex_replace (
|
|
regex, content, -1, 0, "", 0, NULL);
|
|
webkit_dom_node_set_text_content (child, tmp, NULL);
|
|
g_free (tmp);
|
|
g_free (content);
|
|
content = webkit_dom_node_get_text_content (child);
|
|
g_regex_unref (regex);
|
|
}
|
|
|
|
if (to_plain_text && !changing_mode) {
|
|
gchar *class;
|
|
const gchar *css_align;
|
|
|
|
if (strstr (content, UNICODE_NBSP)) {
|
|
GString *nbsp_free;
|
|
|
|
nbsp_free = e_str_replace_string (
|
|
content, UNICODE_NBSP, " ");
|
|
|
|
g_free (content);
|
|
content = g_string_free (nbsp_free, FALSE);
|
|
}
|
|
|
|
class = webkit_dom_element_get_class_name (WEBKIT_DOM_ELEMENT (node));
|
|
if ((css_align = strstr (class, "-x-evo-align-"))) {
|
|
gchar *align;
|
|
gchar *content_with_align;
|
|
gint length;
|
|
gint word_wrap_length =
|
|
e_html_editor_selection_get_word_wrap_length (
|
|
e_html_editor_view_get_selection (view));
|
|
|
|
if (!g_str_has_prefix (css_align + 13, "left")) {
|
|
if (g_str_has_prefix (css_align + 13, "center"))
|
|
length = (word_wrap_length - g_utf8_strlen (content, -1)) / 2;
|
|
else
|
|
length = word_wrap_length - g_utf8_strlen (content, -1);
|
|
|
|
if (length < 0)
|
|
length = 0;
|
|
|
|
if (g_str_has_suffix (content, " ")) {
|
|
char *tmp;
|
|
|
|
length++;
|
|
align = g_strnfill (length, ' ');
|
|
|
|
tmp = g_strndup (content, g_utf8_strlen (content, -1) -1);
|
|
|
|
content_with_align = g_strconcat (
|
|
align, tmp, NULL);
|
|
g_free (tmp);
|
|
} else {
|
|
align = g_strnfill (length, ' ');
|
|
|
|
content_with_align = g_strconcat (
|
|
align, content, NULL);
|
|
}
|
|
|
|
g_free (content);
|
|
g_free (align);
|
|
content = content_with_align;
|
|
}
|
|
}
|
|
|
|
g_free (class);
|
|
}
|
|
|
|
if (to_plain_text || changing_mode)
|
|
g_string_append (buffer, content);
|
|
|
|
g_free (content);
|
|
|
|
goto next;
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_COMMENT (child) || !WEBKIT_DOM_IS_ELEMENT (child))
|
|
goto next;
|
|
|
|
/* Leave caret position untouched */
|
|
if (element_has_id (WEBKIT_DOM_ELEMENT (child), "-x-evo-caret-position")) {
|
|
if (changing_mode && to_plain_text) {
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
}
|
|
if (to_html)
|
|
remove_node (child);
|
|
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "Apple-tab-span")) {
|
|
if (!changing_mode && to_plain_text) {
|
|
gchar *content, *tmp;
|
|
GRegex *regex;
|
|
gint char_count = 0;
|
|
|
|
content = webkit_dom_node_get_text_content (child);
|
|
/* Replace tabs with 8 whitespaces, otherwise they got
|
|
* replaced by single whitespace */
|
|
if (strstr (content, "\x9")) {
|
|
if (buffer->str && *buffer->str) {
|
|
gchar *start_of_line = g_strrstr_len (
|
|
buffer->str, -1, "\n") + 1;
|
|
|
|
if (start_of_line && *start_of_line)
|
|
char_count = strlen (start_of_line);
|
|
} else
|
|
char_count = 0;
|
|
|
|
regex = g_regex_new ("\x9", 0, 0, NULL);
|
|
tmp = g_regex_replace_eval (
|
|
regex,
|
|
content,
|
|
-1,
|
|
0,
|
|
0,
|
|
(GRegexEvalCallback) replace_to_whitespaces,
|
|
GINT_TO_POINTER (char_count),
|
|
NULL);
|
|
|
|
g_string_append (buffer, tmp);
|
|
g_free (tmp);
|
|
g_regex_unref (regex);
|
|
} else if (content && *content) {
|
|
/* Some it happens that some text is written inside
|
|
* the tab span element, so save it. */
|
|
g_string_append (buffer, content);
|
|
}
|
|
g_free (content);
|
|
}
|
|
if (to_html) {
|
|
element_remove_class (
|
|
WEBKIT_DOM_ELEMENT (child),
|
|
"Applet-tab-span");
|
|
}
|
|
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
|
|
/* Leave blockquotes as they are */
|
|
if (WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (child)) {
|
|
if (changing_mode && to_plain_text) {
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
skip_node = TRUE;
|
|
goto next;
|
|
} else {
|
|
if (!changing_mode && to_plain_text) {
|
|
if (get_citation_level (child, FALSE) == 0) {
|
|
gchar *value = webkit_dom_element_get_attribute (
|
|
WEBKIT_DOM_ELEMENT (child), "type");
|
|
|
|
if (value && g_strcmp0 (value, "cite") == 0)
|
|
g_string_append (buffer, "\n");
|
|
g_free (value);
|
|
}
|
|
}
|
|
process_blockquote (WEBKIT_DOM_ELEMENT (child));
|
|
if (to_html)
|
|
remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
|
|
}
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-indented"))
|
|
process_blockquote (WEBKIT_DOM_ELEMENT (child));
|
|
|
|
if (WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (child) ||
|
|
WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (child)) {
|
|
if (to_plain_text) {
|
|
if (changing_mode) {
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
} else {
|
|
process_list_to_plain_text (
|
|
view, WEBKIT_DOM_ELEMENT (child), 1, buffer);
|
|
}
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
}
|
|
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-resizable-wrapper") &&
|
|
!element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
|
|
WebKitDOMNode *image =
|
|
webkit_dom_node_get_first_child (child);
|
|
|
|
if (to_html && WEBKIT_DOM_IS_HTML_IMAGE_ELEMENT (image)) {
|
|
remove_evolution_attributes (
|
|
WEBKIT_DOM_ELEMENT (image));
|
|
|
|
webkit_dom_node_replace_child (
|
|
node, image, child, NULL);
|
|
}
|
|
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
|
|
/* Leave paragraphs as they are */
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-paragraph")) {
|
|
if (changing_mode && to_plain_text) {
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
if (to_html) {
|
|
remove_base_attributes (WEBKIT_DOM_ELEMENT (child));
|
|
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
|
|
}
|
|
}
|
|
|
|
/* Signature */
|
|
if (WEBKIT_DOM_IS_HTML_DIV_ELEMENT (child) &&
|
|
element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-signature-wrapper")) {
|
|
WebKitDOMNode *first_child;
|
|
|
|
first_child = webkit_dom_node_get_first_child (child);
|
|
|
|
if (to_html) {
|
|
remove_base_attributes (
|
|
WEBKIT_DOM_ELEMENT (first_child));
|
|
remove_evolution_attributes (
|
|
WEBKIT_DOM_ELEMENT (first_child));
|
|
}
|
|
if (to_plain_text && !changing_mode) {
|
|
g_string_append (buffer, "\n");
|
|
content = webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (first_child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
skip_nl = TRUE;
|
|
}
|
|
if (to_plain_text && changing_mode) {
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
}
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
|
|
/* Replace smileys with their text representation */
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-smiley-wrapper")) {
|
|
if (to_plain_text && !changing_mode) {
|
|
WebKitDOMNode *text_version;
|
|
|
|
text_version = webkit_dom_node_get_last_child (child);
|
|
content = webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (text_version));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
if (to_html) {
|
|
WebKitDOMElement *img;
|
|
|
|
img = WEBKIT_DOM_ELEMENT (
|
|
webkit_dom_node_get_first_child (child)),
|
|
|
|
remove_evolution_attributes (img);
|
|
remove_base_attributes (img);
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (child),
|
|
WEBKIT_DOM_NODE (img),
|
|
child,
|
|
NULL);
|
|
remove_node (child);
|
|
skip_node = TRUE;
|
|
goto next;
|
|
}
|
|
}
|
|
|
|
/* Leave PRE elements untouched */
|
|
if (WEBKIT_DOM_IS_HTML_PRE_ELEMENT (child)) {
|
|
if (changing_mode && to_plain_text) {
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
skip_node = TRUE;
|
|
}
|
|
if (to_html)
|
|
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (child));
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_HTMLBR_ELEMENT (child)) {
|
|
if (to_plain_text) {
|
|
if (element_has_class (WEBKIT_DOM_ELEMENT (child), "-x-evo-wrap-br")) {
|
|
g_string_append (buffer, changing_mode ? "<br>" : "\n");
|
|
goto next;
|
|
}
|
|
|
|
/* Insert new line when we hit the BR element that is
|
|
* not the last element in the block */
|
|
if (!webkit_dom_node_is_same_node (
|
|
child, webkit_dom_node_get_last_child (node))) {
|
|
g_string_append (buffer, changing_mode ? "<br>" : "\n");
|
|
} else {
|
|
/* In citations in the empty lines the BR element
|
|
* is on the end and we have to put NL there */
|
|
WebKitDOMNode *parent;
|
|
|
|
parent = webkit_dom_node_get_parent_node (child);
|
|
if (webkit_dom_node_get_next_sibling (parent)) {
|
|
parent = webkit_dom_node_get_parent_node (parent);
|
|
|
|
if (is_citation_node (parent))
|
|
g_string_append (buffer, changing_mode ? "<br>" : "\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (WEBKIT_DOM_IS_HTML_ANCHOR_ELEMENT (child)) {
|
|
if (changing_mode && to_plain_text) {
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
skip_node = TRUE;
|
|
}
|
|
if (!changing_mode && to_plain_text) {
|
|
content = webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (child));
|
|
g_string_append (buffer, content);
|
|
g_free (content);
|
|
skip_node = TRUE;
|
|
}
|
|
}
|
|
next:
|
|
if (webkit_dom_node_has_child_nodes (child) && !skip_node)
|
|
process_elements (
|
|
view, child, to_html, changing_mode, to_plain_text, buffer);
|
|
g_object_unref (child);
|
|
}
|
|
|
|
if (to_plain_text && (
|
|
WEBKIT_DOM_IS_HTML_DIV_ELEMENT (node) ||
|
|
WEBKIT_DOM_IS_HTML_PARAGRAPH_ELEMENT (node) ||
|
|
WEBKIT_DOM_IS_HTML_PRE_ELEMENT (node) ||
|
|
WEBKIT_DOM_IS_HTML_QUOTE_ELEMENT (node))) {
|
|
|
|
gboolean add_br = TRUE;
|
|
WebKitDOMNode *next_sibling = webkit_dom_node_get_next_sibling (node);
|
|
WebKitDOMNode *last_child = webkit_dom_node_get_last_child (node);
|
|
|
|
if (last_child && WEBKIT_DOM_IS_HTMLBR_ELEMENT (last_child))
|
|
if (webkit_dom_node_get_previous_sibling (last_child))
|
|
add_br = FALSE;
|
|
|
|
/* If we don't have next sibling (last element in body) or next element is
|
|
* signature we are not adding the BR element */
|
|
if (!next_sibling)
|
|
add_br = FALSE;
|
|
else if (next_sibling && WEBKIT_DOM_IS_HTML_DIV_ELEMENT (next_sibling)) {
|
|
if (webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (next_sibling),
|
|
"span.-x-evo-signature", NULL)) {
|
|
|
|
add_br = FALSE;
|
|
}
|
|
}
|
|
|
|
if (add_br && !skip_nl)
|
|
g_string_append (buffer, changing_mode ? "<br>" : "\n");
|
|
}
|
|
|
|
g_object_unref (nodes);
|
|
}
|
|
|
|
static void
|
|
remove_wrapping_from_view (EHTMLEditorView *view)
|
|
{
|
|
gint length;
|
|
gint ii;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNodeList *list;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
list = webkit_dom_document_query_selector_all (document, "br.-x-evo-wrap-br", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
|
|
g_object_unref (list);
|
|
}
|
|
|
|
void
|
|
remove_image_attributes_from_element (WebKitDOMElement *element)
|
|
{
|
|
webkit_dom_element_remove_attribute (element, "background");
|
|
webkit_dom_element_remove_attribute (element, "data-uri");
|
|
webkit_dom_element_remove_attribute (element, "data-inline");
|
|
webkit_dom_element_remove_attribute (element, "data-name");
|
|
}
|
|
|
|
static void
|
|
remove_background_images_in_document (WebKitDOMDocument *document)
|
|
{
|
|
gint length, ii;
|
|
WebKitDOMNodeList *elements;
|
|
|
|
elements = webkit_dom_document_query_selector_all (
|
|
document, "[background][data-inline]", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (elements);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMElement *element = WEBKIT_DOM_ELEMENT (
|
|
webkit_dom_node_list_item (elements, ii));
|
|
|
|
remove_image_attributes_from_element (element);
|
|
g_object_unref (element);
|
|
}
|
|
|
|
g_object_unref (elements);
|
|
}
|
|
|
|
static void
|
|
remove_images_in_element (EHTMLEditorView *view,
|
|
WebKitDOMElement *element)
|
|
{
|
|
gint length, ii;
|
|
WebKitDOMNodeList *images;
|
|
|
|
images = webkit_dom_element_query_selector_all (
|
|
element, "img:not(.-x-evo-smiley-img)", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (images);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (images, ii);
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
}
|
|
|
|
g_object_unref (images);
|
|
}
|
|
|
|
static void
|
|
remove_images (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
remove_images_in_element (
|
|
view, WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)));
|
|
}
|
|
|
|
static void
|
|
toggle_smileys (EHTMLEditorView *view)
|
|
{
|
|
gboolean html_mode;
|
|
gint length;
|
|
gint ii;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNodeList *smileys;
|
|
|
|
html_mode = e_html_editor_view_get_html_mode (view);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
smileys = webkit_dom_document_query_selector_all (
|
|
document, "img.-x-evo-smiley-img", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (smileys);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *img = webkit_dom_node_list_item (smileys, ii);
|
|
WebKitDOMNode *text = webkit_dom_node_get_next_sibling (img);
|
|
WebKitDOMElement *parent = webkit_dom_node_get_parent_element (img);
|
|
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (html_mode ? text : img),
|
|
"style",
|
|
"display: none",
|
|
NULL);
|
|
|
|
webkit_dom_element_remove_attribute (
|
|
WEBKIT_DOM_ELEMENT (html_mode ? img : text), "style");
|
|
|
|
if (html_mode)
|
|
element_add_class (parent, "-x-evo-resizable-wrapper");
|
|
else
|
|
element_remove_class (parent, "-x-evo-resizable-wrapper");
|
|
g_object_unref (img);
|
|
}
|
|
|
|
g_object_unref (smileys);
|
|
}
|
|
|
|
static void
|
|
toggle_paragraphs_style_in_element (EHTMLEditorView *view,
|
|
WebKitDOMElement *element,
|
|
gboolean html_mode)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gint ii, length;
|
|
WebKitDOMNodeList *paragraphs;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
paragraphs = webkit_dom_element_query_selector_all (
|
|
element, ".-x-evo-paragraph", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (paragraphs);
|
|
|
|
for (ii = 0; ii < length; ii++) {
|
|
gchar *style;
|
|
const gchar *css_align;
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
|
|
|
|
if (html_mode) {
|
|
style = webkit_dom_element_get_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "style");
|
|
|
|
if ((css_align = strstr (style, "text-align: "))) {
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (node),
|
|
"style",
|
|
g_str_has_prefix (css_align + 12, "center") ?
|
|
"text-align: center" :
|
|
"text-align: right",
|
|
NULL);
|
|
} else {
|
|
/* In HTML mode the paragraphs don't have width limit */
|
|
webkit_dom_element_remove_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "style");
|
|
}
|
|
g_free (style);
|
|
} else {
|
|
WebKitDOMNode *parent;
|
|
|
|
parent = webkit_dom_node_get_parent_node (node);
|
|
/* If the paragraph is inside indented paragraph don't set
|
|
* the style as it will be inherited */
|
|
if (!element_has_class (WEBKIT_DOM_ELEMENT (parent), "-x-evo-indented")) {
|
|
const gchar *style_to_add = "";
|
|
style = webkit_dom_element_get_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "style");
|
|
|
|
if ((css_align = strstr (style, "text-align: "))) {
|
|
style_to_add = g_str_has_prefix (
|
|
css_align + 12, "center") ?
|
|
"text-align: center;" :
|
|
"text-align: right;";
|
|
}
|
|
|
|
/* In plain text mode the paragraphs have width limit */
|
|
e_html_editor_selection_set_paragraph_style (
|
|
selection, WEBKIT_DOM_ELEMENT (node),
|
|
-1, 0, style_to_add);
|
|
|
|
g_free (style);
|
|
}
|
|
}
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (paragraphs);
|
|
}
|
|
|
|
static void
|
|
toggle_paragraphs_style (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
toggle_paragraphs_style_in_element (
|
|
view,
|
|
WEBKIT_DOM_ELEMENT (webkit_dom_document_get_body (document)),
|
|
view->priv->html_mode);
|
|
}
|
|
|
|
static gchar *
|
|
process_content_for_saving_as_draft (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMElement *document_element;
|
|
gchar *content;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = webkit_dom_document_get_body (document);
|
|
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-evo-draft", "", NULL);
|
|
|
|
document_element = webkit_dom_document_get_document_element (document);
|
|
content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (document_element));
|
|
|
|
webkit_dom_element_remove_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-evo-draft");
|
|
|
|
return content;
|
|
}
|
|
|
|
static gchar *
|
|
process_content_for_mode_change (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNode *body;
|
|
GString *plain_text;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
|
|
|
|
plain_text = g_string_sized_new (1024);
|
|
|
|
process_elements (view, body, FALSE, TRUE, TRUE, plain_text);
|
|
|
|
g_string_append (plain_text, "</body></html>");
|
|
|
|
return g_string_free (plain_text, FALSE);
|
|
}
|
|
|
|
static void
|
|
convert_element_from_html_to_plain_text (EHTMLEditorView *view,
|
|
WebKitDOMElement *element,
|
|
gboolean *wrap,
|
|
gboolean *quote)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gint blockquotes_count;
|
|
gchar *inner_text, *inner_html;
|
|
gboolean restore = TRUE;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *top_signature, *signature, *blockquote, *main_blockquote;
|
|
WebKitDOMNode *signature_clone, *from;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
document = webkit_dom_node_get_owner_document (WEBKIT_DOM_NODE (element));
|
|
|
|
top_signature = webkit_dom_element_query_selector (
|
|
element, ".-x-evo-top-signature", NULL);
|
|
signature = webkit_dom_element_query_selector (
|
|
element, "span.-x-evo-signature", NULL);
|
|
main_blockquote = webkit_dom_element_query_selector (
|
|
element, "#-x-evo-main-cite", NULL);
|
|
|
|
blockquote = webkit_dom_document_create_element (
|
|
document, "blockquote", NULL);
|
|
|
|
if (main_blockquote) {
|
|
WebKitDOMElement *input_start;
|
|
|
|
webkit_dom_element_set_attribute (
|
|
blockquote, "type", "cite", NULL);
|
|
|
|
input_start = webkit_dom_element_query_selector (
|
|
element, "#-x-evo-input-start", NULL);
|
|
|
|
restore = input_start ? TRUE : FALSE;
|
|
|
|
if (input_start)
|
|
add_selection_markers_into_element_start (
|
|
document, WEBKIT_DOM_ELEMENT (input_start), NULL, NULL);
|
|
from = WEBKIT_DOM_NODE (main_blockquote);
|
|
} else {
|
|
if (signature) {
|
|
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (signature));
|
|
signature_clone = webkit_dom_node_clone_node (parent, TRUE);
|
|
remove_node (parent);
|
|
}
|
|
from = WEBKIT_DOM_NODE (element);
|
|
}
|
|
|
|
blockquotes_count = create_text_markers_for_citations_in_element (
|
|
WEBKIT_DOM_ELEMENT (from));
|
|
|
|
inner_text = webkit_dom_html_element_get_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (from));
|
|
|
|
webkit_dom_html_element_set_inner_text (
|
|
WEBKIT_DOM_HTML_ELEMENT (blockquote), inner_text, NULL);
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (blockquote));
|
|
|
|
parse_html_into_paragraphs (
|
|
view, document,
|
|
main_blockquote ? blockquote : WEBKIT_DOM_ELEMENT (element),
|
|
NULL,
|
|
inner_html);
|
|
|
|
if (main_blockquote) {
|
|
webkit_dom_node_replace_child (
|
|
webkit_dom_node_get_parent_node (
|
|
WEBKIT_DOM_NODE (main_blockquote)),
|
|
WEBKIT_DOM_NODE (blockquote),
|
|
WEBKIT_DOM_NODE (main_blockquote),
|
|
NULL);
|
|
|
|
remove_evolution_attributes (WEBKIT_DOM_ELEMENT (element));
|
|
} else {
|
|
WebKitDOMNode *first_child;
|
|
|
|
if (signature) {
|
|
if (!top_signature) {
|
|
signature_clone = webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (element),
|
|
signature_clone,
|
|
NULL);
|
|
} else {
|
|
webkit_dom_node_insert_before (
|
|
WEBKIT_DOM_NODE (element),
|
|
signature_clone,
|
|
webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (element)),
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
first_child = webkit_dom_node_get_first_child (
|
|
WEBKIT_DOM_NODE (element));
|
|
if (first_child) {
|
|
if (!webkit_dom_node_has_child_nodes (first_child)) {
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (first_child),
|
|
"<br>",
|
|
NULL);
|
|
}
|
|
add_selection_markers_into_element_start (
|
|
document, WEBKIT_DOM_ELEMENT (first_child), NULL, NULL);
|
|
}
|
|
}
|
|
|
|
*wrap = TRUE;
|
|
*quote = main_blockquote || blockquotes_count > 0;
|
|
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (element), "data-converted", "", NULL);
|
|
|
|
g_free (inner_text);
|
|
g_free (inner_html);
|
|
|
|
if (restore)
|
|
e_html_editor_selection_restore (selection);
|
|
}
|
|
|
|
static gchar *
|
|
process_content_for_plain_text (EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gboolean wrap = FALSE, quote = FALSE, clean = FALSE;
|
|
gboolean converted, is_from_new_message;
|
|
gint length, ii;
|
|
GString *plain_text;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNode *body, *source;
|
|
WebKitDOMNodeList *paragraphs;
|
|
|
|
plain_text = g_string_sized_new (1024);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = WEBKIT_DOM_NODE (webkit_dom_document_get_body (document));
|
|
converted = webkit_dom_element_has_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-converted");
|
|
is_from_new_message = webkit_dom_element_has_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-new-message");
|
|
source = webkit_dom_node_clone_node (WEBKIT_DOM_NODE (body), TRUE);
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
/* If composer is in HTML mode we have to move the content to plain version */
|
|
if (view->priv->html_mode) {
|
|
if (converted || is_from_new_message) {
|
|
toggle_paragraphs_style_in_element (
|
|
view, WEBKIT_DOM_ELEMENT (source), FALSE);
|
|
remove_images_in_element (
|
|
view, WEBKIT_DOM_ELEMENT (source));
|
|
remove_background_images_in_document (
|
|
document);
|
|
} else {
|
|
gchar *inner_html;
|
|
WebKitDOMElement *div;
|
|
|
|
inner_html = webkit_dom_html_element_get_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (body));
|
|
|
|
div = webkit_dom_document_create_element (
|
|
document, "div", NULL);
|
|
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (div), inner_html, NULL);
|
|
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (body),
|
|
WEBKIT_DOM_NODE (div),
|
|
NULL);
|
|
|
|
paragraphs = webkit_dom_element_query_selector_all (
|
|
div, "#-x-evo-input-start", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (paragraphs);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *paragraph;
|
|
|
|
paragraph = webkit_dom_node_list_item (paragraphs, ii);
|
|
|
|
webkit_dom_element_remove_attribute (
|
|
WEBKIT_DOM_ELEMENT (paragraph), "id");
|
|
g_object_unref (paragraph);
|
|
}
|
|
g_object_unref (paragraphs);
|
|
|
|
convert_element_from_html_to_plain_text (
|
|
view, div, &wrap, "e);
|
|
|
|
g_object_unref (source);
|
|
|
|
source = WEBKIT_DOM_NODE (div);
|
|
|
|
clean = TRUE;
|
|
}
|
|
}
|
|
|
|
paragraphs = webkit_dom_element_query_selector_all (
|
|
WEBKIT_DOM_ELEMENT (source), ".-x-evo-paragraph", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (paragraphs);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *paragraph;
|
|
|
|
paragraph = webkit_dom_node_list_item (paragraphs, ii);
|
|
|
|
if (WEBKIT_DOM_IS_HTMLO_LIST_ELEMENT (paragraph) ||
|
|
WEBKIT_DOM_IS_HTMLU_LIST_ELEMENT (paragraph)) {
|
|
WebKitDOMNode *item = webkit_dom_node_get_first_child (paragraph);
|
|
|
|
while (item) {
|
|
WebKitDOMNode *next_item =
|
|
webkit_dom_node_get_next_sibling (item);
|
|
|
|
if (WEBKIT_DOM_IS_HTMLLI_ELEMENT (item)) {
|
|
e_html_editor_selection_wrap_paragraph (
|
|
selection, WEBKIT_DOM_ELEMENT (item));
|
|
}
|
|
item = next_item;
|
|
}
|
|
} else {
|
|
e_html_editor_selection_wrap_paragraph (
|
|
selection, WEBKIT_DOM_ELEMENT (paragraph));
|
|
}
|
|
g_object_unref (paragraph);
|
|
}
|
|
g_object_unref (paragraphs);
|
|
|
|
paragraphs = webkit_dom_element_query_selector_all (
|
|
WEBKIT_DOM_ELEMENT (source),
|
|
"span[id^=\"-x-evo-selection-\"], span#-x-evo-caret-position",
|
|
NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (paragraphs);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (paragraphs, ii);
|
|
WebKitDOMNode *parent = webkit_dom_node_get_parent_node (node);
|
|
|
|
remove_node (node);
|
|
g_object_unref (node);
|
|
webkit_dom_node_normalize (parent);
|
|
}
|
|
g_object_unref (paragraphs);
|
|
|
|
if (view->priv->html_mode || quote)
|
|
quote_plain_text_recursive (document, source, source, 0);
|
|
|
|
process_elements (view, source, FALSE, FALSE, TRUE, plain_text);
|
|
|
|
if (clean)
|
|
remove_node (source);
|
|
else
|
|
g_object_unref (source);
|
|
|
|
/* Return text content between <body> and </body> */
|
|
return g_string_free (plain_text, FALSE);
|
|
}
|
|
|
|
static gchar *
|
|
process_content_for_html (EHTMLEditorView *view)
|
|
{
|
|
gchar *html_content;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *marker;
|
|
WebKitDOMNode *node, *document_clone;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
document_clone = webkit_dom_node_clone_node (
|
|
WEBKIT_DOM_NODE (webkit_dom_document_get_document_element (document)), TRUE);
|
|
node = WEBKIT_DOM_NODE (webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (document_clone), "style#-x-evo-quote-style", NULL));
|
|
if (node)
|
|
remove_node (node);
|
|
node = WEBKIT_DOM_NODE (webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (document_clone), "style#-x-evo-a-color-style", NULL));
|
|
if (node)
|
|
remove_node (node);
|
|
/* When the Ctrl + Enter is pressed for sending, the links are activated. */
|
|
node = WEBKIT_DOM_NODE (webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (document_clone), "style#-x-evo-style-a", NULL));
|
|
if (node)
|
|
remove_node (node);
|
|
node = WEBKIT_DOM_NODE (webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (document_clone), "body", NULL));
|
|
marker = webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (node), "#-x-evo-selection-start-marker", NULL);
|
|
if (marker)
|
|
remove_node (WEBKIT_DOM_NODE (marker));
|
|
marker = webkit_dom_element_query_selector (
|
|
WEBKIT_DOM_ELEMENT (node), "#-x-evo-selection-end-marker", NULL);
|
|
if (marker)
|
|
remove_node (WEBKIT_DOM_NODE (marker));
|
|
|
|
process_elements (view, node, TRUE, FALSE, FALSE, NULL);
|
|
|
|
html_content = webkit_dom_html_element_get_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (document_clone));
|
|
|
|
g_object_unref (document_clone);
|
|
|
|
return html_content;
|
|
}
|
|
|
|
static gboolean
|
|
show_lose_formatting_dialog (EHTMLEditorView *view)
|
|
{
|
|
gint result;
|
|
GtkWidget *toplevel, *dialog;
|
|
GtkWindow *parent = NULL;
|
|
|
|
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view));
|
|
|
|
if (GTK_IS_WINDOW (toplevel))
|
|
parent = GTK_WINDOW (toplevel);
|
|
|
|
dialog = gtk_message_dialog_new (
|
|
parent,
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_WARNING,
|
|
GTK_BUTTONS_NONE,
|
|
_("Turning HTML mode off will cause the text "
|
|
"to lose all formatting. Do you want to continue?"));
|
|
gtk_dialog_add_buttons (
|
|
GTK_DIALOG (dialog),
|
|
_("_Don't lose formatting"), GTK_RESPONSE_CANCEL,
|
|
_("_Lose formatting"), GTK_RESPONSE_OK,
|
|
NULL);
|
|
|
|
result = gtk_dialog_run (GTK_DIALOG (dialog));
|
|
|
|
if (result != GTK_RESPONSE_OK) {
|
|
gtk_widget_destroy (dialog);
|
|
/* Nothing has changed, but notify anyway */
|
|
g_object_notify (G_OBJECT (view), "html-mode");
|
|
return FALSE;
|
|
}
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
convert_when_changing_composer_mode (EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gboolean quote = FALSE, wrap = FALSE;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMHTMLElement *body;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = webkit_dom_document_get_body (document);
|
|
|
|
convert_element_from_html_to_plain_text (
|
|
view, WEBKIT_DOM_ELEMENT (body), &wrap, "e);
|
|
|
|
if (wrap)
|
|
e_html_editor_selection_wrap_paragraphs_in_document (selection, document);
|
|
|
|
if (quote) {
|
|
e_html_editor_selection_save (selection);
|
|
if (wrap)
|
|
quote_plain_text_elements_after_wrapping_in_document (
|
|
document);
|
|
else
|
|
body = WEBKIT_DOM_HTML_ELEMENT (e_html_editor_view_quote_plain_text (view));
|
|
e_html_editor_selection_restore (selection);
|
|
}
|
|
|
|
toggle_paragraphs_style (view);
|
|
toggle_smileys (view);
|
|
remove_images (view);
|
|
remove_background_images_in_document (document);
|
|
|
|
clear_attributes (document);
|
|
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-converted", "", NULL);
|
|
|
|
/* Update fonts - in plain text we only want monospace */
|
|
e_html_editor_view_update_fonts (view);
|
|
|
|
e_html_editor_view_force_spell_check (view);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_embed_styles (EHTMLEditorView *view)
|
|
{
|
|
WebKitWebSettings *settings;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *sheet;
|
|
gchar *stylesheet_uri;
|
|
gchar *stylesheet_content;
|
|
const gchar *stylesheet;
|
|
gsize length;
|
|
|
|
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
g_object_get (
|
|
G_OBJECT (settings),
|
|
"user-stylesheet-uri", &stylesheet_uri,
|
|
NULL);
|
|
|
|
stylesheet = strstr (stylesheet_uri, ",");
|
|
stylesheet_content = (gchar *) g_base64_decode (stylesheet, &length);
|
|
g_free (stylesheet_uri);
|
|
|
|
if (length == 0) {
|
|
g_free (stylesheet_content);
|
|
return;
|
|
}
|
|
|
|
e_web_view_create_and_add_css_style_sheet (document, "-x-evo-composer-sheet");
|
|
|
|
sheet = webkit_dom_document_get_element_by_id (document, "-x-evo-composer-sheet");
|
|
webkit_dom_element_set_attribute (
|
|
sheet,
|
|
"type",
|
|
"text/css",
|
|
NULL);
|
|
|
|
webkit_dom_html_element_set_inner_html (WEBKIT_DOM_HTML_ELEMENT (sheet), stylesheet_content, NULL);
|
|
|
|
g_free (stylesheet_content);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_remove_embed_styles (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMElement *sheet;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
sheet = webkit_dom_document_get_element_by_id (
|
|
document, "-x-evo-composer-sheet");
|
|
|
|
if (sheet)
|
|
remove_node (WEBKIT_DOM_NODE (sheet));
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_set_link_color (EHTMLEditorView *view,
|
|
GdkRGBA *color)
|
|
{
|
|
gchar *color_str = NULL;
|
|
WebKitDOMHTMLHeadElement *head;
|
|
WebKitDOMElement *style_element;
|
|
WebKitDOMDocument *document;
|
|
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
g_return_if_fail (color != NULL);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
head = webkit_dom_document_get_head (document);
|
|
|
|
style_element = webkit_dom_document_get_element_by_id (document, "-x-evo-a-color-style");
|
|
if (!style_element) {
|
|
style_element = webkit_dom_document_create_element (document, "style", NULL);
|
|
webkit_dom_element_set_id (style_element, "-x-evo-a-color-style");
|
|
webkit_dom_element_set_attribute (style_element, "type", "text/css", NULL);
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (head), WEBKIT_DOM_NODE (style_element), NULL);
|
|
}
|
|
|
|
color_str = g_strdup_printf ("a { color: #%06x; }", e_rgba_to_value (color));
|
|
webkit_dom_html_element_set_inner_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (style_element), color_str, NULL);
|
|
|
|
g_free (color_str);
|
|
}
|
|
|
|
static void
|
|
set_link_color (EHTMLEditorView *view)
|
|
{
|
|
GdkColor *color = NULL;
|
|
GdkRGBA rgba;
|
|
GtkStyleContext *context;
|
|
|
|
context = gtk_widget_get_style_context (GTK_WIDGET (view));
|
|
gtk_style_context_get_style (
|
|
context, "link-color", &color, NULL);
|
|
|
|
if (color == NULL) {
|
|
rgba.alpha = 1;
|
|
rgba.red = 0;
|
|
rgba.green = 0;
|
|
rgba.blue = 1;
|
|
} else {
|
|
rgba.alpha = 1;
|
|
rgba.red = ((gdouble) color->red) / G_MAXUINT16;
|
|
rgba.green = ((gdouble) color->green) / G_MAXUINT16;
|
|
rgba.blue = ((gdouble) color->blue) / G_MAXUINT16;
|
|
}
|
|
|
|
e_html_editor_view_set_link_color (view, &rgba);
|
|
|
|
gdk_color_free (color);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_load_status_changed (EHTMLEditorView *view)
|
|
{
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitLoadStatus status;
|
|
|
|
status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view));
|
|
if (status != WEBKIT_LOAD_FINISHED)
|
|
return;
|
|
|
|
/* Dispatch queued operations - as we are using this just for load
|
|
* operations load just the latest request and throw away the rest. */
|
|
if (view->priv->post_reload_operations &&
|
|
!g_queue_is_empty (view->priv->post_reload_operations)) {
|
|
|
|
PostReloadOperation *op;
|
|
|
|
op = g_queue_pop_head (view->priv->post_reload_operations);
|
|
|
|
op->func (view, op->data);
|
|
|
|
if (op->data_free_func)
|
|
op->data_free_func (op->data);
|
|
g_free (op);
|
|
|
|
g_queue_clear (view->priv->post_reload_operations);
|
|
|
|
return;
|
|
}
|
|
|
|
view->priv->reload_in_progress = FALSE;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = webkit_dom_document_get_body (document);
|
|
|
|
webkit_dom_element_remove_attribute (WEBKIT_DOM_ELEMENT (body), "style");
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-message", "", NULL);
|
|
|
|
if (view->priv->convert_in_situ) {
|
|
html_editor_convert_view_content (view, NULL);
|
|
/* Make the quote marks non-selectable. */
|
|
disable_quote_marks_select (document);
|
|
html_editor_view_set_links_active (view, FALSE);
|
|
set_link_color (view);
|
|
view->priv->convert_in_situ = FALSE;
|
|
|
|
return;
|
|
}
|
|
|
|
/* Make the quote marks non-selectable. */
|
|
disable_quote_marks_select (document);
|
|
set_link_color (view);
|
|
html_editor_view_set_links_active (view, FALSE);
|
|
put_body_in_citation (document);
|
|
move_elements_to_body (document);
|
|
repair_gmail_blockquotes (document);
|
|
|
|
if (webkit_dom_element_get_attribute (WEBKIT_DOM_ELEMENT (body), "data-evo-draft")) {
|
|
/* Restore the selection how it was when the draft was saved */
|
|
e_html_editor_selection_move_caret_into_element (
|
|
document, WEBKIT_DOM_ELEMENT (body), FALSE);
|
|
e_html_editor_selection_restore (
|
|
e_html_editor_view_get_selection (view));
|
|
e_html_editor_view_remove_embed_styles (view);
|
|
}
|
|
|
|
/* The composer body could be empty in some case (loading an empty string
|
|
* or empty HTML. In that case create the initial paragraph. */
|
|
if (!webkit_dom_node_get_first_child (WEBKIT_DOM_NODE (body))) {
|
|
EHTMLEditorSelection *selection;
|
|
WebKitDOMElement *paragraph;
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
paragraph = prepare_paragraph (selection, document, TRUE);
|
|
webkit_dom_element_set_id (paragraph, "-x-evo-input-start");
|
|
webkit_dom_node_append_child (
|
|
WEBKIT_DOM_NODE (body), WEBKIT_DOM_NODE (paragraph), NULL);
|
|
e_html_editor_selection_restore (selection);
|
|
}
|
|
|
|
/* Register on input event that is called when the content (body) is modified */
|
|
register_input_event_listener_on_body (view);
|
|
register_html_events_handlers (view, body);
|
|
|
|
if (view->priv->html_mode)
|
|
change_cid_images_src_to_base64 (view);
|
|
|
|
if (view->priv->inline_spelling)
|
|
e_html_editor_view_force_spell_check (view);
|
|
else
|
|
e_html_editor_view_turn_spell_check_off (view);
|
|
}
|
|
|
|
static void
|
|
wrap_paragraphs_in_quoted_content (EHTMLEditorSelection *selection,
|
|
WebKitDOMDocument *document)
|
|
{
|
|
gint ii, length;
|
|
WebKitDOMNodeList *paragraphs;
|
|
|
|
paragraphs = webkit_dom_document_query_selector_all (
|
|
document, "blockquote[type=cite] > .-x-evo-paragraph", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (paragraphs);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMNode *paragraph;
|
|
|
|
paragraph = webkit_dom_node_list_item (paragraphs, ii);
|
|
|
|
e_html_editor_selection_wrap_paragraph (
|
|
selection, WEBKIT_DOM_ELEMENT (paragraph));
|
|
g_object_unref (paragraph);
|
|
}
|
|
g_object_unref (paragraphs);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_html_mode:
|
|
* @view: an #EHTMLEditorView
|
|
* @html_mode: @TRUE to enable HTML mode, @FALSE to enable plain text mode
|
|
*
|
|
* When switching from HTML to plain text mode, user will be prompted whether
|
|
* he/she really wants to switch the mode and lose all formatting. When user
|
|
* declines, the property is not changed. When they accept, the all formatting
|
|
* is lost.
|
|
*/
|
|
void
|
|
e_html_editor_view_set_html_mode (EHTMLEditorView *view,
|
|
gboolean html_mode)
|
|
{
|
|
EHTMLEditorSelection *selection;
|
|
gboolean is_from_new_message, converted, edit_as_new, message, convert;
|
|
gboolean reply, hide;
|
|
WebKitDOMElement *blockquote;
|
|
WebKitDOMHTMLElement *body;
|
|
WebKitDOMDocument *document;
|
|
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
selection = e_html_editor_view_get_selection (view);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
body = webkit_dom_document_get_body (document);
|
|
|
|
is_from_new_message = webkit_dom_element_has_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-new-message");
|
|
converted = webkit_dom_element_has_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-converted");
|
|
edit_as_new = webkit_dom_element_has_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-edit-as-new");
|
|
message = webkit_dom_element_has_attribute (
|
|
WEBKIT_DOM_ELEMENT (body), "data-message");
|
|
|
|
reply = !is_from_new_message && !edit_as_new && message;
|
|
hide = !reply && !converted;
|
|
|
|
convert = message && ((!hide && reply && !converted) || (edit_as_new && !converted));
|
|
|
|
/* If toggling from HTML to plain text mode, ask user first */
|
|
if (convert && view->priv->html_mode && !html_mode) {
|
|
if (!show_lose_formatting_dialog (view))
|
|
return;
|
|
|
|
view->priv->html_mode = html_mode;
|
|
|
|
convert_when_changing_composer_mode (view);
|
|
|
|
e_html_editor_selection_scroll_to_caret (selection);
|
|
|
|
goto out;
|
|
}
|
|
|
|
if (html_mode == view->priv->html_mode)
|
|
return;
|
|
|
|
view->priv->html_mode = html_mode;
|
|
|
|
/* Update fonts - in plain text we only want monospace */
|
|
e_html_editor_view_update_fonts (view);
|
|
|
|
blockquote = webkit_dom_document_query_selector (
|
|
document, "blockquote[type|=cite]", NULL);
|
|
|
|
if (view->priv->html_mode) {
|
|
if (blockquote)
|
|
e_html_editor_view_dequote_plain_text (view);
|
|
|
|
toggle_paragraphs_style (view);
|
|
toggle_smileys (view);
|
|
remove_wrapping_from_view (view);
|
|
} else {
|
|
gchar *plain;
|
|
|
|
e_html_editor_selection_save (selection);
|
|
|
|
if (blockquote) {
|
|
wrap_paragraphs_in_quoted_content (selection, document);
|
|
quote_plain_text_elements_after_wrapping_in_document (
|
|
document);
|
|
}
|
|
|
|
toggle_paragraphs_style (view);
|
|
toggle_smileys (view);
|
|
remove_images (view);
|
|
remove_background_images_in_document (document);
|
|
|
|
plain = process_content_for_mode_change (view);
|
|
|
|
if (*plain) {
|
|
webkit_dom_html_element_set_outer_html (
|
|
WEBKIT_DOM_HTML_ELEMENT (
|
|
webkit_dom_document_get_document_element (document)),
|
|
plain,
|
|
NULL);
|
|
e_html_editor_selection_restore (selection);
|
|
e_html_editor_view_force_spell_check (view);
|
|
}
|
|
|
|
g_free (plain);
|
|
}
|
|
|
|
out:
|
|
g_object_notify (G_OBJECT (view), "html-mode");
|
|
}
|
|
|
|
static void
|
|
html_editor_view_drag_end_cb (EHTMLEditorView *view,
|
|
GdkDragContext *context)
|
|
{
|
|
gint ii, length;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMDOMWindow *window;
|
|
WebKitDOMDOMSelection *selection;
|
|
WebKitDOMNodeList *list;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
/* When the image is DnD inside the view WebKit removes the wrapper that
|
|
* is used for resizing the image, so we have to recreate it again. */
|
|
list = webkit_dom_document_query_selector_all (document, ":not(span) > img[data-inline]", NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMElement *element;
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
|
|
element = webkit_dom_document_create_element (document, "span", NULL);
|
|
webkit_dom_element_set_class_name (element, "-x-evo-resizable-wrapper");
|
|
|
|
webkit_dom_node_insert_before (
|
|
webkit_dom_node_get_parent_node (node),
|
|
WEBKIT_DOM_NODE (element),
|
|
node,
|
|
NULL);
|
|
|
|
webkit_dom_node_append_child (WEBKIT_DOM_NODE (element), node, NULL);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
/* When the image is moved the new selection is created after after it, so
|
|
* lets collapse the selection to have the caret right after the image. */
|
|
window = webkit_dom_document_get_default_view (document);
|
|
selection = webkit_dom_dom_window_get_selection (window);
|
|
if (length > 0)
|
|
webkit_dom_dom_selection_collapse_to_start (selection, NULL);
|
|
else
|
|
webkit_dom_dom_selection_collapse_to_end (selection, NULL);
|
|
|
|
e_html_editor_view_force_spell_check (view);
|
|
}
|
|
|
|
static void
|
|
e_html_editor_view_init (EHTMLEditorView *view)
|
|
{
|
|
WebKitWebSettings *settings;
|
|
GSettings *g_settings;
|
|
GSettingsSchema *settings_schema;
|
|
ESpellChecker *checker;
|
|
gchar **languages;
|
|
gchar *comma_separated;
|
|
const gchar *user_cache_dir;
|
|
|
|
view->priv = E_HTML_EDITOR_VIEW_GET_PRIVATE (view);
|
|
|
|
webkit_web_view_set_editable (WEBKIT_WEB_VIEW (view), TRUE);
|
|
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
|
|
|
|
g_object_set (
|
|
G_OBJECT (settings),
|
|
"enable-developer-extras", TRUE,
|
|
"enable-dom-paste", TRUE,
|
|
"enable-file-access-from-file-uris", TRUE,
|
|
"enable-plugins", FALSE,
|
|
"enable-scripts", FALSE,
|
|
"enable-spell-checking", TRUE,
|
|
"respect-image-orientation", TRUE,
|
|
NULL);
|
|
|
|
webkit_web_view_set_settings (WEBKIT_WEB_VIEW (view), settings);
|
|
|
|
view->priv->old_settings = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
|
|
|
|
/* Override the spell-checker, use our own */
|
|
checker = e_spell_checker_new ();
|
|
webkit_set_text_checker (G_OBJECT (checker));
|
|
g_object_unref (checker);
|
|
|
|
/* Don't use CSS when possible to preserve compatibility with older
|
|
* versions of Evolution or other MUAs */
|
|
e_html_editor_view_exec_command (
|
|
view, E_HTML_EDITOR_VIEW_COMMAND_STYLE_WITH_CSS, "false");
|
|
|
|
g_signal_connect (
|
|
view, "drag-end",
|
|
G_CALLBACK (html_editor_view_drag_end_cb), NULL);
|
|
g_signal_connect (
|
|
view, "user-changed-contents",
|
|
G_CALLBACK (html_editor_view_user_changed_contents_cb), NULL);
|
|
g_signal_connect (
|
|
view, "selection-changed",
|
|
G_CALLBACK (html_editor_view_selection_changed_cb), NULL);
|
|
g_signal_connect (
|
|
view, "should-show-delete-interface-for-element",
|
|
G_CALLBACK (html_editor_view_should_show_delete_interface_for_element), NULL);
|
|
g_signal_connect (
|
|
view, "resource-request-starting",
|
|
G_CALLBACK (html_editor_view_resource_requested), NULL);
|
|
g_signal_connect (
|
|
view, "notify::load-status",
|
|
G_CALLBACK (html_editor_view_load_status_changed), NULL);
|
|
|
|
view->priv->selection = g_object_new (
|
|
E_TYPE_HTML_EDITOR_SELECTION,
|
|
"html-editor-view", view,
|
|
NULL);
|
|
|
|
g_settings = e_util_ref_settings ("org.gnome.desktop.interface");
|
|
g_signal_connect (
|
|
g_settings, "changed::font-name",
|
|
G_CALLBACK (e_html_editor_settings_changed_cb), view);
|
|
g_signal_connect (
|
|
g_settings, "changed::monospace-font-name",
|
|
G_CALLBACK (e_html_editor_settings_changed_cb), view);
|
|
view->priv->font_settings = g_settings;
|
|
|
|
g_settings = e_util_ref_settings ("org.gnome.evolution.mail");
|
|
view->priv->mail_settings = g_settings;
|
|
|
|
/* This schema is optional. Use if available. */
|
|
settings_schema = g_settings_schema_source_lookup (
|
|
g_settings_schema_source_get_default (),
|
|
"org.gnome.settings-daemon.plugins.xsettings", FALSE);
|
|
if (settings_schema != NULL) {
|
|
g_settings = e_util_ref_settings ("org.gnome.settings-daemon.plugins.xsettings");
|
|
g_signal_connect (
|
|
settings, "changed::antialiasing",
|
|
G_CALLBACK (e_html_editor_settings_changed_cb), view);
|
|
view->priv->aliasing_settings = g_settings;
|
|
}
|
|
|
|
view->priv->inline_images = g_hash_table_new_full (
|
|
g_str_hash, g_str_equal,
|
|
(GDestroyNotify) g_free,
|
|
(GDestroyNotify) g_free);
|
|
|
|
e_html_editor_view_update_fonts (view);
|
|
|
|
/* Give spell check languages to WebKit */
|
|
languages = e_spell_checker_list_active_languages (checker, NULL);
|
|
comma_separated = g_strjoinv (",", languages);
|
|
g_strfreev (languages);
|
|
|
|
g_object_set (
|
|
G_OBJECT (settings),
|
|
"spell-checking-languages", comma_separated,
|
|
NULL);
|
|
|
|
g_free (comma_separated);
|
|
|
|
view->priv->body_input_event_removed = TRUE;
|
|
view->priv->is_message_from_draft = FALSE;
|
|
view->priv->is_message_from_selection = FALSE;
|
|
view->priv->is_message_from_edit_as_new = FALSE;
|
|
view->priv->remove_initial_input_line = FALSE;
|
|
view->priv->convert_in_situ = FALSE;
|
|
view->priv->return_key_pressed = FALSE;
|
|
view->priv->space_key_pressed = FALSE;
|
|
view->priv->smiley_written = FALSE;
|
|
|
|
g_object_set (
|
|
G_OBJECT (settings),
|
|
"enable-scripts", FALSE,
|
|
"enable-plugins", FALSE,
|
|
NULL);
|
|
|
|
/* Make WebKit think we are displaying a local file, so that it
|
|
* does not block loading resources from file:// protocol */
|
|
webkit_web_view_load_string (
|
|
WEBKIT_WEB_VIEW (view), "", "text/html", "UTF-8", "file://");
|
|
|
|
if (emd_global_http_cache == NULL) {
|
|
user_cache_dir = e_get_user_cache_dir ();
|
|
emd_global_http_cache = camel_data_cache_new (user_cache_dir, NULL);
|
|
|
|
/* cache expiry - 2 hour access, 1 day max */
|
|
camel_data_cache_set_expire_age (
|
|
emd_global_http_cache, 24 * 60 * 60);
|
|
camel_data_cache_set_expire_access (
|
|
emd_global_http_cache, 2 * 60 * 60);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_inline_spelling:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Returns whether automatic spellchecking is enabled or not. When enabled,
|
|
* editor will perform spellchecking as user is typing. Otherwise spellcheck
|
|
* has to be run manually from menu.
|
|
*
|
|
* Returns: @TRUE when automatic spellchecking is enabled, @FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
e_html_editor_view_get_inline_spelling (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->inline_spelling;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_inline_spelling:
|
|
* @view: an #EHTMLEditorView
|
|
* @inline_spelling: @TRUE to enable automatic spellchecking, @FALSE otherwise
|
|
*
|
|
* Enables or disables automatic spellchecking.
|
|
*/
|
|
void
|
|
e_html_editor_view_set_inline_spelling (EHTMLEditorView *view,
|
|
gboolean inline_spelling)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
if (view->priv->inline_spelling == inline_spelling)
|
|
return;
|
|
|
|
view->priv->inline_spelling = inline_spelling;
|
|
|
|
if (inline_spelling)
|
|
e_html_editor_view_force_spell_check (view);
|
|
else
|
|
e_html_editor_view_turn_spell_check_off (view);
|
|
|
|
g_object_notify (G_OBJECT (view), "inline-spelling");
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_magic_links:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Returns whether automatic links conversion is enabled. When enabled, the editor
|
|
* will automatically convert any HTTP links into clickable HTML links.
|
|
*
|
|
* Returns: @TRUE when magic links are enabled, @FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
e_html_editor_view_get_magic_links (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->magic_links;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_magic_links:
|
|
* @view: an #EHTMLEditorView
|
|
* @magic_links: @TRUE to enable magic links, @FALSE to disable them
|
|
*
|
|
* Enables or disables automatic links conversion.
|
|
*/
|
|
void
|
|
e_html_editor_view_set_magic_links (EHTMLEditorView *view,
|
|
gboolean magic_links)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
if (view->priv->magic_links == magic_links)
|
|
return;
|
|
|
|
view->priv->magic_links = magic_links;
|
|
|
|
g_object_notify (G_OBJECT (view), "magic-links");
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_magic_smileys:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Returns whether automatic conversion of smileys is enabled or disabled. When
|
|
* enabled, the editor will automatically convert text smileys ( :-), ;-),...)
|
|
* into images or Unicode characters.
|
|
*
|
|
* Returns: @TRUE when magic smileys are enabled, @FALSE otherwise.
|
|
*/
|
|
gboolean
|
|
e_html_editor_view_get_magic_smileys (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->magic_smileys;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_magic_smileys:
|
|
* @view: an #EHTMLEditorView
|
|
* @magic_smileys: @TRUE to enable magic smileys, @FALSE to disable them
|
|
*
|
|
* Enables or disables magic smileys.
|
|
*/
|
|
void
|
|
e_html_editor_view_set_magic_smileys (EHTMLEditorView *view,
|
|
gboolean magic_smileys)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
if (view->priv->magic_smileys == magic_smileys)
|
|
return;
|
|
|
|
view->priv->magic_smileys = magic_smileys;
|
|
|
|
g_object_notify (G_OBJECT (view), "magic-smileys");
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_unicode_smileys:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Returns whether to use Unicode characters for smileys.
|
|
*
|
|
* Returns: @TRUE when Unicode characters should be used, @FALSE otherwise.
|
|
*
|
|
* Since: 3.16
|
|
*/
|
|
gboolean
|
|
e_html_editor_view_get_unicode_smileys (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->unicode_smileys;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_unicode_smileys:
|
|
* @view: an #EHTMLEditorView
|
|
* @unicode_smileys: @TRUE to use Unicode characters, @FALSE to use images
|
|
*
|
|
* Enables or disables the usage of Unicode characters for smileys.
|
|
*
|
|
* Since: 3.16
|
|
*/
|
|
void
|
|
e_html_editor_view_set_unicode_smileys (EHTMLEditorView *view,
|
|
gboolean unicode_smileys)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
if (view->priv->unicode_smileys == unicode_smileys)
|
|
return;
|
|
|
|
view->priv->unicode_smileys = unicode_smileys;
|
|
|
|
g_object_notify (G_OBJECT (view), "unicode-smileys");
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_spell_checker:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Returns an #ESpellChecker object that is used to perform spellchecking.
|
|
*
|
|
* Returns: An always-valid #ESpellChecker object
|
|
*/
|
|
ESpellChecker *
|
|
e_html_editor_view_get_spell_checker (EHTMLEditorView *view)
|
|
{
|
|
return E_SPELL_CHECKER (webkit_get_text_checker ());
|
|
}
|
|
|
|
static CamelMimePart *
|
|
e_html_editor_view_add_inline_image_from_element (EHTMLEditorView *view,
|
|
WebKitDOMElement *element,
|
|
const gchar *attribute,
|
|
const gchar *uid_domain)
|
|
{
|
|
CamelStream *stream;
|
|
CamelDataWrapper *wrapper;
|
|
CamelMimePart *part = NULL;
|
|
gsize decoded_size;
|
|
gssize size;
|
|
gchar *mime_type = NULL;
|
|
gchar *element_src, *cid, *name;
|
|
const gchar *base64_encoded_data;
|
|
guchar *base64_decoded_data = NULL;
|
|
|
|
if (!WEBKIT_DOM_IS_ELEMENT (element)) {
|
|
return NULL;
|
|
}
|
|
|
|
element_src = webkit_dom_element_get_attribute (
|
|
WEBKIT_DOM_ELEMENT (element), attribute);
|
|
|
|
base64_encoded_data = strstr (element_src, ";base64,");
|
|
if (!base64_encoded_data)
|
|
goto out;
|
|
|
|
mime_type = g_strndup (
|
|
element_src + 5,
|
|
base64_encoded_data - (strstr (element_src, "data:") + 5));
|
|
|
|
/* Move to actual data */
|
|
base64_encoded_data += 8;
|
|
|
|
base64_decoded_data = g_base64_decode (base64_encoded_data, &decoded_size);
|
|
|
|
stream = camel_stream_mem_new ();
|
|
size = camel_stream_write (
|
|
stream, (gchar *) base64_decoded_data, decoded_size, NULL, NULL);
|
|
|
|
if (size == -1)
|
|
goto out;
|
|
|
|
wrapper = camel_data_wrapper_new ();
|
|
camel_data_wrapper_construct_from_stream_sync (
|
|
wrapper, stream, NULL, NULL);
|
|
g_object_unref (stream);
|
|
|
|
camel_data_wrapper_set_mime_type (wrapper, mime_type);
|
|
|
|
part = camel_mime_part_new ();
|
|
camel_medium_set_content (CAMEL_MEDIUM (part), wrapper);
|
|
g_object_unref (wrapper);
|
|
|
|
cid = camel_header_msgid_generate (uid_domain);
|
|
camel_mime_part_set_content_id (part, cid);
|
|
g_free (cid);
|
|
name = webkit_dom_element_get_attribute (element, "data-name");
|
|
camel_mime_part_set_filename (part, name);
|
|
g_free (name);
|
|
camel_mime_part_set_encoding (part, CAMEL_TRANSFER_ENCODING_BASE64);
|
|
out:
|
|
g_free (mime_type);
|
|
g_free (element_src);
|
|
g_free (base64_decoded_data);
|
|
|
|
return part;
|
|
}
|
|
|
|
static GList *
|
|
html_editor_view_get_parts_for_inline_images (EHTMLEditorView *view,
|
|
const gchar *uid_domain,
|
|
GHashTable **inline_images)
|
|
{
|
|
GList *parts = NULL;
|
|
gint length, ii;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNodeList *list;
|
|
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);
|
|
g_return_val_if_fail (inline_images != NULL, NULL);
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
list = webkit_dom_document_query_selector_all (document, "img[data-inline]", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (list);
|
|
if (length == 0)
|
|
goto background;
|
|
|
|
*inline_images = g_hash_table_new_full (
|
|
g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
for (ii = 0; ii < length; ii++) {
|
|
const gchar *id;
|
|
gchar *cid;
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
gchar *src = webkit_dom_element_get_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "src");
|
|
|
|
if ((id = g_hash_table_lookup (*inline_images, src)) != NULL) {
|
|
cid = g_strdup_printf ("cid:%s", id);
|
|
g_free (src);
|
|
} else {
|
|
CamelMimePart *part;
|
|
|
|
part = e_html_editor_view_add_inline_image_from_element (
|
|
view, WEBKIT_DOM_ELEMENT (node), "src", uid_domain);
|
|
parts = g_list_append (parts, part);
|
|
|
|
id = camel_mime_part_get_content_id (part);
|
|
cid = g_strdup_printf ("cid:%s", id);
|
|
|
|
g_hash_table_insert (*inline_images, src, g_strdup (id));
|
|
}
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "src", cid, NULL);
|
|
g_free (cid);
|
|
g_object_unref (node);
|
|
}
|
|
g_object_unref (list);
|
|
|
|
background:
|
|
list = webkit_dom_document_query_selector_all (
|
|
document, "[data-inline][background]", NULL);
|
|
|
|
length = webkit_dom_node_list_get_length (list);
|
|
if (length == 0) {
|
|
g_object_unref (list);
|
|
return parts;
|
|
}
|
|
if (!*inline_images)
|
|
*inline_images = g_hash_table_new_full (
|
|
g_str_hash, g_str_equal, g_free, g_free);
|
|
|
|
for (ii = 0; ii < length; ii++) {
|
|
CamelMimePart *part;
|
|
const gchar *id;
|
|
gchar *cid = NULL;
|
|
WebKitDOMNode *node = webkit_dom_node_list_item (list, ii);
|
|
gchar *src = webkit_dom_element_get_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "background");
|
|
|
|
if ((id = g_hash_table_lookup (*inline_images, src)) != NULL) {
|
|
cid = g_strdup_printf ("cid:%s", id);
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
|
|
g_free (src);
|
|
} else {
|
|
part = e_html_editor_view_add_inline_image_from_element (
|
|
view, WEBKIT_DOM_ELEMENT (node), "background", uid_domain);
|
|
if (part) {
|
|
parts = g_list_append (parts, part);
|
|
id = camel_mime_part_get_content_id (part);
|
|
g_hash_table_insert (*inline_images, src, g_strdup (id));
|
|
cid = g_strdup_printf ("cid:%s", id);
|
|
webkit_dom_element_set_attribute (
|
|
WEBKIT_DOM_ELEMENT (node), "background", cid, NULL);
|
|
} else
|
|
g_free (src);
|
|
}
|
|
g_object_unref (node);
|
|
g_free (cid);
|
|
}
|
|
|
|
g_object_unref (list);
|
|
|
|
return parts;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_add_inline_image_from_mime_part:
|
|
* @composer: a composer object
|
|
* @part: a CamelMimePart containing image data
|
|
*
|
|
* This adds the mime part @part to @composer as an inline image.
|
|
**/
|
|
void
|
|
e_html_editor_view_add_inline_image_from_mime_part (EHTMLEditorView *view,
|
|
CamelMimePart *part)
|
|
{
|
|
CamelDataWrapper *dw;
|
|
CamelStream *stream;
|
|
GByteArray *byte_array;
|
|
gchar *src, *base64_encoded, *mime_type, *cid_src;
|
|
const gchar *cid, *name;
|
|
|
|
stream = camel_stream_mem_new ();
|
|
dw = camel_medium_get_content (CAMEL_MEDIUM (part));
|
|
g_return_if_fail (dw);
|
|
|
|
mime_type = camel_data_wrapper_get_mime_type (dw);
|
|
camel_data_wrapper_decode_to_stream_sync (dw, stream, NULL, NULL);
|
|
camel_stream_close (stream, NULL, NULL);
|
|
|
|
byte_array = camel_stream_mem_get_byte_array (CAMEL_STREAM_MEM (stream));
|
|
|
|
if (!byte_array->data)
|
|
return;
|
|
|
|
base64_encoded = g_base64_encode ((const guchar *) byte_array->data, byte_array->len);
|
|
|
|
name = camel_mime_part_get_filename (part);
|
|
/* Insert file name before new src */
|
|
src = g_strconcat (name, ";data:", mime_type, ";base64,", base64_encoded, NULL);
|
|
|
|
cid = camel_mime_part_get_content_id (part);
|
|
if (!cid) {
|
|
camel_mime_part_set_content_id (part, NULL);
|
|
cid = camel_mime_part_get_content_id (part);
|
|
}
|
|
cid_src = g_strdup_printf ("cid:%s", cid);
|
|
|
|
g_hash_table_insert (view->priv->inline_images, cid_src, src);
|
|
|
|
g_free (base64_encoded);
|
|
g_free (mime_type);
|
|
g_object_unref (stream);
|
|
}
|
|
|
|
static void
|
|
restore_images (gchar *key,
|
|
gchar *value,
|
|
EHTMLEditorView *view)
|
|
{
|
|
gchar *selector;
|
|
gint length, ii;
|
|
WebKitDOMDocument *document;
|
|
WebKitDOMNodeList *list;
|
|
|
|
document = webkit_web_view_get_dom_document (WEBKIT_WEB_VIEW (view));
|
|
|
|
selector = g_strconcat ("[data-inline][background=\"cid:", value, "\"]", NULL);
|
|
list = webkit_dom_document_query_selector_all (document, selector, NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMElement *element = WEBKIT_DOM_ELEMENT (
|
|
webkit_dom_node_list_item (list, ii));
|
|
|
|
webkit_dom_element_set_attribute (element, "background", key, NULL);
|
|
g_object_unref (element);
|
|
}
|
|
g_free (selector);
|
|
g_object_unref (list);
|
|
|
|
selector = g_strconcat ("[data-inline][src=\"cid:", value, "\"]", NULL);
|
|
list = webkit_dom_document_query_selector_all (document, selector, NULL);
|
|
length = webkit_dom_node_list_get_length (list);
|
|
for (ii = 0; ii < length; ii++) {
|
|
WebKitDOMElement *element = WEBKIT_DOM_ELEMENT (
|
|
webkit_dom_node_list_item (list, ii));
|
|
|
|
webkit_dom_element_set_attribute (element, "src", key, NULL);
|
|
g_object_unref (element);
|
|
}
|
|
g_free (selector);
|
|
g_object_unref (list);
|
|
}
|
|
|
|
static void
|
|
html_editor_view_restore_images (EHTMLEditorView *view,
|
|
GHashTable **inline_images)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
g_hash_table_foreach (*inline_images, (GHFunc) restore_images, view);
|
|
|
|
/* Remove all hashed images as user can modify them. */
|
|
g_hash_table_remove_all (*inline_images);
|
|
g_hash_table_destroy (*inline_images);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_text_html:
|
|
* @view: an #EHTMLEditorView:
|
|
*
|
|
* Returns processed HTML content of the editor document (with elements attributes
|
|
* used in Evolution composer)
|
|
*
|
|
* Returns: A newly allocated string
|
|
*/
|
|
gchar *
|
|
e_html_editor_view_get_text_html (EHTMLEditorView *view,
|
|
const gchar *from_domain,
|
|
GList **inline_images)
|
|
{
|
|
gchar *html = NULL;
|
|
GHashTable *inline_images_to_restore = NULL;
|
|
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), NULL);
|
|
|
|
if (inline_images && from_domain)
|
|
*inline_images = html_editor_view_get_parts_for_inline_images (
|
|
view, from_domain, &inline_images_to_restore);
|
|
|
|
html = process_content_for_html (view);
|
|
|
|
if (inline_images && from_domain && inline_images_to_restore)
|
|
html_editor_view_restore_images (view, &inline_images_to_restore);
|
|
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_text_html_for_drafts:
|
|
* @view: an #EHTMLEditorView:
|
|
*
|
|
* Returns HTML content of the editor document (without elements attributes
|
|
* used in Evolution composer)
|
|
*
|
|
* Returns: A newly allocated string
|
|
*/
|
|
gchar *
|
|
e_html_editor_view_get_text_html_for_drafts (EHTMLEditorView *view)
|
|
{
|
|
return process_content_for_saving_as_draft (view);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_get_text_plain:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Returns plain text content of the @view. The algorithm removes any
|
|
* formatting or styles from the document and keeps only the text and line
|
|
* breaks.
|
|
*
|
|
* Returns: A newly allocated string with plain text content of the document.
|
|
*/
|
|
gchar *
|
|
e_html_editor_view_get_text_plain (EHTMLEditorView *view)
|
|
{
|
|
return process_content_for_plain_text (view);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_convert_and_insert_plain_text (EHTMLEditorView *view,
|
|
const gchar *text)
|
|
{
|
|
html_editor_view_insert_converted_html_into_selection (view, FALSE, text);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_convert_and_insert_html_to_plain_text (EHTMLEditorView *view,
|
|
const gchar *html)
|
|
{
|
|
html_editor_view_insert_converted_html_into_selection (view, TRUE, html);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_text_html:
|
|
* @view: an #EHTMLEditorView
|
|
* @text: HTML code to load into the editor
|
|
*
|
|
* Loads given @text into the editor, destroying any content already present.
|
|
*/
|
|
void
|
|
e_html_editor_view_set_text_html (EHTMLEditorView *view,
|
|
const gchar *text)
|
|
{
|
|
WebKitLoadStatus status;
|
|
|
|
/* It can happen that the view is not ready yet (it is in the middle of
|
|
* another load operation) so we have to queue the current operation and
|
|
* redo it again when the view is ready. This was happening when loading
|
|
* the stuff in EMailSignatureEditor. */
|
|
status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view));
|
|
if (status != WEBKIT_LOAD_FINISHED) {
|
|
html_editor_view_queue_post_reload_operation (
|
|
view,
|
|
(PostReloadOperationFunc) e_html_editor_view_set_text_html,
|
|
g_strdup (text),
|
|
g_free);
|
|
return;
|
|
}
|
|
|
|
view->priv->reload_in_progress = TRUE;
|
|
|
|
if (view->priv->is_message_from_draft) {
|
|
webkit_web_view_load_string (
|
|
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
|
|
return;
|
|
}
|
|
|
|
if (view->priv->is_message_from_selection && !view->priv->html_mode) {
|
|
if (text && *text)
|
|
view->priv->convert_in_situ = TRUE;
|
|
webkit_web_view_load_string (
|
|
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
|
|
return;
|
|
}
|
|
|
|
/* Only convert messages that are in HTML */
|
|
if (!view->priv->html_mode) {
|
|
if (strstr (text, "<!-- text/html -->")) {
|
|
if (!show_lose_formatting_dialog (view)) {
|
|
e_html_editor_view_set_html_mode (view, TRUE);
|
|
webkit_web_view_load_string (
|
|
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
|
|
return;
|
|
}
|
|
}
|
|
if (text && *text)
|
|
view->priv->convert_in_situ = TRUE;
|
|
webkit_web_view_load_string (
|
|
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
|
|
} else
|
|
webkit_web_view_load_string (
|
|
WEBKIT_WEB_VIEW (view), text, NULL, NULL, "file://");
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_set_text_plain:
|
|
* @view: an #EHTMLEditorView
|
|
* @text: A plain text to load into the editor
|
|
*
|
|
* Loads given @text into the editor, destryoing any content already present.
|
|
*/
|
|
void
|
|
e_html_editor_view_set_text_plain (EHTMLEditorView *view,
|
|
const gchar *text)
|
|
{
|
|
WebKitLoadStatus status;
|
|
|
|
/* It can happen that the view is not ready yet (it is in the middle of
|
|
* another load operation) so we have to queue the current operation and
|
|
* redo it again when the view is ready. This was happening when loading
|
|
* the stuff in EMailSignatureEditor. */
|
|
status = webkit_web_view_get_load_status (WEBKIT_WEB_VIEW (view));
|
|
if (status != WEBKIT_LOAD_FINISHED) {
|
|
html_editor_view_queue_post_reload_operation (
|
|
view,
|
|
(PostReloadOperationFunc) e_html_editor_view_set_text_plain,
|
|
g_strdup (text),
|
|
g_free);
|
|
return;
|
|
}
|
|
|
|
view->priv->reload_in_progress = TRUE;
|
|
|
|
html_editor_convert_view_content (view, text);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_paste_as_text:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Pastes current content of clipboard into the editor without formatting
|
|
*/
|
|
void
|
|
e_html_editor_view_paste_as_text (EHTMLEditorView *view)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
html_editor_view_paste_as_text (view);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_paste_clipboard_quoted:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Pastes current content of clipboard into the editor as quoted text
|
|
*/
|
|
void
|
|
e_html_editor_view_paste_clipboard_quoted (EHTMLEditorView *view)
|
|
{
|
|
EHTMLEditorViewClass *class;
|
|
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
class = E_HTML_EDITOR_VIEW_GET_CLASS (view);
|
|
g_return_if_fail (class->paste_clipboard_quoted != NULL);
|
|
|
|
class->paste_clipboard_quoted (view);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_update_fonts:
|
|
* @view: an #EHTMLEditorView
|
|
*
|
|
* Forces the editor to reload font settings from WebKitWebSettings and apply
|
|
* it on the content of the editor document.
|
|
*/
|
|
void
|
|
e_html_editor_view_update_fonts (EHTMLEditorView *view)
|
|
{
|
|
gboolean mark_citations, use_custom_font;
|
|
GdkColor *visited = NULL;
|
|
gchar *base64, *font, *aa = NULL, *citation_color;
|
|
const gchar *styles[] = { "normal", "oblique", "italic" };
|
|
const gchar *smoothing = NULL;
|
|
GString *stylesheet;
|
|
GtkStyleContext *context;
|
|
PangoFontDescription *ms, *vw;
|
|
WebKitWebSettings *settings;
|
|
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
use_custom_font = g_settings_get_boolean (
|
|
view->priv->mail_settings, "use-custom-font");
|
|
|
|
if (use_custom_font) {
|
|
font = g_settings_get_string (
|
|
view->priv->mail_settings, "monospace-font");
|
|
ms = pango_font_description_from_string (font ? font : "monospace 10");
|
|
g_free (font);
|
|
} else {
|
|
font = g_settings_get_string (
|
|
view->priv->font_settings, "monospace-font-name");
|
|
ms = pango_font_description_from_string (font ? font : "monospace 10");
|
|
g_free (font);
|
|
}
|
|
|
|
if (view->priv->html_mode) {
|
|
if (use_custom_font) {
|
|
font = g_settings_get_string (
|
|
view->priv->mail_settings, "variable-width-font");
|
|
vw = pango_font_description_from_string (font ? font : "serif 10");
|
|
g_free (font);
|
|
} else {
|
|
font = g_settings_get_string (
|
|
view->priv->font_settings, "font-name");
|
|
vw = pango_font_description_from_string (font ? font : "serif 10");
|
|
g_free (font);
|
|
}
|
|
} else {
|
|
/* When in plain text mode, force monospace font */
|
|
vw = pango_font_description_copy (ms);
|
|
}
|
|
|
|
stylesheet = g_string_new ("");
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"body {\n"
|
|
" font-family: '%s';\n"
|
|
" font-size: %dpt;\n"
|
|
" font-weight: %d;\n"
|
|
" font-style: %s;\n"
|
|
" -webkit-line-break: after-white-space;\n",
|
|
pango_font_description_get_family (vw),
|
|
pango_font_description_get_size (vw) / PANGO_SCALE,
|
|
pango_font_description_get_weight (vw),
|
|
styles[pango_font_description_get_style (vw)]);
|
|
|
|
if (view->priv->aliasing_settings != NULL)
|
|
aa = g_settings_get_string (
|
|
view->priv->aliasing_settings, "antialiasing");
|
|
|
|
if (g_strcmp0 (aa, "none") == 0)
|
|
smoothing = "none";
|
|
else if (g_strcmp0 (aa, "grayscale") == 0)
|
|
smoothing = "antialiased";
|
|
else if (g_strcmp0 (aa, "rgba") == 0)
|
|
smoothing = "subpixel-antialiased";
|
|
|
|
if (smoothing != NULL)
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
" -webkit-font-smoothing: %s;\n",
|
|
smoothing);
|
|
|
|
g_free (aa);
|
|
|
|
g_string_append (stylesheet, "}\n");
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"pre,code,.pre {\n"
|
|
" font-family: '%s';\n"
|
|
" font-size: %dpt;\n"
|
|
" font-weight: %d;\n"
|
|
" font-style: %s;\n"
|
|
"}",
|
|
pango_font_description_get_family (ms),
|
|
pango_font_description_get_size (ms) / PANGO_SCALE,
|
|
pango_font_description_get_weight (ms),
|
|
styles[pango_font_description_get_style (ms)]);
|
|
|
|
context = gtk_widget_get_style_context (GTK_WIDGET (view));
|
|
gtk_style_context_get_style (
|
|
context, "visited-link-color", &visited, NULL);
|
|
|
|
if (visited == NULL) {
|
|
visited = g_slice_new0 (GdkColor);
|
|
visited->red = G_MAXINT16;
|
|
}
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"a:visited {\n"
|
|
" color: #%06x;\n"
|
|
"}\n",
|
|
e_color_to_value (visited));
|
|
|
|
/* See bug #689777 for details */
|
|
g_string_append (
|
|
stylesheet,
|
|
"p,pre,code,address {\n"
|
|
" margin: 0;\n"
|
|
"}\n"
|
|
"h1,h2,h3,h4,h5,h6 {\n"
|
|
" margin-top: 0.2em;\n"
|
|
" margin-bottom: 0.2em;\n"
|
|
"}\n");
|
|
|
|
/* When inserting a table into contenteditable element the width of the
|
|
* cells is nearly zero and the td { min-height } doesn't work so put
|
|
* unicode zero width space before each cell. */
|
|
g_string_append (
|
|
stylesheet,
|
|
"td::before {\n"
|
|
" content: \"\xe2\x80\x8b\";"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
"img "
|
|
"{\n"
|
|
" height: inherit; \n"
|
|
" width: inherit; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
"span.-x-evo-resizable-wrapper:hover "
|
|
"{\n"
|
|
" outline: 1px dashed red; \n"
|
|
" resize: both; \n"
|
|
" overflow: hidden; \n"
|
|
" display: inline-block; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
"ul,ol "
|
|
"{\n"
|
|
" -webkit-padding-start: 7ch; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
".-x-evo-align-left "
|
|
"{\n"
|
|
" text-align: left; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
".-x-evo-align-center "
|
|
"{\n"
|
|
" text-align: center; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
".-x-evo-align-right "
|
|
"{\n"
|
|
" text-align: right; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
".-x-evo-list-item-align-left "
|
|
"{\n"
|
|
" text-align: left; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
".-x-evo-list-item-align-center "
|
|
"{\n"
|
|
" text-align: center; \n"
|
|
" -webkit-padding-start: 0ch; \n"
|
|
" margin-left: -3ch; \n"
|
|
" margin-right: 1ch; \n"
|
|
" list-style-position: inside; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
".-x-evo-list-item-align-right "
|
|
"{\n"
|
|
" text-align: right; \n"
|
|
" -webkit-padding-start: 0ch; \n"
|
|
" margin-left: -3ch; \n"
|
|
" margin-right: 1ch; \n"
|
|
" list-style-position: inside; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
"ol,ul "
|
|
"{\n"
|
|
" -webkit-margin-before: 0em; \n"
|
|
" -webkit-margin-after: 0em; \n"
|
|
"}\n");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
"blockquote "
|
|
"{\n"
|
|
" -webkit-margin-before: 0em; \n"
|
|
" -webkit-margin-after: 0em; \n"
|
|
"}\n");
|
|
|
|
citation_color = g_settings_get_string (
|
|
view->priv->mail_settings, "citation-color");
|
|
mark_citations = g_settings_get_boolean (
|
|
view->priv->mail_settings, "mark-citations");
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
"blockquote[type=cite] "
|
|
"{\n"
|
|
" padding: 0.0ex 0ex;\n"
|
|
" margin: 0ex;\n"
|
|
" -webkit-margin-start: 0em; \n"
|
|
" -webkit-margin-end : 0em; \n");
|
|
|
|
if (mark_citations && citation_color)
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
" color: %s !important; \n",
|
|
citation_color);
|
|
|
|
g_free (citation_color);
|
|
citation_color = NULL;
|
|
|
|
g_string_append (stylesheet, "}\n");
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
".-x-evo-quote-character "
|
|
"{\n"
|
|
" color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (1));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character"
|
|
"{\n"
|
|
" color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (2));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character"
|
|
"{\n"
|
|
" color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (3));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character"
|
|
"{\n"
|
|
" color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (4));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character+"
|
|
".-x-evo-quote-character"
|
|
"{\n"
|
|
" color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (5));
|
|
|
|
g_string_append (
|
|
stylesheet,
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"{\n"
|
|
" padding: 0ch 1ch 0ch 1ch;\n"
|
|
" margin: 0ch;\n"
|
|
" border-width: 0px 2px 0px 2px;\n"
|
|
" border-style: none solid none solid;\n"
|
|
" border-radius: 2px;\n"
|
|
"}\n");
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"{\n"
|
|
" border-color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (1));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"{\n"
|
|
" border-color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (2));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"{\n"
|
|
" border-color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (3));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"{\n"
|
|
" border-color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (4));
|
|
|
|
g_string_append_printf (
|
|
stylesheet,
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"blockquote[type=cite]:not(.-x-evo-plaintext-quoted) "
|
|
"{\n"
|
|
" border-color: %s;\n"
|
|
"}\n",
|
|
e_web_view_get_citation_color_for_level (5));
|
|
|
|
gdk_color_free (visited);
|
|
|
|
base64 = g_base64_encode ((guchar *) stylesheet->str, stylesheet->len);
|
|
g_string_free (stylesheet, TRUE);
|
|
|
|
stylesheet = g_string_new ("data:text/css;charset=utf-8;base64,");
|
|
g_string_append (stylesheet, base64);
|
|
g_free (base64);
|
|
|
|
settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
|
|
g_object_set (
|
|
G_OBJECT (settings),
|
|
"default-font-size", pango_font_description_get_size (vw) / PANGO_SCALE,
|
|
"default-font-family", pango_font_description_get_family (vw),
|
|
"monospace-font-family", pango_font_description_get_family (ms),
|
|
"default-monospace-font-size", (pango_font_description_get_size (ms) / PANGO_SCALE),
|
|
"user-stylesheet-uri", stylesheet->str,
|
|
NULL);
|
|
|
|
g_string_free (stylesheet, TRUE);
|
|
|
|
pango_font_description_free (ms);
|
|
pango_font_description_free (vw);
|
|
}
|
|
|
|
/**
|
|
* e_html_editor_view_check_magic_links
|
|
* @view: an #EHTMLEditorView
|
|
* @include_space: If TRUE the pattern for link expects space on end
|
|
*
|
|
* Check if actual selection in given editor is link. If so, it is surrounded
|
|
* with ANCHOR element.
|
|
*/
|
|
void
|
|
e_html_editor_view_check_magic_links (EHTMLEditorView *view,
|
|
gboolean include_space)
|
|
{
|
|
WebKitDOMRange *range;
|
|
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
range = html_editor_view_get_dom_range (view);
|
|
html_editor_view_check_magic_links (view, range, include_space);
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_set_is_message_from_draft (EHTMLEditorView *view,
|
|
gboolean value)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
view->priv->is_message_from_draft = value;
|
|
}
|
|
|
|
gboolean
|
|
e_html_editor_view_is_message_from_draft (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->is_message_from_draft;
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_set_is_message_from_selection (EHTMLEditorView *view,
|
|
gboolean value)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
view->priv->is_message_from_selection = value;
|
|
}
|
|
|
|
gboolean
|
|
e_html_editor_view_is_message_from_edit_as_new (EHTMLEditorView *view)
|
|
{
|
|
g_return_val_if_fail (E_IS_HTML_EDITOR_VIEW (view), FALSE);
|
|
|
|
return view->priv->is_message_from_edit_as_new;
|
|
}
|
|
void
|
|
e_html_editor_view_set_is_message_from_edit_as_new (EHTMLEditorView *view,
|
|
gboolean value)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
view->priv->is_message_from_edit_as_new = value;
|
|
}
|
|
|
|
void
|
|
e_html_editor_view_set_remove_initial_input_line (EHTMLEditorView *view,
|
|
gboolean value)
|
|
{
|
|
g_return_if_fail (E_IS_HTML_EDITOR_VIEW (view));
|
|
|
|
view->priv->remove_initial_input_line = value;
|
|
}
|